Skip to main content
Status: Draft Type: Standards Track Category: Core Created: 2026-06-17 Requires: CIP-2 (Off-chain Compute / Runners), CIP-20 (Fungible Tokens), CIP-24 (CBSS Secrets Manager — v1 SEALED mode uses the §3.4.6 Time-Lock Release amendment, now merged; see §Sealed-Bid), CIP-25 (Cross-Chain Architecture)
Revision history
  • r1 (2026-06-17) — Initial draft. Defines the intent-settlement primitive (Verifier system actor + runner-solvers + CBSS sealing) and pluggable cross-chain backends (native CIP-25, ERC-7683/OIF, NEAR Intents) as CIP-25 L3 applications.
  • r2 (2026-06-29) — Audit fixups (Marshal review of #195): pin the account-path CBY deposit to a direct native-CBY account debit (the tx model has no attached-value field); state the v1 SEALED-mode CIP-24 dependency precisely (a caller-chosen auction identity + height-triggered release require a CIP-24 amendment, feasible on its tlock construction); specify checked-i128 conservation arithmetic (CIP-20 does not bound supply below 2^127); correct the opcode-allocation references (the Technical WP has a §9.1 address table but no single §9.2 opcode table); specify the sealed-solver dispute mapping; fix the EventListener citation (WP §16.3) and note that Transaction / Instruction / SystemInstruction now live in cowboy-protocol-codec.
  • r6 (2026-06-29) — Governance bundle (post-merge): WP §9.1 allocates 0x14 to INTENT_SETTLEMENT (this change amends the table) and reassigns the deferred EventListener to 0x15; reserve opcodes 146151 (next free above codec max 145) for the six Intent* instructions; fill the Parameters table with concrete defaults (MAX_SETTLE_INTENTS=64, MAX_DIFFS_PER_INTENT=16, GC_GRACE=256, AUCTION_GRACE=50, RECLAIM_GRACE=256, WITHDRAW_TTL=86_400 blocks, SETTLEMENT_DOMAIN_VERSION=1; inbound caps per-corridor + mandatory kill switch). Resolves the standing 0x14 tie-break and the parameter/opcode under-specification (round-4 items).
  • r5 (2026-06-29) — Audit fixups round 4 (Marshal run 381): constrain a Withdraw intent to exactly one outbound diff (unambiguous escrow target); enumerate the signing-digest method/kind as a fixed IntentKind enum (SETTLE | WITHDRAW) so a signature for one kind cannot be replayed as another. (Round 4 otherwise found the spec converged — these are the two remaining LOW nitpicks.)
  • r4 (2026-06-29) — Audit fixups round 3 (Marshal run 380): scope TokenDiff “price improvement” to the solver (a non-solver signer is credited exactly their MinReceive floor; surplus is the solver’s fee — resolves the floor-vs-surplus ambiguity); add a consolidated Parameters table + flag-day note and disambiguate GRACE into GC_GRACE / AUCTION_GRACE / RECLAIM_GRACE; pin min_finality (dest-chain blocks) + a finality_level() backend method + begin()/prove_delivery enforcement; fix SETTLEMENT_DOMAIN_VERSION (a stable u16, not the doc revision); define the open_auction AuctionGrant delegation; note CBY’s Ledger-Backing analog + the pegged-token mint_authority bootstrap. (credit_deposit’s CIP-20 mint was verified to invoke no transfer hook — no reentrancy surface — so no change there.)
  • r3 (2026-06-29) — Audit fixups round 2 (Marshal run 379): back inbound credit_deposit of a CIP-20 token with a checked CIP-20 mint (Settlement = pegged-token mint_authority) + new Ledger-Backing invariant (no unbacked balance); bound bundle work (MAX_SETTLE_INTENTS / MAX_DIFFS_PER_INTENT) so one settle cannot monopolize the shared system lane; deadline-bound the consumed-(signer,nonce) set + broadcast records (GC to the active-deadline window) so consensus state stays finite; require compute-then-commit buffering in settle (the engine does not roll back partial handler writes); reserve CBY_SENTINEL outside the token_id space; clarify max_value denominates the single outbound token; state request_withdraw/credit_deposit reentrancy posture.
  • r7 (2026-07-03)As-built alignment (v1 SEALED implemented + merged). SEALED mode is now built and merged (node devnet + cbss main), so §Sealed-Bid is rewritten to the implementation of record. The material corrections vs the r1–r6 design: (1) reveal is a permissionless RevealAuction transaction (opcode 156), NOT a CIP-5 timer callback — CIP-5 timers only fire PVM actor handlers, and a native reveal must be crank-able by anyone after reveal_height; (2) the three sealed instructions get concrete opcodes — OpenAuction=154, SubmitSealedBid=155, RevealAuction=156 (next free above the 146153 block; 152/153 are CIP-24 §3.4.6 RegisterTlockRelease/SubmitTlockRelease, the tlock backend this consumes); (3) the bid ciphertext is a versioned WrappedDek (the CIP-24 §3.4.3 IBE envelope, tlock HKDF domain cbss/tlock/v1) with the on-chain-recomputed base-AAD contract request_id ‖ u64_le(reveal_height); reveal does the threshold-IBE decrypt natively on chain (Lagrange-combine ≥ t committee partials → σ = MSK·I → AES-256-GCM); (4) the AuctionGrant originator is bound to the intent’s true owner via the broadcast resting record (not a self-asserted field), and the grant digest is chain-bound to the node’s own chain_id; (5) winner selection is deterministic first-valid in bidder-list order among bids that both decode to a conserving SettlementBundle and actually fill request_id, applied via a per-attempt-gas, compute-then-commit try-settle loop; (6) the liveness fallback cancels (CancelledGraceExpired) — no escrow is held in v1, so nothing is refunded; and a released-but-no-valid-bid reveal likewise cancels (CancelledNoValidBid, distinct: the key WAS released but no decrypted bid was a valid conserving fill) — neither “degrades to OPEN”; (7) concrete Parameters (MAX_SEALED_BIDS_PER_AUCTION=256, MAX_AUCTION_REVEAL_HORIZON=1_000_000, MAX_SEALED_BID_BYTES=8192, reveal gas) and error codes (17461754). Note: the solver-mode dispute (r2/§Solver Model) remains a design item — not yet implemented. Reveal was multi-round independently audited (0 code defects); the node↔cbss crypto envelope is cross-anchored by shared golden vectors verified on both mains. A multi-round review of this alignment then swept the whole document (and CIP-24’s cross-references) for surviving old-design claims and fixed them: the “degrade to OPEN” fallback (risk table + CIP-24 §3.4.6/§9.6) → cancel-only; “refund escrow” → no escrow held in v1 (cancel moves no funds); the open_auction check list reordered to the code’s actual precedence (error codes are consensus-visible); leftover “timer-driven reveal” / “best solver wins” in the AuctionMode comment, Worked Example B, and the Reference Implementation → permissionless RevealAuction / first-valid; the reveal-gas lane (system, not CIP-2); the solver-mode dispute framed honestly as unbuilt; and the present-tense “needs a CIP-24 amendment” → satisfied. A second hardening pass then caught the residuals the first sweep introduced/missed (the AuctionMode.OPEN “best-price wins” line; this bullet’s own “refunds”) and added the consensus MUSTs a spec-of-record needs: the reshare epoch-pin (reveal combines only same-quorum_epoch partials, else not-releasable — twin of CIP-24 §9a MixedEpochReceipts, now also stated in CIP-24 §3.4.6) and the 1-based committee-index Lagrange convention.
  • r8 (2026-07-03)0x14 committee bootstrap named. §Sealed-Bid presumed Account(0x14)’s CBSS committee/MPK already existed but never said how it comes to be — and 0x14 is keyless, so it cannot self-issue the owner-keyed RequestAccountDkg. Adds the back-reference to GovRequestSystemDkg (CIP-24 §3.5, opcode 158), the one governance-authorized (0x09) DKG-bootstrap path for a keyless system-actor scope, which MUST run once before the first OpenAuction. Documents the force_rekey hazard (a re-DKG installs a fresh MPK and strands in-flight sealed bids → their auctions cancel; routine refresh uses RequestReshare, which preserves MPK). Spec-only; the CIP-24 side (opcode 158, §3.5 + §9.6) is the primary home. Reference implementation: codec cowboyinc/cowboy-protocol#19, node cowboyinc/node#909.

Abstract

CIP-34 defines intent-based settlement for Cowboy and its pluggable cross-chain backends. An account — a user, agent, or solver holding a secp256k1 key — signs a declarative intent — “I give up exactly X of token A; I want at least Y of token B” — expressed as a signed token diff, signed with the account’s own secp256k1 key. (PVM actors hold no account key; they participate directly via the in-actor translator, authorized by caller provenance — see §Intent Authorization.) Off-chain solvers (CIP-2 runners) compete to fill it. An on-chain Settlement system actor (the Verifier — a native module at a reserved address, like CBSS/RAS) holds deposited balances as an internal ledger and applies a matched set of intents atomically, only if the bundle conserves value (no token is created). Solver competition can be run as a CBSS-sealed auction (CIP-24 time-lock) to eliminate last-look and bid-sniping — a property NEAR’s open solver market does not provide. Cross-chain legs go through a uniform ICrossChainSettlement interface with pluggable backends, governance-selected per corridor: the native CIP-25 bridge (first-party, trust-minimized), an ERC-7683 / Open Intents Framework solver-facing adapter (EVM corridors, Across-led), and NEAR Intents (1Click + Chain Signatures, for BTC / non-EVM reach). One honesty runs through the CIP: no standard is a liquidity source. External liquidity comes from solvers who front capital plus a settlement rail they trust — ERC-7683 standardizes only how a solver evaluates an order, and NEAR’s reach is gated by their chain onboarding. The native settlement core depends on none of them. This CIP specifies the on-chain settlement core in full and the external backends at the interface level; which backend is deployed per corridor is a governance decision per CIP-25 §1.4 / WP-v2 §16.2.

Motivation

Why intents, not an order book or an AMM

Cowboy already has continuous-clearing auctions for launches (CIP-22) and liquidity pools (CIP-21). Neither solves general-purpose, cross-asset, cross-chain settlement for the agent economy this protocol is built for:
  • AMMs leak value to LPs and MEV, and price degrades with pool depth. Poor fit for large or long-tail pairs.
  • Order books need continuous liquidity and on-chain matching cost.
  • Intents let a third party (a solver) do the hard work — sourcing liquidity, routing, crossing chains — while the chain only checks that the outcome the signer asked for was delivered and that no value was conjured. This is the natural settlement model for agent-to-agent commerce (CIP-28 banking), inference billing (CIP-2 outputs priced in CIP-20 tokens), and agent swarms that must exchange value without a human in the loop — accounts authorizing by signature, in-actor logic by caller provenance.

Why Cowboy is unusually well-suited

The intent-settlement pattern needs exactly the tiers Cowboy already ships:

Why external backends — and what they do not give you

The cross-chain problem splits into transport (prove a fact across chains), settlement (escrow + reimburse a filler), and liquidity (a counterparty willing to front capital). CIP-25 solves transport natively; the native settlement core (below) handles escrow/reimbursement. Neither produces liquidity — that comes only from solvers willing to inventory a corridor. External backends are how Cowboy reaches existing solver liquidity instead of bootstrapping all of it:
  • NEAR Intents — a production solver network with BTC / non-EVM reach (1Click API + Chain Signatures). Onboarding Cowboy as a supported chain is curated by NEAR.
  • ERC-7683 / OIF — the EVM-world order-evaluation standard (Across-led, broad EVM ecosystem). It lets external solvers evaluate a Cowboy order without a bespoke integration — but it standardizes only the solver-facing resolution, not escrow, settlement, pricing, or liquidity (§Where liquidity comes from).
Both are reach plays layered on the native core; neither is required, and neither is free.

What this CIP is NOT

  • Not an L0 / consensus-mechanism change. It adds a system actor — a new WP §9.1 address plus new SystemInstruction opcodes (allocated in the enum + uniqueness test; there is no central WP §9.2 opcode table — see §Protocol Allocations) — but does not modify block production, finality, or the token model. Settled assets are existing CIP-20 tokens and CBY.
  • Not a mandate to use any one backend. NEAR Intents, the ERC-7683/OIF adapter (an IResolver order surface over a CIP-25 reimbursement rail — 7683 alone is not a settlement backend), and the native CIP-25 bridge are interchangeable ICrossChainSettlement backends; selection is per-corridor governance (CIP-25 §1.4).
  • Not a re-specification of CIP-25. Transport, anchoring, and the lock-mint/burn-release bridge mechanics live in CIP-25. This CIP consumes them.

Specification

Overview

The on-chain core (Settlement actor) is self-contained and usable for purely domestic settlement (agent-to-agent, inference billing) with no cross-chain dependency. The cross-chain backend is engaged only by Withdraw/Deposit intents.

Core Data Structures

Intent Authorization Model

The Settlement actor is a system actor — a native module at a reserved low-byte address, the pattern Cowboy already uses for RAS, CBSS, the runner registry, entitlements, basefee, etc. (node/execution/src). Account intents are signed by the account’s own secp256k1 key (actor intents are authorized by caller provenance instead — see “How callers invoke Settlement” below).
  1. The user signs an EIP-712-style typed digest of the intent with the same secp256k1 key that signs transactions.
  2. The Settlement system actor recovers signer from the EthSignature via native recover_address, checks it matches the intent’s declared signer, then enforces nonce + deadline.
Signing domain (replay boundary). Because the account’s own secp256k1 key signs intents directly, the typed digest MUST bind a domain so a signature cannot be replayed across chains, deployments, or methods: { chain/network id, INTENT_SETTLEMENT address, SETTLEMENT_DOMAIN_VERSION (a fixed u16 bumped ONLY on consensus-critical interface/invariant changes, never on doc revisions), method/kind (the IntentKindenum —SETTLE|WITHDRAW — so a signature for one kind cannot be replayed as another), signer, nonce, deadline, every (token_id, amount) diff, dest_chain, dest_recipient, and any trust constraints (allowed_backends / max_value / min_finality / min_received) }. This borrows EIP-712’s field-binding discipline but is not EIP-712 wire-format (the preimage is a flat concatenation INTENT_SIGNING_DOMAIN ‖ u16_be(SETTLEMENT_DOMAIN_VERSION) ‖ INTENT_SETTLEMENT ‖ u64_be(chain_id) ‖ codec(intent_without_signature) hashed with a single keccak256 — no 0x1901 prefix, no hashStruct/typeHash; wallets sign the raw 32-byte digest). Omitting any field widens the replay surface — cross-chain especially, given the NEAR / Chain-Signatures legs. This gives the defining intent-system property — the user signs once off-chain, a solver relays and settles — with the account’s own key and no account-abstraction ceremony. It also runs the O(intents × tokens) settle loop natively in the system lane (LANE_SYSTEM_CYCLES, exactly 2× the block-cycle target — defined in node/types/src/constants.rs (LANE_SYSTEM_CYCLES), relation asserted in basefee.rs SPEC-MG-1) — because Settlement executes as an Instruction::System opcode, not PVM user-lane bytecode — removing the per-instruction PVM cycle ceiling a bytecode actor would hit. But LANE_SYSTEM_CYCLES is a per-block budget shared by all system instructions — once exhausted, further system txs in that block are rejected — so a bundle is explicitly bounded to keep one settle from monopolizing the lane and starving basefee/RAS/CBSS/runner-registry/timer ops: settle enforces MAX_SETTLE_INTENTS and MAX_DIFFS_PER_INTENT (a bounded intents × tokens), sized so a single bundle’s worst-case cycles stay well below LANE_SYSTEM_CYCLES, and rejects an over-cap bundle before any work. How callers invoke Settlement. Two paths:
  • Account (signature). A user/agent/solver/relayer holding a secp256k1 key submits a top-level Instruction::System tx carrying any Intent* instruction; authorized by signature, relayable by a solver. This is the only path for IntentSettle — the heavy O(intents×tokens) loop runs top-level with explicit gas.
  • PVM actor (provenance). An actor sends a message to INTENT_SETTLEMENT; v0 adds an explicit system-target branch to actor_instruction.rs that — for the self-service instructions IntentBroadcast / IntentDeposit / IntentWithdraw only — decodes/validates the payload and creates a deferred Instruction::System(...) with sender = message.from (as the job_dispatcher branch does, actor_instruction.rs ~:1200-1210). It does not enqueue a message to the system address, and not translate IntentSettle (next-block-deferred execution is capped at DEFERRED_CYCLES_LIMIT = 100k per constants.rs, too small for the settle loop) nor the cross-chain instructions.
Provenance authorization (fail-closed). For an actor-origin instruction the runtime sets signer = tx.from (it never trusts a payload signer) and requires all of: empty signature; signer is a live actor (get_actor(signer).is_some()); and the actor manifest grants an econ.intent entitlement with token/amount bounds — mirroring how system Transfer gates actor senders via econ.transfer (the Transfer arm’s require_manifest("econ.transfer") gate in system_instruction.rs). Account-origin requires a non-empty, valid signature. The two modes are mutually exclusive — an empty-signature account intent is rejected, never silently treated as actor-mode. Resting intents + matching. An actor cannot relay provenance, so it authorizes via IntentBroadcast, which records a resting intent bound to its canonical intent_hash over {signer, nonce, deadline, diffs, dest_chain, dest_recipient, method}. The record activates at the broadcast-execution block (next block, not PVM-emit time), and deadline is checked there and again at settle. A second active broadcast for the same (signer, nonce) is rejected (no silent replace). A solver later includes the intent in a top-level IntentSettle bundle; settle() accepts an unsigned/actor intent only if it matches an active record by exact intent_hash, consuming the record and (signer, nonce) atomically. An inline unsigned intent with no matching record is rejected — a solver cannot fabricate actor intents. State bounds (consumed set + records). A non-monotonic consumed set and the resting broadcast records are kept finite by deadline, not grown without bound: because every intent carries a deadline that settle/broadcast re-check, a consumed (signer, nonce) pair or an unsettled record need only be retained until deadline + GC_GRACE — past that, the deadline check alone rejects any replay, so both are garbage-collected down to the active-deadline window. (A monotonic-nonce O(1) counter is unavailable by design, since out-of-order/concurrent settlement is a goal; this deadline-bounded GC is what keeps the consensus state of an unbounded-throughput settlement actor finite.) Alternatives considered and rejected:

Protocol Allocations

Cowboy’s executor dispatches top-level transactions by Instruction type, not by target address: Instruction::System(SystemInstruction) routes to execute_system_instruction, Instruction::Actor to PVM execution, and Instruction::Custom is unsupported (node/execution/src/execution/transaction.rs). A native system actor is therefore exposed as explicit SystemInstruction variants, each an opcode with a match arm — exactly like RunnerRegister, TokenTransfer, EntitlementGrant. (Since COW-2360 / node#846 the canonical Transaction / Instruction / SystemInstruction types live in the cowboy-protocol-codec crate — github.com/cowboyinc/cowboy-protocol — re-exported by node/types/src/execution.rs; the file pointers below are those re-exports.) Settlement must touch the WP §9.1 address table, the system-instruction opcode allocation, and — for the actor-authorization path — the entitlement registry:
  • WP §9.1 (system-actor address space). INTENT_SETTLEMENT is allocated 0x14 in the WP §9.1 table (this governance bundle amends it), per the §9.1.3 next-free rule (0x10 RECEIPT_REGISTRY, 0x11 VALIDATOR_SET (CIP-11, code-deployed), 0x12 PaymentGate, 0x13 CONTAINER_REGISTRY (CIP-10, code-deployed) were already reserved; note BankActor/CIP-28 was formerly mis-penciled at 0x13 and must relocate — see WP §9.1 reconciliation). The deferred EventListener bridge oracle, formerly penciled for 0x14 in WP §16.3, is reassigned to 0x15: §16.3 deferred its address to allocation time (0x14 there was an as-of-revision snapshot, not a binding §9.1-table reservation), so CIP-34 is the first specification to bind this slot in the §9.1 table per the §9.1.3 rules, and the §16.3 snapshot moves to the next free 0x15. The implementation MUST add the INTENT_SETTLEMENT = 0x14 constant and extend the uniqueness/low-byte reservation test (note the non-dense reservations EVENT_SUBSCRIPTION_SYSTEM_ACTOR 0x1D and TradingPost 0x1E in node/types/src/constants.rs).
  • System-instruction opcodes. Settlement introduces new SystemInstruction variants — IntentDeposit, IntentWithdraw, IntentSettle, IntentBroadcast, IntentRequestWithdraw, IntentCreditDeposit — each claiming the next free opcode and adding an arm in execute_system_instruction. There is no single WP §9.2 opcode table to amend: the Technical WP normatively fixes only the §9.1 address table; opcodes are allocated in the SystemInstruction enum + the opcode-uniqueness test (cowboy-protocol-codec) and documented per-subsystem in the relevant sub-whitepaper ranges (e.g. timers 48–50, CBSS WP §3 opcodes 68–84). The PR MUST therefore (i) claim the reserved contiguous block 146151 — the next free opcodes above the current max 145 in cowboy-protocol-codec’s SystemInstruction::sub_type() (as of codec rev c36a17b): IntentDeposit=146, IntentWithdraw=147, IntentSettle=148, IntentBroadcast=149, IntentRequestWithdraw=150, IntentCreditDeposit=151 — re-pinned at implementation time against the then-current max by (ii) extending the opcode-uniqueness test, and (iii) record the block where the existing subsystem opcodes are documented (no central normative opcode registry exists today). As built (codec rev dadd9330), the block extends contiguously through the SEALED-mode instructions: RegisterTlockRelease=152 / SubmitTlockRelease=153 (the CIP-24 §3.4.6 tlock backend this consumes), and Settlement’s own OpenAuction=154 / SubmitSealedBid=155 / RevealAuction=156. IntentRequestWithdraw/IntentCreditDeposit (150/151) decode+dispatch but return UnsupportedInstruction pending the v2 cross-chain path. Naming: the Intent* prefix and INTENT_SETTLEMENT address deliberately avoid the existing, unrelated UpdateSettlementConfig / SettlementConfig (staking-reward settlement, in cowboy-protocol-codec’s SystemInstruction). Option (b)‘s general ecrecover host call would be an additional, separate opcode — out of scope here.
  • Entitlement registry (econ.intent). The actor-authorization path (§Intent Authorization) gates actor-origin intents on an econ.intent manifest entitlement that does not yet exist in node/types/src/registry.rs. The PR MUST add econ.intent to the entitlement registry and to manifest validation (node/types/src/manifest_validate.rs).

Settlement Invariants

Settlement-Conservation is the heart of the model: the actor never needs to trust the solver, because a bundle that would create value simply fails the check. A non-solver signer therefore receives exactly their Settlement-MinReceive floor; the per-token surplus is the solver’s fee, not a price improvement paid to ordinary signers (see TokenDiff). Arithmetic safety. The per-token accumulator is checked i128. CIP-20 declares max_supply: u256 | None and does not bound a token’s supply below 2^127, so a TokenDiff may legitimately carry |amount| ≥ 2^127; any single diff or running per-token sum that would overflow signed 128-bit makes Settlement-Conservation unverifiable, and the bundle reverts (the arithmetic never wraps). Equivalently: the actor rejects any bundle it cannot conservation-check in checked i128 rather than settling on a wrapped total. (If a token’s supply is itself ≥ 2^127, its diffs cannot be represented as a signed i128 delta and such a token is not settleable until CIP-20 caps supply or the ledger widens — called out so the bound is enforced, not assumed.) Pegged-token setup (bootstrap). Before the first inbound credit_deposit for a corridor asset, governance creates the pegged CIP-20 representation with mint_authority = INTENT_SETTLEMENT (token_create with owner = governance, mint_authority = Settlement), and records the token_id in the corridor metadata. A credit_deposit for a token whose mint_authority is not Settlement reverts (it cannot mint → the credit would be unbacked), so this setup is a hard precondition of Ledger-Backing, not an assumption.

Settlement Actor Interface

The signatures below are the conceptual interface; on the wire each maps to a SystemInstruction variant (§Protocol Allocations) — deposit → IntentDeposit, withdraw_local → IntentWithdraw, settle → IntentSettle, broadcast_intent → IntentBroadcast, request_withdraw → IntentRequestWithdraw, credit_deposit → IntentCreditDeposit.

Custody & Reentrancy

The v0 Settlement actor is a system actor (native module at a reserved address; §Intent Authorization). It custodies two asset classes in one internal ledger, keyed by token_id:
  • CIP-20 tokens — the caller first token_approve(token_id, INTENT_SETTLEMENT, amount). The native IntentDeposit handler then invokes token core directly with spender = INTENT_SETTLEMENT (allowance key (user, INTENT_SETTLEMENT, token_id), node/execution/src/.../token/core.rs). It MUST NOT route through the public SystemInstruction::TokenTransferFrom, which has no spender field and uses caller = tx.from (node/execution/src/.../system_instruction.rs) — that would check a user→user allowance and fail. Withdraw is symmetric: the native IntentWithdraw handler invokes token core with from = INTENT_SETTLEMENT, recipient = caller — likewise NOT the public SystemInstruction::TokenTransfer, which debits sender = tx.from (system_instruction.rs, token/core.rs) and would debit the user instead of the Settlement balance. Prefer increase_allowance/decrease_allowance over bare approve (CIP-20 approve race). Amounts are u128 (CIP-20 type).
  • Native CBY — CBY is the protocol-native currency and is NOT a CIP-20 token (CIP-20 §“CBY Relationship”); it occupies a reserved CBY_SENTINEL token_id in the ledger. The Cowboy tx model has no attached-value field (Transaction / Instruction carry none; native CBY moves only via an explicit Transfer), so both deposit paths debit the caller’s CBY account balance directly — there is no “attached value” to pin. Account path: IntentDeposit(CBY_SENTINEL, amount) debits signer’s CBY account under the tx signature, exactly as SystemInstruction::Transfer debits tx.from. Actor path: the deferred IntentDeposit debits the actor’s CBY account under the econ.intent grant (a send_message carries value = 0, actor_instruction.rs ~:1131-1145). Both credit balances[caller][CBY_SENTINEL]; withdraw is symmetric (debit ledger, credit the CBY account). CBY_SENTINEL is a fixed reserved bytes32 outside the CIP-20 token_id space (token_id = keccak256(creator ‖ symbol ‖ nonce)); token_create rejects it, so no CIP-20 token can collide with the native-CBY ledger key.
Two reentrancy boundaries:
  • CIP-20 transfer hooks. CIP-20 tokens may carry transfer hooks. deposit/withdraw_local are the only paths that call CIP-20 transfer; they MUST follow checks-effects-interactions (mutate the ledger, then transfer) and hold a reentrancy guard, so a hostile token hook cannot re-enter settle/withdraw_local mid-update. The hot path — settle — moves value only within the internal ledger with no external token calls, so it has no hook-reentrancy surface. request_withdraw likewise makes no external token call — it escrows already-deposited internal-ledger balance (no hook surface); credit_deposit’s only external call is the CIP-20 mint into Settlement’s own token-core balance (CEI + the reentrancy guard, exactly like deposit).
  • Surplus accounting. The conserved surplus (−Σ diff per token, ≥ 0) is credited inside the same execution; nothing is transferred out during settle.

Sealed-Bid Auctions (CBSS / CIP-24)

OPEN mode is the minimum viable settlement path. SEALED mode is the differentiated one and is built on CIP-24’s threshold-IBE time-lock. Auctions are always caller-driven, never protocol-driven — the Settlement actor never auto-opens an auction; sealed mode is opt-in per request_id. CIP-24 dependency (v1). CIP-24’s IBE construction is tlock (drand), so a height-based time-lock is feasible — but its current interface binds each ciphertext to a fixed per-version identity hash(SecretId ‖ version ‖ wrap_epoch) and releases the key on a signed ReleaseRequest at job time. Sealed solving instead needs (a) a caller-chosen auction identity (identity = request_id) and (b) automatic release at a height/round (reveal_height) with no per-secret request — so that a non-revealing bidder cannot block clearing. v1 therefore requires a CIP-24 amendment exposing this “auction-identity, height-triggered” release profile (feasible on the existing tlock construction). This dependency is now satisfied by CIP-24 §3.4.6 (Time-Lock Release mode) — a domain-separated (cbss/tlock/v1), height-gated, permissionless release on the same CBSS committee: a bid encrypts to identity = request_id with target_height = reveal_height, and the committee posts the release at the height (RegisterTlockRelease / SubmitTlockRelease). v0 (OPEN) and Backend A carry no such dependency — only SEALED mode does. Three native SystemInstructions implement it — OpenAuction (opcode 154), SubmitSealedBid (155), RevealAuction (156). Reveal is a permissionless transaction, not a timer (as originally sketched): CIP-5 timers only fire PVM actor handlers, and a native reveal must be crankable by anyone once the key exists, so any account may submit RevealAuction(request_id) after reveal_height.
Bid envelope (WrappedDek). A sealed bid is a versioned WrappedDek (v2) — the SAME envelope CIP-24 §3.4.3 uses for IBE, differing only in the HKDF domain (cbss/tlock/v1): {version, ciphertext, nonce(12), ephemeral_u(96 = compressed G2 U = r·G2), aad}. The AEAD aad MUST equal base_aad ‖ compress(U) where the CIP-34 base-AAD is request_id(32) ‖ u64_le(reveal_height) — reveal reconstructs this on chain and full-equality-checks it, binding each bid to its auction + height. The key is K = HKDF-SHA256(ikm = compress(e(σ, U)) [288 B], salt = "cbss/tlock/v1", info = "cbss/tlock/v1" ‖ aad), and e(σ, U) = e(MSK·I, r·G2) = e(I, MPK)^r matches the encryptor. The tlock identity is I = hash_to_G1(request_id ‖ u64_le(reveal_height), "cbss/tlock/v1"); the release committee is the INTENT_SETTLEMENT (0x14) account’s CBSS committee — bootstrapped once by governance via GovRequestSystemDkg (CIP-24 §3.5, opcode 158) before the first OpenAuction, since 0x14 is keyless and cannot self-issue RequestAccountDkg (CIP-24 §9.6). This envelope + decrypt is implemented identically in node (execution/src/tlock_ibe.rs) and cbss-crypto (tlock_ibe_encrypt/_decrypt), cross-anchored by shared golden vectors. Reveal combines the posted partials only if their quorum_epoch equals the current committee epoch (reshare epoch-pin): the Lagrange x-coordinates are committee positions in the epoch the partials were signed under, so a committee reshuffle after the partial set reached threshold makes the release not-combinable — reveal returns not-releasable (TlockNotReleased, → CancelledGraceExpired after grace) rather than silently producing the wrong σ. The interpolation is evaluated at x = 0 with each partial’s x-coordinate set to its 1-based index in the committee ordering (its position in the release key’s committee list, +1); index 0 and duplicate indices are rejected. This ordering is exactly cbss-crypto’s, so node and CBSS agree byte-for-byte (pinned by the shared combine_partials golden vector).
Scope (normative): sealed bids keep their own base AAD — they are OUT of the CIP-24 account-secret AAD binding. The sealed-bid base AAD is request_id(32) ‖ u64_le(reveal_height) under the time-lock domain cbss/tlock/v1, and it is deliberately not the account-secret §3.4.3 layout. It does not gain the chain_id prefix or the trailing mpk_g2 that CIP-24’s account-secret base_aad binds (chain_id ‖ account ‖ key_hash ‖ version ‖ wrap_epoch ‖ mpk_g2, 172 bytes; CIP-24 §3.4.3 / History). Base AADs in the CBSS IBE families are per-family and do not inherit — the account-secret chain/release-key binding applies only to account secrets, exactly as CIP-24 §3.4.6 states for time-lock release. A change to the account-secret AAD does not alter this envelope, its golden vectors, or the request_id ‖ reveal_height contract. The residual key-substitution concern for sealed bids (a RpcCommitteeMpk that serves a substituted committee mpk') is not remedied by an AAD change here — submit_sealed_bid stores ciphertext verbatim with no envelope validation, and a submit-time rejection would in any case arrive after the substituting party already holds both the key and the bid. It is instead addressed by authenticating the committee MPK at the encryptor against the release key’s release_key_material_hash (which the release-key RPC already serves), and by an independent committee-side reveal-height check on the tlock signer — both tracked as separate CBSS workstreams, not as changes to this base AAD.
Properties this buys, that an open market cannot:
  • No last-look. A solver cannot observe competitors’ bids and re-quote; all bids are committed before the key exists.
  • No bid-sniping. The reveal height is fixed; there is no end-of-auction timing game (cf. CIP-22’s anti-sniping rationale, achieved here cryptographically rather than by gradual release).
  • Optional private order flow. The intent itself may be IBE-encrypted to the auction identity, so front-runners cannot see the order until reveal.
Liveness fallback. If the CIP-24 committee fails to release the key by reveal_height + AUCTION_GRACE (default 50 blocks), a permissionless RevealAuction cancels the auction (CancelledGraceExpired) and clear_auction deletes the record, bidder list, and all stored ciphertexts. (Earlier drafts spoke of “degrading to OPEN mode”; the implementation simply cancels — it does not re-open a plaintext round.) No escrow is held at open/bid time in v1 — a bid stores only its ciphertext; balances move only inside settle_bundle at a successful reveal — so a cancelled auction moves no funds and traps nothing (nothing to refund). No PVM secret path. PVM actors are not trusted decryptors or auction adjudicators in v0 or v1: sealing, key release, and bid evaluation are CBSS-committee + native only. An actor never receives a decryption key or adjudicates sealed bids.

Solver Model (CIP-2 runners)

  • Solvers are CIP-2 runners. Open-mode solving is permissionless (anyone may submit a valid settle); sealed-mode bidding requires CIP-2 registration so misbehavior (e.g., submitting undecryptable ciphertext, or a revealed bundle that fails verification) is slashable. Dispute mapping. These offences are on-chain-detectable at reveal/settle (the native handler sees an undecryptable ciphertext, or a revealed bundle that fails settle verification), so they are adjudicated by a solver-mode dispute that reuses CIP-2’s stake + slashing economics but is triggered by these settlement-time failures within a dispute window — not the JobSpec commit-reveal job-dispute lifecycle, which solvers do not use (the solver loop is a distinct runner mode; see below). Status: the solver-mode dispute is not yet implemented — v1 SEALED shipped with the registration gate only (an unregistered solver is rejected), and the slashing adjudicator is deferred to a follow-on. Until it lands, an undecryptable/invalid sealed bid is simply skipped at reveal (it never wins), but its submitter is not penalized.
  • Mechanism: an external solver loop, not the Job Dispatcher. A solver is an off-chain program that watches IntentBroadcast events and submits settlement transactions. It runs on CIP-2 runner infrastructure (for stake, identity, slashing) but is a distinct, new runner modenot the current JobSpec/Job-Dispatcher request/response path (LLM/HTTP/MCP jobs with a submitter address, node/runner/src/types.rs). This CIP introduces the solver loop as that new mode.
  • A solver’s “fill” is just one or more Intents it signs with its own secp256k1 account key, included in the bundle, whose diffs oppose the users’. The solver profits from the surplus (−Σ diff per token, ≥ 0) credited to its account.
  • Solvers discover open intents via IntentBroadcast events (and an optional off-chain mempool service). LLM-assisted routing (the local-inference path) is an off-chain solver concern; the chain only sees the resulting signed bundle.

Cross-Chain Backends

The Settlement actor calls a uniform ICrossChainSettlement interface for any leg that leaves Cowboy. Two concerns must stay separate:
  • Order surface — how an external solver sees and evaluates a Cowboy intent.
  • Settlement rail — how value crosses and how the filler is reimbursed and trusts that reimbursement.
A backend pairs the two. Three are defined; all are governance-selected per corridor (CIP-25 §1.4). None of them manufactures liquidity — see §Where liquidity comes from.
Backend A — Native CIP-25 (first-party / bootstrap). Order surface and settlement rail are both Cowboy’s own: begin emits a CIP-25 L2 message, an L3 asset bridge (lock-mint or burn-release, CIP-25 §3.1) moves value, and prove_delivery is a CIP-25 L1 inclusion proof of the destination Delivered event. Trust = the CIP-25 backend for that pair (runner committee / ZK / optimistic); no third party beyond Cowboy’s runners. Liquidity is your own first-party solver. This is the bootstrap that is never blocked. Backend B — ERC-7683 / OIF adapter (EVM corridors, Across-led).
  • Order surface: a solver-facing IResolver that surfaces a Cowboy intent as a 7683 ResolvedOrder (steps / variables / payments / Assumption[]). The current EIP-7683 is resolver-centric and standardizes only this solver-facing resolution — it “does not require protocols to use a common escrow, settlement contract, or fill function” (the old open/fill settler model is its deprecated “Previous Draft”). Because Cowboy is non-EVM execution, the resolver is an EVM-RPC-shaped gateway an external solver can eth_call; without it, EVM solver tooling cannot read a native Cowboy actor. Assumption[] carries the proof backend, finality/challenge window, TTL/reclaim, and value caps a filler must accept.
  • Settlement rail: the native actor escrows and reimburses; a filler is repaid against a CIP-25 settlement proof they have opted into. 7683 does not make a filler trust Cowboy — they must have onboarded Cowboy’s CIP-25 backend and accepted its finality/challenge economics. Targeting Across specifically is an Across-over-7683 adapter (Across may expose its own surface), not generic-7683 compliance.
Backend C — NEAR Intents (BTC / non-EVM reach). The filling solver acts as a NEAR Intents / 1Click client:
Backend C is a consciously trusted, capped backend — not trust-minimized settlement, and its attestation is NOT equivalent to a CIP-25 inclusion proof. Where the destination chain has no objective proof path and the only attester is NEAR/1Click-correlated, it is allowed only under a hard low corridor/backend cap + rate limit, labeled trusted reach. Cowboy-account control uses Chain Signatures (next); onboarding Cowboy as a NEAR-supported chain is NEAR-gated (§Where liquidity comes from).

Cross-Chain Trust Constraints (user-signed)

A Withdraw intent MAY narrow how it is filled, via fields bound into the signed digest (a relayer cannot strip them):
  • allowed_backends — only these ICrossChainSettlement backends may fill (e.g. native CIP-25 only, no NEAR).
  • max_value — per-intent cap on the single cross-chain outbound token of the Withdraw (not a multi-token aggregate — there is no cross-token denomination); effective cap = min(max_value, governance corridor cap).
  • min_finality — minimum destination-chain finality the user accepts, in dest-chain blocks.
  • min_received — signed delivery floor on the destination leg, enforced at prove_delivery.
Enforcement ordering. request_withdraw validates and stores canonical RequestConstraints under request_id. begin() MUST select a backend satisfying both the stored user constraints and current governance corridor policy, then persist the chosen backend + proof policy; prove_delivery verifies against that stored choice. Governance may tighten after the request (lower a cap, pause a backend) but MUST NEVER loosen a user-signed constraint. min_finality is enforced at begin(): the actor reverts if the selected backend’s finality_level(dst_chain) is below it, and prove_delivery re-checks the delivered event’s finality depth against the persisted policy.

Chain Signatures (secp256k1, domain 0)

Cowboy accounts are secp256k1/EVM-style (20-byte address; EthSignature recovered over a keccak256 digest). NEAR’s Chain Signatures MPC signs secp256k1 (domain 0) and ed25519 (domain 1); secp256k1 domain 0 is the same path NEAR already uses to control Ethereum and Bitcoin accounts. So a NEAR identity can control a Cowboy account exactly as it controls an Ethereum account — the signing primitive is already in production, not new work. The crypto is therefore not the hard part. The hard part is transaction construction. The MPC signs an arbitrary 32-byte digest; it does not know how to build a valid Cowboy transaction (the canonical fields, encoding, and keccak digest the node recovers against). Off-node hand-rolling of Cowboy’s tx format has already caused signature-validity failures elsewhere in the stack. This CIP therefore requires that the Chain-Signatures relayer construct Cowboy transactions through the canonical node transaction builder — the exact encoding the node validates — never a reimplementation. A divergent encoder is a correctness bug, not a convenience. What this unlocks, once the relayer exists:
  • Inbound settlement into Cowboy. NEAR can settle an intent into a Cowboy account by having its MPC sign a Cowboy transaction that calls credit_deposit or transfers CBY — no wrapped representation required.
  • NEAR custody of native CBY for listing native Cowboy assets on NEAR Intents.
This is the Path D spike: a secp256k1 Cowboy-tx-construction-and-relay spike, not an ed25519 compatibility spike.

Where liquidity comes from

The load-bearing honesty of this CIP: no standard or backend is a liquidity source. Liquidity is solvers willing to front capital on a corridor, plus a settlement rail they trust to reimburse them.
  • ERC-7683 is a compatibility layer for solver evaluation, not liquidity and not a settlement guarantee. Solver inventory stays fragmented per protocol — a solver that fills Across orders is not automatically able to fill UniswapX orders, even though both are 7683. Each network onboards Cowboy, inventories the corridor, and accepts a settlement path independently.
  • NEAR reach is gated by NEAR onboarding Cowboy as a supported chain.
  • The only liquidity Cowboy fully controls is its own first-party solver over Backend A (native CIP-25). Everything external is BD + a settlement rail fillers accept + (for non-EVM Cowboy) an adapter.
So v0 (domestic), v1 (sealed), and Backend A — including a trust-minimized inbound path — need no external cooperation. Backends B and C extend reach but each requires solver onboarding + a settlement rail fillers accept; neither is a faucet.

Timeout, Cancellation, Refund

Follows the CIP-25 §2.6 timeout-and-reclaim pattern, with settlement-actor-specific grace sizing (RECLAIM_GRACE = 256 blocks, sized against DISPUTE_WINDOW_BLOCKS = 75, is a shorter, deadline-relative grace than CIP-25 §2.6’s expiry + GRACE where GRACE ≈ 1h ≈ 3,600 blocks — the settlement deadline already bounds validity, so the extra margin need only cover finality lag):
  • Every Intent.deadline and every cross-chain Quote.quote_deadline is enforced; expired intents/quotes revert.
  • A Withdraw that is not proven delivered within WITHDRAW_TTL (default 24h, matching CIP-25 asset-bridge TTL) unlocks reclaim(request_id): the user recovers escrow after deadline + RECLAIM_GRACE.
  • The Settlement actor invokes the originating actor’s on_settlement_timeout(request_id) hook so application state can unwind. The hook is best-effort: a reverting or out-of-gas hook MUST NOT block the reclaim/refund — it can never trap the user’s escrow.

Events

Parameters

All are consensus-affecting — changing one changes which txs/bundles succeed → receipt_root → a coordinated flag-day activation. Governance-set unless a default is given.

Worked Examples

A. Domestic agent-to-agent settlement (no cross-chain)

B. Cross-chain swap via Backend C / NEAR Intents (CBY → USDC on Ethereum)


Security Considerations

Adversary notes

  • Conservation is the trust anchor. Unlike a bridge that must attest external truth, domestic settlement requires zero external trust — the worst a malicious solver can do is submit a bundle that reverts.
  • Cross-chain inherits backend trust. Backend A’s adversary model is exactly CIP-25’s (committee/ZK/optimistic). Backend B (7683) inherits whatever CIP-25 settlement backend the filler opted into — 7683 adds no trust. Backend C additionally trusts NEAR’s solver network and the chosen delivery attestation. Both external backends are the price of liquidity reach and must be conscious, capped, governance choices — not defaults.

Rationale

Why a token-diff Verifier rather than an order book? Conservation-checking a signed diff set is O(intents) and stateless beyond the ledger; it needs no resting orders, no matching engine on-chain, and no price oracle. Solvers absorb all complexity off-chain. This is the same insight NEAR’s verifier and CowSwap’s batch settlement exploit. Why a system actor rather than a PVM actor? (See §Intent Authorization.) Settlement is core economic infrastructure — it custodies all deposits and runs a hot conservation loop — the same category in which CIP-20 made tokens native. Native execution verifies the account’s own secp256k1 key (no separate intent key, no PVM ecrecover host call) and escapes the per-settle PVM cycle ceiling. Pre-mainnet, claiming the protocol surface is free; later it would be a migration. Only the stable core goes native; solvers, sealing, and cross-chain stay off the consensus-critical path. Why reuse CIP-2 runners as solvers instead of a new role? Runners already carry stake, VRF selection, and a dispute window — the exact accountability a sealed-bid auction needs. Inventing a parallel “solver registry” would duplicate CIP-2 and fragment slashing. Why CBSS sealing rather than commit-reveal? Naive commit-reveal lets a solver withhold its reveal to grief the auction. A threshold-IBE time-lock makes the key become available at the reveal height independent of any bidder (under the CBSS committee-liveness assumption — committee liveness is still required, and §Sealed-Bid specifies the AUCTION_GRACE fallback if it fails), so a non-revealing solver cannot block clearing. This is the property that makes sealed solving robust. CIP-24’s tlock construction supports it, and the height-triggered, auction-identity release profile is the CIP-24 §3.4.6 Time-Lock Release amendment (now merged; §Sealed-Bid). Why make external networks backends rather than the mechanism? CIP-25 §1.4 / WP-v2 §16.2 already establish that bridge selection is governance, not protocol-baked. Treating NEAR Intents and the ERC-7683/OIF adapter as ICrossChainSettlement implementations keeps trust-minimized native settlement (Backend A) available and lets governance opt into external reach (Backends B/C) only where the liquidity is worth the added trust and onboarding cost. Why not just use NEAR Intents end-to-end and skip the native actor? That would forfeit Cowboy’s differentiators — CBSS-sealed fair auctions, domestic zero-trust agent-to-agent settlement, and inference billing — and put a custodial third party on the critical path for every settlement, including ones that never leave Cowboy.

Backwards Compatibility

New standard; no installed base. Settled assets are existing CIP-20 tokens and CBY. The Settlement actor is additive; no existing CIP changes behavior. Consistent with the protocol’s no-back-compat-shim policy.

Reference Implementation

  • Settlement system actor (ledger + settle + invariants + native secp256k1 verify): a native module in node/execution/src at address 0x14 (WP §9.1), invoked via its SystemInstruction opcodes (allocated in the cowboy-protocol-codec enum + uniqueness test; no central WP §9.2 table — see §Protocol Allocations). Plus the actor_instruction.rs system-target branch for INTENT_SETTLEMENT (decode/validate the self-service IntentBroadcast/IntentDeposit/IntentWithdraw only; deferred Instruction::System with sender = message.from; no message enqueue; distinct source event; tests for sender-stamping, no-enqueue, malformed-payload fail-closed, insufficient-gas, duplicate-nonce, and forged-signer rejection), an OPEN-mode solver-loop example, and a domestic A↔B match (Worked Example A). Target: devnet, no cross-chain dependency.
  • examples/settlement-sealed/ — CIP-24 sealed-bid auction with permissionless RevealAuction (opcode 156) reveal (v1; native handler + CBSS committee only, no PVM secret path) (Worked Example B, sans external leg).
  • ERC-7683 resolver adapter (Backend B): an EVM-RPC gateway exposing IResolver/ResolvedOrder; the Across-over-7683 settlement path is a follow-on.
  • NEAR backend (Backend C) and the Chain-Signatures relayer spike are tracked separately; the relayer MUST build Cowboy txs via the node’s canonical builder.

Phasing

  • v0 — Settlement system actor (native core: ledger + conservation + secp256k1 verify) + PVM-actor self-service (translator for IntentBroadcast/IntentDeposit/IntentWithdraw → provenance auth + econ.intent gate; IntentSettle stays top-level) + OPEN mode + one runner-solver; domestic only. Validates on devnet.
  • v1 — SEALED mode (CIP-24) + competitive solvers; the differentiated product.
  • v2 — Cross-chain reach: Backend A (native CIP-25) first; then Backend B (ERC-7683/OIF resolver adapter, Across corridor) and Backend C (NEAR 1Click + Chain-Signatures) as reach extensions, each gated on a settlement rail fillers accept.

Acknowledgments

The token-diff / solver / verifier settlement model follows NEAR Intents and its Verifier contract (introduction, intent types & execution), and the broader intents lineage (CowSwap batch auctions, UniswapX). The ERC-7683 / Open Intents Framework backend follows the current resolver-centric EIP-7683 (IResolver / ResolvedOrder) and Across’s ERC-7683 settlement model. NEAR docs corroborate the verifier internal-ledger model, Message Bus atomic execution, the 1Click trusted-swapping-agent custody window, and Chain Signatures domains (0 = secp256k1, 1 = ed25519). Chain Signatures background from NEAR’s chain-abstraction docs. The sealed-auction anti-sniping rationale parallels CIP-22; the cross-chain timeout/refund semantics follow CIP-25 §2.6.