Token-Owned Addresses
Abstract
Token-Owned Addresses (TOA) define a deterministic Cardano enterprise script address for a controlling asset class intended to represent a unique non-fungible token. For TOA v1, the address is derived from the hash of a canonical Plutus V3 validator applied to a single canonical parameter value containing (toa_version = 1, policy_id, asset_name).
A TOA is spendable by any transaction that consumes exactly one unit of the controlling asset class as input, produces exactly one unit of that same asset class as output, and does not mint or burn that asset class in the same transaction. The TOA validator does not require a specific signer, wallet identity, or external ownership registry; authorisation is based on the presence of the controlling NFT in consumed transaction inputs.
The standard is an off-chain interoperability CIP. It does not introduce a new ledger address type. It standardises the validator artifact, parameter encoding, address derivation, validator rules, wallet and indexer behaviour, and test vectors needed for wallets, explorers, dApps, and libraries to derive and display the same TOA for the same controlling asset class.
Motivation: Why is this CIP necessary?
On Cardano, there is no native concept of an account owned by a token. Other ecosystems address this need through different patterns:
- Ethereum / EVM: ERC-6551 Token-Bound Accounts are the closest conceptual prior art — every NFT has a deterministic account address derived from its contract address and token ID.
- Solana: Associated Token Accounts (ATAs) demonstrate deterministic address derivation from canonical inputs. ATAs are token accounts owned by the SPL Token program, derived from a wallet/mint pair; they are not token-owned accounts, but they are prior art for the derivation-from-canonical-inputs pattern.
- Object-based chains: Sui and Aptos provide object/resource ownership models that make asset-owned state more native than it is on Cardano.
Token-gating patterns can already be implemented on Cardano today using Plutus validators, but each dApp implements them differently. This produces three concrete problems:
- Fragmentation. dApp-specific address derivation means an NFT may be associated with one address in one dApp and a different address in another, with no canonical answer.
- Wallet UX. Wallets cannot display NFT-controlled balances, deposit into NFT-derived addresses, or warn about NFT-control transfer because there is no standard pattern to recognise.
- Indexer and explorer support. Indexers cannot label addresses as token-owned because there is no canonical derivation to follow.
This CIP standardises the pattern. A single canonical validator, applied to a single canonical parameter shape, yields a single canonical address per (policy_id, asset_name). Every wallet, indexer, and dApp implementing the standard derives the same address for the same controlling asset class.
Representative use cases:
- NFT-owned vaults and inventories — character or item NFTs holding ADA, sub-tokens, equipment, or per-NFT state. Control transfers atomically with the NFT.
- Revenue routing per NFT — royalties, licenses, and subscription proceeds sent directly to the NFT's address. The party or script able to consume the controlling NFT is the current revenue controller; no re-keying is required on transfer.
- Role and authority NFTs — a
Treasurer,Admin, or governance NFT controls an operational address. Transferring the NFT transfers the authority without redeploying scripts or rotating signers. - DeFi-position auxiliary state — LP-receipt or strategy NFTs holding ancillary state — accumulated rewards, sub-positions, history markers, or receipts — that the host protocol does not encode in its own UTxOs.
- Account-by-NFT (token-bound accounts) — the NFT acts as a transferable identity primitive. The TOA is "the account belonging to this NFT," and inherits the control logic of whatever script currently holds the NFT — lending, escrow, fractionalisation, marketplace, DAO. This composability is intentional; see Security Considerations — Temporary control during lending, escrow, or rental.
Stakeholders. Wallets (display, deposit, spend); indexers and explorers (canonical derivation, labelling); dApp authors (target a single standard rather than a bespoke pattern); NFT issuers (predictable per-NFT addressing for vault, royalty, and membership use cases).
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, RECOMMENDED, and MAY in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.
The TOA address is derived as follows:
- The canonical Plutus V3 validator
TOA Validator v1is defined and a single canonical PlutusData parameterTOAParamsV1is bound to it — containing(toa_version, policy_id, asset_name). - The applied script bytes are reconstructed by the byte-level procedure R (specified in Address Derivation), using the pinned byte-level constants
FLAT_PREFIX_TOA_V1andFLAT_SUFFIX_TOA_V1and the canonical PlutusData CBOR ofTOAParamsV1. - The script hash is the Cardano ledger Plutus V3 script hash of the reconstructed applied script.
- The hash becomes the payment credential of the address.
- No stake credential is attached in TOA v1.
- The result is a deterministic enterprise script address for that NFT.
TOA v1 commits explicitly to one parameter. Implementations MUST treat the controlling-NFT parameters as a single PlutusData value matching the CDDL toa_params_v1 shape; under R, the canonical PlutusData CBOR params_cbor plays that role in address derivation. An implementation that uses a non-conformant CBOR encoding for the parameter — for example, definite-length Constr 0 field arrays in place of the indefinite-length form produced by the Plutus V3 serialiseData builtin — produces a different params_cbor, a different reconstructed applied script, and therefore a different script hash, even when the three underlying values are identical. Such an implementation is non-conforming.
Delegated/staking TOAs are explicitly out of scope for v1: any optional stake credential would create multiple valid addresses for the same controlling NFT, breaking the address-per-asset-class invariant. A future CIP may introduce a delegated variant under a new toa_version.
Definitions
-
Token-Owned Address (TOA): a Cardano enterprise script address whose payment credential is the hash of the
TOA v1validator applied to a specific(toa_version, policy_id, asset_name). The TOA exists conceptually whether or not any UTxO currently sits at it. -
Controlling NFT: the asset class
(policy_id, asset_name)baked into a TOA's parameters. The term "Controlling NFT" is used throughout this CIP for readability, but TOA v1 operates mechanically on an asset class; historical uniqueness of that asset class is not proven by the validator (see Policy Classification). -
Controller: any party able to construct a transaction that consumes a UTxO containing the controlling NFT and preserves that NFT in the outputs. In the ordinary case this is the NFT holder. If the controlling NFT is inside its own TOA (see Self-deposit), control becomes permissionless. If it is inside another TOA, control passes transitively to whoever controls that TOA's controlling NFT (see Security Considerations — Control cycles and nested TOAs).
-
Carry-through: the validator requirement that the controlling NFT must appear in transaction outputs with total quantity exactly 1. The recipient of the carry-through output is unconstrained by the validator.
-
Self-deposit: the case where the controlling NFT is sent to its own TOA. Self-deposit intentionally makes the TOA permissionlessly spendable (subject only to standard ledger witness requirements such as a datum preimage for a
Datum HashUTxO); see Security Considerations — Self-deposit of the controlling NFT for the full explanation. -
TOA deposit: a UTxO sent to a TOA address. Classified by the datum shape of the UTxO into one of four categories:
- Unit Datum — inline datum
Constr 0 [](CBORd8 79 80). RECOMMENDED for generic deposits intended to be spendable by any conforming TOA wallet. - Inline Datum — any non-unit inline datum. MAY carry application-specific payloads (offer markers, inventory metadata, badges, receipts, state markers). Wallets that do not recognise the schema MUST treat the deposit as opaque value plus an unknown datum payload; they MUST NOT assume the datum is semantically meaningful unless they understand the schema.
- Datum Hash — a bare 32-byte hash with no on-chain preimage. Spending requires the off-chain datum preimage.
- No Datum — no datum attached.
The TOA v1 validator does not reject a UTxO based on its datum classification.
- Unit Datum — inline datum
-
toa_version: an integer baked into the validator parameters that pins the standard revision. A change intoa_versionyields a different address for the same(policy_id, asset_name).
Canonical Parameter Encoding (CDDL)
toa_params_v1 = #6.121([
toa_version : uint, ; = 1 in v1
policy_id : bytes .size 28, ; blake2b-224 of minting script
asset_name : bytes .size (0..32), ; raw asset name bytes
])#6.121 is the CBOR tag for Constr 0 in PlutusData. Serialisation MUST be canonical PlutusData CBOR as produced by the Plutus V3 serialiseData builtin: a Constr 0 with a non-empty field list — always the case for toa_params_v1's three fields — is encoded with an indefinite-length array (0x9f … 0xff), while an empty Constr 0 [] encodes as the definite-length d8 79 80 (the Unit Datum byte sequence in Definitions); integers and bytestrings use smallest CBOR major-type form. For the unsigned-integer field this means: values 0..23 are encoded in the initial byte alone (major type 0 — toa_version = 1 → 0x01, toa_version = 2 → 0x02); values 24..255 as 0x18 followed by one value byte. This is byte-identical to what Codec.Serialise.serialise produces over PlutusLedgerApi.V3.Data in plutus-core. Note this is not strict RFC 8949 §4.2.1 (which mandates definite-length items); see Mandatory Normative Artifacts — Identity of the parameter encoder.
Address Derivation
TOA v1 targets Plutus V3. Validator hashes depend on the Plutus language version, so this is pinned. V2 fallback is out of scope for toa_version = 1.
The canonical TOA v1 address-derivation procedure — R — is specified at the byte level. It is executable on-chain using standard Plutus V3 builtins (appendByteString, consByteString, lengthOfByteString, serialiseData, blake2b_224) and implementable off-chain in any language with bytestring concatenation and blake2b-224.
1. params_cbor := serialiseData(TOAParamsV1(toa_version = 1, policy_id, asset_name))
2. chunked_param := consByteString(lengthOfByteString(params_cbor), params_cbor)
|| consByteString(0x00, "")
3. flat_body := FLAT_PREFIX_TOA_V1 || chunked_param || FLAT_SUFFIX_TOA_V1
4. script_bytes := cbor_bytestring_header(lengthOfByteString(flat_body)) || flat_body
5. hash := blake2b_224(0x03 || script_bytes) // 0x03 = Plutus V3 language tag
6. cred := payment_credential(script_hash = hash)
7. address := enterprise_address(network, cred) // no stake credential in v1
8. bech32 := encode_bech32(prefix in {addr, addr_test}, address)Each ingredient:
params_cboris the canonical PlutusData CBOR as produced by the Plutus V3serialiseDatabuiltin (Constr 0fields encoded with indefinite-length array0x9f…0xff; integers and bytestrings in smallest CBOR major-type form). The CDDLtoa_params_v1in Canonical Parameter Encoding defines the input.FLAT_PREFIX_TOA_V1(528 bytes) andFLAT_SUFFIX_TOA_V1(1 byte =0x01) are normative byte-level artifacts pinned by blake2b-256 — see Mandatory Normative Artifacts.- The chunked-bytestring frame (
consByteString(len, params_cbor) || 0x00) is the on-chain flat encoding ofparams_cboras aConstant ByteString: a single 1-byte length chunk header followed by the chunk content, terminated by a0x00byte. This frame is valid forlen(params_cbor) ≤ 255, a precondition always satisfied for TOA v1 (len(params_cbor)is at most 69 bytes). Longer parameters would require multi-chunk encoding and a newtoa_version. cbor_bytestring_header(n)is a pure deterministic function specified inline below.
The CBOR bytestring-header encoder is specified as:
cbor_bytestring_header(n) :=
if n <= 0x17 then [0x40 + n]
if n <= 0xff then [0x58, n]
if n <= 0xffff then [0x59, (n >> 8) & 0xff, n & 0xff] // big-endian
if n <= 0xffffffff then [0x5a, (n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]
otherwise errorFor TOA v1, n = 531 + len(params_cbor) with len(params_cbor) ∈ [36, 69] (the range steps by 1 byte at the CBOR header transition of asset_name length 23→24), so n ∈ [567, 600] and only the 0x59 + 2-byte-length branch is ever active; the spec defines the full function for forward compatibility.
Reference recipe in Plinth/Plutus-Tx style (illustrative; this is the same procedure executed on-chain):
toaScriptHash :: BuiltinByteString -- policy_id (28 bytes)
-> BuiltinByteString -- asset_name (0..32 bytes)
-> BuiltinByteString -- 28-byte script hash
toaScriptHash policyId assetName =
let paramCbor = serialiseData (Constr 0 [I 1, B policyId, B assetName])
paramLen = lengthOfByteString paramCbor
chunked = consByteString paramLen paramCbor
`appendByteString` consByteString 0x00 ""
flatBody = flat_prefix_toa_v1
`appendByteString` chunked
`appendByteString` flat_suffix_toa_v1
hdr = cborBytestringHeader (lengthOfByteString flatBody)
applied = hdr `appendByteString` flatBody
in blake2b_224 (consByteString 0x03 applied)The same procedure, executed off-chain in any language with bytestring concatenation and blake2b-224, produces the identical 28-byte hash — which is the payment credential of the TOA address. Consumers compare it against the payment credential of an Address extracted from ScriptInfo/TxInfo (on-chain) or from a UTxO's address field (off-chain).
Derivation of the canonical artifacts
The byte-level constants FLAT_PREFIX_TOA_V1 and FLAT_SUFFIX_TOA_V1 are derived from the compiled, unapplied validators/ToaV1.uplc artifact in the reference repository. The derivation procedure — applying a Constant Data parameter to the unapplied UPLC, observing that the resulting applied bytes admit a unique byte-aligned decomposition around params_cbor, and extracting the invariant prefix/suffix slices — is documented and reproducible via the toa-verify-reconstruction executable. This audit path is informative; the byte-level constants and the procedure above are normative.
Header bytes follow CIP-0019; the resulting addresses are type-7 (enterprise script), header byte 0b0111_xxxx where the low nibble encodes the network (0x0 testnet, 0x1 mainnet) — i.e., concrete header bytes 0x70 and 0x71. The textual form (derivation step 8) is bech32 per BIP-173 — checksum constant 1, not bech32m — with human-readable prefixes addr (mainnet) and addr_test (testnets), as CIP-0019 prescribes.
Validator Rules
Let ac = (policy_id, asset_name) be the controlling asset class.
A TOA-spending transaction is valid only if all of the following hold:
-
No mint or burn of the controlling asset class.
valueOf(txInfoMint, ac) == 0 -
Total quantity of
acin consumed transaction inputs is exactly 1.sumSpentInputs(ac) == 1sumSpentInputs(ac)is computed over regular spent (consumed) transaction inputs only. Reference inputs are explicitly excluded and do not authorise TOA spending; including the controlling NFT in a reference input does not contribute toward satisfying this rule. -
Total output quantity of
acis exactly 1.sumOutputs(ac) == 1
The validator does not require the controlling NFT input to come from a key wallet, from the transaction signer, or from an address different from the TOA itself. Control is defined purely by NFT presence in spent transaction inputs.
Self-deposit. If the controlling NFT is locked inside its own TOA, any transaction author who can provide the required ledger witnesses — including any required datum preimage for a Datum Hash UTxO — may spend that TOA UTxO by consuming it and carrying the NFT through. See Security Considerations — Self-deposit of the controlling NFT.
Non-spending invocations. The canonical validator rejects every non-spending script purpose — minting, rewarding, certifying, voting, proposing — with trace T3; spending a UTxO at the TOA address is the only ledger interaction it validates. (On Conway ledgers a stake-address registration certificate for the script credential requires no witness and is therefore possible without the validator running, but it creates only inert state: delegation and withdrawal do invoke the validator and are rejected.)
Through these rules, the standard states clearly:
You can spend from a token-owned address only if exactly one unit of the controlling NFT enters as input and exactly one unit leaves as output, and no minting or burning of that asset class occurs in the same transaction.
The rationale for choosing transaction-local NFT uniqueness over an attempt at historical-supply enforcement, and for excluding reference inputs, is in Rationale — Design Decisions.
Datum and Redeemer Schema
For Plutus V3 spending scripts, the datum is not a required validator argument: per CIP-0069, the datum argument is removed from the validator interface, and the script accesses the spending UTxO's datum (if any) via ScriptInfo — the SpendingScript variant carries Maybe Datum. TOA v1 does not inspect this field: absence of datum, hash-only datum, and non-unit inline datum do not by themselves cause the spend to fail. Wallet and indexer conventions are stated below as SHOULD-level guidance.
TOA deposits SHOULD use Unit Datum, i.e. the inline PlutusData value Constr 0 [] (CBOR #6.121([]), byte sequence d8 79 80). This makes such deposits trivially recognisable to wallets, explorers, and indexers, and lets any conforming wallet spend them without recovering an off-chain datum preimage.
Deposits MAY use Inline Datum when they carry application-specific data — marketplace offer markers, state markers, inventory, badges, receipts. The TOA validator does not inspect or interpret application datums; wallets that do not recognise the schema MUST treat the deposit as opaque value plus an unknown datum payload, surfacing the Inline Datum classification per Wallet Behaviour — Surface the datum classification.
Per Definitions — TOA deposit, the TOA v1 validator does not fail based on datum classification: UTxOs classified as No Datum, Datum Hash, or non-unit Inline Datum remain spendable if and only if Validator Rules are satisfied. Datum shape determines wallet and indexer interpretation, not on-chain spend validity.
The spend redeemer SHOULD be unit (Constr 0 [], same encoding as above). The validator does not inspect the redeemer; using a canonical redeemer improves recognisability and avoids unnecessary implementation divergence, but a non-unit redeemer does not by itself cause a TOA spend to fail.
Policy Classification
TOA v1 cannot prove on-chain that the controlling asset class is historically unique. This section defines a RECOMMENDED three-value vocabulary for communicating what safety claims an implementation makes to users about a controlling policy.
ProvenClosed— the controlling asset's minting policy has been verified to be incapable of further minting or burning.ProvenClosedSHOULD be reserved for cases such as: native scripts whose relevant time-lock has provably expired; policies listed in an accepted audited registry; or policies verified by an implementation-specific trusted analysis mechanism. A policy for which no such grounds exist is notProvenClosed.KnownOpen— the minting policy is known to permit further minting or burning under some condition — e.g. an unrestricted native multi-sig, or a Plutus policy with a visible mint/burn path.Unknown— closure has not been proven. By default, every controlling policy isUnknownunless explicitly upgraded toProvenClosedor downgraded toKnownOpen. Static analysis of arbitrary Plutus policies is not generally automatable, so most policies encountered in the wild will remainUnknown.
Wallets, indexers, and other tooling that display TOAs SHOULD assign each displayed TOA's controlling policy one of these three classifications, defaulting to Unknown. Implementations MAY upgrade a policy to ProvenClosed or downgrade it to KnownOpen using implementation-specific analysis, audited registries, or trusted attestations; the classification mechanism itself (static analysis, registry lookup, audited list, third-party attestation) is out of scope for TOA v1, as is the exact vocabulary an implementation exposes in its UI.
The one hard requirement is the safety outcome: implementations MUST NOT present a TOA as safely controlled by a unique NFT unless closure of the controlling policy has been verified — i.e. unless the policy meets the ProvenClosed bar above. See Wallet Behaviour, Indexer and Explorer Behaviour, and Security Considerations — Permanent loss and policy-permissiveness risks.
Mandatory Normative Artifacts
Two hashes are distinguished throughout this CIP:
- Template hash — the hash of the un-parameterised
TOA v1validator (a single value pinned by this CIP), concretelyblake2b_224(0x03 || unapplied_script_bytes). It identifies the TOA validator code itself, independently of any NFT. Under R, the template hash is an audit reference: it identifies the compiled artifact from whichFLAT_PREFIX_TOA_V1andFLAT_SUFFIX_TOA_V1are derived, but it is NOT on the critical address-derivation path. - Applied script hash — the per-NFT 28-byte hash produced by Address Derivation (R). This is the payment credential of every TOA address. Under R it is computed directly from
FLAT_PREFIX_TOA_V1,FLAT_SUFFIX_TOA_V1,params_cbor, and the CBOR bytestring-header encoder — no parameter-application step is required.
Conformance to TOA v1 is determined by R and the byte-level artifacts below, NOT by the compiled validator. The Plinth source, the compiled UPLC artifact, the pinned toolchain, and the template hash are published so that any implementer can independently verify (via the toa-verify-reconstruction executable or equivalent) that R is consistent with the canonical validator's compiled form. The byte-level constants are pinned by blake2b-256 content hash so any implementer can verify byte-for-byte that they hold the exact bytes referenced below.
| Artifact | Role | Reference |
|---|---|---|
| Plinth source | Audit reference | Onchain/Validators/ToaV1Validator.hs at en7angled/toa@0.2.1 |
| Compiled unapplied UPLC artifact | Audit reference | validators/ToaV1.uplc — 529 bytes, blake2b-256 60e2e90cd3b48b3daab28a409b257cfa0554bd24b4c552b985df9aee654fbda0 |
| Pinned toolchain | Audit reference | GHC 9.6 series; Plutus Core target version 1.1.0; plutus-tx / plutus-tx-plugin / plutus-ledger-api at Conway-era IOG CHaP pins frozen via cabal.project.freeze at tag 0.2.1 |
| Template hash | Audit reference | blake2b_224(0x03 || unapplied_script_bytes) = b4e7310faacb77c9e5a68f325eb348a93d2025ecf472bc43007d5e1c |
FLAT_PREFIX_TOA_V1 | Normative for derivation | validators/FLAT_PREFIX_TOA_V1.bin — 528 bytes, blake2b-256 6ab7ef002cda6f7e3c60e5975fce175c6e56a91b51b7d488d2ae69af23520235 |
FLAT_SUFFIX_TOA_V1 | Normative for derivation | validators/FLAT_SUFFIX_TOA_V1.bin — 1 byte (0x01), blake2b-256 ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25 |
| CBOR bytestring-header encoder | Normative for derivation | Specified inline in Address Derivation (function cbor_bytestring_header(n)) |
| Parameter encoder | Normative for derivation | serialiseData over TOAParamsV1 per the CDDL in Canonical Parameter Encoding (canonical PlutusData CBOR) |
Identity of the parameter encoder. The serialiseData referenced in Address Derivation and in this table is the Plutus V3 builtin serialiseData. Its output is the canonical PlutusData CBOR of the input: Constr 0 is encoded as CBOR tag 121 (0xd8 0x79) followed by an indefinite-length array (0x9f … 0xff) for a non-empty field list (an empty Constr 0 [] encodes as definite-length d8 79 80 — the Unit Datum byte sequence); integers and bytestrings use smallest CBOR major-type headers. This is byte-identical to what Codec.Serialise.serialise produces over PlutusLedgerApi.V3.Data in plutus-core, allowing off-chain implementations to reproduce the same bytes. Any implementation that uses a non-matching CBOR encoder (e.g. definite-length Constr arrays) does NOT conform.
Wallet Behaviour
Wallets that integrate TOA have the following obligations. The MUST items are deliberately few. Together with the misrepresentation prohibitions stated in context elsewhere — never present a TOA deposit as escrowed (Security Considerations — TOA is not an escrow); treat unrecognised datum schemas as opaque (Definitions — TOA deposit) — they are the minimum for a wallet to claim TOA v1 support: derive addresses correctly, do not misrepresent control safety, and do not hide spendability-relevant UTxO state. The SHOULD and MAY items describe the full recommended user experience; a wallet may adopt them incrementally.
MUST:
- Derive TOA addresses using the canonical algorithm in Address Derivation. Wallets MUST NOT infer TOA identity from any other heuristic.
- Warn that TOA control follows the ability to consume the controlling NFT. Any party or script that can cause the controlling NFT to be consumed as a transaction input controls the TOA for that transaction. Sending, lending, escrowing, renting, or otherwise transferring practical control of the controlling NFT may transfer practical control of the TOA. Sending the controlling NFT to its own TOA SHOULD be described as making the TOA permissionlessly spendable, subject to ordinary ledger witness requirements such as datum-preimage availability for
Datum HashUTxOs — not merely as "transferring the NFT to another address." In particular, wallets MUST warn before any action that sends the controlling NFT to its own TOA (self-deposit). (See Security Considerations — Self-deposit of the controlling NFT and Security Considerations — Temporary control during lending, escrow, or rental for the full explanations.) - Never present a TOA as safely controlled by a unique NFT without verified policy closure. This is the safety outcome defined in Policy Classification: unless the controlling policy meets the
ProvenClosedbar, the wallet MUST NOT claim the TOA is safely controlled. Wallets SHOULD assign each TOA's controlling policy a classification from Policy Classification (defaultUnknown) and SHOULD warn forKnownOpenandUnknownpolicies (see Security Considerations — Permanent loss and policy-permissiveness risks). - Surface the datum classification of every TOA UTxO. Each UTxO falls into exactly one of
Unit Datum,Inline Datum,Datum Hash, orNo Datum(see Definitions — TOA deposit). Wallets MUST display this classification before any spend, because it affects whether spending requires off-chain data (aDatum HashUTxO cannot be spent without the off-chain datum preimage) and whether the deposit followed the canonical TOA convention. Wallets MUST NOT silently coerce all classifications into a single bucket.
SHOULD:
- Surface the TOA version to the user before any deposit or spend. A v1 TOA and a hypothetical v2 TOA with the same
(policy_id, asset_name)resolve to different addresses; users must know which version they are interacting with. (See also: Versioning.) - Compute minimum-UTxO ADA for TOA deposits from current protocol parameters at the moment of transaction construction, not from cached or hard-coded defaults. TOA outputs are normal Cardano UTxOs — the ledger itself enforces the minimum-UTxO requirement — and token-heavy TOA deposits (many policy IDs, many asset names, or a large inline datum) may require substantially more ADA than a pure-ADA deposit.
- Display the CIP-0014 fingerprint of the controlling NFT (
asset1...) alongside the TOA address, so users can recognise which asset controls a given TOA without manually decoding(policy_id, asset_name). - Label TOA addresses distinctly from regular wallet addresses in UI (e.g. "Token-Owned Address for asset X"), so users do not confuse a TOA with a personal address.
- Default the NFT carry-through recipient to the spending user's own wallet. The validator permits any recipient (e.g. atomic-sale spends), but the safe default in a wallet UI is to return the NFT to the user; any other recipient should require an explicit confirmation to avoid accidental NFT loss. If the carry-through recipient is the TOA itself, the wallet MUST warn that this makes the TOA permissionlessly spendable, subject to ordinary ledger witness requirements such as datum-preimage availability for
Datum HashUTxOs (per the MUST "Warn that TOA control follows the ability to consume the controlling NFT" above). - Treat self-deposit as an advanced action. Wallets should require explicit confirmation before allowing the controlling NFT to be sent to its own TOA. Wallets MAY also offer the action only behind an "advanced" toggle.
- Warn when key-loss risk is elevated. Wallets should warn before deposits to a TOA whose controlling NFT is held in cold storage or otherwise at elevated key-loss risk, since loss of the controlling key renders the TOA permanently inaccessible.
- Warn before constructing a spend of a
Datum HashUTxO. The UTxO is spendable per Validator Rules, but spending requires the off-chain datum preimage in the witness set. Wallets SHOULD surface this requirement and SHOULD NOT attempt to build the spend transaction without the preimage available. - Surface
No Datumand non-unitInline DatumUTxOs as outside the canonical convention. These deposits are spendable per Validator Rules but did not arrive through a recognisedUnit Datumworkflow. Wallets SHOULD surface the classification so users understand the UTxO does not match the recommended TOA pattern.
MAY:
- Accept a CIP-0014 fingerprint as a lookup key, resolving it to
(policy_id, asset_name)via an indexer before address derivation. - Filter or hide spam UTxOs at a TOA in the UI, to reduce visual clutter and improve spend-cost predictability. The validator does not require this; it is a UX choice. Wallets that filter spam MUST still preserve access to the raw UTxO set when the user requests it (e.g. an "advanced" view or an explicit reveal).
Indexer and Explorer Behaviour
Indexers and explorers that claim TOA v1 support address the read-side of TOA: derivation, display, and UTxO enumeration. Where their obligations overlap with Wallet Behaviour, this section back-references rather than restates.
MUST:
- Derive TOA addresses from the canonical applied validator hash per Address Derivation. Indexers MUST NOT infer TOA identity from datum content, address-labelling conventions, or any other heuristic.
- Preserve raw UTxO access. Even when the UI hides or collapses spam UTxOs (see MAY below), the indexer MUST expose the full raw UTxO set at a TOA address — via an "advanced" view, an explicit reveal, or an API endpoint that returns the unfiltered UTxO set.
- Never present a TOA as safely controlled by a unique NFT without verified policy closure, per Policy Classification. Indexers SHOULD attach a classification (default
Unknown) to every TOA they display.
SHOULD:
- Display the TOA version, the controlling policy ID, the controlling asset name as both raw hex and (where decodable) UTF-8, the CIP-0014 fingerprint, the policy classification, and whether the controlling NFT currently resides at a TOA address — its own (self-deposit) or another's (nested control; see Security Considerations — Control cycles and nested TOAs).
- Label TOA addresses distinctly from regular script addresses (e.g. "Token-Owned Address for asset X").
- Surface the control-follows-NFT warning described in Wallet Behaviour on any UI that displays a TOA balance or recent activity. A self-deposited TOA SHOULD be signalled as currently permissionlessly spendable, subject to ordinary ledger witness requirements such as datum-preimage availability for
Datum HashUTxOs.
MAY:
- Filter or hide spam UTxOs in UI, subject to the raw-UTxO-access MUST above.
- Accept a CIP-0014 fingerprint as a lookup key, resolving it to
(policy_id, asset_name)before address derivation.
Public API and Transaction Shapes
The Validator Rules and Test Vector Format define TOA v1 conformance. Any implementation that derives the expected addresses (per Address Derivation and the address-derivation test vectors) and constructs transactions satisfying Validator Rules (cross-checked against the validator-scenario test vectors) is conforming. Wallets and libraries are free to construct transactions in any language and using any builder; no specific transaction-template DSL or codegen toolchain is required.
Reference Artifacts
Normative components
The normative components of TOA v1 are:
- the canonical address-derivation procedure R (in Address Derivation), comprising:
FLAT_PREFIX_TOA_V1andFLAT_SUFFIX_TOA_V1, pinned by blake2b-256 content hash;- the CBOR bytestring-header encoder specification (inline in Address Derivation);
- the parameter encoder specification (the CDDL in Canonical Parameter Encoding with
serialiseDataper canonical PlutusData CBOR);
- the validator rules (in Validator Rules);
- the normative test vectors (in Test Vector Format).
The compiled, unapplied Plutus V3 validator artifact and the template hash are audit artifacts. They are useful for reproducing FLAT_PREFIX_TOA_V1 and FLAT_SUFFIX_TOA_V1 from source via the toa-verify-reconstruction executable in the reference repository, but they are NOT on the critical derivation path. Conformance is determined by R and the byte-level artifacts pinned in Mandatory Normative Artifacts, not by the compiled validator. The Plinth source is required for auditability and reproducibility of the byte-level constants, but no implementation needs to compile it or apply parameters to it in order to derive a TOA address. The Haskell reference off-chain library and any supplementary language bindings are non-normative reference artifacts.
A conflict between a non-normative reference artifact and any normative component is resolved in favour of the normative component. A conflict among normative components is a specification erratum and MUST be corrected.
What lives where
The CIP folder contains only:
- this README;
- the parameter-encoding CDDL (inline in Canonical Parameter Encoding).
All other artifacts (normative and audit) — FLAT_PREFIX_TOA_V1, FLAT_SUFFIX_TOA_V1, Plinth source, compiled UPLC, pinned toolchain, test vectors, and non-normative reference artifacts (SDKs, demo frontend, HTTP API) — live in the external en7angled/toa repository, pinned by immutable git tag and (for binary artifacts) by blake2b-256 content hash. See Mandatory Normative Artifacts.
Reference validator (Plinth)
The canonical TOA v1 validator is written in Plinth (Haskell compiled to Plutus Core) and published at Onchain/Validators/ToaV1Validator.hs in the reference repository. Under TOA v1's byte-level R derivation, the Plinth source and the compiled UPLC artifact are audit references, not derivation steps. Implementers do NOT need to compile the validator or apply parameters to it in order to derive a TOA address; they implement R directly using the byte-level constants FLAT_PREFIX_TOA_V1 and FLAT_SUFFIX_TOA_V1 and the CBOR encoders specified inline. The validator artifact is published so that any implementer can verify (via the toa-verify-reconstruction executable or equivalent) that R is consistent with the canonical validator's compiled form.
Address derivation helper
Specified normatively in Address Derivation above. A reference implementation in Haskell accompanies the CIP and is packaged alongside the off-chain library. Supplementary TypeScript and Rust ports of the helper are encouraged for ecosystem reach but are non-normative.
Reference off-chain library — external repository
A non-normative Haskell reference implementation is published at en7angled/toa (tag 0.2.1), comprising:
onchain-lib— the canonical Plinth validator (Onchain.Validators.ToaV1Validator),TOAParamsV1parameter type, andOnchain.Derivation.R(Plinth on-chain R implementation, exposed via the canonical .bin files);offchain-lib— Atlas-based transaction building, queries, CIP-14 fingerprinting, canonical PlutusData CBOR encoding ofTOAParamsV1, and address derivation. It now exposesTxBuilding.Toa.DerivationR— a pure-Haskell mirror of R usable without Plutus tooling;webapi-lib+interaction-api— a Servant HTTP service exposing/toa/derive,/toa/utxos,/toa/spend, and basic transaction submission, with a Swagger UI;toa-gen-vectors— the executable that deterministically emits bothtest-vectors/toa-v1.jsonandvalidators/ToaV1.uplcfrom a single in-process generation step (no node interaction);toa-bench— a synthetic-context micro-benchmark for the validator with baseline-diff support for regression checks on execution units and script size.
The off-chain library, HTTP API, generator, and benchmark are non-normative. Conformance is determined by R and the normative test vectors, per Reference Artifacts — Normative components.
Demo frontend — external
A non-normative reference frontend exercising address derivation, deposit, spend, and inspection is hosted at toa.e7d.tech. The frontend consumes the HTTP API exposed by interaction-api in the reference repository and is provided for illustrative purposes only — conformance is determined by R and the normative test vectors, not by the frontend.
Test Vector Format
The normative test-vector file is test-vectors/toa-v1.json — published at en7angled/toa@0.2.1, blake2b-256 = 03ebfb996f8e2b6cde462d30a8292dc6e8c403c224074c4cbdb86aa247b77a81. The file is regenerated deterministically from the same Plinth source as the validator artifact, by the toa-gen-vectors executable.
Test vectors are split into two categories with distinct conformance rules:
- (a) Address-derivation vectors. Implementations MUST reproduce the
expected_script_hash,expected_address_mainnet, andexpected_address_testnetfields byte-for-byte for the corresponding(toa_version, policy_id, asset_name). Six such vectors are currently published (see Coverage below). - (b) Validator scenarios. Each scenario specifies the situation (inputs, outputs, mint, datums, redeemers) and the expected pass/fail result. Implementations need not produce byte-identical transaction bodies unless a scenario explicitly includes canonical transaction CBOR — different transaction builders may legitimately differ on input ordering, fee balancing, change outputs, collateral selection, and reference-script handling. Validator scenarios are not yet published in
toa-v1.json; they are tracked as outstanding work in Path to Active — Implementation Plan (step 4).
Conformance for category (b) is pass/fail correctness only. Execution-unit usage and transaction-size budgets are deliberately not part of the standard: the validator's correctness is independent of the Plutus cost model, and pinning a cost model would impose ongoing erratum maintenance every time governance amends it. The reference repository's toa-bench tool tracks ExUnits and script-size as a non-normative regression baseline; it is informational and has no bearing on conformance.
Field-level schema documentation — the JSON envelope shape, top-level field semantics, and per-vector field semantics — is published alongside the vectors at test-vectors/README.md, identified by blake2b-256 content hash 234d5a00e9c8a0da08c873f9d9294ac22d8ece4f2de8a8d2f445438249af180a. Conformance for category (a) is determined by the expected_script_hash, expected_address_mainnet, and expected_address_testnet fields per vector; other per-vector fields (params_cbor_hex, applied_script_cbor_hex, applied_script_bytes, flat_body_length, etc.) are diagnostic aids whose semantics are documented in the external schema.
Rationale for asset_name length coverage at the CBOR-threshold boundaries. Length 23 is the last asset_name length that fits in a single-byte CBOR bytestring header (0x57 || <23 bytes>). Length 24 is the first length that requires an explicit length byte (0x58 0x18 || <24 bytes>). Exercising these boundary lengths confirms that FLAT_PREFIX_TOA_V1 and FLAT_SUFFIX_TOA_V1 remain byte-invariant across the full domain len(asset_name) ∈ [0, 32], and that no CBOR encoding transition (in either params_cbor or the outer flat-body envelope) breaks R for any valid input. Today this verification is performed by the toa-verify-reconstruction audit executable, which checks lengths 0, 1, 16, 23, 24, 25, 31, and 32 against the canonical artifact (see Rationale — Design Decisions); the published category-(a) vectors cover the domain endpoints (empty, max32), and dedicated boundary-length vectors are listed as outstanding in Coverage below.
Coverage MUST include at minimum:
- ASCII asset name — published (
ascii); - empty asset name — published (
empty); - maximum-length (32-byte) asset name — published (
max32); - CIP-0067 / CIP-0068 label 100 asset name (reference NFT) — published (
cip67-100); - CIP-0067 / CIP-0068 label 222 asset name (user NFT) — published (
cip67-222); toa_versionisolation — at least one vector withtoa_version≠ 1 that produces a distinct script hash and addresses for the same(policy_id, asset_name)as a v1 vector, confirming version-bump address-divergence — published (ascii-v2);- UTF-8 / non-ASCII Unicode asset name — outstanding;
- CBOR-boundary asset-name lengths — dedicated address-derivation vectors at lengths 23 and 24 (single-byte → two-byte bytestring-header transition) and 31 (just below the domain maximum) — outstanding;
- single-TOA negative cases — spends rejecting
sumSpentInputs(ac) ≠ 1(zero units and multiple copies),sumOutputs(ac) ≠ 1(zero and more than one unit carried through), andmint(ac) ≠ 0— outstanding; - self-deposit spend case — the controlling NFT is at its own TOA and is spent permissionlessly — outstanding;
- reference-input attack case — the controlling NFT appears only in a reference input; regular spent inputs contain zero units; expected result: fail (the reference input does not satisfy
sumSpentInputs(ac) == 1) — outstanding; - authorisation-scope batch case — one controlling-NFT input authorises spending many UTxOs at the same TOA in a single transaction, with a single carry-through output; expected result: pass (see Security Considerations — Authorisation scope) — outstanding;
- multi-TOA composition cases — two distinct TOAs spent in one transaction: pass when both controlling NFTs are consumed and carried through; fail when either NFT is absent from spent inputs; pass when an unrelated asset class is additionally present (see Security Considerations — Authorisation scope) — outstanding;
- transitive-control drain — NFT
Yresides atTOA(X); a single transaction consumingXspendsTOA(X)(obtainingY) andTOA(Y)together; expected result: pass (see Security Considerations — Control cycles and nested TOAs) — outstanding; - control-cycle case —
Xresides atTOA(Y)andYatTOA(X); a single transaction consuming both spends both TOAs with no external authorisation; expected result: pass (see Security Considerations — Control cycles and nested TOAs) — outstanding; - non-spending purpose rejection — the validator is invoked with a non-spending script purpose; expected result: fail with trace
T3(see Validator Rules — Non-spending invocations) — outstanding; - value-shape stress case — spend of a token-heavy TOA UTxO (many policy IDs and asset names in its
Value); expected result: pass, exercising worst-case value traversal (see Security Considerations — Value-cost and dust risks) — outstanding; - datum-classification coverage — all four must be spendable: (i) Unit Datum, (ii) Inline Datum (non-unit), (iii) Datum Hash, (iv) No Datum — outstanding;
- permissive-policy warning fixture — a vector documenting (as metadata, not validator proof) that the controlling minting policy permits further mints, so wallets and indexers can verify their warning UX against a concrete example — outstanding;
- single-
policy_id-change vectors — at least one pair of vectors differing only inpolicy_id(sametoa_version, sameasset_name), used to isolate which region of R depends onpolicy_id— outstanding; - single-
asset_name-change vectors — at least one pair of vectors differing only inasset_name(sametoa_version, samepolicy_id), used to isolate which region of R depends onasset_name— outstanding.
Rationale: How does this CIP achieve its goals?
Design Decisions
-
Why a parameterised validator (vs. a datum-carried params + a single shared validator)? Parameters baked into the script hash mean the address itself uniquely encodes the controlling NFT. Anyone can pay in to the correct, predictable address without trusting a datum.
-
Why is the canonical address derivation specified at the byte level (R) rather than via
apply_params?apply_paramsis the conventional procedure for parameterised validators in the Cardano ecosystem, but it has two properties that make it a poor fit for the TOA use case. First, it is not executable on-chain — Plutus V3 does not expose a primitive that applies parameters to an unapplied script and serialises the result in ledger form, so a validator cannot derive a TOA address usingapply_paramsalone. Second, it requires Plutus tooling to implement off-chain, which is significant friction for an ecosystem-wide standard targeted at wallets, indexers, and libraries written in TypeScript, Rust, Python, and other non-Haskell languages.The canonical TOA v1 validator artifact has been empirically verified to admit a byte-level decomposition of its applied form, calculable by simple concatenation and length arithmetic. Specifying address derivation at this byte level — function R — yields a procedure that:
- is executable on-chain using standard Plutus V3 builtins (
appendByteString,consByteString,lengthOfByteString,serialiseData,blake2b_224); - is implementable in any language using only bytestring operations and hashing, with no Plutus-specific dependencies;
- is identical off-chain and on-chain, eliminating the dual-procedure conformance burden.
R is normatively grounded in the same compiled artifact that
apply_paramswould target — the byte-level constantsFLAT_PREFIX_TOA_V1andFLAT_SUFFIX_TOA_V1are extracted fromToaV1.uplcand pinned by content hash. The artifact and its derivation via Plinth and a pinned toolchain remain published for audit, and any implementer can independently verify (via thetoa-verify-reconstructionexecutable) that R reproduces the same bytes thatapply_paramswould produce. This audit path is documented in Address Derivation — Derivation of the canonical artifacts and is informative, not normative.Technical precondition for R. The standard Plutus V3 primitives are sufficient to derive a TOA address on-chain if and only if the ledger-serialised applied script bytes admit a byte-aligned decomposition around the encoded parameter. This is not a property guaranteed by Plinth,
plutus-tx, or any current Plutus toolchain — UPLC flat encoding is bit-aligned by default, and whether the parameter falls on a byte boundary in the applied script depends on the specific compilation output. For TOA v1, the parameter is delivered via a single UPLCConstant Dataterm (the same shapecardano-api'sapplyArgumentsproduces), and the resulting flat-encoded applied script does admit the byte-aligned decomposition — empirically verified bytoa-verify-reconstructionforasset_namelengths 0, 1, 16, 23, 24, 25, 31, 32 across the canonical artifact. If a future revision of the artifact lost this property, R would no longer be definable for that revision and a newtoa_versionwould be required (see Open Questions and Limitations — Stability ofFLAT_PREFIX_TOA_V1andFLAT_SUFFIX_TOA_V1).The parameter-encoding choice — embedding the raw canonical PlutusData CBOR vs. hashing the parameter — is treated separately in Why raw parameter embedding rather than hashing parameters? below, with the prior-art context for the byte-level technique in Related Work and Prior Art.
- is executable on-chain using standard Plutus V3 builtins (
-
Why a hash-derived address (vs. a CIP-0068-style reference-NFT pointer)? Determinism: any wallet can compute the address from
(policy_id, asset_name)alone, with no indexer lookups, no chain queries, no metadata fetches. -
Why an off-chain CIP (vs. a ledger change introducing a new address kind)? TOA works today on Plutus V3 with zero protocol modifications. A future ledger-level token-bound account remains an option for a separate CIP; TOA is the standard that ships now.
-
Why Plutus V3 specifically? V3 is the Conway-era Plutus ledger language version and supports the CIP-0069 unified script interface, where spending validators receive only the redeemer and
ScriptInforather than a separate datum argument. This lets TOA v1 avoid making datum presence part of the authorisation rule (see Datum and Redeemer Schema) and keeps the validator's predicate purely about input and output value and minting. V3 is also the intended target for new Conway-era Plutus standards. Any future Plutus ledger language version (V4+) would require a newtoa_version, because the language tag is part of the script hash and therefore part of every derived TOA address. -
Why not normative transaction-shape DSL? Conformance is fully defined by Validator Rules and Test Vector Format. Constraining the shape of valid transactions beyond what the validator enforces would force every implementation through a specific DSL or codegen toolchain without strengthening the on-chain guarantee.
-
Why no stake credential in v1? Any optional stake credential is an interop fork — two wallets disagreeing on the canonical choice produce different addresses for the same NFT. v1 picks the safest single value (none); delegated TOAs are deferred to a future CIP.
-
Why transaction-local NFT uniqueness (
sumSpentInputs(ac) == 1,sumOutputs(ac) == 1) rather than "validator enforces supply = 1"? The validator cannot prove historical total supply on-chain — it sees only one transaction at a time. Transaction-local uniqueness over spent inputs is the strongest invariant the validator can actually enforce: it rules out in-transaction multi-copy edge cases and reference-input attacks (where the NFT is observed but not consumed), while leaving the responsibility of choosing a sound minting policy to the NFT issuer (and to wallet warnings, per Wallet Behaviour). -
Why total quantity rather than "some input contains the NFT"? Checking
sumSpentInputs(ac) == 1andsumOutputs(ac) == 1rejects three failure modes that "some input contains the NFT" would miss: (1) zero NFT inputs, (2) multi-copy NFT inputs (only possible if the upstream minting policy is permissive), and (3) an NFT visible only as a reference input. The third case is particularly important: a reference input lets a transaction observe a UTxO without consuming it, and treating reference-input visibility as authorisation would let any party who finds a UTxO containing the controlling NFT (anywhere on the ledger) drain the TOA. The validator counts only spent inputs. -
Why permit self-deposit rather than forbid it? TOA control is defined by NFT presence in transaction inputs, not by external ownership. Disallowing self-deposit is technically possible — a Plutus V3 spending validator can recover its own input via
ScriptInfo'sSpendingScriptpurpose and reject transactions where the controlling NFT input originates at the same TOA script credential — but TOA v1 deliberately does not add such an input-origin restriction. The chosen semantics define control solely by NFT presence in transaction inputs, which keeps the design eUTxO-natural and lets the NFT flow through wallets, escrow scripts, lending scripts, marketplace scripts, and, intentionally, its own TOA. The permissionless-spend consequence of self-deposit is made explicit in Definitions and Security Considerations, and wallets MUST warn before performing self-deposit (per Wallet Behaviour). -
Why raw parameter embedding rather than hashing parameters? Background on the byte-level technique and the prior generic library is in Related Work and Prior Art. TOA embeds
params_cbordirectly in the applied script bytes rather than hashing each component to a 28-byte footprint as that library's base module does. The reasons:Keystone: the canonical validator does not need to re-derive its own address. When spending from a TOA, the ledger already requires that the script whose hash equals the UTxO's payment credential be supplied and executed — so the ledger enforces the address↔hash match. The validator does not prove its own address; it only applies the NFT-in / NFT-out rule using parameters it holds directly via parameterisation. On-chain re-derivation (the library's
apply_param-style reconstruction) is therefore relevant only to third-party consumer contracts — for example, a royalty splitter verifying "this output goes to NFT X's TOA" — not to the standardised validator itself.The remaining considerations are supporting rather than load-bearing:
- Single composite parameter at the tail. TOA's parameter
Constr 0 [toa_version, policy_id, asset_name]is self-delimiting, so raw embedding works with a fixed 1-byte suffix regardless ofasset_namelength. The constant-length property the library requires is driven mainly by its support for multiple interleaved parameters, which TOA does not need. - Public, bounded parameters.
policy_id(28 bytes) andasset_name(0–32 bytes) are small, public, length-bounded identifiers. None of the three reasons a generic library hashes parameters — type-agnostic constant footprint, multi-parameter interleaving, commit/hide-by-hash — applies here. The parameters are not secret; that is precisely why hashing buys nothing. - Simpler spend logic. With parameters embedded directly, the validator has them on hand and the spending rule reads at a glance. A hashed encoding would force the validator to receive resolved
(policy_id, asset_name)via the redeemer and verify them with a wrapper (blake2b_224(serialise(param)) == hash) before use — adding indirection and a trust surface (redeemer-supplied values gated by a hash check) directly in the security-critical path.
- Single composite parameter at the tail. TOA's parameter
Related Work and Prior Art
External prior art is discussed in Motivation — chiefly ERC-6551 (Ethereum Token-Bound Accounts), Solana Associated Token Accounts, and the object-ownership models of Sui and Aptos. TOA's contribution is to bring the deterministic, NFT-derived address idea to Cardano in a form that requires no ledger changes.
Within the Cardano CIPs corpus:
- CIP-0113 (Programmable Token-Like Assets) — incorporating the now-
InactiveCIP-0143 and addressing CPS-0003 (Smart Tokens) — is architecturally related to TOA but uses a different deterministic-derivation mechanism. CIP-0113 keeps the payment credential fixed (a sharedprogrammableLogicBasescript used by programmable-token addresses) and varies the stake credential per holder/user credential, producing "smart wallet" addresses under that shared script. TOA varies the payment credential instead — by parameter application of(toa_version, policy_id, asset_name)into the validator — and carries no stake credential in v1, producing one address per controlling asset class under a per-asset script hash. The two are complementary rather than overlapping: CIP-0113 standardises programmable transfer behaviour for token-like Cardano native assets via per-user smart-wallet addresses; TOA standardises NFT-controlled vault addresses via per-asset scripts. - CIP-0160 (Receiving Script Purpose and Addresses) proposes a ledger-level mechanism — a new
Receivingscript purpose plus aProtectedAddressconcept — that would let scripts validate UTxO creation at their address. TOA v1 does not depend on CIP-0160 and remains compatible with the current unprotected script-address model; if CIP-0160 is adopted, a future TOA version may use it to restrict or validate deposits into TOAs (see Open Questions and Limitations).
Within the Aiken / Plutus design-patterns ecosystem:
- The
parameter-validationmodule of Anastasia Labs' aiken-design-patterns library (pinned atv1.8.0) captures, in a more general form, the byte-level technique TOA's R uses to reconstruct a parameterised script's hash without invokingapply_paramsat runtime — splicing precomputed prefix and suffix bytes around the parameter region and hashing the result withblake2b_224. Example usage of the library is atvalidators/examples/parameter-validation.ak. TOA's R is conceptually a specific instance of the same pattern, specialised to a single composite parameter and pinned to a single canonical artifact. The library's base module hashes each parameter to a constant 28-byte footprint (blake2b_224(serialise(param))) so that arbitrary parameter types — and, in particular, multiple interleaved parameters — share a uniform on-chain layout; TOA embeds the rawserialiseDataoutput of a single composite parameterConstr 0 [toa_version, policy_id, asset_name], variable in length, framed by a fixed 1-byte suffix. Sincev1.8.0the library'sparameter-validation/advancedmodule also provides the raw-embedding form:advanced.apply_param(version, flat_prefix, parameter)serialises an arbitraryDataparameter on-chain, frames it as a flat bytestring chunk (length byte || bytes || 0x00), appends the0x01tail, wraps the result in the CBOR bytestring header, and hashes it under the language-version byte — the same construction as R, generalised to any flat prefix (and to multi-chunk parameters, which TOA v1 excludes). Called withFLAT_PREFIX_TOA_V1as the prefix andConstr 0 [toa_version, policy_id, asset_name]as the parameter, it computes the TOA v1 script hash. Checked with Aiken 1.1.23, it reproduces theexpected_script_hashof every published address-derivation vector (harness:verification/aikenin the reference repository). The two base encodings reflect different design goals — generality across parameter shapes versus a specialised single-parameter form — not a quality judgement. See Why raw parameter embedding rather than hashing parameters? in Rationale — Design Decisions for the specific reasons TOA chose the latter.
CIPs TOA builds on directly: CIP-0019 (Cardano Addresses) for the enterprise script address format; CIP-0014 (User-Facing Asset Fingerprint) for the asset1... identifier displayed alongside TOA addresses; CIP-0069 (Plutus Script Type Uniformization) for the V3 spending validator interface; and CIP-0067 / CIP-0068 (asset name labels and datum-based metadata) for asset-name handling in test vectors. None of these require modification.
Backward Compatibility
TOA is purely additive — no existing standard or implementation changes behaviour because of TOA.
- CIP-0014 (user-facing asset fingerprint): TOA derivation operates on raw
(policy_id, asset_name)bytes; the CIP-0014 fingerprint (asset1...) is a parallel user-facing identifier and is independent of address derivation. Wallets and explorers SHOULD display the CIP-0014 fingerprint of the controlling NFT alongside the TOA address so users can recognise which asset controls a given TOA. Tooling MAY accept a CIP-0014 fingerprint as a lookup key, resolving it to(policy_id, asset_name)via an indexer before derivation. No conflict. - CIP-0025 (NFT metadata): TOA addresses are derived from
(policy_id, asset_name)regardless of which metadata standard the asset uses. No conflict. - CIP-0067 (asset name labels): TOA derivation treats
asset_nameas raw bytes. CIP-0067 labels are not interpreted by the TOA validator; they are simply part of the asset name bytes, so different labelled asset names produce different TOAs. No conflict. - CIP-0068 (datum-based metadata): TOA derivation is independent of datum content and equally agnostic to CIP-0068 label semantics. A CIP-0068 reference NFT (label 100) and its corresponding user NFT (label 222) have distinct asset names and therefore each have their own TOA. No derivation conflict — but note the control implication: a label-100 reference NFT is conventionally locked at the issuer's metadata validator, so a TOA controlled by a label-100 asset is controlled by that validator's spending policy, not by the holder of the corresponding label-222 user NFT. In configurations where the reference NFT can never leave the metadata validator, such a TOA is permanently inaccessible from creation (see Security Considerations — Permanent loss and policy-permissiveness risks). The
cip67-100test vector exercises byte-level derivation only; it is not a recommendation to use label-100 assets as controlling assets. - Existing token-gating implementations: TOA does not invalidate any current pattern. It offers a standard target that new implementations should converge on, with no requirement to migrate existing dApps.
Security Considerations
These risks are intrinsic to the design and must be addressed by implementers and surfaced to users.
Permanent loss and policy-permissiveness risks
A TOA is recoverable only by a party that can construct a transaction consuming the controlling NFT as an input. If the controlling NFT is burned (only possible if the minting policy permits burns) or becomes locked in a script from which no valid transaction can consume it, all assets at the TOA become permanently inaccessible — there is no recovery path at the ledger level. A concrete instance of the locked-forever case is a CIP-0068 label-100 reference NFT used as controlling asset while permanently locked at a metadata validator (see Backward Compatibility — CIP-0068). The mirror failure mode is Self-deposit (below), in which the TOA becomes not inaccessible but permissionlessly spendable.
If the controlling NFT's minting policy is not ProvenClosed (i.e. is classified KnownOpen or Unknown per Policy Classification), the policy may permit duplication or burning of the controlling asset by a party with mint authority — and TOA v1 cannot prove on-chain that this isn't the case. The validator's mint == 0 rule closes only the single-transaction variant of any duplication-then-drain attack: an attacker cannot atomically mint-and-drain in one transaction. It does not close the two-transaction variant (mint in tx A, then spend the TOA in tx B). The validator's contribution is removing the atomic coupling and making any duplication observable on-chain between the two transactions; the residual risk is mitigated at the wallet/UX layer per Wallet Behaviour: wallets MUST NOT present a TOA as safely controlled without verified policy closure, and SHOULD warn for KnownOpen and Unknown policies.
Per Wallet Behaviour, wallets MUST NOT present a TOA as safely controlled by a unique NFT unless the controlling policy's closure has been verified, SHOULD warn for KnownOpen and Unknown controlling-asset policies, and SHOULD additionally warn before any deposit to a TOA whose controlling NFT is held in cold storage or otherwise at elevated key-loss risk.
Self-deposit of the controlling NFT
If the controlling NFT is locked inside its own TOA, any transaction author who can provide the required ledger witnesses — including any required datum preimage for a Datum Hash UTxO — may spend that TOA UTxO by consuming it and carrying the NFT through. This is what we mean by "permissionlessly spendable."
For Unit Datum, Inline Datum, and No Datum outputs no off-chain datum preimage is needed; the TOA UTxO containing the NFT is both the value being spent and the proof of authorisation. For Datum Hash outputs the datum preimage is still required by the ledger even though the TOA validator itself does not inspect it.
A self-deposited TOA is therefore publicly controllable: subject to the above witness requirement, any transaction author can move the TOA contents anywhere while carrying the controlling NFT forward to any address (including back into the same TOA, perpetuating the public-control state). The TOA validator does not require a specific signer, wallet identity, or other off-chain credential — it authorises the spend purely from the on-chain state.
This is not a validator bug. It is an intentional consequence of defining control by NFT presence in transaction inputs rather than by signatures or by an external ownership address. Disallowing self-deposit would require enforcing an "input must not come from this script address" predicate, which breaks the eUTxO-natural design and the symmetry that lets the NFT itself flow through lending, escrow, and marketplace scripts.
Control cycles and nested TOAs
Self-deposit is the length-1 case of a more general phenomenon: control is transitive across TOAs. If NFT Y resides at TOA(X), then whoever can consume X controls TOA(X), can thereby obtain Y in the same transaction, and therefore also controls TOA(Y). A single transaction can consume X, spend TOA(X) and TOA(Y) together, and satisfy both validators — each sees its own controlling NFT among the spent inputs.
Cycles collapse to permissionless control. If X resides at TOA(Y) and Y resides at TOA(X) (a 2-cycle), a single transaction consuming both UTxOs satisfies both validators with no external authorisation — exactly like self-deposit (the 1-cycle). Any cycle of TOAs whose controlling NFTs all reside inside the cycle is permissionlessly spendable as a whole.
The wallet implication: a check implementing only the letter of self-deposit ("the NFT appears at its own TOA") reports both nested and cyclic configurations as safe when they are not. Wallets and indexers SHOULD detect the one-hop case — the controlling NFT resides at a TOA address, its own or another's — and MAY additionally detect full cycles (which requires resolving each nesting hop to its controlling asset). The corresponding validator-scenario coverage cases (transitive drain, control cycle) are listed in Test Vector Format — Coverage.
Wallets MUST warn users before sending the controlling NFT to its own TOA, and SHOULD describe the action as making the TOA publicly spendable rather than merely as transferring the NFT to another address (see Wallet Behaviour — Warn that TOA control follows the ability to consume the controlling NFT).
Authorisation scope: one NFT input authorises any number of TOA UTxOs
A single valid controlling-NFT input authorises the transaction to spend any number of UTxOs at that NFT's TOA, subject only to transaction-size and execution-unit limits. This is intentional: authorisation is per transaction, not per TOA UTxO. It enables efficient batched cleanup of spam deposits and atomic multi-output spends from a TOA, and it is what allows the carry-through rule (sumOutputs(ac) == 1) to remain coherent in spite of multiple TOA inputs.
Distinct TOAs compose independently in one transaction. A transaction may spend UTxOs at several different TOAs at once; each TOA's validator counts only its own controlling asset class ac, so the rules compose without double-satisfaction: one controlling NFT cannot stand in for another, and the presence of unrelated asset classes in inputs or outputs does not affect any TOA's rule. Each spent TOA independently requires its own controlling NFT to be consumed and carried through.
The validator-scenario coverage cases for both properties — the positive batched-spend case (one NFT input → many TOA UTxOs in / single carry-through out), the multi-TOA composition cases, and the negative cases rejecting sumSpentInputs(ac) ≠ 1, sumOutputs(ac) ≠ 1, or mint(ac) ≠ 0, including the reference-input attack — are enumerated (outstanding) in Test Vector Format — Coverage.
Value-cost and dust risks
TOA addresses are open vaults; the validator cannot disallow foreign tokens without destroying the use case. Two consequences:
- TOA-spending transactions traverse the full
Valueof every TOA UTxO they consume, so cost grows with the size of the token map. Worst-case behaviour must be covered by test vectors and by sensible transaction-builder defaults (UTxO selection, output coalescing). - Spam deposits (covered separately in Spam and DoS via deposits) compound this risk by inflating UTxO counts. Wallets SHOULD coalesce conservatively when constructing TOA spends and SHOULD allow the user to select which TOA UTxOs to include rather than consuming the full set.
Validator bugs are unrecoverable
A bug in the canonical TOA v1 validator could allow assets to be drained from every TOA derived from it. Because the validator hash is baked into every TOA address, no fix can be retrofitted to existing addresses — a corrected validator is a new toa_version with new addresses, and funds on the old version remain at the old version. The canonical validator therefore requires extensive independent audit before publication; any future revision is by definition a new toa_version, per Versioning.
Spam and DoS via deposits
Any party can send UTxOs to a TOA, and the standard does not (and cannot) prevent this at the validator level — this is fundamental to the deterministic-address model. High UTxO counts inflate the cost of TOA-spending transactions and can in extreme cases push them over the transaction-size or execution-unit limits. CIP-0160 is the proposed ledger-level mitigation (see Open Questions and Limitations). Wallets MAY filter spam in UI and SHOULD coalesce UTxOs sensibly when constructing TOA spends.
Temporary control during lending, escrow, or rental
Any party or script that can cause the controlling NFT to be consumed as a transaction input can control the TOA for that transaction. This includes borrowers, escrow contracts, marketplaces, lending protocols, and any other script that temporarily holds or controls the NFT.
This is intentional (see Open Questions and Limitations) and is what makes TOA composable with the rest of the eUTxO ecosystem, but it does mean that any borrower can drain a TOA before returning the NFT. The wallet-side MUST for surfacing this risk is stated normatively in Wallet Behaviour and is the same obligation, not an additional one. Lending protocols built on top of TOA-controlling NFTs SHOULD treat the TOA balance as part of the economic exposure of lending the controlling NFT, and SHOULD ensure the TOA balance is part of the loan collateral terms.
TOA is not an escrow
A TOA is controlled by whichever party can consume the controlling NFT as a transaction input. Assets sent to a TOA can be spent by that party at any time, without giving the depositor anything in return. The TOA validator enforces NFT carry-through; it does not enforce any payment-for-NFT, time-lock, or exchange semantics.
TOA v1 provides no escrow semantics. Binding marketplace offers that escrow real value require a separate offer validator that enforces the exchange (e.g. "pay X ADA, receive the controlling NFT atomically"). A TOA MAY serve as a discovery channel for such offers — the offer validator's address can be referenced from data deposited at the TOA, or the offer UTxO can be created at the TOA with an inline datum describing the terms — but the TOA v1 validator alone does not enforce sale semantics.
Wallets and dApp authors MUST NOT present a TOA deposit as "escrowed" to the depositor unless an external offer validator with appropriate escrow semantics also locks the assets. Treating a TOA as escrow is the most likely user-level misuse of this standard.
Consumer contracts deriving TOA addresses on-chain
R's on-chain executability is a headline design goal: a third-party validator (a "consumer contract" — e.g. a royalty splitter checking "this output pays NFT X's TOA") can derive a TOA payment credential using only standard Plutus V3 builtins. Three hazards are specific to this usage:
- Constants provenance. A consumer contract that embeds incorrect
FLAT_PREFIX_TOA_V1/FLAT_SUFFIX_TOA_V1bytes derives a credential that corresponds to no real TOA — value paid to it is unrecoverable, since no controlling NFT can ever authorise a spend there. Consumer-contract build pipelines SHOULD verify the embedded constants against the pinned blake2b-256 hashes in Mandatory Normative Artifacts at build time. - Double-satisfaction. "Some output pays TOA(X)" is a predicate that a single output can satisfy for multiple consumer scripts running in the same transaction. Consumer contracts SHOULD identify the specific output they require (by index, uniqueness check, or an application-level marker) rather than merely asserting existence of a matching output.
- Payee semantics. "Paid to TOA(X)" means paid to whichever party can consume
Xat some future time — not settlement to a fixed counterparty. The recipient can change with every transfer ofX, including into scripts, nested TOAs, or self-deposit. Consumer contracts whose economics assume a stable payee need additional mechanisms outside TOA v1.
Front-running
In the ordinary case (controlling NFT outside the TOA, held by a wallet or specific script), spend transactions are NFT-gated at the input level: a mempool observer cannot front-run a TOA spend without also being able to consume the UTxO containing the controlling NFT. No additional mitigation is required beyond what the validator already enforces.
In the self-deposit case (controlling NFT inside its own TOA), spending is intentionally permissionless for any transaction author able to provide the required ledger witnesses. Mempool competition is expected and is not treated as front-running against an owner — it is a direct consequence of self-deposit semantics, surfaced to the user by the self-deposit warning required by Wallet Behaviour — Warn that TOA control follows the ability to consume the controlling NFT.
Open Questions and Limitations
The following known limitations are intentionally left unresolved in TOA v1. Each is listed with an explicit Disposition so reviewers don't have to guess what stance the CIP takes.
1. UTxO spam and junk deposits
Anyone can send UTxOs to an ordinary Cardano script address. TOA v1 deliberately uses ordinary enterprise script addresses, so it cannot prevent unsolicited deposits at the validator level — this is fundamental to the deterministic-address model.
Disposition: Out of scope for v1. Wallets and indexers MAY filter or hide spam UTxOs in UI. Transaction builders SHOULD allow users to select which TOA UTxOs to consume. CIP-0160 ("Receiving Script Purpose and Addresses") proposes a ledger-level mechanism — a new Receiving script purpose plus a ProtectedAddress concept — that would let scripts validate UTxO creation at their address. If CIP-0160 is adopted, a future TOA version may use it to restrict or validate deposits into TOAs. TOA v1 does not depend on CIP-0160 and remains compatible with the current unprotected script-address model.
2. Token lending and temporary delegation
If the NFT owner lends the NFT, the borrower temporarily controls the TOA.
Disposition: Intentional feature, not a bug. See Security Considerations — Temporary control during lending, escrow, or rental.
3. Multiple address variants
Changes to stake_credential, toa_version, or the validator template all produce different addresses.
Disposition: Resolved in v1 by pinning a single template, toa_version = 1, and no stake credential. Future variants (delegated TOAs, alternative templates) live in separate CIPs.
4. Multi-quantity asset classes
What if the controlling asset has total supply > 1?
Disposition: Out of scope for v1. The validator cannot prove historical total supply on-chain; instead it enforces transaction-local uniqueness (sumSpentInputs(ac) == 1, sumOutputs(ac) == 1, mint == 0). Asset classes with intended supply > 1 fall outside this CIP's scope. See Validator Rules and Rationale — Design Decisions.
5. Reference scripts
Could publishing the TOA validator as a reference script change the derived address?
Disposition: No — reference scripts do not change the derived TOA address. The address is determined by the hash of the applied validator script, not by which UTxO carries the reference script. Reference scripts are safe and encouraged to reduce transaction size.
However, because TOA uses a parameterised validator, a reference script must correspond to the applied script for the specific (toa_version, policy_id, asset_name) being spent. A reference script for the unapplied template is not sufficient to spend a concrete TOA — each TOA needs (or shares with peers) a reference UTxO carrying its own applied script bytes.
6. Stability of FLAT_PREFIX_TOA_V1 and FLAT_SUFFIX_TOA_V1
The byte-level constants used by R are extracted from the pinned compiled UPLC artifact. The byte-aligned decomposition that makes R definable is a property of the specific compiled artifact, not a property of the Plinth source code, nor a property of TOA v1 as a specification. A future recompilation with a different Plinth, plutus-tx, plutus-tx-plugin, or plutus-ledger-api version may produce applied bytes that do not admit the same decomposition — because flat-encoding bit-alignment around the parameter is not guaranteed by the surface-level Haskell source.
Any recompilation of the canonical validator with a different toolchain version would therefore yield different constants and likely different addresses for the same (toa_version, policy_id, asset_name) — exactly as recompilation would under any address-derivation procedure.
Disposition. The pinned UPLC artifact and the derived R constants are immutable for toa_version = 1. Any change to the underlying validator — including a recompilation with a different toolchain — requires a new toa_version per Versioning. The CIP authors MUST NOT republish ToaV1.uplc, FLAT_PREFIX_TOA_V1, or FLAT_SUFFIX_TOA_V1 under the same toa_version after this CIP reaches Active. The verification procedure is the toa-verify-reconstruction executable in the reference repository, which is run on every release of the artifact and MUST pass before publication.
Path to Active
Acceptance Criteria
These are the concrete, testable conditions that must hold at the moment the CIP moves from Proposed to Active. Progress toward them is tracked in Implementation Plan below.
- Mandatory Normative Artifacts published in the reference implementation at en7angled/toa, pinned by immutable git tag and blake2b-256 content hash:
- the canonical Plinth source for the TOA v1 validator;
- the compiled, unapplied Plutus V3 validator artifact (binary UPLC bytes) — 529 bytes, blake2b-256 =
60e2e90cd3b48b3daab28a409b257cfa0554bd24b4c552b985df9aee654fbda0; - pinned compiler/toolchain versions — GHC 9.6 series, Plutus Core target version 1.1.0, plutus-tx / plutus-tx-plugin / plutus-ledger-api at Conway-era IOG CHaP pins frozen via
cabal.project.freeze; - the template hash, computed as
blake2b_224(0x03 || unapplied_script_bytes)per Mandatory Normative Artifacts —b4e7310faacb77c9e5a68f325eb348a93d2025ecf472bc43007d5e1c; - the canonical byte-level R implementation (Haskell reference in
offchain-lib, Plinth implementation inonchain-lib), verified against the compiled UPLC artifact by thetoa-verify-reconstructionexecutable on every release of the artifact.
- Normative test vectors published per Test Vector Format — both address-derivation vectors (category (a)) and validator scenarios (category (b)) — and verified against at least two independent implementations.
- At least one non-normative reference off-chain implementation published (address derivation, deposit, spend, NFT carry-through), exercised end-to-end on a public testnet.
- At least two independent integrations: e.g. one wallet displaying TOA addresses with proper labelling, and one explorer/indexer (Cexplorer, Pool.pm, Cardanoscan, or equivalent) labelling TOA addresses as such.
Implementation Plan
The Implementation Plan describes the ordered work required to move TOA v1 from Proposed to Active. It is distinct from Acceptance Criteria (the conditions that must hold at the moment of activation) — this section is the sequence of steps that produce those conditions.
- Publish the canonical Plinth validator source and the compiled, unapplied Plutus V3 artifact (binary UPLC bytes), with pinned toolchain versions and the documented template hash. Identify the validator artifact by its immutable content hash, published as an external immutable release per Reference Artifacts — What lives where. (Done — see Mandatory Normative Artifacts and Acceptance Criteria.)
- Publish the parameter-encoding CDDL and confirm it round-trips through at least two independent CBOR libraries. (CDDL published inline in Canonical Parameter Encoding; multi-library round-trip verification outstanding.)
- Publish the address-derivation test vectors (category (a) in Test Vector Format) covering ASCII / non-ASCII Unicode / empty / max-length / CIP-0067 label 100 / CIP-0067 label 222 asset names. (Done for ASCII / empty / max-length / label-100 / label-222 plus a
toa_version-isolation vector; non-ASCII Unicode outstanding — see Test Vector Format — Coverage.) - Publish the validator-scenario test vectors (category (b)) covering the cases enumerated in Test Vector Format — Coverage: positive, negative, authorisation-scope, multi-TOA composition, transitive-control and control-cycle, non-spending purpose, value-shape stress, and reference-input cases. Each scenario asserts pass/fail correctness only; ExUnit and tx-size measurements are tracked non-normatively in
toa-bench. (Outstanding.) - Publish the canonical byte-level R implementation (Haskell reference in
offchain-liband Plinth implementation inonchain-lib; supplementary TypeScript and Rust ports as non-normative), and verify it against the compiled UPLC artifact via thetoa-verify-reconstructionexecutable on every artifact release. (Done —offchain-libandonchain-libin the reference repository. An independently authored Aiken implementation of the same construction — aiken-design-patternsv1.8.0advanced.apply_param— reproduces theexpected_script_hashof all six published vectors; see Related Work and Prior Art. Supplementary ports outstanding.) - Publish at least one non-normative reference off-chain implementation, including address derivation, deposit, spend, and NFT carry-through. (Done —
offchain-lib+interaction-api+ toa.e7d.tech.) - Verify against independent implementation #1 — an external party (wallet team, library author, or auditor) reproduces every test vector byte-for-byte for address derivation and pass/fail-correct for validator scenarios.
- Verify against independent implementation #2 — a second independent party does the same against a different stack (e.g. lucid-evolution, MeshJS, cardano-transaction-lib, or PyCardano).
- Integrate explorer and wallet display — at least one Cardano explorer (Cexplorer / Pool.pm / Cardanoscan / equivalent) and at least one mainstream wallet label TOA addresses as such and surface the policy classification (
ProvenClosed/KnownOpen/Unknown) and self-deposit state.
CIP editors will verify completion of each step and apply the Active tag when fully complete. Steps 7–9 are owned by the ecosystem and are the primary criteria for activation.
Versioning
toa_version is a script parameter and therefore part of the hashed input. Concrete consequences:
- A TOA v1 address and a hypothetical TOA v2 address for the same
(policy_id, asset_name)resolve to different addresses. - Addresses do not migrate between versions. Funds on a v1 TOA stay on the v1 TOA.
- Wallets SHOULD surface the TOA version to the user before any deposit or spend (see Wallet Behaviour).
- Each new version is a new validator and lives in its own CIP (or as a versioned addendum to this one).
References
Cardano CIPs referenced:
- CIP-0014 — User-Facing Asset Fingerprint
- CIP-0019 — Cardano Addresses
- CIP-0025 — Media NFT Metadata Standard
- CIP-0067 — Asset Name Label Registry
- CIP-0068 — Datum Metadata Standard
- CIP-0069 — Plutus Script Type Uniformization
- CIP-0113 — Programmable Token-Like Assets (candidate; incorporates CIP-0143)
- CIP-0143 — Interoperable Programmable Tokens (Inactive; incorporated into candidate CIP-0113)
- CIP-0160 — Receiving Script Purpose and Addresses
- CPS-0003 — Smart Tokens
External standards and tools:
- ERC-6551 — Token-Bound Accounts (Ethereum)
- BIP-173 — Base32 address format (bech32)
- RFC 2119 — Key words for use in RFCs to Indicate Requirement Levels
- RFC 8174 — Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words
- RFC 8949 — Concise Binary Object Representation (CBOR), §4.2.1 "Core Deterministic Encoding"
- Atlas — Haskell transaction-building framework used by the reference off-chain library
Copyright
This CIP is licensed under CC-BY-4.0.