The community takes over.
The original TAO Punks team deployed a contract with an unfrozen setBaseURI() — a kill switch that lets the owner rug every single holder's metadata and images at any moment. When the community discovered this, the team took the site down instead of fixing it.
So we fixed it ourselves.
| V1 Problem | V2 Fix |
|---|---|
setBaseURI() can rug all metadata |
No setter exists. URI is set once in the constructor. There is no code path to change it — ever. |
freezeMetadata() requires trust |
Removed. Nothing to freeze when there's no setter. |
setDefaultRoyalty() can extract fees |
No ERC2981. 0% royalties, hardcoded at the interface level. |
mint() public payable |
Removed. No public minting. Airdrop only. |
ownerMint() unlimited |
Removed. airdropBatch() is owner-only and permanently locked after finalization. |
setPayoutRecipient() redirects funds |
Removed. No mint revenue to redirect. |
| No burn function | burn(tokenId) — holders control their own tokens. |
| Single-step ownership transfer | Ownable2Step — requires the new owner to explicitly accept. |
- 3,333 tokens — same max supply
- "TAO Punks" / "TAOPUNK" — same name and symbol
- Token IDs 1–3333 — every V2 token ID matches its V1 original
- Same IPFS metadata — same CID, same images, same traits
- ERC721A — same gas-efficient standard
Your punk looks the same. Your token ID is the same. The only difference is that nobody can pull the rug.
TaoPunksV2
├── ERC721A — Gas-optimized ERC721 (Chiru Labs v4.3.0)
├── ERC721ABurnable — Holder-controlled burn
├── Ownable2Step — Two-step ownership transfer (OpenZeppelin v5.1)
├── Pausable — Emergency circuit breaker
└── ReentrancyGuard — Reentrancy protection
122 lines of Solidity. No bloat, no hidden functions, no admin backdoors.
| Function | Access | Purpose |
|---|---|---|
airdropBatch(address[]) |
Owner only | Mint tokens to snapshot recipients in order |
finalizeAirdrop() |
Owner only | Permanently lock minting — irreversible |
burn(tokenId) |
Token holder | Burn your own token |
pause() / unpause() |
Owner only | Emergency halt on all transfers |
renounceOwnership() |
Disabled | Always reverts — prevents lockout |
- Snapshot all 3,333 V1 token owners from on-chain data
- Build ordered recipient array:
recipients[0]= owner of V1 token #1, etc. - Replace confirmed team wallets with
0x000000000000000000000000000000000000dEaD - Execute
airdropBatch()in chunks of 100 (34 transactions) - Finalize — permanently lock minting
8 tokens from 3 confirmed team wallets are sent to the dead address:
| Token IDs | V1 Wallet | Evidence |
|---|---|---|
| #91, #2034, #2036 | 0x58c9...20d6 |
Contract deployer |
| #92, #443, #2859 | 0xC68A...77a9 |
Direct deployer transfers |
| #442, #2858 | 0xDC97...0d41 |
Direct deployer transfers |
These wallets were identified through on-chain forensic analysis of deployer transfer histories. Only wallets with confirmed deployer links are burned. The community voted on this list.
V2 points to the exact same IPFS metadata as V1:
tokenURI(1)
→ ipfs://bafybeielhkzlrzz6dhz4ixgtywf43s3z7mdpk4fysaqapehz3bijck6cqa/1
→ { "image": "ipfs://bafybeicvdilmqgjf23lrb4heggim3humcug7tcelcfayvnijvsbhphjboa/1.png" }
| Asset | CID | Files |
|---|---|---|
| Metadata JSON | bafybeielhkzlrzz6dhz4ixgtywf43s3z7mdpk4fysaqapehz3bijck6cqa |
3,333 |
| Image PNGs | bafybeicvdilmqgjf23lrb4heggim3humcug7tcelcfayvnijvsbhphjboa |
3,333 |
IPFS is content-addressed. Same CID = same data. The images and traits are identical.
50 out of 50 tests passing. Full test suite in test/TaoPunksV2.t.sol (781 lines).
| Category | Tests | Status |
|---|---|---|
| Constructor & Metadata | 5 | All pass |
| Airdrop (core + edge cases) | 10 | All pass |
| Finalization | 4 | All pass |
| Burn | 5 | All pass |
| Pause | 6 | All pass |
| Ownership (Ownable2Step) | 4 | All pass |
| Interface (no ERC2981) | 2 | All pass |
| View helpers | 2 | All pass |
| Fuzz tests (256 runs each) | 2 | All pass |
| Security | 5 | All pass |
| Full 3,333-token simulation | 1 | All pass |
| Gas benchmarks | 3 | All pass |
| End-to-end CTO scenario | 1 | All pass |
- No public mint function exists
- Owner cannot transfer or burn holder tokens
- Max supply (3,333) enforced as compile-time constant
- Reentrancy guard on airdropBatch
renounceOwnership()permanently disabled- No
setBaseURI— verified at bytecode level supportsInterface(ERC2981)returnsfalse
| Operation | Gas |
|---|---|
| 100-token airdrop batch | ~4,700,000 |
| Single burn | ~28,000 |
| Single transfer | ~31,000 |
# Install Foundry: https://book.getfoundry.sh/getting-started/installation
forge install
forge build
forge test -vv# Set PRIVATE_KEY in ../.env
bash script/deploy-taopunks-v2.shnode script/execute-airdrop.js <V2_CONTRACT_ADDRESS>| Chain | Bittensor EVM |
| Chain ID | 964 |
| V1 Contract | 0xd7553eF9AFf4827451643dd13181D98A4832d718 |
ERC-8004 + ERC-8041 | Pay-Per-Call AI Agents | 100% Holder Revenue
The Agent Registry turns each TAO Punk into a queryable AI agent endpoint. Punk holders earn TAO every time someone queries their agent. No protocol fee — 100% of revenue goes to holders.
1. Holder activates their punk as an agent (one-time, irreversible)
2. Anyone queries the agent by paying TAO (0.0001 - 1 TAO per query)
3. Relay fulfills the query off-chain, credits 100% to the holder
4. Holder calls claim() to withdraw accumulated earnings
TaoPunkAgentRegistry.sol (454 lines)
├── IERC8041Collection — Fixed-supply agent NFT standard
├── AccessControl — Role-based permissions (Admin, Governor, Fulfiller)
└── ReentrancyGuard — Mutex on query() and claim()
| Decision | Rationale |
|---|---|
| 100% to holders | No protocol fee, no referrer split. Every wei of query revenue goes to the punk owner. |
| Pull-based claim() | Fees accumulate in pendingWithdrawals. Holders withdraw on their own schedule. Eliminates DOS vectors. |
| Immutable economics | Fee bounds (0.0001-1 TAO) and revenue model are Solidity constant — no admin can change them. |
| fulfill() has zero external calls | Pure storage write. Cannot be griefed by malicious holder contracts. |
| Agent bound to punk | Control follows ownerOf(). Sell your punk = sell your agent. No separate token. |
MIN_QUERY_FEE = 0.0001 ether // Minimum per query
MAX_QUERY_FEE = 1 ether // Maximum per query
// fulfill() credits 100% to holder
// claim() withdraws accumulated balance
// 0% protocol fee. 0% referrer. 100% holder.96 tests, 5 fuzz campaigns, 100% pass rate.
| Category | Tests |
|---|---|
| Activation + Management | 17 |
| Query / Fulfill / Claim | 15 |
| Refund / Expiry | 3 |
| Governance | 7 |
| ERC-8041 Compliance | 4 |
| Hardened Security | 22 |
| Claim Edge Cases | 10 |
| Fuzz (1,281 runs) | 5 |
| Gas Benchmarks | 4 |
| End-to-End | 1 |
| TOTAL | 96 |
Findings: 2 HIGH (both RESOLVED), 1 LOW (acknowledged), 5 INFO (documented).
Full audit report: TaoPunks_AgentRegistry_Security_Audit.pdf
cd contracts
forge install
forge build
forge test --match-contract TaoPunkAgentRegistryTest -vv| Contract | Lines | Purpose |
|---|---|---|
src/TaoPunkAgentRegistry.sol |
454 | ERC-8004 agent registry with pay-per-call |
src/TaoPunksV2.sol |
122 | Immutable ERC721A (CTO collection) |
test/TaoPunkAgentRegistry.t.sol |
~1500 | 96-test security suite |
test/TaoPunksV2.t.sol |
781 | 50-test V2 suite |
Built by the community, for the community. No team allocation. No royalties. No kill switches. Just punks.