Every render is one small object you can read, link and rebuild.
The studio, the share links, the gallery and the provenance record all speak the same preset format. Scripts can fetch the library as JSON, build presets in code and open them in the studio; nobody needs a key.
The pieces
Three pieces, all open, none of them holding a key for you:
Algorithm, pixel size, tone, palette and screen settings in one plain JSON object.
The canonical preset, base64url-encoded, in the studio address. Opens the same look anywhere.
Keyless JSON endpoints on this site and the RastiProvenanceBook on Base.
First steps
Fetch the preset library, pick one, and open its studio link:
curl -s https://rasti.live/api/presets | jq '.presets[] | {name, studio}'Or build a preset in code and turn it into a link:
const preset = { v: 1, algorithm: "bayer4", pixel: 3, contrast: 20, brightness: 0,
threshold: 128, palette: ["#0a0a0a", "#f2c66d"], forcePalette: true,
serpentine: true, dot: 8, angle: 45, invert: false };
// canonical JSON: keys sorted, no spaces
const canon = "{" + Object.keys(preset).sort()
.map((k) => JSON.stringify(k) + ":" + JSON.stringify(preset[k])).join(",") + "}";
const code = btoa(canon).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
console.log("https://rasti.live/studio?p=" + code);Preset format
Unknown keys are dropped and numbers are clamped to their range, so a preset from anywhere is always safe to open.
| Key | Type · range | Meaning |
|---|---|---|
v | 1 | Format version. |
algorithm | floyd · atkinson · bayer2 · bayer4 · bayer8 · halftone · threshold · pixelate | How pixels are mapped to the palette. |
pixel | 1–32 | Source pixels per output pixel. |
contrast | -100–100 | Tone contrast before mapping. |
brightness | -100–100 | Tone shift before mapping. |
threshold | 0–255 | Cut point (two-colour Threshold) or light/dark bias. |
palette | 2–16 × "#rrggbb" | Allowed output colours, darkest first by convention. |
forcePalette | boolean | Pixelate only: map to the palette instead of keeping source colours. |
serpentine | boolean | Diffusion scans alternate direction per row. |
dot | 3–32 | Halftone cell size. |
angle | 0–90 | Halftone screen angle in degrees. |
invert | boolean | Swap light and dark before mapping. |
Share codes
A share code is the canonical JSON (sorted keys) in base64url without padding. The default preset becomes:
/studio?p=eyJhbGdvcml0aG0iOiJhdGtpbnNvbiIsImFuZ2xlIjo0NSwiYnJpZ2h0bmVzcyI6MCwiY29udHJhc3QiOjEyLCJkb3QiOjgsImZvcmNlUGFsZXR0ZSI6dHJ1ZSwiaW52ZXJ0IjpmYWxzZSwicGFsZXR0ZSI6WyIjMGIwYjBjIiwiI2YxZWZlNyJdLCJwaXhlbCI6Miwic2VycGVudGluZSI6dHJ1ZSwidGhyZXNob2xkIjoxMjgsInYiOjF9
Remix links add the parent: &parent=<gallery id> for a gallery work, and &ph=<file hash>&pc=<wallet> for an on-chain record. The settings hash in a provenance record is the SHA-256 of the same canonical JSON.
Endpoints
JSON over HTTPS on this site, no key, rate limited per network. A refused call answers { "error": "…" } plus a matching status code.
| Path | What you get |
|---|---|
GET /api/presets | Algorithms, palettes and the preset library, each preset with its share code and studio link. |
GET /api/gallery?limit=60 | Published works, newest first: title, note, preset, remix flag, hashes, creator, preview. Hidden works are left out. |
GET /api/chain | Live Base facts: block, block time, gas price, ETH price, records in the book, token data once launched. |
GET /api/nonce?address=0x… | One-time nonce for a signed write (10 minutes). |
POST /api/records | Signed gallery writes: publish, remove, report. The wallet signs a plain-text message (personal_sign) that includes the SHA-256 of the exact payload. |
Provenance contract
RastiProvenanceBook on Base (chain ID 8453): not deployed yet. No owner, no fee, no upgrade. Functions:
record(bytes32 artworkHash, bytes32 settingsHash, bytes32 parentHash,
address parentCreator, uint8 license) returns (uint256 id)
recordOf(bytes32 artworkHash, address creator)
-> (bool exists, bytes32 settingsHash, bytes32 parentHash, address parentCreator,
uint64 recordedAt, uint64 blockNumber, uint8 license, uint256 id)
claimCount(bytes32 artworkHash) · claimantAt(bytes32 artworkHash, uint256 i)
entryCount() · entryAt(uint256 id) · creatorCount(address) · creatorEntryAt(address, uint256 i)
event ArtworkRecorded(uint256 indexed id, bytes32 indexed artworkHash, address indexed creator,
bytes32 settingsHash, bytes32 parentHash, address parentCreator, uint8 license)Rules the contract enforces: a non-zero file hash; license 0 or 1; one record per wallet per file; a parent must exist under the named wallet and allow remixes; a work cannot be its own parent.
Hashing a file
The file hash is plain SHA-256 of the file bytes, so any tool gives the same answer as the studio. The verify page does it in your browser.
shasum -a 256 rasti-work.png # macOS / Linux
// browser
const hash = "0x" + [...new Uint8Array(await crypto.subtle.digest("SHA-256", await file.arrayBuffer()))]
.map((b) => b.toString(16).padStart(2, "0")).join("");Limits
WebP, at most 720 px a side and 132 KB.
12 works per wallet per day, plus per-network limits.
One counted report per wallet and per network per work, wallets with Base history only; hidden after 3.
Gas only. Public and permanent once included in a block.