func Approve
crossing ActionApprove grants (or, to the empty address, withdraws) the per-token approval. Refused while the token is held: approvals do not pass through a ledger hook, so this is the one place the hold is checked.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package nsnft is the token, and only the token.
Package nsnft is the token, and only the token.
WHY THIS REALM EXISTS AT ALL.
A name here is a GRC-721. An external marketplace that wants to list one has to hard-code an address, and whatever address that is becomes the thing we can never move. Until now the interface was split: the READS lived on nsdata, which is permanent, and the WRITES — Approve, SetApprovalForAll, TransferFrom — lived on nslogic, which changed address three times in a single day. So the half an integrator needs in order to settle a sale was the half that kept moving, and the reads kept answering while it did, which is the worst way for it to break.
The fix is to pick ONE realm to freeze and make it the smallest, most boring one there is. That is this realm: an ownership ledger, the approval bookkeeping GRC-721 requires, the governance that decides who may mint, and nothing else. No prices, no expiry, no profiles, no policy — none of the things that will certainly need changing.
Everything with an opinion in it stays in the registry, which becomes replaceable BECAUSE ownership no longer lives there. A migration then moves records, not tokens: nobody's holdings are stranded and no listing anywhere goes stale, because the token contract never moved.
THE LEDGER IS THE CHAIN'S OWN gno.land/p/nt/grc721/v0, NOT A COPY.
chain.Emit stamps the package whose code emits, and wallets and indexers (Adena, the Onbloc indexer) accept only Transfer and NewToken events stamped gno.land/p/nt/grc721/v0. The first version of this realm minted on a vendored GRC-721 deployed under our own p/ path, so every event it would ever have emitted carried OUR path, and no wallet would ever have listed a single name — permanently, because events cannot be re-emitted and this realm cannot be replaced. The ledger object lives here, created once in init; only this realm holds its PrivateLedger, and no Teller is ever handed out (see the note on holds below).
THIS REALM HAS ITS OWN GOVERNANCE, AND THAT IS THE POINT.
It would be shorter to ask nsdata who the admin is. It would also re-couple this realm to the exact thing it exists to let us replace — nsnft would then depend on one nsdata forever and we would have moved the problem rather than solved it. So roles, trust and the timelock are local (governance.gno), and nsdata is not imported.
WHAT MAY BE CHANGED LATER AND WHAT MAY NOT.
May, slowly: which registries are trusted, the roles, the timelock, and which view realm draws the art. May, instantly: withdrawing a registry's trust or the art, which only ever removes a power. Through a trusted registry only: the hold bits on a token. May not: anything else. If this realm ever needs a new function, the design has failed and the honest answer is that it was not small enough to freeze.
Approve grants (or, to the empty address, withdraws) the per-token approval. Refused while the token is held: approvals do not pass through a ledger hook, so this is the one place the hold is checked.
Burn destroys a token. Reachable only by a trusted registry, which calls it when a name lapses past its grace period and is reclaimed.
NOT reachable by the holder. A GRC-721 that lets an owner burn is correct for collectibles and wrong here: the token is a lease on a name, and destroying it would leave the registry holding a record for a name whose token no longer exists. Letting go of a name is letting it expire.
The hold and the epoch are cleared by the burn hook (holds.OnBurn), the per-token approval by the ledger itself.
ClearRenderer withdraws the art at once, approval included, so the realm that drew it cannot simply register again: approving one takes the timelock. Admin, recovery or guardian.
Epoch reports the ownership epoch of a token, or 0 for one that does not exist. See epochs.
ExecutePending applies a matured change. Anyone may call it.
ID is the collection id wallets and indexers key on: "<this realm's path>.MNS.0000000".
ListPending names the kinds with a live pending change, comma-separated.
LockBits reports the current hold; 0 means free to move.
Mint creates a token, optionally already held in place.
`lockBits` is the hold it starts with — a free-tier name is minted locked so there is never an instant in which it could be approved and sold before the registry got round to locking it (nothing runs between the ledger write and the line that sets the bits). Zero for an ordinary paid name.
ProposeAdminTransfer rotates the admin after the timelock. Admin or recovery; when recovery proposes it, the admin cannot cancel it.
ProposeRecoveryTransfer rotates recovery after the timelock — twice the timelock when the admin proposes it. Admin or recovery; recovery can cancel either, so a stolen admin key cannot take it.
ProposeRenderer approves a view realm to draw the art, after the timelock. Admin only. The realm then registers itself (art.gno).
ProposeTimelockDelay changes the timelock, after the current one. Admin only.
ProposeTrust starts trusting a registry, by package path, after the timelock. Admin only.
RegisterRenderer is called BY the approved view realm, handing over the object that draws. Refused from anywhere else.
Render builds its string by concatenation rather than with ufmt.
NO DEPENDENCIES THIS REALM DOES NOT ABSOLUTELY NEED. It is meant to be deployed once and never replaced, so every import is a permanent commitment to somebody else's package. grc721/v0 (with avl and seqid, which it already depends on) is here because it IS the thing this realm is; a formatter, for one page of text, is not worth that.
RevokeApproval withdraws a token's per-token approval.
grc721/v0 revokes by approving the empty address; this keeps the named entry point the site and tools already call, and allows it while the token is held — withdrawing a permission is the safe direction. The owner, or an operator acting for the owner, the same two callers Approve accepts.
SafeTransferFrom is kept for the standard signature. grc721/v0 has no receiver hook (gno has no contract-receiver registry to check), so it is TransferFrom — as the vendored version effectively was, its receiver check being a stub that always said yes.
SetLock turns one hold bit on or off. Trusted registry only.
The registry decides what a bit means; this realm only guarantees that while any bit is set, nothing moves the token.
TokenMetadata is the token's metadata as a camelCase JSON string, the shape Adena reads. The image is not in it; that is TokenURI.
TokenURI is the token's image, as a data: URI, for a token that exists. A renderer that answers nothing gets the built-in card instead: an empty <img src> is a broken picture in every wallet.
TransferFrom carries NO expiry check, and that is a deliberate reversal of what nslogic once did.
Refusing to transfer an expired token is an anti-scam measure, and it belongs in the REGISTRY, not here: this realm does not know what expiry is and must not learn, because the moment it holds policy it stops being the small frozen thing it exists to be. The registry keeps the rule by refusing to RESOLVE an expired name. A buyer can still acquire a worthless token from a careless seller, which is true of every NFT on every chain, and the marketplace disclaimers say so.
The hold is checked here for a clear error and again by the ledger hook, which also moves the epoch.
Trusted lists every address that may mint or burn, comma-separated. nsdata compares this with its own address before adopting tokens, so the format is part of the contract: addresses only, no spaces.
TrustedPath reports the package path a registry was trusted as, or "".
Untrust withdraws a registry's power to mint and burn, at once. Admin, recovery or guardian: removing a power is the safe direction, and a registry that has gone wrong must be stoppable in one transaction.
Renderer draws one token for wallets. Implemented by the view realm.