argus-mint
Provenance tokens for game assets. argus-mint renders art from reviewed templates, pins it to IPFS, builds canonical ERC-721 metadata, mints transferable cards and soulbound deeds, and anchors commit/reveal secrets and Merkle roots on-chain so anyone can check a game's records.
It contains no game logic. A consumer supplies a template's variables, a record to show, and 32-byte leaf hashes; argus-mint does the rest. Tokens carry no in-game power, and nothing pays out.
Part of the Argus suite.
Status: pre-alpha. Milestones M0–M5 are done: pure core, contracts, clients, JS verifier, and a live demo on Base Sepolia, plus a pre-review demo on Base mainnet (below). Renders go through argus-darkroom.
What's in the box
| Part | What it does |
|---|---|
argus_mint (Python) |
template registry, darklight render client with retries, card frames, a draft → review → publish flow, IPFS pinning, canonical metadata, OpenZeppelin-compatible Merkle trees, EIP-712 mint vouchers, SIWE, a reorg-safe event indexer |
contracts/ (Foundry) |
Card (ERC-721 + ERC-4906 + ERC-2981, voucher mint), Deed (ERC-721 + ERC-5192 soulbound), Ledger (commit/reveal/anchor notary) |
argus-mint serve (HTTP) |
the draft → review → publish flow for argus-studio and argus-assay, with a pinned wire schema and a Docker image |
verifier/ (ES module) |
verify Merkle proofs against a Ledger root, revealed secrets against commitments, and drand quicknet beacons |
Install
uv pip install argus-mint # pure Python core
uv pip install "argus-mint[chain]" # + web3 for the indexer and ledger client
uv pip install "argus-mint[cli]" # + the argus-mint command
uv pip install "argus-mint[frames]" # + Pillow, to composite frames onto renders
uv pip install "argus-mint[server]" # + the HTTP service (argus-mint serve)
Quick look
argus-mint templates templates/ # validate templates, show status and hash
argus-mint draft --template my-approved.yaml --out drafts/unit-17 --placeholder \
--var role=ranger --var colour=none --var pose=standing --seed 81234 \
--asset unit-17 --to 0x7099…79C8 --name "Hana of the Strait"
argus-mint review drafts/unit-17 --as moderator --by mod-1 --approve
argus-mint review drafts/unit-17 --as owner --by 0x7099…79C8 --approve
argus-mint publish drafts/unit-17 --card 0x5FbD…0aa3 --chain-id 31337 --dry-run # metadata, voucher, signature
from argus_mint import merkle, templates, vouchers
from argus_mint.metadata import card_metadata, content_hash
tree = merkle.build(leaf_hashes) # OpenZeppelin StandardMerkleTree-compatible
proof = tree.proof(3) # verifies in Ledger.verify and MerkleProof.verify
Develop
Needs uv, Foundry and Node (for fixtures and the verifier).
git clone --recurse-submodules https://github.com/smk762/argus-mint
make install # submodules + venv with all extras
make check # lint + pytest + forge test
make help # everything else
Versions are derived from git tags via hatch-vcs. Tagging vX.Y.Z publishes to PyPI.
Deploy (Base)
cp .env.example .env # RPC URLs and role addresses (no paid API keys needed)
cast wallet new deployer # new key, straight into Foundry's encrypted keystore; fund its address
make deploy # Base Sepolia; `make deploy NETWORK=base` for mainnet
The script deploys Card, Deed and Ledger over plain JSON-RPC, grants roles, and verifies the sources on Sourcify for free (Blockscout displays them). VERIFIER=blockscout verifies on Blockscout directly; VERIFIER=etherscan uses Basescan and needs an Etherscan API key. Step by step, including wallet backup, funding and role setup: docs/deploy.md.
Making a card: draft, review, publish
Every card image is seen by people before it is published. Rendering makes a local draft. The moderator and the card's owner each review that exact image (its sha256), and only when both approve does publish pin it and sign the mint voucher. A rejection closes the draft; render a new one.
make ipfs # local Kubo node (compose.yaml): RPC 127.0.0.1:5001, gateway :8080
# reviewing a template or frame, before approving it
argus-mint preflight my-template.yaml --darkroom http://localhost:8106 # text check, every combination
argus-mint sample my-template.yaml --out samples/ --count 6 --frame frames/my-frame.yaml \
--darkroom http://localhost:8106 # sample images, local files only
# a card
argus-mint draft --template my-template.yaml --frame frames/my-frame.yaml --out drafts/unit-17 \
--var ... --asset unit-17 --to 0x… --name "…" --darkroom http://localhost:8106 \
--bio bios/my-bio.yaml --writer http://<chat api> --text "class=Ranger of the Strait" --text "stats=No. 17"
argus-mint review drafts/unit-17 --as moderator --by <name> --approve # or --reject --note "why"
argus-mint review drafts/unit-17 --as owner --by <name> --approve
argus-mint publish drafts/unit-17 --card 0x… --ipfs-api http://127.0.0.1:5001 > unit-17.json # pins, signs
Frames. Renders are clean, full-bleed art. A frame is a reviewed PNG overlay, transparent inside its art window, with an optional slot for the card's name. It's described in a YAML file next to it, which goes through the same draft | approved | retired review as templates (frames/card-green-v1.yaml). Render at the art window's aspect ratio (the template's width/height): the art is scaled to cover the window and the overflow is cropped. A framed card's provenance names the frame and its hash, and points to the unframed art on IPFS.
Card text. A frame has text slots, each with a font, size and box; text shrinks to fit and is clipped with an ellipsis if it still doesn't. argus-mint fills name and bio. The consumer fills the rest (--text class=…, --text stats=…) with display text from its own records. That text is shown on the card, never written into the metadata as stats.
Bios. A language model writes each card's flavour text from a reviewed bio template (bios/example-bio-v1.yaml: Mistral NeMo, fruity). It sees only the template's fixed-list values and writes a {name} token; argus-mint puts the real name in afterwards, so a player's name never reaches a model. darkroom's text check screens every bio, and flagged ones are rewritten. The moderator and the owner then read it on the card during review. The bio becomes the card's description.
On-chain, each step signs with a Foundry keystore account (--account / $ARGUS_MINT_ACCOUNT) or $ARGUS_MINT_SIGNER_KEY:
argus-mint submit unit-17.json # send the voucher to Card.mint
argus-mint freeze --card 0x… --token-id 1 --uri <metadata cid> # fix tokenURI at ipfs://…
argus-mint anchor leaves.txt --ledger 0x… --epoch 1 --tree epoch-1.json # Merkle root into the Ledger
The Kubo RPC port is an admin API and is published on localhost only. Swarm port 4001 is public so wallets and gateways can fetch pinned art; behind NAT, pin on a VPS (native Kubo, reached through an SSH tunnel) (docs/deploy.md §6) so cards stay retrievable when this host is offline.
Running the service
argus-mint serve puts the card workflow behind HTTP for the suite's UIs: argus-studio's pages and argus-assay, the standalone review UI. The server decides; a UI only shows what it says.
make serve # local, 127.0.0.1:8108, settings from .env
make serve-up # or in docker with Kubo (compose profile `server`)
| Endpoint | |
|---|---|
GET /health |
version, wire version, network, Card, what it can do (capabilities), which backends answer |
GET /templates /frames /bios |
the reviewed files, every status, with hashes; GET /frames/{id}/{version}/overlay |
GET /drafts |
newest first, ?status=pending|approved|rejected|published, paged |
GET /drafts/{id} |
the draft and its reviews; the pins and signed voucher once published |
GET /drafts/{id}/image /art |
the card image reviewers approve, and the unframed render |
POST /drafts |
render, write the bio, frame: a new pending draft (moderator token) |
POST /drafts/{id}/challenge |
the sign-in message the owner's wallet signs to approve or reject |
POST /drafts/{id}/review |
moderator by token, owner by signed challenge |
POST /drafts/{id}/publish |
pin and sign an approved draft (moderator token) |
POST /drafts/{id}/edit |
the owner changes the card's text or style, with a wallet signature covering exactly that change. The card is redrawn on the same art and needs both approvals again. The moderator only approves or rejects |
POST /frames/{id}/{version}/layout, GET /frames/{id}/{version}/fonts/… |
where each line of text goes, and the frame's fonts, so a browser can preview a restyled card as SVG before it is baked |
GET /library, GET /library/{id}/image[?thumb=w] |
pre-rendered portraits; POST /drafts with library: <id> makes a card from one without a GPU |
POST /samples, GET /samples[/{id}] |
sample cards of a template, frame and bio (any status) for their reviewer, as background jobs; local only (moderator token) |
POST /frames/{id}/{version}/preview |
a frame (any status) on a draft's or sample's art, as a PNG |
POST /{templates,frames,bios}/{id}/{version}/review |
approve a draft or retire an approved file. This writes status, approved_by and approved_at into the YAML, keeping its comments (moderator token) |
Who can do what. The moderator token (ARGUS_MINT_MODERATOR_TOKEN) creates drafts, renders samples, approves or retires templates, frames and bio templates, reviews as the moderator, and publishes. The owner reviews by signing an EIP-4361 (Sign-In with Ethereum) message with the wallet the card will be minted to. The message names the draft, the decision and the image's sha256, and works once. Neither role can act as the other.
Checkpoints, sampling and LoRAs. A template lists the checkpoints a card may use (models, reviewed with it; model is the default). draft --model, sample --model and POST /drafts with model choose one; anything else is refused.
The template's sampling and negative apply unless sampling_by_family or negative_by_family has an entry for the checkpoint's family. The family is its top folder: sdxl, flux, sd35. Flux sets guidance, and its negative is "", since darkroom's Flux workflows have no slot for one.
A template can also offer lora_options, each with:
- allowed
weights trigger_words- the checkpoints it pairs with
A card may choose up to max_loras of them (--lora NAME[@WEIGHT], or loras in the API).
Rendering other families, and LoRAs, needs argus-darkroom 0.1.3 or later with a workflow directory (its Docker image's default). darkroom picks a workflow by family and LoRA count, and returns a 400 when none fits. Its GET /models lists each checkpoint's lora_counts.
A library for demos without a GPU. argus-mint library build TEMPLATE --out library --count 48 renders COUNT diverse combinations on every checkpoint the template allows. The same combination gets the same seed on each, so they compare like for like. It renders one checkpoint at a time, so each loads once, and an interrupted build resumes. Every image passes darkroom's safety check, as always. argus-mint serve offers the library at /library (ARGUS_MINT_LIBRARY_DIR), and draft --library DIR --entry ID uses it from the CLI.
An entry can make a card only if the approved template renders exactly the request the entry was rendered from. The draft then records the entry, and its provenance is that of a live render.
Auto-moderation (ARGUS_MINT_AUTO_MODERATE=1, off by default): the platform gates on safety, the owner on quality.
- When it approves: if darkroom's image guards passed the art, its text check passed the bio, and the name and display text pass a word filter, the moderator's approval is recorded as
auto: platform checks. - When it holds: anything else waits for a human moderator, with the reason in
auto_check.held. - The word filter: better-profanity's list, which catches letter swaps, plus your own terms in
ARGUS_MINT_BLOCKLIST, one per line. - The owner always reviews.
Editing and styling are the owner's. The owner can change the name, the bio and the display text. They can also restyle the frame:
- Border: a tint colour and strength.
- Each text slot: colour, an effect (shadow, outline or glow) and a background (colour, opacity, rounded corners).
Any colours are allowed; if the owner wants black on black, so be it. A frame can set style_locked: true to refuse restyling.
The browser previews a restyle as SVG from /layout and the fonts, using the same tint matrix as the server. argus-mint bakes the PNG when the owner confirms, and that PNG is what gets reviewed and published. Provenance records the style, so the card can be redrawn exactly. Edited text never reaches a model, so an edited bio goes through the word filter rather than darkroom.
Configuration is ARGUS_MINT_* (see .env.example):
- Files:
DRAFTS_DIR,TEMPLATES_DIR,FRAMES_DIR,BIOS_DIR. - Backends:
DARKROOM_URL,WRITER_URL,IPFS_API. - Signing:
ACCOUNTplusKEYSTORE_PASSWORD_FILE, orSIGNER_KEY. - Chain:
NETWORK,CARD. - Access:
MODERATOR_TOKEN,SIWE_DOMAIN(the UI's host, as wallets show it),CORS_ORIGINS,READ_ONLY.
Like the other suite services, it binds to localhost and refuses writes from browser origins it wasn't told about.
For UI authors:
- The wire contract is schema/mint-wire.schema.json (
argus-mint schema), pinned by release tag and sha256 the way argus-studio pins the other services. - fixtures/api/ holds recorded responses with a draft in each state, for demo mode.
- The image is
ghcr.io/smk762/argus-mint.
Live demo (Base Sepolia)
Deployed 2026-11-08 from this repo (make deploy), source verified on Sourcify:
| Contract | Address |
|---|---|
| Card | 0xCAa3573b5550870A5e15C47c650b8700D70a8fA7 |
| Deed | 0xe44D67bdFd3416F97206fdE7aeE55F9008EB5D7c |
| Ledger | 0x4b41B687C694bd8bf3778871752637445c2Aa254 |
Card #1 was made end to end. Every command's output is in demo/base-sepolia/.
- Render. The approved
example-portraitv1 template was rendered with fixed values and seed 42:portrait of a female dwarf, bronze heraldry, mining, cave, …. darkroom passed it on the first attempt under its SFWdefaultpolicy (recordcb2bd951…). - Pin. The image and canonical metadata were pinned on Kubo. The metadata's
provenancecarries the template hash, seed and attempt count. - Sign. A mint voucher was signed for the Card (EIP-712), carrying
contentHash= keccak256 of the metadata's image and provenance. - Submit minted token 1.
- Freeze fixed
tokenURI(1)atipfs://bafkreid3o6zha6adg7ndl4rpmnsv7bc22t6ak2ik4opr6oed56v74uhefu.
Epoch 1 anchor: four demo records (records.txt) were hashed with sha256 into a Merkle tree. Its root, 0x4cafc06c…1ee1f736, was anchored in the Ledger.
Verify it yourself
Anchored records, with the JS verifier and the public RPC (needs only Node):
make verifier-install
node demo/base-sepolia/verify.mjs # each record checks out; a changed one is rejected
To check the card, read tokenURI(1) and contentHashOf(1) from the Card and fetch the metadata from IPFS. Then confirm keccak256(canonical({image, provenance})) equals the on-chain contentHash:
from argus_mint import chain
from argus_mint.metadata import content_hash
card = chain.contract(chain.connect("base-sepolia"), "Card", "0xCAa3573b5550870A5e15C47c650b8700D70a8fA7")
# doc = the JSON at card.functions.tokenURI(1).call()
assert content_hash(doc) == bytes(card.functions.contentHashOf(1).call())
Demo on Base mainnet (pre-review)
The same script was deployed to Base (8453) on 2026-11-08. It was the deployer's first transaction there, so the addresses equal the testnet ones, and all three contracts are verified on Sourcify. This is a demo: the contracts and deployment get a review before anything goes live, and the deployer still holds every role.
Six cards were rendered fresh from the approved example-portrait v1 template, one darkroom attempt each. They were pinned on a VPS Kubo node, minted and frozen by demo/base/mint-batch.sh, and every step's output is in demo/base/. Each card's on-chain contentHash matches its pinned metadata. Both demos predate the review step: they used the one-shot mint command that draft / review / publish replaced, and their template drew its own card frames, which led to frames becoming a separate step.
| Token | Name | Mint | Freeze |
|---|---|---|---|
| 1 | Azure Knight | tx | tx |
| 2 | Emerald Wizard | tx | tx |
| 3 | Steel Cyborg | tx | tx |
| 4 | Silver Huntress | tx | tx |
| 5 | Crimson Orc | tx | tx |
| 6 | Golden Priest | tx | tx |
The collection is also on OpenSea. The same contentHash check as above works with chain.connect("base").
License
MIT
Metadata
Release files for argus-mint 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| argus_mint-0.2.0.tar.gz | 185.1 kB | With provenance | Details for argus_mint-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| argus_mint-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for argus_mint-0.2.0-py3-none-any.whl |
Total release size: 289.9 kB
Release files / argus_mint-0.2.0.tar.gz
| Download URL | argus_mint-0.2.0.tar.gz |
|---|---|
| Size | 185.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fdd0f16eefaa3f93e3676d2e3c2550b15dcf737b59d35b161bd436f6faa904bd
|
|
BLAKE2b-256 checksum How to use checksums |
2c1fa22a053f0f4036ee0c97584a879e971b4d173c2dc34c54918aed7f07d568
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logRelease files / argus_mint-0.2.0-py3-none-any.whl
| Download URL | argus_mint-0.2.0-py3-none-any.whl |
|---|---|
| Size | 104.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d92b16708a04a414754f489a9002d6a1844cf17ad9b1746c3ccb605df1a36fe1
|
|
BLAKE2b-256 checksum How to use checksums |
2e10d11a0b60b4d6fdfb95d4feb042d020db9d122c2dab6ab93577c9c0116ab6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logSecurity for argus-mint 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for argus_mint-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.