NFTs
A marketplace and a candy machine on COTI, where the metadata is sealed to whoever holds the token.
What is different here
An NFT on any other chain is public. The image, the traits, the unlockable content behind it - all of it sits on a server or on IPFS, and 'reveal after mint' means somebody has agreed not to flip a flag early. It is a promise, and promises are broken by accident as often as on purpose.
On COTI it is arithmetic. Every DEVOXPAD collection carries two halves: a public preview that any marketplace can render, and private metadata held on-chain as a ciphertext. The private half is sealed to the current owner's key, and re-sealed to the new owner every time the token moves. Nobody who does not hold it can read it - not the creator, not the marketplace, not an indexer, not us.
A card with nothing on it is not a listing. If everything were encrypted, no marketplace could show the collection at all and nobody could decide whether to buy. So the preview stays public on purpose, and the part worth protecting is the part that gets sealed.
What the chain actually stores
| Value | Visibility | Who can read it |
|---|---|---|
| previewURI | Public | Anyone. It is meant to be rendered. |
| Traits, name, description | Public | Pinned to IPFS alongside the preview. |
| The sealed metadata | Ciphertext | The current holder, and only them. |
| Owner, supply, price | Public | Ordinary ERC-721 state. |
Two formats
The Studio launches one of two contracts, and the choice is not cosmetic - they behave differently because they are for different things.
- 1Scheduled drop (ERC-721)
A fixed run of unique tokens with a supply that cannot grow, an optional start time, an optional allowlist presale, and a per-wallet cap. This is the candy machine. Each token is unique, has one owner, and its private metadata is sealed to that owner.
- 2Open collection (ERC-1155)
Editions the creator keeps adding to, each mintable by many people at once, with a supply that can be left open-ended. A print shop rather than a gallery opening.
How privacy works when an edition has many holders
A unique token has exactly one owner, so COTI seals its metadata to that one address. An edition has hundreds of holders at once, so there is no single address to seal to. Instead each edition keeps one network ciphertext - readable by nobody, including the contract - and every holder gets their own copy of it sealed to their own key the first time a copy reaches them.
The same secret, encrypted two hundred separate ways. Each one is useless to the other hundred and ninety-nine. This is verified on a live network rather than asserted: the test mints an edition, sends one copy to a second wallet with its own onboarded key, and checks that both read the same plaintext from different ciphertext, while a third wallet holding a valid COTI key reads only noise.
Access is granted on receipt and deliberately never revoked when a holder's balance falls to zero. Revoking would be theatre - they have already read it, and a chain cannot un-tell somebody something - and it would punish the honest case where somebody sells one of three copies.
Solo, or paired with a token
The second choice in the Studio is what the collection is worth holding for.
- 1Solo
The collection stands alone. Holders own the art and its sealed metadata, and nothing else. No rewards, no pool, no promises.
- 2Paired with $token
The launcher deposits a reward budget in the same flow, and a staking pool opens against it. Holders stake their NFTs and earn a fixed rate per NFT per year, paid from tokens the staking contract already holds.
The escrow is transferred before the pool opens. That ordering is the whole guarantee: the yield is paid from tokens that already exist in the contract, not from an intention to fund it later. When the budget runs out, rewards stop - and every pool publishes its runway so you can see how long it has.
How the APY is worked out
A launcher sets two numbers: how much one staked NFT earns per year, and a notional value per NFT to measure that against. The percentage is simply the first divided by the second.
apyBps = rewardPerNftPerYear * 10000 / notionalPerNft
If the notional is zero - which is the honest setting for a free mint, because there is no cost basis to be a percentage of - apyBps returns 0 and the interface shows the absolute rate instead, e.g. '500 DEVOX per NFT per year'. Inventing a notional so a percentage could be displayed would be inventing the percentage.
DEVOXPAD Genesis
The official collection, and the reference implementation of everything above.
- Supply
- 10,000
- Price
- Free mint
- Per wallet
- 10
- Launch method
- Paired with $DEVOX
- Reward
- 500 DEVOX per NFT per year
- Escrowed up front
- 5,000,000 DEVOX
- Royalty
- 5% to the creator
Its address was mined with CREATE2 to end in 8888, like every DEVOXPAD launch, and it carries the official badge on the marketplace. The badge is set by the marketplace owner and cannot be set by a collection about itself, which is what makes it worth anything.
The marketplace
Listings are approval-based. Your token stays in your wallet from the moment you list until the moment somebody buys it, and the marketplace only ever moves it as part of a sale that pays you in the same transaction.
It could not, without breaking the privacy. Transferring a token to an escrow contract would re-seal its private metadata to the escrow contract - which can hold a ciphertext but has no key to read it, and would then have to re-seal again to the buyer. Leaving the token with its owner avoids two pointless MPC round trips and one very confusing intermediate state.
Because a listing does not take custody, it can go stale: the seller may transfer or sell the token elsewhere. Rather than hiding those, every listing is checked against the chain and a dead one is shown with the reason it cannot be filled.
Offers
Offers work the other way round. An offer escrows the bidder's ERC-20 up front, because an offer that cannot be paid is not an offer. Cancelling returns it, and accepting settles both sides at once.
Unlocking what you own
Press Unlock on a token you hold. The first time, your wallet signs once to derive your COTI AES key; after that it is cached in your browser and every unlock is instant.
- 1Read the ciphertext
tokenURI on a drop, or secretOf(editionId, you) on an open collection. Both return a ctString - an array of numbers, not a URL.
- 2Decrypt locally
The SDK decrypts it in the page with your key. Nothing is sent anywhere; the plaintext exists only in that tab.
- 3It follows the token
Sell it and the contract re-seals the metadata to the buyer on transfer. Your cached key stops opening it, because it is no longer yours.
The AES key is per account and per network. COTI mainnet and testnet run separate MPC networks, so a key derived on one does not decrypt the other's ciphertext. The app keys its cache by both, which is why switching networks does not silently show you the wrong thing.
Contracts
The NFT stack on COTI mainnet, chain 2632500.
- DevoxNFTFactory
- 0xca4E24923724C09F905593988487338780e3424a
- DevoxNFTEditionsFactory
- 0x47506dFEA23658333178eb52997e549Bf197E079
- DevoxNFTMarket
- 0x83dAA54A3d5D96434458a294Af60a39A6EF04791
- DevoxNFTStaking
- 0x2438202dd999022da10c6E6ac914cBC6a72E0cd2
- DEVOXPAD Genesis
- 0x262ee68C9a01fC3f362e06c857CF0D6384898888
Reading a sealed value yourself
import { Wallet, JsonRpcProvider } from "@coti-io/coti-ethers";
const wallet = new Wallet(PRIVATE_KEY, new JsonRpcProvider("https://mainnet.coti.io/rpc"));
await wallet.generateOrRecoverAes();
const drop = new Contract(GENESIS, dropAbi, wallet);
// Not a URL - a ciphertext sealed to whoever owns this token.
const sealed = await drop.tokenURI(1n);
// decryptValue is async. Forgetting to await it returns "[object Promise]",
// which is a string, and will pass a naive "did I get something back" check.
const plaintext = await wallet.decryptValue(sealed);In Telegram
The bot shares one wallet link with the website: connect once, and both sides know who you are. It can browse collections, show a collection's stats, and report what you hold and what you are earning.
/nft the marketplace: official and newest collections /nft <address> one collection - supply, price, staking, whether it is official /mynft what you hold, and what it is earning /nftstake every pool, its rate and its runway
Decrypting needs your COTI AES key. Putting that key anywhere near a server - even briefly, even ours - would undo the entire point of sealing the metadata in the first place. So the bot shows you what you own and links you to the browser, where the key stays.