Imported from skullzarmy/aleatory (
AGENTS.md). Install upstream withnpx skills add skullzarmy/aleatory. Copyright stays with the author.
Working in this repository
Aleatory is fully on-chain generative art on Tezos. A generator is one HTML file stored on chain; a piece is that file plus a seed fixed at mint. The contracts and ALEATORY-001 are the platform, and a piece resolves from chain state, so the code here is one implementation of a spec anyone can implement.
Working on the platform is this file. Using it is
public/skill/SKILL.md, served at
/skill/SKILL.md: a map, and three skills
beside it for the artist, the provider operator and anyone building against
ALEATORY-001. public/llms.txt at /llms.txt is the artist
one again, as a single page for anything that reads a site rather than
installing a skill.
First time here
npm ci
npm test
test:dep, test:payable, test:ping and test:stats read the chain and a
package registry. Each skips its network half when there is no connection, so
the suite passes offline with fewer assertions.
SmartPy work needs Python 3 with smartpy-tezos installed, which
npm run build:contracts and npm run test:contracts call.
What is here
Five things ship from one checkout.
| the site | src/ |
Next 15, App Router. Reads public chain state. |
| the isolate | isolate/ |
A separate origin where artwork runs. |
| the admin console | admin/ |
Its own Next app and its own Netlify site. |
| the render provider | provider/provider.mts |
Renders pieces, pins them, publishes. Run as a process by provider/daemon.mts. |
| the stats bot | bot/ |
Reads the chain, writes figures into Discord channel names. |
The provider and the bot run on a machine the operator keeps. Everything in
bot/ imports from bot/, so the directory is the whole program.
contract/ SmartPy sources, tests, build and deploy
docs/ the public documentation, including ALEATORY-001
public/ templates, and llms.txt
scripts/ build steps, the documentation checks, one-off tooling
Rendering
Artwork runs in isolate/, on its own origin, under a CSP that blocks the
network. That CSP is what makes "a piece cannot phone home" a property of the
browser, so a piece draws from the bytes it carries and the seed it is given.
The harness has two implementations: the isolate, and
provider/render.mts. Each conforms to ALEATORY-001 §7 and
src/lib/conformance.test.ts holds them to it. When they disagree, the spec
decides.
The contracts
Seven, in contract/aleatory.py and contract/marketplace.py.
| Router | Names the current factory, marketplace, registry and resolver. One address in the environment, the rest read from it. |
| Factory | Originates generators, and records what it deployed. |
| Collection | One generator, one edition, its tokens. The UI calls this a generator; AleatoryCollection is its on-chain name and does not change. |
| Marketplace | Listings, offers, fees. |
| Provider | One renderer's price and working key. |
| Registry | The list of providers. Permissionless. |
| Resolver | Which keys may write resolution entries. |
The artist holds every authority a generator has: pause the sale, reprice
the unsold remainder, reduce or close the edition, switch render provider, edit
display metadata, and hand the contract on in two steps. code, code_uri,
code_hash and royalties are written at origination and stand for the life of
the contract, and so do aleatory:libraries and aleatory:params, which
set_metadata refuses: everything deciding what a piece runs is fixed.
A bug in the template is therefore frozen into every generator made from it,
which is why the template is small and is audited before it ships.
Things that have cost real time
- SmartPy lays out record fields alphabetically. Encode operation parameters by field name through Taquito.
sp.consprepends, sorouter.factoriesruns newest first andfactories[0]is where a deploy goes.- The marketplace lineage lives in the router's storage history. The first marketplace is written at origination and emits no event, so an event scan finds every marketplace except that one, and loses the listings and escrowed offers still held there.
- The router can name one factory twice.
add_factoryconses on, so re-pointing at an earlier one adds a second entry. Dedupe before querying. - Taquito encodes an origination from the contract's own storage schema,
taking the keys it finds there. A field left in
deploy.tsafter a rename is dropped in silence and the deploy succeeds.scripts/check-contracts.mjscompares the two. - An implicit account always accepts tez; a
KT1accepts it through adefaultentrypoint of type unit. The marketplace askssp.contract(sp.unit, recipient)before paying a royalty and pays the seller when the answer is None, becauseroyaltiesis immutable and one bad address would otherwise revert every sale of that generator forever. - 32,768 bytes is the operation ceiling, code included. The factory embeds the generator template, which makes it the largest contract here.
- On shadownet the per-operation gas cap equals the per-block cap, so an
operation at the per-operation maximum consumes the whole block budget and is
rejected.
contract/deploy.tsreads both and stays under.
Running things
npm test the JS suite
npm run test:contracts SmartPy scenarios
npm run build:contracts compile to contract/build
npm run deploy originate. --dry-run prices it first
npm run dev:all the isolate and the site together
npm run provider:check a provider pass that reads only
npm run bot:check read the chain, print the channel names
Reach for a :check first. A provider run spends render budget, pinning quota
and gas. set_token_metadata is rewritable by an authorised writer, on
purpose, so a publish whose confirmation was missed can be retried; the
spending is what a dry run saves.
The operator runs their own dev server. Leave the ports alone.
On tests
Tests here run the code they are about: a template is parsed, a zip is packaged, a schema is resolved, an API route is called.
Four source scans survive, and each earns it. hooks.test.ts is a lint rule
for React hook order. check-contracts.mjs counts contracts and compares
deploy.ts storage against what each contract declares.
conformance.test.ts checks the harness implementations agree. check-env.mjs
reads every process.env in the repo and holds the two .env.example files to
it, both ways. Each caught a bug that had shipped.
Conventions
.gitignore allow-lists the top level. A new directory becomes visible to
git when !/name/ is added, which makes adding one a deliberate act.
Some docs are private and gitignored: the audit and its response, the roadmap, the decision log, open questions, the sitemap, the pipeline notes. They are working notes. Keep them out of git and out of anything public.
Prose, in docs, comments, commit messages and replies. Say what a thing is and stop. Describe the model as it stands now. Use commas, colons and parentheses. American spelling. Four habits to keep out: em dashes, "not an X, a Y" constructions, accounts of decisions since replaced, and justification by contrast ("rather than", "instead of").
Comments earn their place. Write one when there is something to communicate that the code does not communicate on its own: a constraint arriving from outside the file, a failure the shape of the code is avoiding, an ordering nothing in the language enforces, a number whose source is elsewhere. Anything else goes. Do not restate the line below. Do not narrate what the code used to be. Do not leave anecdotes about how a choice was made, which icon was picked, or what was tried first. A file with four comments that each carry weight is worth more than one with forty. When in doubt, delete it: the code is the description, and a wrong comment costs more than a missing one.
Git: every change lands through a pull request, a maintainer's included. A
ruleset on the repository protects main and refuses a direct push, a force
push and a deletion, and holds the site and contracts checks. Branch, push
the branch, open the pull request.
Everybody outside the project forks first, which is what CONTRIBUTING.md describes. This file is the working document for the platform; that one is where somebody arriving starts.
.env holds secret keys and is gitignored. Key material stays out of the
repository.
Further
- docs/architecture.md, the whole model
- docs/interface.md, ALEATORY-001
- docs/deploying.md, standing up a network and the provider
- docs/provider.md, running a render provider
- bot/README.md, the stats bot
- admin/README.md, the operator console