argus-mint 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. argus_mint-0.1.0/.gitignore +20 -0
  2. argus_mint-0.1.0/CHANGELOG.md +27 -0
  3. argus_mint-0.1.0/LICENSE +21 -0
  4. argus_mint-0.1.0/PKG-INFO +190 -0
  5. argus_mint-0.1.0/README.md +145 -0
  6. argus_mint-0.1.0/pyproject.toml +100 -0
  7. argus_mint-0.1.0/src/argus_mint/__init__.py +20 -0
  8. argus_mint-0.1.0/src/argus_mint/__main__.py +5 -0
  9. argus_mint-0.1.0/src/argus_mint/_codec.py +63 -0
  10. argus_mint-0.1.0/src/argus_mint/_version.py +24 -0
  11. argus_mint-0.1.0/src/argus_mint/abi/Card.json +1342 -0
  12. argus_mint-0.1.0/src/argus_mint/abi/Deed.json +874 -0
  13. argus_mint-0.1.0/src/argus_mint/abi/Ledger.json +539 -0
  14. argus_mint-0.1.0/src/argus_mint/abi/__init__.py +19 -0
  15. argus_mint-0.1.0/src/argus_mint/chain.py +101 -0
  16. argus_mint-0.1.0/src/argus_mint/cli.py +472 -0
  17. argus_mint-0.1.0/src/argus_mint/darkroom.py +213 -0
  18. argus_mint-0.1.0/src/argus_mint/indexer.py +152 -0
  19. argus_mint-0.1.0/src/argus_mint/keys.py +95 -0
  20. argus_mint-0.1.0/src/argus_mint/ledger.py +76 -0
  21. argus_mint-0.1.0/src/argus_mint/merkle.py +126 -0
  22. argus_mint-0.1.0/src/argus_mint/metadata.py +121 -0
  23. argus_mint-0.1.0/src/argus_mint/networks.py +72 -0
  24. argus_mint-0.1.0/src/argus_mint/pin.py +84 -0
  25. argus_mint-0.1.0/src/argus_mint/py.typed +0 -0
  26. argus_mint-0.1.0/src/argus_mint/render.py +151 -0
  27. argus_mint-0.1.0/src/argus_mint/siwe.py +284 -0
  28. argus_mint-0.1.0/src/argus_mint/templates.py +264 -0
  29. argus_mint-0.1.0/src/argus_mint/vouchers.py +140 -0
  30. argus_mint-0.1.0/tests/__init__.py +0 -0
  31. argus_mint-0.1.0/tests/conftest.py +127 -0
  32. argus_mint-0.1.0/tests/fixtures/drand_quicknet.json +32 -0
  33. argus_mint-0.1.0/tests/fixtures/ledger_rpc.json +156 -0
  34. argus_mint-0.1.0/tests/fixtures/merkle.json +1323 -0
  35. argus_mint-0.1.0/tests/fixtures/proofs.json +1049 -0
  36. argus_mint-0.1.0/tests/fixtures/vouchers.json +25 -0
  37. argus_mint-0.1.0/tests/test_anvil_clients.py +135 -0
  38. argus_mint-0.1.0/tests/test_anvil_contracts.py +69 -0
  39. argus_mint-0.1.0/tests/test_cli.py +151 -0
  40. argus_mint-0.1.0/tests/test_cli_chain.py +135 -0
  41. argus_mint-0.1.0/tests/test_darkroom.py +176 -0
  42. argus_mint-0.1.0/tests/test_fixtures.py +17 -0
  43. argus_mint-0.1.0/tests/test_indexer.py +98 -0
  44. argus_mint-0.1.0/tests/test_keys.py +94 -0
  45. argus_mint-0.1.0/tests/test_merkle.py +78 -0
  46. argus_mint-0.1.0/tests/test_metadata.py +119 -0
  47. argus_mint-0.1.0/tests/test_networks.py +57 -0
  48. argus_mint-0.1.0/tests/test_pin.py +62 -0
  49. argus_mint-0.1.0/tests/test_render.py +89 -0
  50. argus_mint-0.1.0/tests/test_siwe.py +136 -0
  51. argus_mint-0.1.0/tests/test_smoke.py +8 -0
  52. argus_mint-0.1.0/tests/test_templates.py +143 -0
  53. argus_mint-0.1.0/tests/test_vouchers.py +87 -0
@@ -0,0 +1,20 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ # hatch-vcs writes this at build time (version derived from git tags)
10
+ src/argus_mint/_version.py
11
+
12
+ # Foundry
13
+ contracts/out/
14
+ contracts/cache/
15
+ contracts/broadcast/*/31337/
16
+ # JS (verifier + fixture generator)
17
+ node_modules/
18
+ # Local deploy settings (see .env.example)
19
+ .env
20
+ contracts/broadcast/**/*.lock
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Versions come from git tags (`hatch-vcs`).
4
+
5
+ ## Unreleased
6
+
7
+ ## 0.1.0 (2026-11-08)
8
+
9
+ First public release, covering milestones M0–M5. Pre-alpha: the Python API may still change, and the contracts have not had an external review. The Base mainnet deployment is a demo, not a production deployment.
10
+
11
+ ### Added
12
+
13
+ - Pre-review demo on Base mainnet: same contract addresses as testnet (deployer's first transaction), Sourcify-verified, six cards rendered fresh, pinned on a VPS node, minted and frozen by `demo/base/mint-batch.sh`.
14
+ - `scripts/kubo-vps.sh`: native Kubo as a systemd service for a public pinning node. Docker port publishing (rootless especially) left the node unable to accept incoming connections, so gateways couldn't fetch new images.
15
+ - M5 live demo on Base Sepolia: contracts deployed and Sourcify-verified, card #1 rendered through darkroom, pinned, minted and frozen, epoch 1 Merkle root anchored. Outputs and a JS verification script are in `demo/base-sepolia/`, and the README has the walkthrough.
16
+ - On-chain CLI: `submit` (voucher → `Card.mint`), `freeze` (fix tokenURI at ipfs://), `anchor` (Merkle root → Ledger, with the tree dump). Each pre-checks what the contract would refuse and decodes custom-error reverts. `keys` module: Foundry keystore signers (password file for unattended use, or a hidden prompt) alongside `$ARGUS_MINT_SIGNER_KEY`; `mint --account`. `chain.send(value=)`, `chain.explain_revert`.
17
+ - `compose.yaml` with a pinned Kubo node (`make ipfs` / `make ipfs-down`); the example template now names real darkroom models. Verified end to end: darkroom render → Kubo pin → metadata → signed voucher.
18
+ - darkroom render client (`darkroom.DarkroomClient`): guarded generation over SSE with record and image-hash checks, outcomes mapped onto the retry protocol; `argus-mint preflight` checks every template combination before approval; live `mint` (`--darkroom`, `--ipfs-api`). Templates gain `sampling` and weighted LoRAs with trigger words.
19
+ - Keyless RPC: public Base endpoints with `chain.connect()` failover; contract verification defaults to Sourcify (free), with Blockscout or Basescan as options.
20
+ - Base as the target chain: `networks` module (Base 8453, Base Sepolia 84532), `mint --network`, Foundry RPC and Basescan verification config, `make deploy`, `.env.example`, and `Web3Rpc(head="safe"|"finalized")` for following OP Stack safe/finalized heads.
21
+ - M4 verifier: ES module under `verifier/` (Merkle proofs, Ledger root/commitment/reveal reads over JSON-RPC, on-chain `verify` cross-check, drand quicknet BLS beacon verification), tested against OpenZeppelin and Python proof fixtures, real quicknet rounds, and JSON-RPC responses recorded from anvil.
22
+ - M3 clients: `render` (darklight `Protocol`, safety rejections retried with sha256-derived seeds up to `max_attempts`, every attempt recorded; non-safety errors stop at once), `pin` (`Pinner` protocol, `KuboPinner` for a self-hosted node, `LocalPinner` for tests/dry runs), `ledger` (`LedgerClient`), `indexer` (stateless confirmed-blocks follower with deep-reorg detection, `Web3Rpc` adapter), `chain` helpers, and the CLI (`templates`, `mint --dry-run`).
23
+ - M2 contracts: `Card` (ERC-721 + ERC-4906 + ERC-2981, EIP-712 voucher mint, live base URI then per-token freeze to `ipfs://`), `Deed` (ERC-721 + ERC-5192 soulbound, one deed per record id, holder may burn), `Ledger` (sha256 commit/reveal and Merkle-root anchors per epoch, write-once), `Deploy.s.sol`. Forge tests cover every revert, fuzz voucher fields and proofs, and consume Python-built fixtures; anvil-backed pytest mints a Python-signed voucher and verifies Python-built proofs on a live node.
24
+ - Committed contract ABIs in `src/argus_mint/abi/` (`make abi`), checked against the build in CI.
25
+ - M1 pure core: `templates` (registry, validation, `fill`; template hash excludes `status`), `metadata` (canonical ERC-721 JSON, `content_hash` over image + provenance), `merkle` (OpenZeppelin `StandardMerkleTree` `["bytes32"]` port with `standard-v1` dump/load), `vouchers` (EIP-712 `MintVoucher` for the `Card` contract), `siwe` (EIP-4361 build/parse/verify).
26
+ - Merkle fixtures generated by `@openzeppelin/merkle-tree@1.0.8` (`make fixtures`) for 1, 2, 3, 7 and 1,000 leaves.
27
+ - M0 scaffold: Python package from `argus-pkg-template`, Foundry project under `contracts/` with OpenZeppelin Contracts v5.4.0 and forge-std v1.17.0 as submodules, Makefile targets for both toolchains, standalone CI (Python + contracts).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smk762
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.5
2
+ Name: argus-mint
3
+ Version: 0.1.0
4
+ Summary: Provenance tokens for game assets: render from reviewed templates, pin to IPFS, mint ERC-721 cards and soulbound deeds, and anchor commitments and Merkle roots on-chain
5
+ Project-URL: Repository, https://github.com/smk762/argus-mint
6
+ Project-URL: Homepage, https://github.com/smk762/argus-mint#readme
7
+ Project-URL: Issues, https://github.com/smk762/argus-mint/issues
8
+ Project-URL: Changelog, https://github.com/smk762/argus-mint/blob/main/CHANGELOG.md
9
+ Project-URL: Argus suite, https://github.com/smk762?tab=repositories&q=argus
10
+ Author: smk762
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: commit-reveal,eip-4361,eip-712,erc-5192,erc-721,ethereum,foundry,game-assets,generative-ai,ipfs,merkle-tree,nft,provenance,siwe,solidity,soulbound
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Framework :: Pydantic :: 2
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Security :: Cryptography
24
+ Classifier: Topic :: Software Development :: Libraries
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: eth-abi>=5.0
28
+ Requires-Dist: eth-account>=0.13
29
+ Requires-Dist: eth-utils>=5.0
30
+ Requires-Dist: httpx>=0.27
31
+ Requires-Dist: pycryptodome>=3.20
32
+ Requires-Dist: pydantic>=2.7
33
+ Requires-Dist: pyyaml>=6.0
34
+ Requires-Dist: structlog>=24.0
35
+ Provides-Extra: chain
36
+ Requires-Dist: web3>=7.0; extra == 'chain'
37
+ Provides-Extra: cli
38
+ Requires-Dist: rich>=13.0; extra == 'cli'
39
+ Requires-Dist: typer>=0.12; extra == 'cli'
40
+ Provides-Extra: dev
41
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
42
+ Requires-Dist: pytest>=8.0; extra == 'dev'
43
+ Requires-Dist: ruff==0.15.16; extra == 'dev'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # argus-mint
47
+
48
+ 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.
49
+
50
+ 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.
51
+
52
+ Part of the [Argus suite](https://github.com/smk762?tab=repositories&q=argus).
53
+
54
+ > 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.
55
+
56
+ ## What's in the box
57
+
58
+ | Part | What it does |
59
+ | --- | --- |
60
+ | `argus_mint` (Python) | template registry, darklight render client with retries, IPFS pinning, canonical metadata, OpenZeppelin-compatible Merkle trees, EIP-712 mint vouchers, SIWE, a reorg-safe event indexer |
61
+ | `contracts/` (Foundry) | `Card` (ERC-721 + ERC-4906 + ERC-2981, voucher mint), `Deed` (ERC-721 + ERC-5192 soulbound), `Ledger` (commit/reveal/anchor notary) |
62
+ | `verifier/` (ES module) | verify Merkle proofs against a Ledger root, revealed secrets against commitments, and drand quicknet beacons |
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ uv pip install argus-mint # pure Python core
68
+ uv pip install "argus-mint[chain]" # + web3 for the indexer and ledger client
69
+ uv pip install "argus-mint[cli]" # + the argus-mint command
70
+ ```
71
+
72
+ ## Quick look
73
+
74
+ ```bash
75
+ argus-mint templates templates/ # validate templates, show status and hash
76
+ argus-mint mint --template my-approved.yaml \
77
+ --var role=ranger --var colour=none --var pose=standing --seed 81234 \
78
+ --asset unit-17 --to 0x7099…79C8 --name "Hana of the Strait" \
79
+ --card 0x5FbD…0aa3 --chain-id 31337 --dry-run # prints metadata, voucher and signature
80
+ ```
81
+
82
+ ```python
83
+ from argus_mint import merkle, templates, vouchers
84
+ from argus_mint.metadata import card_metadata, content_hash
85
+
86
+ tree = merkle.build(leaf_hashes) # OpenZeppelin StandardMerkleTree-compatible
87
+ proof = tree.proof(3) # verifies in Ledger.verify and MerkleProof.verify
88
+ ```
89
+
90
+ ## Develop
91
+
92
+ Needs [uv](https://docs.astral.sh/uv/), [Foundry](https://book.getfoundry.sh/) and Node (for fixtures and the verifier).
93
+
94
+ ```bash
95
+ git clone --recurse-submodules https://github.com/smk762/argus-mint
96
+ make install # submodules + venv with all extras
97
+ make check # lint + pytest + forge test
98
+ make help # everything else
99
+ ```
100
+
101
+ Versions are derived from git tags via `hatch-vcs`. Tagging `vX.Y.Z` publishes to PyPI.
102
+
103
+ ## Deploy (Base)
104
+
105
+ ```bash
106
+ cp .env.example .env # RPC URLs and role addresses (no paid API keys needed)
107
+ cast wallet new deployer # new key, straight into Foundry's encrypted keystore; fund its address
108
+ make deploy # Base Sepolia; `make deploy NETWORK=base` for mainnet
109
+ ```
110
+
111
+ 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](docs/deploy.md).
112
+
113
+ ## Rendering with argus-darkroom, pinning with Kubo
114
+
115
+ ```bash
116
+ make ipfs # local Kubo node (compose.yaml): RPC 127.0.0.1:5001, gateway :8080
117
+ argus-mint preflight my-template.yaml --darkroom http://localhost:8106 # reviewer check, every combination
118
+ argus-mint mint --template my-template.yaml --var ... --asset unit-17 --to 0x… --name "…" --card 0x… \
119
+ --darkroom http://localhost:8106 --ipfs-api http://127.0.0.1:5001 # live: renders, pins, signs
120
+ ```
121
+
122
+ On-chain, each step signs with a Foundry keystore account (`--account` / `$ARGUS_MINT_ACCOUNT`) or `$ARGUS_MINT_SIGNER_KEY`:
123
+
124
+ ```bash
125
+ argus-mint submit demo-1.json # send the voucher to Card.mint
126
+ argus-mint freeze --card 0x… --token-id 1 --uri <metadata cid> # fix tokenURI at ipfs://…
127
+ argus-mint anchor leaves.txt --ledger 0x… --epoch 1 --tree epoch-1.json # Merkle root into the Ledger
128
+ ```
129
+
130
+ 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](docs/deploy.md#6-pin-on-a-vps-optional)) so cards stay retrievable when this host is offline.
131
+
132
+ ## Live demo (Base Sepolia)
133
+
134
+ Deployed 2026-11-08 from this repo (`make deploy`), source verified on Sourcify:
135
+
136
+ | Contract | Address |
137
+ |---|---|
138
+ | Card | [`0xCAa3573b5550870A5e15C47c650b8700D70a8fA7`](https://sepolia.basescan.org/address/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7) |
139
+ | Deed | [`0xe44D67bdFd3416F97206fdE7aeE55F9008EB5D7c`](https://sepolia.basescan.org/address/0xe44D67bdFd3416F97206fdE7aeE55F9008EB5D7c) |
140
+ | Ledger | [`0x4b41B687C694bd8bf3778871752637445c2Aa254`](https://sepolia.basescan.org/address/0x4b41B687C694bd8bf3778871752637445c2Aa254) |
141
+
142
+ **Card #1** was made end to end. Every command's output is in [demo/base-sepolia/](demo/base-sepolia/).
143
+
144
+ 1. **Render.** The approved `example-portrait` v1 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 SFW `default` policy (record `cb2bd951…`).
145
+ 2. **Pin.** The image and canonical metadata were pinned on Kubo. The metadata's `provenance` carries the template hash, seed and attempt count.
146
+ 3. **Sign.** A mint voucher was signed for the Card (EIP-712), carrying `contentHash` = keccak256 of the metadata's image and provenance.
147
+ 4. **[Submit](https://sepolia.basescan.org/tx/0x194f515824b65ff610fc61010ff01370745ed7c8a134cca2385d36bc080c5324)** minted token 1.
148
+ 5. **[Freeze](https://sepolia.basescan.org/tx/0x0f3140ec791db3069693ee9cb655f5ded8ca65b3bfcc33c9dfc7e4989de84bb4)** fixed `tokenURI(1)` at `ipfs://bafkreid3o6zha6adg7ndl4rpmnsv7bc22t6ak2ik4opr6oed56v74uhefu`.
149
+
150
+ **Epoch 1 anchor:** four demo records ([records.txt](demo/base-sepolia/records.txt)) were hashed with sha256 into a Merkle tree. Its root, `0x4cafc06c…1ee1f736`, was [anchored in the Ledger](https://sepolia.basescan.org/tx/0x98899e0c2d074f0b63e30af5d7d52ee1ace470024932fa64124cdcda10584695).
151
+
152
+ ### Verify it yourself
153
+
154
+ Anchored records, with the JS verifier and the public RPC (needs only Node):
155
+
156
+ ```bash
157
+ make verifier-install
158
+ node demo/base-sepolia/verify.mjs # each record checks out; a changed one is rejected
159
+ ```
160
+
161
+ 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`:
162
+
163
+ ```python
164
+ from argus_mint import chain
165
+ from argus_mint.metadata import content_hash
166
+ card = chain.contract(chain.connect("base-sepolia"), "Card", "0xCAa3573b5550870A5e15C47c650b8700D70a8fA7")
167
+ # doc = the JSON at card.functions.tokenURI(1).call()
168
+ assert content_hash(doc) == bytes(card.functions.contentHashOf(1).call())
169
+ ```
170
+
171
+ ## Demo on Base mainnet (pre-review)
172
+
173
+ 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.
174
+
175
+ 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](demo/base/mint-batch.sh), and every step's output is in [demo/base/](demo/base/). Each card's on-chain `contentHash` matches its pinned metadata.
176
+
177
+ | Token | Name | Mint | Freeze |
178
+ |---|---|---|---|
179
+ | [1](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/1) | Azure Knight | [tx](https://basescan.org/tx/0x595626d4645f5e067b8e1bd7c8b67cc2ea12c8051a5fb1846784659110496c30) | [tx](https://basescan.org/tx/0x39089c0f17410e044a407f0b9cf7d9125d5ecde71400200702e25df0669f6e50) |
180
+ | [2](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/2) | Emerald Wizard | [tx](https://basescan.org/tx/0x15a60342852c7f40f7ea283a55637195f102b69899bb82a2ff638f1130808bc8) | [tx](https://basescan.org/tx/0x52ead3396a533c8232ecab5540d61f9c3ef3d0bcbbf7b816aad1790088683b28) |
181
+ | [3](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/3) | Steel Cyborg | [tx](https://basescan.org/tx/0xfed072d1830d14c14bf4c60b912c079873e36cdfafc5bdb6cc6640e5b91d2a3e) | [tx](https://basescan.org/tx/0xa783b4aa35d4f382f1d19b21cb46377b70c83cb5a3a6c31f240d991e39935991) |
182
+ | [4](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/4) | Silver Huntress | [tx](https://basescan.org/tx/0xef0aecf8187f30aaa919f3fc48db4dfdd8a34aeb64485851c4ffff09d79a94ae) | [tx](https://basescan.org/tx/0x69af3f6c0ff486aae3f3aa2e63602e22e2c268382becec430a7eecaf7ea6c155) |
183
+ | [5](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/5) | Crimson Orc | [tx](https://basescan.org/tx/0xd3e4dde31bdec93d97d8ed469028eaddd1c98d8e1a33f40b6517cc3e04170858) | [tx](https://basescan.org/tx/0xc8c1b77f81b5b9338a37836d6905e60bb665b06b6d61c61bf4f75237d761ab16) |
184
+ | [6](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/6) | Golden Priest | [tx](https://basescan.org/tx/0x9c7d71c1e30d34eef8ba3e149800d70c7d44f5705127e69e5e81c6ea33d6b192) | [tx](https://basescan.org/tx/0x437f03b5ad49bab8b6505a02179277bce97b395c959518b1c801c923238c0008) |
185
+
186
+ The collection is also on [OpenSea](https://opensea.io/collection/argus-mint-card). The same `contentHash` check as above works with `chain.connect("base")`.
187
+
188
+ ## License
189
+
190
+ MIT
@@ -0,0 +1,145 @@
1
+ # argus-mint
2
+
3
+ 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.
4
+
5
+ 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.
6
+
7
+ Part of the [Argus suite](https://github.com/smk762?tab=repositories&q=argus).
8
+
9
+ > 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.
10
+
11
+ ## What's in the box
12
+
13
+ | Part | What it does |
14
+ | --- | --- |
15
+ | `argus_mint` (Python) | template registry, darklight render client with retries, IPFS pinning, canonical metadata, OpenZeppelin-compatible Merkle trees, EIP-712 mint vouchers, SIWE, a reorg-safe event indexer |
16
+ | `contracts/` (Foundry) | `Card` (ERC-721 + ERC-4906 + ERC-2981, voucher mint), `Deed` (ERC-721 + ERC-5192 soulbound), `Ledger` (commit/reveal/anchor notary) |
17
+ | `verifier/` (ES module) | verify Merkle proofs against a Ledger root, revealed secrets against commitments, and drand quicknet beacons |
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ uv pip install argus-mint # pure Python core
23
+ uv pip install "argus-mint[chain]" # + web3 for the indexer and ledger client
24
+ uv pip install "argus-mint[cli]" # + the argus-mint command
25
+ ```
26
+
27
+ ## Quick look
28
+
29
+ ```bash
30
+ argus-mint templates templates/ # validate templates, show status and hash
31
+ argus-mint mint --template my-approved.yaml \
32
+ --var role=ranger --var colour=none --var pose=standing --seed 81234 \
33
+ --asset unit-17 --to 0x7099…79C8 --name "Hana of the Strait" \
34
+ --card 0x5FbD…0aa3 --chain-id 31337 --dry-run # prints metadata, voucher and signature
35
+ ```
36
+
37
+ ```python
38
+ from argus_mint import merkle, templates, vouchers
39
+ from argus_mint.metadata import card_metadata, content_hash
40
+
41
+ tree = merkle.build(leaf_hashes) # OpenZeppelin StandardMerkleTree-compatible
42
+ proof = tree.proof(3) # verifies in Ledger.verify and MerkleProof.verify
43
+ ```
44
+
45
+ ## Develop
46
+
47
+ Needs [uv](https://docs.astral.sh/uv/), [Foundry](https://book.getfoundry.sh/) and Node (for fixtures and the verifier).
48
+
49
+ ```bash
50
+ git clone --recurse-submodules https://github.com/smk762/argus-mint
51
+ make install # submodules + venv with all extras
52
+ make check # lint + pytest + forge test
53
+ make help # everything else
54
+ ```
55
+
56
+ Versions are derived from git tags via `hatch-vcs`. Tagging `vX.Y.Z` publishes to PyPI.
57
+
58
+ ## Deploy (Base)
59
+
60
+ ```bash
61
+ cp .env.example .env # RPC URLs and role addresses (no paid API keys needed)
62
+ cast wallet new deployer # new key, straight into Foundry's encrypted keystore; fund its address
63
+ make deploy # Base Sepolia; `make deploy NETWORK=base` for mainnet
64
+ ```
65
+
66
+ 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](docs/deploy.md).
67
+
68
+ ## Rendering with argus-darkroom, pinning with Kubo
69
+
70
+ ```bash
71
+ make ipfs # local Kubo node (compose.yaml): RPC 127.0.0.1:5001, gateway :8080
72
+ argus-mint preflight my-template.yaml --darkroom http://localhost:8106 # reviewer check, every combination
73
+ argus-mint mint --template my-template.yaml --var ... --asset unit-17 --to 0x… --name "…" --card 0x… \
74
+ --darkroom http://localhost:8106 --ipfs-api http://127.0.0.1:5001 # live: renders, pins, signs
75
+ ```
76
+
77
+ On-chain, each step signs with a Foundry keystore account (`--account` / `$ARGUS_MINT_ACCOUNT`) or `$ARGUS_MINT_SIGNER_KEY`:
78
+
79
+ ```bash
80
+ argus-mint submit demo-1.json # send the voucher to Card.mint
81
+ argus-mint freeze --card 0x… --token-id 1 --uri <metadata cid> # fix tokenURI at ipfs://…
82
+ argus-mint anchor leaves.txt --ledger 0x… --epoch 1 --tree epoch-1.json # Merkle root into the Ledger
83
+ ```
84
+
85
+ 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](docs/deploy.md#6-pin-on-a-vps-optional)) so cards stay retrievable when this host is offline.
86
+
87
+ ## Live demo (Base Sepolia)
88
+
89
+ Deployed 2026-11-08 from this repo (`make deploy`), source verified on Sourcify:
90
+
91
+ | Contract | Address |
92
+ |---|---|
93
+ | Card | [`0xCAa3573b5550870A5e15C47c650b8700D70a8fA7`](https://sepolia.basescan.org/address/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7) |
94
+ | Deed | [`0xe44D67bdFd3416F97206fdE7aeE55F9008EB5D7c`](https://sepolia.basescan.org/address/0xe44D67bdFd3416F97206fdE7aeE55F9008EB5D7c) |
95
+ | Ledger | [`0x4b41B687C694bd8bf3778871752637445c2Aa254`](https://sepolia.basescan.org/address/0x4b41B687C694bd8bf3778871752637445c2Aa254) |
96
+
97
+ **Card #1** was made end to end. Every command's output is in [demo/base-sepolia/](demo/base-sepolia/).
98
+
99
+ 1. **Render.** The approved `example-portrait` v1 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 SFW `default` policy (record `cb2bd951…`).
100
+ 2. **Pin.** The image and canonical metadata were pinned on Kubo. The metadata's `provenance` carries the template hash, seed and attempt count.
101
+ 3. **Sign.** A mint voucher was signed for the Card (EIP-712), carrying `contentHash` = keccak256 of the metadata's image and provenance.
102
+ 4. **[Submit](https://sepolia.basescan.org/tx/0x194f515824b65ff610fc61010ff01370745ed7c8a134cca2385d36bc080c5324)** minted token 1.
103
+ 5. **[Freeze](https://sepolia.basescan.org/tx/0x0f3140ec791db3069693ee9cb655f5ded8ca65b3bfcc33c9dfc7e4989de84bb4)** fixed `tokenURI(1)` at `ipfs://bafkreid3o6zha6adg7ndl4rpmnsv7bc22t6ak2ik4opr6oed56v74uhefu`.
104
+
105
+ **Epoch 1 anchor:** four demo records ([records.txt](demo/base-sepolia/records.txt)) were hashed with sha256 into a Merkle tree. Its root, `0x4cafc06c…1ee1f736`, was [anchored in the Ledger](https://sepolia.basescan.org/tx/0x98899e0c2d074f0b63e30af5d7d52ee1ace470024932fa64124cdcda10584695).
106
+
107
+ ### Verify it yourself
108
+
109
+ Anchored records, with the JS verifier and the public RPC (needs only Node):
110
+
111
+ ```bash
112
+ make verifier-install
113
+ node demo/base-sepolia/verify.mjs # each record checks out; a changed one is rejected
114
+ ```
115
+
116
+ 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`:
117
+
118
+ ```python
119
+ from argus_mint import chain
120
+ from argus_mint.metadata import content_hash
121
+ card = chain.contract(chain.connect("base-sepolia"), "Card", "0xCAa3573b5550870A5e15C47c650b8700D70a8fA7")
122
+ # doc = the JSON at card.functions.tokenURI(1).call()
123
+ assert content_hash(doc) == bytes(card.functions.contentHashOf(1).call())
124
+ ```
125
+
126
+ ## Demo on Base mainnet (pre-review)
127
+
128
+ 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.
129
+
130
+ 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](demo/base/mint-batch.sh), and every step's output is in [demo/base/](demo/base/). Each card's on-chain `contentHash` matches its pinned metadata.
131
+
132
+ | Token | Name | Mint | Freeze |
133
+ |---|---|---|---|
134
+ | [1](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/1) | Azure Knight | [tx](https://basescan.org/tx/0x595626d4645f5e067b8e1bd7c8b67cc2ea12c8051a5fb1846784659110496c30) | [tx](https://basescan.org/tx/0x39089c0f17410e044a407f0b9cf7d9125d5ecde71400200702e25df0669f6e50) |
135
+ | [2](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/2) | Emerald Wizard | [tx](https://basescan.org/tx/0x15a60342852c7f40f7ea283a55637195f102b69899bb82a2ff638f1130808bc8) | [tx](https://basescan.org/tx/0x52ead3396a533c8232ecab5540d61f9c3ef3d0bcbbf7b816aad1790088683b28) |
136
+ | [3](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/3) | Steel Cyborg | [tx](https://basescan.org/tx/0xfed072d1830d14c14bf4c60b912c079873e36cdfafc5bdb6cc6640e5b91d2a3e) | [tx](https://basescan.org/tx/0xa783b4aa35d4f382f1d19b21cb46377b70c83cb5a3a6c31f240d991e39935991) |
137
+ | [4](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/4) | Silver Huntress | [tx](https://basescan.org/tx/0xef0aecf8187f30aaa919f3fc48db4dfdd8a34aeb64485851c4ffff09d79a94ae) | [tx](https://basescan.org/tx/0x69af3f6c0ff486aae3f3aa2e63602e22e2c268382becec430a7eecaf7ea6c155) |
138
+ | [5](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/5) | Crimson Orc | [tx](https://basescan.org/tx/0xd3e4dde31bdec93d97d8ed469028eaddd1c98d8e1a33f40b6517cc3e04170858) | [tx](https://basescan.org/tx/0xc8c1b77f81b5b9338a37836d6905e60bb665b06b6d61c61bf4f75237d761ab16) |
139
+ | [6](https://basescan.org/nft/0xCAa3573b5550870A5e15C47c650b8700D70a8fA7/6) | Golden Priest | [tx](https://basescan.org/tx/0x9c7d71c1e30d34eef8ba3e149800d70c7d44f5705127e69e5e81c6ea33d6b192) | [tx](https://basescan.org/tx/0x437f03b5ad49bab8b6505a02179277bce97b395c959518b1c801c923238c0008) |
140
+
141
+ The collection is also on [OpenSea](https://opensea.io/collection/argus-mint-card). The same `contentHash` check as above works with `chain.connect("base")`.
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,100 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "argus-mint"
7
+ dynamic = ["version"]
8
+ description = "Provenance tokens for game assets: render from reviewed templates, pin to IPFS, mint ERC-721 cards and soulbound deeds, and anchor commitments and Merkle roots on-chain"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "smk762" }]
13
+ keywords = [
14
+ "nft",
15
+ "erc-721",
16
+ "erc-5192",
17
+ "soulbound",
18
+ "eip-712",
19
+ "siwe",
20
+ "eip-4361",
21
+ "merkle-tree",
22
+ "commit-reveal",
23
+ "provenance",
24
+ "ipfs",
25
+ "foundry",
26
+ "solidity",
27
+ "ethereum",
28
+ "game-assets",
29
+ "generative-ai",
30
+ ]
31
+ classifiers = [
32
+ "Development Status :: 3 - Alpha",
33
+ "Environment :: Console",
34
+ "Framework :: Pydantic :: 2",
35
+ "Intended Audience :: Developers",
36
+ "License :: OSI Approved :: MIT License",
37
+ "Programming Language :: Python :: 3",
38
+ "Programming Language :: Python :: 3.11",
39
+ "Programming Language :: Python :: 3.12",
40
+ "Programming Language :: Python :: 3.13",
41
+ "Topic :: Security :: Cryptography",
42
+ "Topic :: Software Development :: Libraries",
43
+ "Typing :: Typed",
44
+ ]
45
+ # Base install is pure Python (no web3): templates, metadata, Merkle, vouchers, SIWE,
46
+ # and the render/pin clients. web3 is only pulled by the `chain` extra.
47
+ dependencies = [
48
+ "pydantic>=2.7",
49
+ "structlog>=24.0",
50
+ "pyyaml>=6.0",
51
+ "eth-account>=0.13",
52
+ "eth-abi>=5.0",
53
+ "eth-utils>=5.0",
54
+ "pycryptodome>=3.20",
55
+ "httpx>=0.27",
56
+ ]
57
+
58
+ [project.optional-dependencies]
59
+ chain = ["web3>=7.0"]
60
+ cli = ["typer>=0.12", "rich>=13.0"]
61
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "ruff==0.15.16"]
62
+
63
+ [project.scripts]
64
+ argus-mint = "argus_mint.cli:app"
65
+
66
+ [project.urls]
67
+ Repository = "https://github.com/smk762/argus-mint"
68
+ Homepage = "https://github.com/smk762/argus-mint#readme"
69
+ Issues = "https://github.com/smk762/argus-mint/issues"
70
+ Changelog = "https://github.com/smk762/argus-mint/blob/main/CHANGELOG.md"
71
+ "Argus suite" = "https://github.com/smk762?tab=repositories&q=argus"
72
+
73
+ [tool.hatch.version]
74
+ source = "vcs"
75
+
76
+ [tool.hatch.build.hooks.vcs]
77
+ version-file = "src/argus_mint/_version.py"
78
+
79
+ [tool.hatch.build.targets.wheel]
80
+ packages = ["src/argus_mint"]
81
+
82
+ [tool.hatch.build.targets.sdist]
83
+ # Python source only: the Foundry project (and its submodules) and the JS verifier
84
+ # are not part of the pip package. The ABIs it needs live in src/argus_mint/abi.
85
+ include = ["/src", "/tests", "/README.md", "/CHANGELOG.md", "/LICENSE", "/pyproject.toml"]
86
+
87
+ [tool.pytest.ini_options]
88
+ asyncio_mode = "auto"
89
+ testpaths = ["tests"]
90
+ filterwarnings = ["ignore::DeprecationWarning"]
91
+ markers = ["anvil: starts a local anvil node (skips without Foundry unless ARGUS_MINT_REQUIRE_ANVIL=1)"]
92
+
93
+ [tool.ruff]
94
+ target-version = "py311"
95
+ line-length = 120
96
+ extend-exclude = ["src/argus_mint/_version.py"]
97
+
98
+ [tool.ruff.lint]
99
+ select = ["E", "F", "W", "I", "UP", "B", "SIM"]
100
+ ignore = ["E501", "B008"]
@@ -0,0 +1,20 @@
1
+ """argus-mint — provenance tokens for game assets.
2
+
3
+ Render from reviewed templates, pin to IPFS, mint ERC-721 cards and soulbound deeds,
4
+ and anchor commitments and Merkle roots on-chain. No game logic lives here.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ try:
10
+ # Written by hatch-vcs at build time (see pyproject [tool.hatch.build.hooks.vcs]).
11
+ from argus_mint._version import __version__
12
+ except ImportError: # running from a source checkout that hasn't been built
13
+ from importlib.metadata import PackageNotFoundError, version
14
+
15
+ try:
16
+ __version__ = version("argus-mint")
17
+ except PackageNotFoundError:
18
+ __version__ = "0.0.0+unknown"
19
+
20
+ __all__ = ["__version__"]
@@ -0,0 +1,5 @@
1
+ """`python -m argus_mint ...` — same app as the `argus-mint` console script."""
2
+
3
+ from argus_mint.cli import app
4
+
5
+ app()
@@ -0,0 +1,63 @@
1
+ """Shared encoding helpers: keccak256, canonical JSON, and Pydantic field types for
2
+ 32-byte values and addresses. Pure; no I/O."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ from typing import Annotated, Any
8
+
9
+ from Crypto.Hash import keccak as _keccak
10
+ from eth_utils import is_address, to_checksum_address
11
+ from pydantic import BeforeValidator, PlainSerializer
12
+
13
+
14
+ def keccak256(data: bytes) -> bytes:
15
+ """Ethereum's keccak256 (not NIST SHA3-256)."""
16
+ return _keccak.new(digest_bits=256, data=data).digest()
17
+
18
+
19
+ def canonical(doc: Any) -> bytes:
20
+ """Canonical JSON: sorted keys, compact separators, UTF-8, no NaN/Infinity.
21
+
22
+ The same document always gives the same bytes, so its hash is stable.
23
+ """
24
+ return json.dumps(doc, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode("utf-8")
25
+
26
+
27
+ def to_bytes32(value: Any) -> bytes:
28
+ """Accept 32 raw bytes or a 0x-prefixed 64-hex-digit string."""
29
+ if isinstance(value, bytes | bytearray):
30
+ b = bytes(value)
31
+ elif isinstance(value, str):
32
+ if not value.startswith("0x") or len(value) != 66:
33
+ raise ValueError("expected 0x followed by 64 hex digits")
34
+ try:
35
+ b = bytes.fromhex(value[2:])
36
+ except ValueError as e:
37
+ raise ValueError("expected 0x followed by 64 hex digits") from e
38
+ else:
39
+ raise ValueError("expected 32 bytes or a 0x hex string")
40
+ if len(b) != 32:
41
+ raise ValueError(f"expected 32 bytes, got {len(b)}")
42
+ return b
43
+
44
+
45
+ def hex0x(b: bytes) -> str:
46
+ return "0x" + b.hex()
47
+
48
+
49
+ def to_address(value: Any) -> str:
50
+ """Validate an address and return its EIP-55 checksum form.
51
+
52
+ A mixed-case address with a wrong checksum is rejected (it's probably a typo).
53
+ """
54
+ if not isinstance(value, str) or not is_address(value):
55
+ raise ValueError(f"not a valid address: {value!r}")
56
+ return to_checksum_address(value)
57
+
58
+
59
+ Bytes32 = Annotated[bytes, BeforeValidator(to_bytes32), PlainSerializer(hex0x, return_type=str, when_used="json")]
60
+ Address = Annotated[str, BeforeValidator(to_address)]
61
+
62
+ UINT64_MAX = 2**64 - 1
63
+ UINT256_MAX = 2**256 - 1
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None