Proof & threat model
Every way a seller could try to keep the money.
This is the threat model the contracts are built against. Every row is covered by a unit test in the registry contract, and the attacks that can be sent as transactions were sent on testnet and refused.
01Seller keeps withdrawing after the sale
withdraw() checks the current NFT owner at call time
ERR_NOT_OWNER02Seller sells while a withdrawal is in flight
The token is locked for the withdrawal; nft_transfer rejects while locked
ERR_TOKEN_LOCKED03Seller lists, then drains the vault before the buyer pays
Withdrawals are blocked while the token has any approval
ERR_TOKEN_LISTED04Seller keeps an access key on the vault
Vaults are created with zero access keys and no upgrade method
no AddKey05Anyone calls the vault directly
The vault accepts calls only from the registry contract
ERR_ONLY_REGISTRY06A stranger approves a derivative into canon
approve() requires the parent's current owner
ERR_NOT_PARENT_OWNER07A studio licenses work with a non-commercial ancestor
buy_license() re-checks clearance across the whole lineage
ERR_NOT_CLEARED08Splits over or under 100%
Registration rejects splits that don't total 10,000 bps
ERR_BAD_SPLITS09Rounding leaks or creates money
Integer math; the remainder goes to the node's vault; sum equals price
ERR_INVARIANT_SUM10An approved file is silently swapped
The file hash is written once at registration; no method can change it
no setter
Registry unit tests
41 passing
Attacks refused on testnet
11 of 11
Keys on the registry and vaults
0
Recorded on NEAR testnet
The whole story, as transactions.
Registry canonhq.testnet. Every row links to the block explorer. Shielded rows are attacks the contract refused.
- 01Publish vault code as NEP-591 global contract Anq6HuuCn243gdRdBjudqvR1uCe8NB8JQzTD5DF9KKtwArCB71ne… ↗
- 02Deploy registry to canonhq.testnet and initialise with the vault hashCXtZrr1A… ↗
- 03ada registers Ember FieldBZjdTYgQ… ↗
- 04ben registers Ember Field — Relief as a derivative of ember9dCPz73M… ↗
- 05ada approves Relief into canon6SiCx6AT… ↗
- 06dee registers Ember Field — Motion as a derivative of ember-relief7ECKs8sP… ↗
- 07ada (not the parent's owner) tries to approve Motionrefused · ERR_NOT_PARENT_OWNERHbPJrGFZ… ↗
- 08studio tries to license Motion before it is approvedrefused · ERR_NOT_CLEARED6Hrquxkq… ↗
- 09ben approves Motion into canon4iLLvMbh… ↗
- 10gil registers Ember Field — Nocturne as a derivative of ember4eiYEmno… ↗
- 11ada approves Nocturne4S2wbwSJ… ↗
- 12hal registers Ember Field — Pixel as a derivative of ember4frNdqus… ↗
- 13ivy registers Moth LightCooAeYpw… ↗
- 14jun registers Moth Light — Fragments as a derivative of moth-light3aPqBWSu… ↗
- 15ivy approves FragmentsHmtcuUaA… ↗
- 16studio tries to license Fragments (parent forbids commercial use)refused · ERR_NOT_CLEARED4bSvXfN9… ↗
- 17lea registers TidalDjRXAp5J… ↗
- 18nox registers Tidal — Neon Remix as a derivative of tidal7hmtbWTD… ↗
- 19lea approves Neon Remix2MTBMttt… ↗
- 20lea revokes Neon Remix8JKH48xc… ↗
- 21studio tries to license the revoked Neon Remixrefused · ERR_NOT_CLEARED4goc1MyP… ↗
- 22kai registers Quarry9cySegDz… ↗
- 23hal tries to derive from Quarry (derivatives closed)refused · ERR_DERIVATIVES_CLOSED6S7b5nYG… ↗
- 24ada registers splits totalling 90%refused · ERR_BAD_SPLITSGQNgq9Vr… ↗
- 25studio licenses Ember Field — Motion for 1.5 Ⓝ (splits: dee vault 0.9, fay 0.225, ben vault 0.21, cy 0.09, ada vault 0.075)HrgXySX1… ↗
- 26studio licenses Quarry for 0.8 ⓃEu9RgUUb… ↗
- 27eve (not the owner) tries to withdraw Relief's vaultrefused · ERR_NOT_OWNERAAeV3SwD… ↗
- 28ben lists Relief on a marketplace (nft_approve)6wr4yTjW… ↗
- 29ben tries to drain Relief's vault while it is listedrefused · ERR_TOKEN_LISTED6825sV2P… ↗
- 30eve's marketplace sells Relief for 1 Ⓝ via NEP-199 nft_transfer_payout: payout = ben 0.8, ember vault 0.2 (resale royalty)F2G9dcGT… ↗
- 31marketplace pays the seller ben 0.8 ⓃEwpCiJPV… ↗
- 32marketplace pays the 0.2 Ⓝ royalty straight into Ember's vaultEbYDjVU4… ↗
- 33ben (the seller) tries to withdraw after the salerefused · ERR_NOT_OWNERBV24rL8m… ↗
- 34eve calls Relief's vault release directly, bypassing the registryrefused · ERR_ONLY_REGISTRY6fxRFGxn… ↗
- 35eve (the buyer) withdraws 0.21 Ⓝ earned before she bought it663gSSN1… ↗
- 36cy claims 0.09 Ⓝ: her credit did not move with the salejDdxY5UA… ↗
- 37dee withdraws Motion's vault (0.9 Ⓝ)5X5uSG6h… ↗
- 38ada withdraws Ember's vault: 0.075 Ⓝ licence share + 0.2 Ⓝ resale royaltyBKJvR3Rr… ↗
- 39fay claims her 0.225 Ⓝ score shareArW18Fes… ↗
- 40fay tries to claim twicerefused · ERR_NOTHING_TO_CLAIM5mNeHjdt… ↗
- 41Delete registry access key ed25519:4T7JEo26gy…Ck5zh9yc… ↗