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.
- argus_mint-0.1.0/.gitignore +20 -0
- argus_mint-0.1.0/CHANGELOG.md +27 -0
- argus_mint-0.1.0/LICENSE +21 -0
- argus_mint-0.1.0/PKG-INFO +190 -0
- argus_mint-0.1.0/README.md +145 -0
- argus_mint-0.1.0/pyproject.toml +100 -0
- argus_mint-0.1.0/src/argus_mint/__init__.py +20 -0
- argus_mint-0.1.0/src/argus_mint/__main__.py +5 -0
- argus_mint-0.1.0/src/argus_mint/_codec.py +63 -0
- argus_mint-0.1.0/src/argus_mint/_version.py +24 -0
- argus_mint-0.1.0/src/argus_mint/abi/Card.json +1342 -0
- argus_mint-0.1.0/src/argus_mint/abi/Deed.json +874 -0
- argus_mint-0.1.0/src/argus_mint/abi/Ledger.json +539 -0
- argus_mint-0.1.0/src/argus_mint/abi/__init__.py +19 -0
- argus_mint-0.1.0/src/argus_mint/chain.py +101 -0
- argus_mint-0.1.0/src/argus_mint/cli.py +472 -0
- argus_mint-0.1.0/src/argus_mint/darkroom.py +213 -0
- argus_mint-0.1.0/src/argus_mint/indexer.py +152 -0
- argus_mint-0.1.0/src/argus_mint/keys.py +95 -0
- argus_mint-0.1.0/src/argus_mint/ledger.py +76 -0
- argus_mint-0.1.0/src/argus_mint/merkle.py +126 -0
- argus_mint-0.1.0/src/argus_mint/metadata.py +121 -0
- argus_mint-0.1.0/src/argus_mint/networks.py +72 -0
- argus_mint-0.1.0/src/argus_mint/pin.py +84 -0
- argus_mint-0.1.0/src/argus_mint/py.typed +0 -0
- argus_mint-0.1.0/src/argus_mint/render.py +151 -0
- argus_mint-0.1.0/src/argus_mint/siwe.py +284 -0
- argus_mint-0.1.0/src/argus_mint/templates.py +264 -0
- argus_mint-0.1.0/src/argus_mint/vouchers.py +140 -0
- argus_mint-0.1.0/tests/__init__.py +0 -0
- argus_mint-0.1.0/tests/conftest.py +127 -0
- argus_mint-0.1.0/tests/fixtures/drand_quicknet.json +32 -0
- argus_mint-0.1.0/tests/fixtures/ledger_rpc.json +156 -0
- argus_mint-0.1.0/tests/fixtures/merkle.json +1323 -0
- argus_mint-0.1.0/tests/fixtures/proofs.json +1049 -0
- argus_mint-0.1.0/tests/fixtures/vouchers.json +25 -0
- argus_mint-0.1.0/tests/test_anvil_clients.py +135 -0
- argus_mint-0.1.0/tests/test_anvil_contracts.py +69 -0
- argus_mint-0.1.0/tests/test_cli.py +151 -0
- argus_mint-0.1.0/tests/test_cli_chain.py +135 -0
- argus_mint-0.1.0/tests/test_darkroom.py +176 -0
- argus_mint-0.1.0/tests/test_fixtures.py +17 -0
- argus_mint-0.1.0/tests/test_indexer.py +98 -0
- argus_mint-0.1.0/tests/test_keys.py +94 -0
- argus_mint-0.1.0/tests/test_merkle.py +78 -0
- argus_mint-0.1.0/tests/test_metadata.py +119 -0
- argus_mint-0.1.0/tests/test_networks.py +57 -0
- argus_mint-0.1.0/tests/test_pin.py +62 -0
- argus_mint-0.1.0/tests/test_render.py +89 -0
- argus_mint-0.1.0/tests/test_siwe.py +136 -0
- argus_mint-0.1.0/tests/test_smoke.py +8 -0
- argus_mint-0.1.0/tests/test_templates.py +143 -0
- 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).
|
argus_mint-0.1.0/LICENSE
ADDED
|
@@ -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,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
|