1. Building Your Project
This page covers the technical requirements for building a generative art script for Art Blocks. Your script will be stored on-chain and run in the Art Blocks Generator — a browser-based environment that injects tokenData and loads your dependency library.
Quick Start: MCP Scaffold
The fastest way to get started is with the Art Blocks MCP Server:
"Scaffold a p5.js Art Blocks project with window.$features for Palette and Density traits"
The scaffold_artblocks_project tool generates a complete index.html + art script with the PRNG, canvas setup, and feature stubs already wired up. Connect the MCP server in under 5 minutes — see the Quick Start.
What the Generator Provides
When your script runs, the Art Blocks Generator has already:
- Loaded your dependency library (p5.js, Three.js, etc.) as a
<script>tag - Injected a
tokenDataglobal with the token's hash and other metadata - Applied CSS for full-viewport canvas rendering
Your script runs after all of this setup is complete.
tokenData
let tokenData = {
hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
tokenId: "42000001"
}
tokenData.hash is a 32-byte hex string — the unique, on-chain random seed for this token. This is your only randomness source.
tokenData.tokenId encodes both the project and invocation:
const projectId = Math.floor(parseInt(tokenData.tokenId) / 1_000_000)
const invocation = parseInt(tokenData.tokenId) % 1_000_000
Deterministic Randomness (PRNG)
Never use Math.random() or Date.now() as a randomness source. These are non-deterministic — the same token would produce different outputs on different loads, which breaks the fundamental guarantee of generative art on Art Blocks.
Instead, seed a pseudo-random number generator (PRNG) from tokenData.hash. The recommended implementation is the sfc32 algorithm:
// Seeded PRNG from tokenData.hash
function sfc32(a, b, c, d) {
return function () {
a |= 0; b |= 0; c |= 0; d |= 0;
let t = (((a + b) | 0) + d) | 0;
d = (d + 1) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
c = (c + t) | 0;
return (t >>> 0) / 4294967296;
};
}
function hashToSeeds(hash) {
const hex = hash.slice(2); // remove "0x"
return [
parseInt(hex.slice(0, 8), 16),
parseInt(hex.slice(8, 16), 16),
parseInt(hex.slice(16, 24), 16),
parseInt(hex.slice(24, 32), 16),
];
}
const [s1, s2, s3, s4] = hashToSeeds(tokenData.hash);
const rand = sfc32(s1, s2, s3, s4);
// Usage:
// rand() → float in [0, 1)
// rand() * (b - a) + a → float in [a, b)
// Math.floor(rand() * n) → integer in [0, n)
Alternatively, use the full Random class that ships in the MCP scaffold, which adds convenient helper methods:
class Random {
constructor() {
const hex = tokenData.hash.slice(2);
const seeds = [
parseInt(hex.slice(0, 8), 16),
parseInt(hex.slice(8, 16), 16),
parseInt(hex.slice(16, 24), 16),
parseInt(hex.slice(24, 32), 16),
];
this._state = seeds;
}
_next() {
let [a, b, c, d] = this._state;
a |= 0; b |= 0; c |= 0; d |= 0;
let t = (((a + b) | 0) + d) | 0;
d = (d + 1) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
c = (c + t) | 0;
this._state = [a, b, c, d];
return (t >>> 0) / 4294967296;
}
random_dec() { return this._next(); }
random_num(a, b) { return a + this._next() * (b - a); }
random_int(a, b) { return Math.floor(a + this._next() * (b - a + 1)); }
random_bool(p) { return this._next() < p; }
random_choice(arr) { return arr[Math.floor(this._next() * arr.length)]; }
}
const R = new Random();
Token Features with window.$features
Token features (traits) are defined by assigning an object to window.$features in your art script. Art Blocks reads this value to index token traits.
// Compute your traits using the PRNG
const palette = R.random_choice(["Midnight", "Flame", "Ocean", "Forest"]);
const density = R.random_int(10, 90);
const animated = R.random_bool(0.3);
window.$features = {
Palette: palette,
Density: density,
Animated: animated,
};
Rules:
- Values must be deterministic: the same hash (and PostParam settings) must always produce the same features
- For numeric traits, use consistent decimal precision
The legacy calculateFeatures() function approach is no longer documented here — see Legacy: Features Script if you have an existing project using that approach.
Resolution Agnosticism
Art Blocks tokens are rendered at different sizes — from thumbnails to large displays. Your script must be resolution agnostic: it should fill the viewport at any size, using relative coordinates rather than fixed pixel dimensions.
For p5.js:
function setup() {
createCanvas(windowWidth, windowHeight);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
For vanilla JS / canvas:
const canvas = document.querySelector("canvas");
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
draw(); // redraw on resize
}
window.addEventListener("resize", resize);
resize();
Coordinate all drawing relative to canvas.width and canvas.height, not hardcoded values.
Script Requirements
Supported Libraries
All libraries must be from the Art Blocks Dependency Registry. Current supported libraries include:
- p5.js — v1.0.0, v1.9.0, v1.11.11
- Three.js — v0.124.0, v0.160.0, v0.167.0
- regl — v2.1.0
- Tone.js — v14.8.15
- Paper.js — v0.12.15
- Babylon.js — v5.0.0, v6.36.0
- A-Frame — v1.2.0, v1.5.0
- Vanilla JS — no dependency (canvas, WebGL, SVG)
Local Development
Set up a local index.html that mirrors the generator environment:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>
<script>
let defined_hash = new URLSearchParams(window.location.search).get("hash");
let tokenData = {
tokenId: "0",
hash: defined_hash || "0x" + Array.from({length: 64}, () =>
"0123456789abcdef"[Math.floor(Math.random() * 16)]).join(""),
};
</script>
<style>
html { height: 100%; }
body { min-height: 100%; margin: 0; padding: 0; }
canvas { padding: 0; margin: auto; display: block; position: absolute;
top: 0; bottom: 0; left: 0; right: 0; }
</style>
</head>
<body>
<canvas></canvas>
<script src="sketch.js"></script>
</body>
</html>
Test with specific hashes by appending ?hash=0x... to the URL. Test with many different hashes to ensure variety and that no hash causes errors.
Conversion Guide: Adapting an Existing Sketch
If you have an existing p5.js or vanilla JS sketch that you want to convert to Art Blocks format:
- Replace
Math.random()with calls to your sfc32 PRNG seeded fromtokenData.hash - Remove CDN
<script>tags — the generator loads your library automatically - Remove canvas creation code — for p5.js, remove
createCanvas()calls or usewindowWidth/windowHeight; for vanilla JS, select the existing<canvas>instead of creating one - Make dimensions relative — replace hardcoded pixel values with percentages of
width/height - Add
window.$features— compute and assign your token traits synchronously - Verify determinism — reload the same
?hash=URL multiple times and confirm identical output
For a full step-by-step conversion walkthrough, use the MCP's scaffold_artblocks_project tool or read the Generator Specification resource available via the MCP server.
Testing Checklist
Before submitting for review:
- Script runs without errors in Chrome, Firefox, and Safari
- Same hash always produces identical output (deterministic)
- Different hashes produce meaningfully different outputs
- No hardcoded pixel dimensions — scales at all viewport sizes
- No use of
Math.random()orDate.now()for randomness -
window.$featuresis assigned synchronously with correct values - Script is a single file with a single dependency (or no dependency)
- No CDN
<script>tags in the script - Tested across at least 20–40 different hashes
- Script locks up or errors for no hash value (test edge cases)