oUTXO: Object Layer (Off-Chain Strong Constraints)
Understanding the ordered UTXO model that bridges Pay2Ingot outputs to object state machines
oUTXO Model
Overview
The oUTXO (object-oriented UTXO) model is an "object state machine" layer that runs off-chain. It organizes the "bytes and policies" provided by Pay2Ingot outputs according to rules, forming an ordered, replayable object graph.
This layer is not written into L1 blocks, but through indexers, wallets, and other client software, as well as publicly available Registry test vectors, the entire community is forced to reach consistent execution.
Why oUTXO?
If we only rely on "byte commitments" on-chain, everyone will get a pile of raw data, but there's no way to guarantee they can be assembled into a stable, consistent object world. For example:
- Multiple transactions concurrently create different versions of the same objectโwhich one is "canonical"?
- Should an object be considered the latest state or already outdated?
- Without schema FSM constraints, fields in the payload may be written incorrectly.
These problems cannot be delegated to L1, because the consensus layer must remain extremely minimal and only handle hard constraints.
Interaction Principles Diagram
โ oUTXO โ UTXO โ oUTXO
Background and Context
-
Pay2Ingot (P2I) is a new output format on-chain.
- It acts as a "container" that commits to a payload (DAG-CBOR encoded bytes), policies (signatures, timelocks, multisig rules), and basic metadata (schema routing, account number, salt, etc.).
- L1 nodes have limited responsibilities: they only check structural validity, hash matching, signature and timelock satisfaction, and fee adequacy, then decide whether the transaction can be included in a block.
- However, L1 does not understand the true semantics of the payload. For example, whether this payload represents an FT, an NFT, or a complex contract, L1 does not care at all.
-
The Problem: If we only rely on these "byte commitments" on-chain, everyone will get a pile of raw data, but there's no way to guarantee they can be assembled into a stable, consistent object world. For example:
- Multiple transactions concurrently create different versions of the same objectโwhich one is "canonical"?
- Should an object be considered the latest state or already outdated?
- Without schema FSM constraints, fields in the payload may be written incorrectly.
These problems cannot be delegated to L1, because the consensus layer must remain extremely minimal and only handle hard constraints.
-
The Solution: oUTXO (ordered UTXO)
- It is an "object state machine" layer that runs off-chain.
- It organizes the "bytes and policies" provided by P2I according to rules, forming an ordered, replayable object graph.
- This layer is not written into L1 blocks, but through indexers, wallets, and other client software, as well as publicly available Registry test vectors, the entire community is forced to reach consistent execution.
Interpretation
-
The off-chain
oUTXO1is the previous active version of the object. -
It is spent, corresponding to the on-chain
UTXO1โ consumed in transactionTX. -
The transaction produces a new
UTXO2, committing to a new payload. -
The indexer derives a new
oUTXO2, becoming the latest tip of this object.
โก oUTXO โ oUTXO โ oUTXO (Pure Off-Chain Perspective)
Interpretation
-
From the off-chain perspective, oUTXO is an "instance chain": Mint โ Transfer โ Mutateโฆ
-
Each instance must "spend old, create new"โthe previous one becomes invalid (stale), and the next becomes the tip.
-
Conflicts are resolved through
(blue_score, block_id, wtxid)lexicographic ordering: first compareblue_score(ascending); if equal, compareblock_id(block hash byte string lexicographic order, big-endian); then comparewtxid(transaction hash including witness, byte string lexicographic order, big-endian).
โข Three-Phase Interaction: oUTXO โ oUTXO โ UTXO โ oUTXO
๐ Summary:
oUTXO โ UTXO โ oUTXO: Explains how on-chain commitments are consumed and re-derived off-chain.oUTXO โ oUTXO โ oUTXO: Emphasizes the generational evolution of object instances, like a family tree.
UTXO โ oUTXO Mapping Relationship
Core Principle: Each Pay2Ingot UTXO typically derives one oUTXO instance; but due to unknown schemas (raw_data), concurrency/reorgs causing stale instances, invisible windows for unlocked timelocks, and other situations, off-chain there may exist a one-to-many relationship of "one active instance + several historical/invalid instances". The oUTXO layer always guarantees that each artifact_id has at most one active tip.
1. Basic Case (Ideal Path)
- Each Pay2Ingot UTXO (on-chain output) is parsed by the indexer and typically derives one oUTXO instance.
- This derived instance carries
artifact_id / instance_idand other off-chain identifiers, entering the oUTXO state graph. - ๐ Under normal circumstances, we can say UTXO โ oUTXO instance โ one-to-one correspondence.
2. Special Cases (Leading to "Non-One-to-One")
-
Unknown/Unregistered Schema
- On-chain P2I UTXO still exists, but off-chain cannot run FSM.
- The indexer marks it as
raw_data, not entering executable object semantics. - You can understand it as "UTXO exists on-chain, but off-chain there's only a pending data record, not a true oUTXO instance".
-
Conflict Resolution (Concurrency or Duplicate Instances)
- Two transactions may concurrently produce seemingly identical instances (e.g., different successors of the same artifact).
- Off-chain rules will determine one as the tip, the other marked as
stale. - Here the UTXO still exists on-chain, but off-chain only recognizes one as "active oUTXO".
- So there will be 1 on-chain UTXO โ 1 stale oUTXO (historical record, inactive) situation.
-
Timelock Not Unlocked
- On-chain P2I UTXO has been produced, but timelock hasn't expired.
- Off-chain instance will be counted as "exists but invisible" (
INDEXER_TIMELOCKED), not entering the active tip set.
3. Reverse Angle: Must oUTXO Have a UTXO?
- Yes, oUTXO instances must have an on-chain UTXO foundation.
- oUTXO is derived from on-chain commitments (
HASH_PAYLOAD,SALT, and other fields), cannot be generated out of thin air. - However: a UTXO may be parsed as "raw_data" or "stale", in which case its oUTXO is not considered an active instance.
โ Summary
- Default Case: One P2I UTXO โ one oUTXO instance (active or historical).
- Exception Cases:
- Unknown schema โ only raw_data, placeholder but not a complete instance;
- Concurrency/conflict โ one active instance + several stale instances;
- Timelock not met โ instance exists, but temporarily not entering the active set.
Therefore, UTXO and oUTXO are not strictly one-to-one, but a "one-to-one or one-to-many (active+historical/invalid)" mapping relationship.
โ Data Structure Overview (Fields and Normalization Standards)
This diagram is like a "dictionary," placing all key structures together: Pay2Ingot output header (on-chain fields), witness, lock, normalized payload, derived identifiers, signature message domain, etc.
- Output Header (Pay2IngotOutput): The core on-chain commitment. It specifies payload size limit, hash, flags, lock, multisig/timelock rules, etc. Here HASH_PAYLOAD calculation is unified to
blake3("ingot/payload" || canonical_bytes), avoiding ambiguity. - Witness: Must be submitted when spending P2I output, containing payload data (if revealed), payload hash, signatures, and optional MAST path. It proves that the "on-chain committed payload" is indeed fully revealed and signatures are correct.
- Lock: Describes unlock rules, L1 nodes strictly check (e.g., timelock not met directly rejects).
- CanonicalPayload: Refers to the DAG-CBOR normalized byte sequence, all hash calculations are based on it.
- Ids (artifact_id / instance_id): Off-chain derived identifiers, describing "what object this is" and "its specific version".
- SigMsg: Signature-bound message domain, ensuring signatures are strongly bound to payload, input, lock, wtxid, preventing replay attacks.
- Unique Active Anchor: For any
artifact_id, at any height there is at most one active tip (active UTXO). - Spend Old, Create New: Any object change must spend the previous generation tip and produce a new tip.
- Lexicographic Resolution: Concurrent conflicts resolved by
(blue_score โ, block_id โ, wtxid โ)uniqueness. - Replayable: Scan from genesis Pay2Ingot โ reassemble payload โ execute oUTXO rules and schema FSM โ get the same state.
- Clear Boundary with L1: Timelock/signature/fees and other hard conditions in L1; oUTXO only handles object semantics and resolution.
โก Identifier and Object Layer Relationships (ER Diagram)
This diagram shows the object layer (oUTXO) identity and evolution logic.
- Artifact: Equivalent to the "class" of an object, identified by
artifact_id. It binds to a schema (defining rules and semantics). - Instance: A specific version of the object, identified by
instance_id. Each change produces a new instance. - P2I Output: On-chain physical commitment, each instance corresponds to one P2I output.
- Parent Instance: Except Mint (first generation of object), other operations must "spend parent instance", ensuring the version chain is a single chain, not arbitrarily forked.
In concurrent situations, two different instances may simultaneously try to inherit the same parent. At this point, oUTXO uses a unified resolution rule (blue_score โ, block_id โ, wtxid โ) to select the unique legal tip, the other marked as stale.
One-sentence summary: This diagram tells you that objects in the oUTXO world are like a family tree: artifact is the surname, instance is a specific generation, must spend old and create new, conflicts retain only one canonical version.
โข On-Chain / Off-Chain Boundary and Interaction (System Component View)
This diagram describes separation of responsibilities: what L1 full nodes handle, what off-chain indexers handle.
- L1 (Left): Only cares "whether this transaction can enter a block". It checks structure compliance (flags, size, field lengths), payload hash consistency, signature and lock validity, timelock satisfaction, and fee adequacy. If not satisfied, transaction directly rejected.
- Off-Chain oUTXO (Right): After getting on-chain approved transactions, organizes the object world. It persists payloads, calculates artifact/instance IDs, determines which instance is the tip, executes schema FSM to generate derived state. It must also guarantee replayability: anyone scanning from genesis to current height can calculate the exact same object state.
- Boundary: L1 doesn't understand semantics, doesn't know what's actually in the payload, only does structure and constraint validation; oUTXO doesn't change consensus, only transforms bytes into "meaningful objects" off-chain.
One-sentence summary: This diagram emphasizes "who handles what": L1 ensures security and determinism, oUTXO ensures semantics and consistency, they complement each other with clear boundaries.
2) Fields and Data Surface
2.1 On-Chain Native Fields (from Pay2Ingot Output and Witness)
SCHEMA_ID : [32]โ Semantic routing (indexer selects state machine based on it); L1 doesn't understand semantics.HASH_PAYLOAD : [32]โ Commitment to payload (DAG-CBOR normalized bytes). Calculation formula:HASH_PAYLOAD = blake3("ingot/payload" || canonical_bytes), wherecanonical_bytesis DAG-CBOR normalized bytes.LEN : u32โ Declared payload upper limit (โค protocol ParamSet).FLAGS : u16โREVEAL_REQUIRED | ALLOW_MUTABLE | INLINE_OK | โฆ.LOCKโNone / PubKey / ScriptHash / MastOnlyetc. (L1 validation).SALT : [32]โ Instance anchoring to prevent reuse (see below).
Instance Unique Primary Key:
instance_id = blake3("ingot/instance" || artifact_id || outpoint_bytes).(artifact_id, outpoint)is only an equivalent index, not a canonical layer uniqueness source. If multiple on-chain instances with the sameinstance_idappear, resolve by(blue_score โ, block_id โ, wtxid โ)to retain a single active instance, others markedcollision_stale(off-chain error codeINDEXER_E_INSTANCE_COLLISION).
PAYLOAD? : bytesโ Revealed payload in witness (indexer persists).
Indexer must persist original payload bytes along with the above header, as replay and audit materials.
2.2 Off-Chain Derived Fields (oUTXO defined)
artifact_id : [32]Formula:blake3("ingot/artifact" || SCHEMA_ID || HASH_PAYLOAD)Role: Cross-version identifier for the same object (deduplication, reference).instance_id : [32]Formula:blake3("ingot/instance" || artifact_id || outpoint_bytes)Role: Identifies this UTXO instance (a specific version of the same artifact).parent_instance_id? : [32]Source: Explicit reference in payload; used for "spend old, create new" topological constraints.tip_flag : boolWhether it is the current active tip (determined by oUTXO three disciplines).ordinal_seq? : u64(non-consensus) First appearance sequence number (deterministic numbering offirst_seen (txid:vout)); display only.
3) Terminology (Glossary)
- Artifact: Object entity (e.g., an inscription, an FT contract, a composable Bundle). Uniquely identified by
artifact_id. - Instance: Object's specific generation instance (product of one state change), corresponding to one Pay2Ingot output; identified by
instance_id. - Tip (Active Anchor): Current instance recognized by oUTXO as uniquely valid latest instance.
- Stale: Instance defeated in concurrency or replaced by successor (historical retention, no longer active).
- Mint / Transfer / Attach / Mutate / Bundle / Rebase: Object operation semantics (defined by schema FSM; oUTXO only handles "sequence and uniqueness").
- Schema FSM: Finite state machine corresponding to each
SCHEMA_ID(executed off-chain), constraining field types, legal operations, and derived state. - Soft-Consensus: Constraints not written into L1, but enforced through public specification + test vectors + multi-implementation consistency to force community compliance.
4) Object-Oriented (OO) Mapping
- Class = Schema:
SCHEMA_IDcorresponds to "class definition" (fields, available operations, constraints and test vectors). - Object = Artifact: An object of a certain "class", identified by
artifact_id. - Constructor = Mint: Creates first instance (first tip).
- Methods = Mutators:
Transfer/Attach/Mutate/Bundle, etc., input is parent tip, output is new instance. - State = Canonical Payload + Derived State: Normalized bytes of payload + FSM-derived balance/ownership, etc.
- Access Control = LOCK: Methods can be called by whom (multisig, timelock, etc. validated at L1).
- Composition = Bundle: Inter-object composition/aggregation (references multiple
artifact_id).
Inheritance/Polymorphism not in v1 goals; recommend using composition and schema upgrades for extension.
5) Invariants and Resolution
- Unique Active Tip
For any
artifact_id, at any height there is at most one instance withtip_flag = true. - Spend Old, Create New
- Mint: No
parent_instance_id, does not spend parent tip, creates first tip. - Transfer/Attach/Mutate/Bundle: If
ALLOW_MUTABLE=1, must spend current tip (same transaction/cross-transaction both allowed); ifALLOW_MUTABLE=0, indexer marks any "attempt to replace tip" update asINDEXER_MUTATION_FORBIDDEN, does not become active tip.
- Mint: No
- Lexicographic Resolution
When concurrent successors compete, select one as tip by
(blue_score, block_id, wtxid): first compareblue_score(ascending); if equal, compareblock_id(block hash byte string lexicographic order, big-endian); then comparewtxid(byte string lexicographic order, big-endian), others markedstale. - Lock Visibility Whether an instance is spendable is completely determined by L1 consensus; off-chain only determines tip visibility based on L1 spendability results.
- Unknown Schema
Mark
raw_data, must not execute FSM, but can be re-indexed and upgraded to executable state (through Registry test vectors).
6) Operation Semantics (Indexer Side)
- Mint: No
parent_instance_id, does not spend parent tip; generates first tip. - Transfer: If
ALLOW_MUTABLE=1, spend tip โ output new instance (ownership and other fields change); allowsHASH_PAYLOADunchanged (only ownership/lock changes). - Attach: Append auxiliary data in payload (reference parent tip); requires
ALLOW_MUTABLE=1, must spend current tip. - Mutate: Change object mutable fields; requires
ALLOW_MUTABLE=1, must spend current tip. - Bundle: Aggregate multiple
artifact_id, form composite object; requiresALLOW_MUTABLE=1, must spend current tip. - Rebase (Suggested): When increments are excessive, produce "consolidated version" (schema semantics; doesn't change invariants).
The above operations are defined by schema FSM for fields and legality; oUTXO only handles sequence and uniqueness.
7) Replay Algorithm
Input: All P2I transactions from genesis to target height (including revealed payload and header).
Process:
-
Witness Validation (rigid validation sequence):
- Structure/limit checks โ payload hash validation โ reassemble normalized
canonical_bytesโ calculateH = blake3("ingot/payload" || canonical_bytes)โ assertH == witness.PAYLOAD_HASH == output.HASH_PAYLOADโ LOCK validation/signature/timelock โ fee check - Payload hash mismatch โ
CONSENSUS_E_HASH_MISMATCH - witness.PAYLOAD_HASH != output.HASH_PAYLOAD โ
CONSENSUS_E_HASH_MISMATCH
- Structure/limit checks โ payload hash validation โ reassemble normalized
-
Verify/recover
payload(DAG-CBOR normalization). -
Calculate
artifact_id / instance_id. -
Apply oUTXO three disciplines:
- Initialize or update tip for this
artifact_id; - Concurrent resolution by
(blue_score, block_id, wtxid)uniqueness: first compareblue_score(ascending); if equal, compareblock_id(block hash byte string lexicographic order, big-endian); then comparewtxid(byte string lexicographic order, big-endian); - Exclude unlocked instances from active set.
- Initialize or update tip for this
-
If
SCHEMA_IDis enabled by Registry โ execute FSM โ update derived state; otherwise markraw_data.
Output:
- Active tip mapping:
artifact_id โ instance_id - Event stream (Mint/Transfer/Attach/Mutate/Bundle)
- Derived state (balance/ownership/index)
8) Indexer Interface (Minimal Set)
get_tip(artifact_id) -> instance_id | Noneget_artifact(artifact_id) -> {schema_id, payload, lock, tip_flag, ...}list_events(artifact_id, from_height)state_query(schema_id, key) -> value(defined by FSM)prove(artifact_id, height) -> {merkle_proof, bls_agg_sig?}(optional: state root and aggregate signature)
Light client recommendation: Pull minimal proof and sampled fragments by
artifact_id, verify againstHASH_PAYLOAD.
9) Error Code Layered Naming Convention
L1 / Consensus Layer Error Codes (CONSENSUS_*)
CONSENSUS_E_INVALID_STRUCTURE: Structure/boundary validation failedCONSENSUS_E_HASH_MISMATCH: Reassembled hash doesn't match witness hash, or witness hash doesn't match output hashCONSENSUS_E_SIGNATURE_INVALID: Signature verification failedCONSENSUS_E_TIMELOCK_NOT_SATISFIED: Timelock not satisfiedCONSENSUS_E_UNKNOWN_VERSION: Unknown versionCONSENSUS_E_SCRIPT_FAILED: Script execution failed (for ScriptHash locks)
Off-Chain / oUTXO Error Codes (INDEXER_*)
INDEXER_CONFLICT_RESOLVED: After concurrent conflict resolution, this instance was judged asstaleINDEXER_MUTATION_FORBIDDEN: Parent instance didn't setALLOW_MUTABLE, mutation forbiddenINDEXER_TIMELOCKED: Instance not unlocked, cannot be counted as tipINDEXER_UNKNOWN_SCHEMA: Not enabled in RegistryINDEXER_FORMAT_INVALID: Payload encoding not DAG-CBOR normalized or missing fieldsINDEXER_INSTANCE_COLLISION: Multiple on-chain conflicts with sameinstance_idINDEXER_PARENT_MISSING: Declaredparent_instance_iddoesn't exist/not visible
Policy / Network Layer Error Codes (POLICY_*)
POLICY_RATELIMITED: Account rate limit triggeredPOLICY_FEE_FLOOR_UNMET: Fee floor not met
Processing Flow:
- L1 rejection errors โ transaction/block directly invalid
- oUTXO errors โ doesn't affect L1, effectively records/rollbacks/resolutions
- DAG reorg: Indexer allows tip flip within
FINALITY_K; beyondKlocks and only rolls back on longer reorg
10) Payload Structure Suggestions (DAG-CBOR, Example Keys)
artifact_type : "inscription" | "ft" | "nft" | "bundle" | "custom"schema_version : u16parent_instance_id? : bytes[32](required for non-Mint)mutator? : { op: "transfer|attach|mutate|bundle", args: {...} }body? : bytes | map(original text or structured data)uris? : [ { uri, content_hash } ](ifALLOW_EXTERNAL_URIS)author_sig? : bytes(display/source proof)
HASH_PAYLOAD Unified Definition:
HASH_PAYLOAD = blake3("ingot/payload" || canonical_bytes), wherecanonical_bytesis DAG-CBOR normalized encoding payload bytes. Witness domainPAYLOAD_HASHcalculation standard is the same, and must satisfy:blake3("ingot/payload" || canonical_bytes(reassembled_payload)) == witness.PAYLOAD_HASH == output.HASH_PAYLOAD. Any step not equal โCONSENSUS_E_HASH_MISMATCH.
11) Version Compatibility (v1.0 / v1.1)
| Item | v1.0 | v1.1 (Hard Fork Activation) |
|---|---|---|
| Signature Algorithm | CopperootSchnorr | CopperootSchnorr + BIP-340 StandardSchnorr |
| MAST | Not Supported | Optional MAST_ROOT[32]; witness requires MAST_PATH + leaf_lock; only validates path and leaf boundedness |
| Field Compatibility | No MAST_ROOT |
New MAST_ROOT (v1.0 nodes fail-closed โ requires hard fork) |
| Witness Validation | No MAST | Additional step: validate MAST_PATH, depth โค 8 |
MAST_ROOT Purpose: Hides specific lock combinations (privacy enhancement), does not introduce script execution, only validates Merkle path and leaf encoding boundedness.
12) Registry Enforcement Mechanism and Admission Pipeline
Registry Enforcement Mechanism Overview
Registry serves as oUTXO protocol's "off-chain hard consensus" mechanism, ensuring consistency of all implementations through test vector enforced verification. Implementations that fail Registry verification can only output raw_data, must not claim "executable state".
Admission Pipeline
-
Testvectors Repository (with commit hash):
S1 Serialization/Endianness: Verifies little-endian encoding of all numeric fieldsS2 MAST Path: Verifies v1.1 MAST path calculation and depth limitsS3 Concurrent Resolution: Verifies(blue_score, block_id, wtxid)resolution rulesS4 Reorg Rollback: Verifies state rollback and re-resolution during DAG reorgsS5 Signature Strictness: Verifies CopperootSchnorr strict validation and BIP-340 SchnorrS6 Error Code Mapping: Verifies correct mapping of layered error codes
-
CI Enforced Validation:
- Must pin commit to pull testvectors
- Must run all 6 test suites (S1-S6)
- 100% pass rate requirement
-
Registry Badge Issuance:
- After passing, generate Registry Badge:
registry.ingot.badge::<schema_id>::<commit> - Badge contains version stamp, commit hash, pass timestamp
- After passing, generate Registry Badge:
-
Ecosystem Whitelist Mechanism:
- Wallet default index source only whitelists implementations with Badge
- Ecosystem listings only display verified implementations
- Unverified implementations can only claim
raw_datamode
-
Runtime Self-Check:
- Implementation must print
registry_version/commitand self-check hash at startup - Facilitates network-side inspection and consistency verification
- Implementation must print
Registry Deprecation Policy
- When Registry publishes
deprecates: ["vX.Y.Z"] - Deprecated versions cannot claim executable after grace period (e.g., 90 days)
- Can still run during grace period, but cannot apply for new Badge
Implementation Requirements
- Indexers: Must pass Registry validation to provide "executable state" API
- Wallets/SDKs: Recommend integrating verified indexer sources
- Ecosystem Applications: Recommend using implementations with Badge to ensure consistency
13) Interface and Boundary with L1
Separation of Responsibilities
- L1 Responsible: Structural integrity, hash consistency, payload size limits, signatures and timelocks, fees adequate.
- oUTXO Responsible: Object uniqueness, version chain, concurrent resolution, FSM invocation and derived state.
- Registry Responsible: Schema documentation and test vector hashes; indexers must pass before enabling FSM.
Timelock Responsibility Boundary
L1 fully executes TIMELOCK_* (unsatisfied rejects tx); oUTXO excludes unlocked instances from visibility, ensuring active tip set only contains spendable instances. They complement rather than conflict.
Unique Primary Key Convention
instance_id = blake3("ingot/instance" || artifact_id || outpoint_bytes) is the unique primary key; (artifact_id, outpoint) is only an equivalent index key, used for implementation-layer lookup and deduplication. External identifiers and interfaces uniformly use instance_id.
Previous: Data Insertion Mechanism | Next: Asset Management | Technical Specifications