XerisCoin
Protocol specification
XerisCoin is a Layer 1 whose blocks are produced every 4 seconds by one pipeline: Proof-of-History orders locally, a stake-weighted draw elects the leader, and Scrypt Proof-of-Work produces the block. Finality is 64 blocks deep. Every block carries a hybrid Ed25519 + ML-DSA-65 (Dilithium3) proposer signature from slot 1. The runtime executes 62 typed instructions over 23 contract types, 15 of them user-deployable, including the Alexandria real-world-asset layer and the Ari agent layer. Mainnet will launch as a federated beta: three roster producers, public self-staking closed. This revision records the CertiK module reviews of May to October 2026; Xeris has answered every round and deferred two consensus items, dependency upgrades and the fee model (§12.3).
1Introduction
XerisCoin is a Layer 1 in Rust (xrs-node 0.1.1). One 4 s slot runs three mechanisms (§2): a Proof-of-History hash chain orders events locally and is not a consensus input; a stake-weighted draw over validators holding at least 1,000 XRS selects the leader; Scrypt Proof-of-Work (N = 4,096, r = 4, p = 1; 2 MiB per hash) produces the block.
The runtime executes 62 native instruction variants (0–61) under one fee and replay model (§3, Appendix B). Contracts are 23 typed templates, no bytecode; 15 are user-deployable, 8 protocol-managed singletons (§5, Appendix C). SubDelegate, ZkPrivateTransfer, ZkIdentityProof and PqSignedTransfer are refused at the dispatcher (§12.2).
From slot 1 every block carries a hybrid Ed25519 + ML-DSA-65 (Dilithium3, FIPS 204) proposer signature; both components must verify (§9.1). Transactions are Ed25519 only. Finality is 64 blocks, ≈ 4.3 min (§2.4). Mainnet will launch as a federated beta: three roster keys, public self-staking closed (§12).
2Consensus
Each 4 s slot runs one pipeline: a Proof-of-History recorder ticks once and supplies the local slot number, a hash and a wall-clock timestamp; a stake-weighted draw over validators holding at least 1,000 XRS names the leader; the producer searches for a Scrypt Proof-of-Work solution for at most 3.9 s, under the leader target or, as a non-leader, under a target four times harder. The block carries a hybrid Ed25519 + ML-DSA-65 proposer signature and is broadcast. Validators recompute the leader, the target and the work; they do not recompute the PoH chain. Fork choice is by summed work and a reorganisation replaces at most 64 blocks.
2.1Proof-of-History
The recorder is a SHA-256 chain seeded with SHA-256("XERIS_V1_MAINNET_GENESIS_SEED_2026"). The producer loop ticks it once per 4 s iteration, not continuously, and the tick counter is the local slot clock. Each tick mixes the elapsed nanoseconds of the monotonic Instant clock into the preimage, so two nodes never produce the same hash for the same slot (XWC-27).
Validators do not replay the chain. Consensus checks the PoH fields of a block as listed below and nothing else. The poh_hash value is bound into the PoW preimage and the proposer signature; it is proposer-chosen bytes committed by work and signature, never recomputed by a peer.
| Field | Rule | Where |
|---|---|---|
| poh_hash | non-empty | every admission path |
| poh_timestamp | not zero | every admission path |
| poh_timestamp | >= parent.poh_timestamp | every admission path |
| poh_timestamp - parent.poh_timestamp | >= 2,000 ms; equality fails | every admission path |
| poh_timestamp - wall clock | <= 2,000 ms (MAX_FUTURE_TIMESTAMP_DRIFT_MS) | live admission and fork ingest |
2.2Proof-of-Work
Every block is mined and verified with Scrypt N = 4,096, r = 4, p = 1, empty salt, 32-byte output (2 MiB per hash). Two earlier parameter sets remain in scrypt_params_for_slot and cannot fire: SCRYPT_V2_SLOT and SCRYPT_UPGRADE_SLOT are both 0.
| Generation | N | r | p | Memory per hash | Status |
|---|---|---|---|---|---|
| Legacy | 1,024 | 1 | 1 | 128 KiB | unreachable |
| v1.1 | 16,384 | 8 | 1 | 16 MiB | unreachable |
| v1.2 | 4,096 | 4 | 1 | 2 MiB | every block |
The preimage is domain-tagged and length-prefixed from POW_PREIMAGE_V2_SLOT = 1, so one solution binds one parent, one timestamp and one transaction set (XWC-05). The target is 32 bytes of which only target[0] is non-zero; a larger byte is an easier target. The nonce starts at a random u64 and increments.
Mining stops at 3.9 s. The producer forfeits the slot and the template's transactions return to the mempool. The difficulty target is recomputed before every block from poh_timestamp values and the running difficulty committed after the parent, in integer arithmetic. Startup replay rebuilds it from 0x1f block by block; reorg simulation seeds it from the ancestor's recorded verdict.
| Rule | Value |
|---|---|
| Genesis target byte | 0x1f, also while fewer than 10 blocks exist |
| Window | newest min(len, 20) blocks |
| Target interval | 4,000 ms |
| Step | base × clamp(avg_ms / 4,000, 0.75, 1.25), fixed-point 1/10,000 |
| Clamp | 0x10 (hardest) to 0x3f (easiest) |
| Stall easing | last gap > 12,000 ms adds min((gap - 12,000) / 4,000, 0x10), capped at 0x3f |
Slot continuity is exact. On every admission path (live, peer, replay, reorg) block.slot == parent.slot + 1, or the block is rejected (XWC-10). A proposer cannot choose among candidate child slots or advance slot-gated state such as unbonding.
A block whose proposer is not the expected leader must meet the non-leader target: the base target shifted right by two bits (base >> 2, 4× harder). Validators enforce this from LEADER_ENFORCEMENT_ACTIVATION_SLOT = 1: a block passes under the leader target if its proposer is the leader, under the non-leader target otherwise, and fails under neither (XWC-04). The producer mines as a non-leader only after NON_LEADER_GRACE_SLOTS = 2, that is 8,000 ms of chain silence; this is a liveness convention of the producer, not a consensus rule.
2.3Leader election
The leader of a slot is a deterministic function of the stake table after the parent block, the parent hash and the slot number. One function computes it on the producer, at live and peer admission, in startup replay, in reorg simulation and in the recovery scorer.
Eligibility is stake of at least 1,000 XRS and nothing else; there is no activity window. The seed uses the parent's PoW hash, not a PoH hash, so predicting a leader requires mining the parent (C-1). Admission re-checks the proposer's stake against the same threshold from slot 1 on every path. On mainnet only the three roster keys can hold stake, so the draw selects among them (§12.1).
2.4Finality and fork choice
There is no vote-based finality. Fork choice is by summed work, and a reorganisation replaces at most MAX_REORG_DEPTH = 64 blocks (≈ 4.3 min at 4 s). A block 64 or more below the tip is final on the public path; the producer roster of §12.1 is an operational gate, not a consensus vote.
| Peer block | Action |
|---|---|
| proposer not in the roster (mainnet) | rejected |
| serialized size > 4 MiB | rejected |
| already canonical | ignored |
| previous_hash == tip.hash and slot == tip.slot + 1 | validated and applied directly |
| slot outside [tip - 64, tip + 64] | rejected |
| poh_timestamp > wall clock + 2,000 ms | rejected, retried later |
| otherwise | header gate, then ForkBuffer |
The header gate checks dimensions, PoH fields, Merkle root and hybrid signature and recomputes the PoW before a block enters the buffer. ForkBuffer holds at most 256 blocks and 260 MiB. A recovery thread assembles branches of at most 65 blocks from a canonical ancestor within 64 blocks and replays the candidate and the reference chain into isolated scratch ledgers through the production block validator.
Work is scored from the target each block was validated against, leader or non-leader, not from realised hash values or height. A candidate is adopted only if it is strictly heavier. Publication renames the candidate ledger over the canonical file, fsyncs the directory, swaps the in-memory ledger, journals the displaced transactions and resets the PoH recorder to the new tip.
3Blocks, transactions and execution
A block is a signed header over a Merkle-committed list of at most 40,000 transactions. A transaction is a Solana-format message, Ed25519-signed, carrying account keys, a recent blockhash and at most 16 instructions. One admission gate runs at RPC and P2P ingress; block validation runs the same structural caps again. Every transaction pays one flat fee before its first instruction (§3.4) and its instructions commit one at a time (§3.5).
3.1Block structure
A block travels as bincode inside a 4-byte big-endian length frame of at most 5 MiB and is stored as one JSON line per block in ledger.dat, fsynced before the block is committed. The signed header is about 5.6 KiB; the producer reserves that footprint before filling the transaction list. hybrid_proposer_sig and the inline proposer_dilithium3_pk are mandatory from slot 1 (§9.1; registry binding from slot 2, §8.2). Slot 0 is not mined: the PoH recorder ticks from 0 to 1 before the first proposal, and a slot-0 block must carry zero transactions.
3.2Limits
| Constant | Value | Enforced at |
|---|---|---|
| MAX_TXS_PER_BLOCK | 40,000 | block validation |
| MAX_BLOCK_SIZE_BYTES | 4 MiB serialised | block validation; producer assembly |
| MAX_IX_PER_TX | 16 | ingress; block validation |
| MAX_ACCOUNTS_PER_TX | 64 | ingress; block validation |
| MAX_IX_DATA_SIZE | 8 KiB per instruction | ingress; block validation |
| MAX_SLASH_IX_DATA_SIZE | 65,535 B, SlashReport only | ingress; block validation |
| MAX_GROTH16_VERIFICATIONS_PER_BLOCK | 64 | block validation |
| MAX_RWA_POLICY_WORK_PER_BLOCK | 256 holder scans | execution; instruction rejected |
| MAX_MODEL_MUTATIONS_PER_BLOCK | 16 | execution; instruction rejected |
The per-transaction caps are one set of constants shared by ingress and block validation, so a producer cannot place in a block what the mempool would refuse (XWC-15).
3.3Transaction admission
Every ingress path, POST /submit and both P2P transaction handlers, runs one gate in a fixed order. sanitize() must pass; num_required_signatures is at least 1; signatures.len() equals it; the instruction list is non-empty and within the caps of Table 5; every instruction decodes as a XerisInstruction or a SystemInstruction. Then tx.verify() checks the Ed25519 signatures. Then a stateless semantics gate rejects the legacy SystemInstruction::Transfer, a NativeTransfer whose to is not a canonical public key or names a reserved __* key, a zero amount on NativeTransfer, TokenMint or RWATransfer, an AgentExecute or ConditionalOrder that nests another, and SubDelegate (XWC-82).
State checks follow. The first signature must be absent from the processed set and from the mempool. recent_blockhash must be one of the last 150 block hashes, evaluated at the slot of the next block. A payer whose balance is below BASE_TX_FEE is routed to the underfunded population cap rather than the normal queue, unless the transaction is a fee-exempt attestation (§4.4). The mainnet Stake gate is in §12.1.
| Mempool bound | Value |
|---|---|
| Entries | 50,000 |
| Bytes | 64 MiB |
| Per transaction | 128 KiB |
| Per payer | 256 transactions; 4 MiB |
| SlashReport transactions | 128 in the pool; 4 per reporter |
| Underfunded payers | 5,000 (10 % of entries) |
| Priority | flat; BASE_TX_FEE for every transaction |
| Evictions per admission | 64, cheapest first, only below the incoming fee |
Transactions in an in-flight proposal are reserved for the mine-to-commit window and count as present for deduplication (XWC-17). Ordering and eviction are in §10.4; write-RPC limits in §10.5.
3.4Fees and replay
The fee is BASE_TX_FEE = 1,000,000 lamports (0.001 XRS) per transaction, independent of instruction count or size. It is debited from account_keys[0] before instruction 0 and credited to the block proposer. Fees are not burned; XRS leaves supply only through slashing. A transaction whose payer cannot cover the fee is skipped without execution, the block stays valid, and its signature is still recorded as processed. The only exemption is a transaction whose sole instruction is a ValidatorAttestation that passes every rule of Table 10 (XWC-60).
The blockhash is the block's Scrypt hash, served by getLatestBlockhash (Table 21). The processed-signature set is written into every snapshot (§10.3).
3.5Execution semantics
Instructions execute in message order. Each produces one outcome (signature, index, ok) that defaults to failed and is set to ok only when the handler commits. A failed instruction changes no state and the next instruction still runs: commit is per instruction, with no transaction-level rollback. Inside a handler every check precedes the first mutation (contract calls: §5.1). The per-block work budgets of Table 5 can reject an otherwise valid instruction, which then fails like any other.
| Transaction status | Rule |
|---|---|
| confirmed | every instruction committed |
| partial | some committed; first_failed_index names the first that did not |
| failed | none committed, or the transaction never executed |
A transaction skipped at the fee gate is not recorded and is never reported as history. Outcomes and receipts live in a SQLite store derived from execution; consensus never reads them (§10.5). An integrator that needs atomicity sends one instruction per transaction.
4Economics
XRS has 9 decimals; 1 XRS is 1,000,000,000 lamports. Genesis credits the treasury 8evPjjozSHNcoGRcv7zzxwan9sf3ubJ8q9CFzms6AK97 with 200,000,000 XRS, of which 1,000 XRS is staked and 199,999,000 XRS is liquid. Every other XRS is minted as a reward against one budget, MAX_EMISSION_SUPPLY = 500,000,000 XRS, shared by mining, staking and attestation. The nominal total is 700,000,000 XRS.
| Allocation | XRS | Share | Mechanism |
|---|---|---|---|
| Treasury | 200,000,000 | 28.6 % | Genesis balance; 1,000 XRS of it staked at genesis |
| Emission budget | 500,000,000 | 71.4 % | Mining + staking + attestation rewards; MAX_EMISSION_SUPPLY |
| Total (nominal) | 700,000,000 | 100 % | Treasury + emission budget |
xrs_native (wXRS, max supply 700,000,000), and UnwrapXrs (14) credits native XRS 1:1 without touching total_mined. The 700,000,000 XRS total therefore rests on the treasury not minting wXRS, not on a protocol check.4.1Emission
Every accepted block credits its proposer with a mining reward. The reward is BASE_BLOCK_REWARD = 10 XRS right-shifted once per 25,000,000 slots (HALVING_INTERVAL), the shift capped at 63. One epoch is 25,000,000 slots × 4 s ≈ 3.17 years. The reward is then clamped to the emission remaining after all earlier rewards and the attestation rewards already granted in the same block.
Within one block the budget is consumed in a fixed order: attestation rewards as transactions execute, then the mining reward, then staking rewards. Each step is capped by what the earlier steps left. total_mined is the sum of all three and is the only counter the cap reads.
4.2Staking rewards
At every slot that is a multiple of 900 (STAKING_REWARD_INTERVAL, one hour) each account with at least 100 XRS staked receives 7 % per year pro rata on its own stake. BLOCKS_PER_YEAR is 7,884,000, so there are exactly 8,760 payouts per year. The reward is credited to the liquid balance; it does not compound. Stakers are paid in Pubkey byte order; when the emission remainder cannot cover all of them, earlier keys are paid in full and the last payable key receives the truncated remainder (XWC-52).
Stake must bring the account to at least 1,000 XRS and a partial Unstake must leave 0 or at least 1,000 XRS. A stake of 100 to 999 XRS can therefore exist only as the remainder after a slash. Rewards need no claim (§12.2).4.3Stake and unbonding
Stake (9) and Unstake (10) require the signer to equal pubkey. Stake moves lamports from the liquid balance to the stake table and counts at the next leader draw and payout. Unstake moves them to an unbonding entry that matures 151,200 slots later (UNBONDING_PERIOD_SLOTS, 7 days). After each applied block the node releases every matured entry to the liquid balance; no instruction claims it. Unbonding stake earns nothing, elects nothing and remains slashable. A rejected Stake or Unstake is a failed instruction (§3.5); its fee is still charged. On mainnet a Stake naming a key outside the three-producer roster is rejected at every ingress and validation path (§12.1).
| Rule | Value |
|---|---|
Stake resulting stake | ≥ 1,000 XRS (MIN_STAKE_TO_MINE); a lower first stake or top-up is rejected |
Unstake partial amount | ≥ 1 XRS (MIN_UNSTAKE_AMOUNT); a full exit is always allowed |
Unstake remainder | 0 or ≥ 1,000 XRS |
| Pending entries per account | ≤ 10; an 11th Unstake is rejected and the stake refunded |
| Unbonding queue | ≤ 10,000 entries (MAX_UNBONDING_QUEUE); overflow is rejected and refunded |
| Maturity | completion_slot = start_slot + 151,200; released after the first block at or past it |
4.4Light-client attestation
ValidatorAttestation (12) pays 0.01 XRS (ATTESTATION_REWARD) to a staked validator that names a recent block by slot and full 32-byte hash. The reward is credited to the liquid balance and counted against the emission budget. A transaction whose only instruction is a valid attestation pays no fee (XWC-60).
| Rule | Value |
|---|---|
| Signer | equals validator |
| Stake | ≥ 100 XRS (MIN_ATTESTOR_STAKE) |
| Age | block.slot − block_slot ≤ 200 (ATTESTATION_SLOT_WINDOW) |
| Hash | block_hash_prefix is the 32-byte hash of the block at block_slot in recent_blocks (last 1,000 blocks) |
| Dedup | (block_slot, validator) is paid once |
| Rate | no prior attestation by the validator for a slot > block_slot − 10; one reward per 10 slots |
| Reward | min(0.01 XRS, MAX_EMISSION_SUPPLY − (total_mined + block_attestation_rewards)) |
4.5Slashing
SlashReport (38) is the only slashing path. The offence is a double-sign: two distinct block headers at one slot by one proposer. Any account other than the offender may report. The evidence is a JSON array of exactly two Block values with empty transaction lists; the handler rebuilds each canonical signing payload and verifies the proposer signature under the regime active at that slot, the hybrid Ed25519 + ML-DSA-65 check using the PQ key bound to the owner at that slot (XWC-53). Evidence older than 302,400 slots (PQ_KEY_HISTORY_RETENTION_SLOTS, 14 days) is rejected; the evidence limit is in Table 5.
The dedup key is recorded in the protocol contract xeris_slashing_registry, so one offence slashes once regardless of how the evidence is encoded or labelled (XWC-30). The slashed share of an unbonding entry is reduced in place and is never released. A report whose applied slash is zero fails and records no key, so the offence stays reportable (XWC-13). This path has no minimum slash amount.
5The contract engine
A contract is a typed state machine. The engine defines 23 ContractType variants, each with a fixed state struct and a fixed method set; there is no bytecode, no virtual machine and no user-supplied code. ContractDeploy (5) creates an instance from a type string and a JSON parameter object; ContractCall (4) invokes one method by name. Fifteen types are user-deployable. Eight are protocol-managed singletons under reserved xeris_* ids, created by the protocol and driven only by their dedicated instructions: DeviceRegistry, ZkVerifierRegistry, PqKeyRegistry, ConditionalOrderBook, DisputeRegistry, DealRegistry, TaskBoard, StateChannelRegistry. Appendix C lists every type with its aliases.
5.1Deploy and call rules
Every deployment and every call passes the rules below before type-specific logic runs. The caller a contract sees is the transaction fee payer. A transaction pays the flat 0.001 XRS fee and nothing more; there is no deploy or call fee. Mutating calls execute against a clone of the contract and the clone replaces the stored contract only when the method returns Ok.
| Rule | Value |
|---|---|
| Contract id | 1–128 characters; alphanumeric (Unicode is_alphanumeric), _ or - |
| Existing id | never overwritten; the deploy is skipped |
| Reserved id namespaces | xeris_*, identity_*, agent_registry_*, __*, *_xrs_pool: not user-deployable |
| Protocol-managed types | the eight singleton types are refused by ContractDeploy |
| Deploy parameters | params_json parsed as a JSON object; unparseable input becomes {} |
| Caller | account_keys[0], the fee payer |
| Call arguments | a UTF-8 JSON object; current_slot is overwritten with the block slot |
| Binary exception | Swap swap_a_to_b / swap_b_to_a with exactly 16 bytes: u64 LE amount ‖ u64 LE min_output |
| Protected methods | singleton internals (e.g. place_order, cancel_order, evaluate on xeris_conditional_orders) refused on generic, delegated and conditional-order paths |
| Inactive contract | every call fails with "Contract is not active" |
| Registry reads | paged: at most 32 items and 16 KiB per query |
5.2TimeLock, Escrow, Vesting, MultiSig
Four custody types hold a token balance and release it under one rule each. They move token_balances only, so native XRS enters them as wrapped xrs_native (WrapXrs, 13). TimeLock, Escrow and Vesting refuse an RWA token at deploy; a MultiSig transfer of one passes the compliance gate of §7.2. Timestamps are block poh_timestamp values in milliseconds; TimeLock does not range-check unlock_timestamp, and a value in the past is releasable at once.
| Type | Deploy parameters | Methods | Rule |
|---|---|---|---|
| TimeLock | beneficiary, token_id, amount, unlock_timestamp (ms); amount debited from the deployer | release | anyone may call once the block timestamp reaches unlock_timestamp; pays the beneficiary; no cancel, extend or reassign |
| Escrow | party_a (must equal the signer), party_b (distinct), token_id, amount | confirm, cancel | both parties confirm → amount to party_b; party_a may cancel until completion, even after party_b confirmed; no expiry |
| Vesting | beneficiary, token_id, total_amount, cliff_timestamp, end_timestamp (ms); cliff ≥ now, end > now, cliff ≤ end | claim | beneficiary only; vested = total × (now − start) / (end − start), linear from the deploy timestamp; the cliff gates the first claim; no revoke |
| MultiSig | signers[] (distinct public keys), threshold (1 ≤ t ≤ n); no funds are locked | propose, approve, reject | one pending proposal at a time, numbered by a nonce that approve and reject must name; at threshold approvals a transfer action moves the deployer's own native (XRS, xrs_native) or token balance; min(t, n − t + 1) rejections clear it |
5.3Swap
A Swap is a constant-product pool of two distinct non-RWA tokens. Deploy debits amount_a and amount_b from the deployer and mints isqrt(amount_a × amount_b) shares, which must exceed 1,000; 1,000 shares go to a dead address and are never redeemable. fee_bps defaults to 30 and may be set from 0 to 10,000 at deploy. The fee stays in the reserves and accrues to liquidity providers; the protocol takes no cut of swaps. add_liquidity and remove_liquidity take signed minimums and fail below them. Shares are entries in the pool state, not a token.
5.4Launchpad
A Launchpad creates the token lp_<id> and sells part of its supply on a constant-product curve with virtual reserves. The token is LaunchpadManaged: its supply is minted only by curve buys and TokenMint is refused. Nothing is debited at deploy. liquidity_bps of the supply (default 2,000; clamped to 500–4,000) is reserved for the graduation pool and the curve sells the remainder; the virtual reserves are chosen so the curve price at sellout equals the opening price of the pool. Every buy and sell pays 1 % to the creator, accrued until claim_rewards, and 0.77 % to the treasury address, credited in the same call. Buyers pay in wrapped xrs_native. A launchpad contract id is at most 116 characters so that the pool id fits in 128.
finalize_curve is permissionless once xrs_collected ≥ T. The ledger credits the pseudo-account __launchpad_pool__ with the collected XRS and the tranche L and deploys the Swap above; the shares belong to that non-signable account, so the liquidity cannot be withdrawn. If the pool id already exists or seeding fails, the finalisation reverts and the launchpad stays open. Launchpad methods execute only through a direct ContractCall: AgentExecute and conditional-order inner calls to a launchpad are refused. vesting_enabled: true is rejected at deploy.
5.5Standing orders
ConditionalOrder (23) places a standing order in the singleton xeris_conditional_orders. The order escrows locked_amount from the signer's native balance into __escrow_order_<id>; the minimum is the refundable 0.01 XRS storage bond, and an inner NativeTransfer must be covered by it. An order lives at most 650,000 slots (≈ 30 days); an owner holds at most 100 live orders, the book at most 10,000; the inner instruction is at most 2,048 bytes of bincode. The protocol evaluates every live order once per block after all transactions and executes triggered orders in order_id order; there are no keepers. A failed or expired order is cancelled and its escrow refunded. CancelConditionalOrder (24) refunds the owner.
| Condition | Source | Fires when |
|---|---|---|
slot_reached | — | current slot ≥ threshold |
price_above / price_below | a deployed Swap contract id | pool median price ≥ / ≤ threshold, in base units of token_b per 10^9 base units of token_a |
balance_above / balance_below | — | the owner's native balance ≥ / ≤ threshold; a missing entry counts as 0 |
oracle_value | an active oracle feed | latest value ≥ threshold; feed owner and type bound at placement; value no older than min(update_interval_slots, 900) slots |
A pool price is observed once per block, after transactions, for the at most 64 pools referenced by live price orders: spot = reserve_b × 10^9 / reserve_a, skipped and the window cleared when either reserve is below 1,000. The trigger price is the median of the last 61 observations and exists only after 20. Moving the median requires holding an off-market price for 31 consecutive blocks against arbitrage.
execute_limit_order and execute_dca_tick return an error. cancel_limit_order and cancel_dca_order refund the escrow in full.6The Ari agent layer
Ari (Autonomous Runtime Infrastructure) is a set of registries reached by dedicated instructions: self-sovereign identities with per-category reputation, bounded delegation from an owner key to agent keys, capability listings, escrowed tasks, stake-weighted dispute panels, two-party deals, payment channels, staked oracle feeds, attested devices, model records and liveness heartbeats. Each registry is a contract created on first use under the id in Table 14; Appendix C marks which types are protocol-managed. SubDelegate (22) is refused at ingress and skipped at the dispatcher; delegation is one tier deep and there is no agent hierarchy (XWC-82).
| Registry | Contract id | Created by |
|---|---|---|
| Identity root | xeris_identities | CreateIdentity (18) |
| Identity | identity_<sha256(pubkey)[..32]> | CreateIdentity (18), one per public key |
| Agent registry | agent_registry_<sha256(owner)[..32]> | RegisterAgent (15), one per owner |
| Oracles | xeris_oracles | RegisterOracle (25) |
| Devices | xeris_devices | HardwareAttest (27), after the proof verifies |
| Capabilities | xeris_capabilities | RegisterCapability (28) |
| Tasks | xeris_tasks | PostTask (31) |
| Models | xeris_models | RegisterModel (34) |
| Disputes | xeris_disputes | OpenDispute (36) or DisputeDeal (58) |
| Deals | xeris_deals | CreateDeal (54) |
| Channels | xeris_channels | OpenChannel (42) |
| Heartbeats | xeris_heartbeats | AgentHeartbeat (45) |
6.1Agent delegation
RegisterAgent (15) is signed by the owner and writes an AgentEntry into the registry of that owner. A registry holds at most 50 agents. UpdateAgent (16) changes any limit and sets or clears revoked; revocation is the kill switch and the owner may reverse it.
AgentExecute (17) is signed by the agent and carries a bincode-encoded inner instruction that executes as the owner. The inner instruction must be one of NativeTransfer, TokenTransfer, ContractCall, WrapXrs, UnwrapXrs, Stake, Unstake, TokenMint, TokenBurn; any other variant is refused. The spend of a transfer, wrap, stake, mint or burn is its amount. The spend of a ContractCall is derived from the method, and a method whose spend cannot be bounded is refused.
| Delegated ContractCall method | Spend charged to the budget |
|---|---|
| buy_tokens · sell_tokens | xrs_amount · token_amount |
| swap | amount_in + input_amount + amount |
| add_liquidity | quoted accepted_a + accepted_b, else amount_a + amount_b |
| remove_liquidity · create_dca_order · place_order | shares · total_amount · locked_amount |
| post · open · create | reward · deposit · amount |
| cancel · reclaim · claim_rewards · redeem · amend · list · status · get_stats · get_key | 0 |
| confirm · verify · any other method | refused: spend lives in contract state (XWC-87) |
Validation runs in this order: the agent is registered, not revoked and not expired; the spend is at most max_per_tx; the window resets after 21,600 slots and the spend is at most max_daily minus daily_spent; the contract allowlist applies to a ContractCall target; the operation allowlist applies to the operation name. The budget is recorded only after the inner instruction succeeds. A delegated call to an agent registry, a sealed protocol method, a Launchpad contract or an RWA contract is refused. A nested AgentExecute or ConditionalOrder is refused at ingress.
6.2Identity and reputation
CreateIdentity (18) creates one contract per public key; the signer must be the identity. identity_type is one of agent, device, service, human; display_name is at most 128 B and metadata_json at most 4,096 B. A non-empty parent_identity must co-sign the transaction, and the child is appended to the parent, which holds at most 200 children. UpdateIdentity (19) changes name and metadata; deactivated: true is one-way and there is no reactivation path. An active identity gates capability listings, task claims, model records, heartbeats and bound devices.
AttestReputation (20) requires an active attestor identity and refuses self-attestation. The category is one of reliability, accuracy, speed, honesty, safety, general; the score is clamped to 0–100 and the identity keeps a running average per category. An identity holds at most 100 credentials. AgentMessage (21) is checked for type, payload size (≤ 8,192 B) and an active sender, and writes no contract state; the message exists only in the block.
6.3Capabilities and tasks
RegisterCapability (28) and UpdateCapability (29) are signed by the provider_identity, which must be active. A listing is keyed provider:category and holds tags, region (default global), description ≤ 2,048 B, price_per_unit, max_concurrent, metadata_json ≤ 4,096 B and reputation_snapshot, the reliability average of the provider at registration. A provider holds at most 100 listings; 50,000 may be active. QueryCapabilities (30) is a no-op in a block; discovery is GET /capabilities/search.
PostTask (31) escrows reward from the poster. min_reputation must be 0; expires_at_slot lies within 648,000 slots (≈ 30 days) of the posting slot; verification is poster_confirm or oracle, where the oracle is a named approver key, and automatic is removed (XWC-74). At most 100,000 tasks are live. ClaimTask (32) requires the claimant to be the signer, to hold an active identity and, when required_category is set, an active listing in that category carrying at least one required_tag. ResolveTask (33) carries one of the resolutions below.
6.4Oracles, devices, models and heartbeats
Four further registries follow the same pattern: a dedicated instruction, a signer bound to the record and a fixed cap. The device registry is protocol-managed and is created only after the first attestation proof verifies.
| Registry | Instruction | Rule |
|---|---|---|
| OracleRegistry | RegisterOracle (25) | feed_type in price, event, sensor, weather, custom; stake >= 1 XRS locked from the caller; description <= 512 B; at most 1,000 active feeds |
| OracleRegistry | OracleSubmit (26) | owner only; metadata <= 1,024 B; the feed keeps the last 1,000 (slot, value) points |
| DeviceRegistry | HardwareAttest (27) | device_type in humanoid, terminal, iot, mobile, secure_element; signer is the device or its bound identity; proof is a 64-byte Ed25519 signature by the device key over the challenge below; at most 100,000 active devices |
| ModelRegistry | RegisterModel (34) · UpdateModel (35) | identity_pubkey == signer with an active identity; a record is at most 2,048 B; at most 10,000 live models; 16 model mutations per block |
| Heartbeats | AgentHeartbeat (45) | identity_pubkey == signer with an active identity; records older than 21,600 slots are dropped; at most 10,000 records; alive = last beat within 5,400 slots (≈ 6 h) |
6.5Disputes
OpenDispute (36) names a defendant and posts a bond; the bond is at least 1 XRS for a deal dispute and may be 0 otherwise. The panel is a snapshot of every validator staked at least 1,000 XRS at the opening slot, minus the two parties, weighted by stake; an empty panel is refused. At most 10,000 disputes are open.
ResolveDispute (37) carries one action. During the 21,600-slot challenge window (≈ 1 day) only evidence from the disputer and defendant_evidence from the defendant are accepted. After it, vote_disputer, vote_defendant and vote_dismiss are accepted from panel members whose stake is still at least 1,000 XRS; one vote per validator, the latest counts. The dispute is ruled when one option reaches an absolute majority of the snapshot weight. A ruling moves only the bond: refunded when the disputer wins, forfeited otherwise. expire after 648,000 slots (≈ 30 days) refunds the bond.
6.6Deals
A deal escrows the same amount from each party; the pot is deposit_a + deposit_b. Every instruction after CreateDeal carries the deal instance, a counter that is never reused. At most 100,000 deals are live.
6.7State channels
OpenChannel (42) escrows the deposit of the opener; the counterparty joins through ContractCall join. CloseChannel (43) carries the 64-byte Ed25519 signature of the other party over the close message below, and the final balances must sum exactly to the deposits. ForceCloseChannel (44) with state_sequence 0 claims the original split; with a newer signed state it opens a 1,000-slot challenge (≈ 67 min), during which challenge_update supersedes it with a newer signed state and after which finalize_dispute pays the recorded split. Expired channels settle automatically, at most 256 per block. At most 100,000 channels are unsettled.
7Alexandria real-world assets
An Alexandria token is a token-registry entry whose rwa_metadata binds it to an off-chain Ricardian document by SHA-256 hash. TokenCreateRWA (6) creates the entry; the signer must equal mint_authority; the registry seeds approved_holders with the issuer. The compliance gate reads this entry, not the issuer contract of §7.3, before every credit.
7.1Token metadata
RWAUpdateStatus (7), signed by mint_authority, writes any of the five statuses from any current one, appends a status_history tuple and replaces valuation, legal_doc_hash and legal_doc_uri when supplied.
7.2Compliance gate
enforce_rwa_credit runs before every credit of an RWA token: TokenMint (0), TokenTransfer (1), RWATransfer (8) and a MultiSig-approved transfer. A token without rwa_metadata passes. The checks run in this order; the first failure rejects the instruction.
- Roster larger than 1,024 entries: refused.
statusother thanactive: no mint, no transfer.transfer_restricted: the recipient must be an approved holder.accredited_only: every recipient, minted or transferred, must be an approved holder.
The fourth check applies even when transfer_restricted is false: the roster is the accreditation roster. RWATransfer adds amount > 0, from != to and signer == from; it is otherwise identical to TokenTransfer.
TimeLock, Escrow, Swap, Vesting, LimitOrder and DcaOrder refuse an RWA token on any leg. A block carries at most 256 units of RWA policy work (MAX_RWA_POLICY_WORK_PER_BLOCK): each RWA TokenMint or TokenTransfer, each RWATransfer and each approve_holder or revoke_holder call costs 1, also as a delegated or conditional inner instruction. A block over the limit fails preflight; an instruction that would exceed the total is rejected, and a triggered conditional order is deferred.
7.3Issuer contract
ContractDeploy (5) with type rwa by the token's mint_authority creates a RealWorldAsset contract for one token_id. asset_type, legal_doc_hash, legal_doc_uri, jurisdiction and approved_holders are copied from the registry; caller-supplied values are ignored. Every method is issuer-only and accepts a direct ContractCall (4) only; AgentExecute inner calls and conditional-order calls to an RWA contract are refused.
| Method | Arguments | Effect |
|---|---|---|
approve_holder | {"address"} | adds a canonical public key to the contract roster and the registry roster in one write; both rosters must already match; refused when the key is present, the roster holds 1,024 entries or the asset is redeemed |
revoke_holder | {"address"} | removes the key from both rosters; the issuer cannot be revoked |
amend_legal_doc | {"legal_doc_hash", "legal_doc_uri"} | replaces the hash and URI in the contract and the registry; refused after redemption |
redeem | none | sets redeemed_at, deactivates the contract and sets the registry status to redeemed |
distribute, toggle_distributions | any | disabled; the call fails |
There are no on-chain distributions; distributions_enabled and distribution_history are never written. Either redeem or RWAUpdateStatus with redeemed redeems the asset.
8ZK and post-quantum cryptography
Three primitives are reachable from consensus: Ed25519, ML-DSA-65 (Dilithium3) and Groth16 on BN254; §9 gives the signing contexts. Groth16 verifies proofs against verification keys registered by staked validators and is classically secure only. crypto.rs also holds Pedersen commitments, a Schnorr proof, a range proof and a WOTS+/XMSS verifier; no dispatcher arm calls them.
| Primitive | Algorithm | Use | Status |
|---|---|---|---|
| Ed25519 | Curve25519 | Transactions; P2P authentication; channel, device and slash messages; classical half of the block signature | active |
| ML-DSA-65 (Dilithium3) | Lattice, FIPS 204, pqcrypto-mldsa | Post-quantum half of the block signature; xeris_pq_keys registry; rotation proofs | active; mandatory from slot 1 |
| Groth16 | BN254 pairing, arkworks | Proof verification against registered VKs (ZkProofSubmit) | active; classical security only |
| Schnorr sigma, Pedersen commitments, range proof | Ristretto255 | Confidential transfers | reserve; unreachable |
| WOTS+/XMSS | SHA-256 hash-based | Fallback post-quantum signatures | reserve; unreachable |
PqSignedTransfer (52) | ML-DSA-65 | Transfer authorised by a post-quantum signature | disabled; fee charged, nothing executes |
8.1Groth16 verification
The registry xeris_zk_verifier is a protocol-managed singleton. A proof is accepted only against a verification key already registered in it. The verifier is arkworks Groth16::<Bn254>; a result other than Ok(true) is a failure.
The per-block cap of 64 Groth16 submissions is enforced by the validator and mirrored by the producer, which defers the transaction that would exceed it. A stored record carries hashes, not proof bytes, so verified cannot change after submission.
The ZK path refuses any post-quantum claim. A case-insensitive substring match over vk_id, claim_type, description, proof_system, proof_type and metadata_json against pq, post-quantum, post_quantum, postquantum, post quantum, dilithium, mldsa, ml-dsa and ml_dsa rejects the instruction (crypto-module XWC-13). A field containing the two letters pq in any word is rejected. PqAttest (53) is a self-asserted adoption marker: the node performs no cryptographic check, stores the record in the separate pq_attestations map with verified = false, and records the caller's flag under self_asserted.
ZkPrivateTransfer (48) and ZkIdentityProof (49) are disabled at the dispatcher (§12.2); no receipt is written. The nullifier table in the registry is never written by any live path.
8.2Post-quantum key registry
xeris_pq_keys maps an Ed25519 address to its current ML-DSA-65 public key and to the history of keys it has held. The only accepted algorithm string is dilithium3; a public key is 1,952 bytes, a secret key 4,032 bytes and a detached signature 3,309 bytes. The crate is pqcrypto-mldsa, which replaced pqcrypto-dilithium (RUSTSEC-2024-0380) with the same parameter set and byte lengths (crypto-module XWC-12).
Bindings in key_history are half-open intervals [valid_from_slot, valid_until_slot); a key installed in block R is admission-active from R + 1. Closed bindings are retained for 302,400 slots (14 days, twice the unbonding period) so slash evidence for a past slot is checked against the key active at that slot. An Ed25519-only compromise cannot replace a registered key: registration is one-shot and rotation requires the current ML-DSA-65 key.
From slot 2 (PQ_REGISTRY_BINDING_ACTIVATION_SLOT) the inline proposer_dilithium3_pk of every block must byte-equal the registry entry for block.proposer; a missing or mismatched entry rejects the block on live and replay paths and is advisory only on the reorg pre-filter. The slot-1 block auto-registers its proposer's inline key, with the capitalised string Dilithium3 as pq_algorithm, and seeds its history. Every other producer registers through PqKeyRegister before its first block.
dilithium3_sk.bin and dilithium3_pk.bin from its working directory, validates the public key, signs and verifies the fixed challenge XRS_DILITHIUM_LOAD_CHECK_V1, and regenerates the pair on any failure. The secret key is written with mode 0o600 and fsynced; a write failure is fatal. A node without both keys does not produce blocks.9Signatures, hashing and the transaction format
Three signing contexts exist. A transaction carries Ed25519 signatures only. A block carries one hybrid Ed25519 + ML-DSA-65 (Dilithium3) proposer signature. A peer authenticates with an Ed25519 signature over a server nonce. Every message the node signs or hashes begins with a fixed ASCII domain tag, and every chain-bound message also mixes in the chain id xeris-mainnet-v1 or xeris-testnet-v1.
9.1The hybrid block signature
From HYBRID_SIG_ACTIVATION_SLOT = 1 every block carries proposer_dilithium3_pk and hybrid_proposer_sig. The signed message is built by hybrid_canonical_message with the purpose block. Every variable field is length-prefixed, so two field sets cannot share one byte string, and a signature under one purpose does not verify under another.
Both halves must verify; there is no classical-only path for a mined block. The ML-DSA-65 half is checked against the key carried in the block, and from slot 2 that key must byte-equal the xeris_pq_keys entry for the proposer (§8.2). The mining worker receives only the proposer's public key; the block is signed on the validator's owning thread after the search completes (XWC-26).
9.2Merkle root
A leaf commits to the whole canonical transaction from MERKLE_FULLTX_ACTIVATION_SLOT = 1 (XWC-07). A transaction with an empty signature vector is an error, never a sentinel: the producer abandons the template and requeues its transactions, and a validator rejects the block (XWC-08). An unpaired node is hashed with the tagged pad, not with a copy of itself, which removes the CVE-2012-2459 duplicate-leaf ambiguity.
9.3Domain-separated messages
Table 19 lists every tag reachable from consensus or the network layer. lp(x) is u32 LE len ‖ x. id(s) is 0x01 ‖ 32-byte key when s parses as a public key, otherwise 0x00 ‖ lp(s). Integers are little-endian unless marked.
| Tag | Signer or use | Layout after the tag |
|---|---|---|
XRS_HYBRID_V1 | block proposer, Ed25519 + ML-DSA-65 | §9.1 |
XRS_POW_V2 | Scrypt preimage | §2.2 |
XRS_LEADER_V1, XRS_LEADER_V1_RETRY | election seed, SHA-256 | last_block_hash ‖ slot u64; retry: seed |
XRS_P2P_AUTH_V2\0 | peer, Ed25519 | nonce (32 B) ‖ peer_version; signed raw, not pre-hashed |
XRS_CH_STATE_V3 | channel counterparty, Ed25519 | lp(chain_id) ‖ lp(channel_id) ‖ generation ‖ created_slot ‖ id(party_a) ‖ id(party_b) ‖ balance_a ‖ balance_b ‖ state_sequence |
XRS_CLOSE_CH_V4 | channel counterparty, Ed25519 | as above, ending final_balance_a ‖ final_balance_b ‖ message_count |
XRS_HW_ATTEST_V2 | device key, Ed25519 | id(device_pubkey) ‖ id(bound_identity) ‖ lp(device_type) ‖ lp(manufacturer) ‖ lp(model) ‖ lp(firmware_version) ‖ slot u64 |
xrs_pq_rotate_v5 | current PQ key, ML-DSA-65 | chain_id ‖ old_pk ‖ new_pk ‖ rotation_count u64; no length prefixes |
XRS_SLASH_ID_V2 | SHA-256, header digest | canonical (the §9.1 message) |
XRS_SLASH_OFFENSE_V2 | SHA-256, offence id | owner_pubkey ‖ violation_slot u64 ‖ min(d_a, d_b) ‖ max(d_a, d_b) |
XRS_FEDERATION_V1\0 | SHA-256, roster domain | chain_id ‖ producer keys, sorted |
XRS_FEDERATION_TIP_V2\0 | roster producer, Ed25519 | domain (32 B) ‖ nonce (32 B) ‖ height u64 ‖ lp(hash) ‖ lp(identity) |
A chain-bound signature from one network does not verify on the other. The chain id changes on every hard fork.
9.4Transaction wire format
A transaction is a legacy solana_sdk::transaction::Transaction serialised with bincode 1.x: fixed-width little-endian integers and short_vec (compact-u16) lengths. Versioned (v0) messages are not accepted. account_keys[0] is the signer and the fee payer. The node ignores program_id_index and accounts for a XerisInstruction; the reference wallet sets the program id to the all-zero key.
Instruction data is bincode of the XerisInstruction enum: a u32 LE variant index, then the fields in declaration order. String and Vec carry a u64 LE length; Option is 0x00 or 0x01 ‖ T; bool is one byte; [u8; 32] is raw. The decoder tolerates trailing bytes, but they change the signature and the Merkle leaf; a client emits canonical bincode.
getLatestBlockhash returns the hash as hex; the client converts it to 32 bytes before signing. A signed transaction is submitted as POST /submit with the body {"tx_base64"} (limits in §10.5). Every rejection is returned as HTTP 200 with {"error"}.
9.5Peer authentication
Peers exchange bincode NetworkMessage frames over plain TCP. The rustls, tokio-rustls and openssl crates in Cargo.toml are not referenced by the source.
The server admits a peer only after the signature verifies against the claimed key. The server does not prove its identity to the client; a roster producer does, through the signed tip of §10.2. On mainnet a non-roster peer is dropped after authentication (§12.1).
10Network, mempool and sync
Nodes exchange length-prefixed bincode frames over plain TCP. Every connection opens with the Ed25519 challenge and response of §9.5.
| Parameter | Value |
|---|---|
| Magic | XRS1, 4 raw bytes, within 5 s |
| Frame | ≤ 5 MiB |
| Block | ≤ 4 MiB, strictly below the frame |
| Handshake | 15 s for the challenge and for the response |
| Peers | 3,000 |
| Inbound per subnet | 8 per IPv4 /24 or IPv6 /64, authenticated |
| Pre-auth per IP | 4 |
| Pending handshakes | 256 |
| Message rate | 600 per 45 s per connection; solicited sync blocks exempt |
| Idle | 45 s |
| Connection lifetime | 1 h |
| Send deadline | 10 s per write |
| Sync turn | 100 blocks or 16 MiB |
GetBlocks cadence | one per 2 s per connection |
| Retained sync bytes | 64 MiB per lane (serve, seed, public) |
| Recent blocks in memory | 1,000 |
| Snapshot interval | 10,000 blocks |
| Ports | P2P 4000; RPC 56001; explorer 50008 |
XRS_SEED_PEERS takes up to 200 IPv4 host[:port] entries. The built-in DNS seed is a GitHub gist and the fallback list is empty, marked "RESTORE BEFORE ANY DEPLOYMENT". On mainnet the seed list is the roster; a dialer outside it is refused.10.1Gossip and relay
The wire carries eight NetworkMessage variants; the legacy AuthRequest is rejected on receipt.
A mined block or an admitted transaction is relayed to ceil(sqrt(n)) outbound peers, clamped to 3–15, each over a fresh connection and handshake that carries one message and closes. Received blocks are not re-relayed. active_peers holds outbound addresses only, so an inbound-only peer is never a relay target.
10.2Sync
The accepting side advertises XRS-V2-SYNC-TURNS. A dialer that echoes it gets a duplex connection: the server sends one turn, then its own GetBlocks, which closes the turn and hands over the write side. A dialer answering XRS-V2 polls one way. History comes from the 1,000 blocks in memory or from a byte-indexed scan of ledger.dat under a 5 s budget.
A received block enters the live ledger only as the exact child of the tip; every other block goes to the recovery coordinator of §2.4 (Table 4). Networking never calls the live Ledger::reorg.
Deep sync is roster-only. A producer that proves a fresh signed tip can have the node spool its branch from slot 1 into blocks.jsonl, replay both chains from genesis and, if the branch wins on work, rename the candidate and exit with code 75 for restart. Spools share XRS_MAX_DEEP_SYNC_BYTES, default 512 GiB.
10.3State, snapshots and replay
ledger.dat is the authoritative state: one JSON block per line, appended and fsynced before the block counts as committed. Startup replays it from genesis and re-validates every block under the full rule set. Replay fails closed: a read error or undecodable record stops the node. Only a trailing record without a line terminator is tolerated and truncated (XWC-57).
ledger_snapshot.json is written every 10,000 blocks with balances, stakes, tokens, contracts, processed signatures and governance maps. It is not read at startup; replay is the only load path, and a reorganisation deletes it. contract_state.json is a diagnostic dump with no consensus role.
Recovery clones the ledger with cp --reflink=always on Linux and clonefile(2) on macOS, at startup and before every candidate replay. On a filesystem without reflinks, ext4 among them, the node refuses to start. XFS with reflink, btrfs and APFS qualify.
10.4Mempool
The mempool is a max-heap keyed by fee, and the fee is flat: mempool_priority_fee returns BASE_TX_FEE for every transaction. Order among residents is heap order, and there is no fee market. Eviction needs a strictly higher incoming fee, so a full pool admits nothing until pruning frees space. Every rejection happens before any mutation. Bounds are in Table 6.
Pruning runs after every accepted block: entries whose blockhash left the 150-block window or whose signature is committed are dropped, committed reservations are released and the underfunded set is reconciled. Transactions displaced by a reorganisation are journaled in SQLite, re-admitted 256 at a time and relayed 16 per 2 s until they commit or the chain passes reorg_height + 64.
10.5Node interfaces
A node runs three listeners: P2P, the write RPC and the explorer API with JSON-RPC (Table 20). XRS_LOCAL_ONLY=1 binds P2P and RPC to loopback; the explorer always binds 0.0.0.0 and allows any origin. Write endpoints take a body of at most 256 KiB, 30 requests per minute per IP, and reject a signature seen within 120 s.
| JSON-RPC method | Result |
|---|---|
getBalance | lamports of the address |
getAccountInfo | lamports, stake, isValidator |
getSlot | tip slot |
getBlockHeight | chain_height |
getLatestBlockhash, alias getRecentBlockhash | tip hash as 64 hex characters; lastValidBlockHeight = slot + 150 |
getBlock | header fields of a block among the 1,000 in memory, else null |
getTransaction | the transaction, from the blocks in memory, with the persisted outcome |
getSignaturesForAddress | receipt-store history, default 20 entries |
getHealth | "ok" |
getVersion | a hard-coded string unrelated to the build |
Transaction history and receipts are derived data in tx_receipts.sqlite, never read by consensus. On testnet a node without a store keeps validating and serves no history; on mainnet an open failure stops startup and an indexing failure writes a rebuild marker and exits with code 75. Endpoints that return 501 are listed in §12.2.
11Governance
Governance is the protocol-managed Governance contract at xeris_governance. The first CreateProposal creates it with min_proposal_stake = 100 XRS. Three instructions reach it: CreateProposal (39), CastVote (40) and ExecuteProposal (41). The generic contract-call path cannot invoke propose, vote or execute; the instructions are the only entry.
11.1Proposals and votes
Vote weight is consensus stake, not a lock. An address without stake cannot propose, and its vote adds zero weight. A vote after voting_end_slot is rejected without changing the proposal. Quorum counts abstentions; passage needs quorum and a strict yes majority. Finalisation happens in the first ExecuteProposal after the window closes: there is no scheduling, no timelock and no second step.
A passed proposal records an outcome and changes nothing on-chain. parameter_json is stored and never read; no handler applies a parameter change. proposal_type is a free-form string with no enumeration. Statuses are voting, passed and rejected; executed and expired are named in the struct comment and never assigned.
governance_delegates and governance_locks, but no instruction writes either map and the vote path reads neither.11.2Activation slots
Consensus rules are gated on slot constants compiled into the node, not on proposals. Every gate is active from the first mined block of this chain (slot 0 is the genesis marker; the first block is slot 1), so the table states the launch posture, not a migration path. A rule change with a new slot gate ships as a node release.
| Constant | Slot | Rule from that slot |
|---|---|---|
FEE_ACTIVATION_SLOT | 1 | Transaction fees are required. |
STRICT_ADMISSION_ACTIVATION_SLOT | 1 | The full admission set, including exact slot continuity, applies to every non-genesis block. |
MERKLE_FULLTX_ACTIVATION_SLOT | 1 | The Merkle leaf commits to the full transaction, not its first signature. |
LEADER_ENFORCEMENT_ACTIVATION_SLOT | 1 | Leader election and the 4x non-leader difficulty penalty are consensus-enforced. |
HYBRID_SIG_ACTIVATION_SLOT | 1 | Every block carries a non-empty proposer_dilithium3_pk and a valid hybrid_proposer_sig. |
POW_PREIMAGE_V2_SLOT | 1 | The PoW preimage binds parent_hash and poh_timestamp. |
HW_ATTEST_V2_ACTIVATION_SLOT | 1 | Hardware attestations are Ed25519 signatures by the device key. |
PQ_REGISTRY_BINDING_ACTIVATION_SLOT | 2 | The inline proposer_dilithium3_pk must equal the xeris_pq_keys entry; slot 1 auto-registers the bootstrap key. |
SCRYPT_V2_SLOT | 0 | Every block is mined and verified under Scrypt v1.2 (N = 4,096, r = 4, p = 1). |
SCRYPT_UPGRADE_SLOT | 0 | The Scrypt v1.1 branch never fires. |
LEGACY_TRANSFER_SUNSET_SLOT | 0 | A block carrying SystemInstruction::Transfer is rejected. |
11.3RPC surface
GET /governance/proposals reads xeris_governance and returns { proposals, total_proposals, total_executed }; it maps voting to Active, passed to Passed and rejected to Failed, and omits votes_abstain and voters. GET /governance/lock/{address} reads the unwritten maps and returns locked_amount 0 and delegate null. The four POST /governance/* routes return HTTP 501 (§12.2); governance writes are instructions 39–41 submitted to POST /submit.
12Mainnet, federation and the audit record
Mainnet is chain id xeris-mainnet-v1, run by xrs-node 0.1.1 with --mainnet. With it the node runs as a federated beta: three roster producers propose blocks, public self-staking is closed, and the surfaces in §12.2 are refused. The consensus rules of §2 are unchanged; participation is gated. The build is a mainnet candidate; mainnet has not launched as of this revision.
12.1The producer roster
The roster is an operational beta gate, not a consensus vote. A fixed set of three keys is admitted to every path that produces, relays or stakes; the table lists each gate. The roster is fixed for the chain: changing keys requires a reviewed epoch migration, not an edit of the variable on restart.
| Gate | Rule |
|---|---|
| Startup | --mainnet without XRS_FEDERATED_PRODUCERS panics. A malformed entry, a duplicate key or address, a non-IPv4 address or port 0 panics. |
| Genesis | A fresh mainnet (height 0) starts only if a roster key holds genesis stake of at least 1,000 XRS. |
| Blocks | Every block on disk and every block from a peer must name a roster key as proposer; federation_block_gate runs over the stored chain file at startup and over each peer block. |
Stake | A Stake instruction naming a non-roster key is rejected at ingress, relay and replay: "Public self-staking is closed during federated beta". |
| Peers | An inbound peer that authenticates with a non-roster key is dropped after the handshake. Outbound dials go only to roster addresses. The seed list is the roster. |
| Proposal | A roster producer proposes on a parent only after another roster peer has been observed at that height and hash within 3 s. This is a 2-of-3 liveness gate, "not finality"; finality stays 64 blocks. |
| Deep sync | Only a freshly signed tip from a roster producer authorises an unbounded history download. The 64-block fast path stays public. |
12.2Disabled surfaces
The surfaces below exist in the build and are refused. An instruction refused at ingress never enters the mempool. An instruction skipped at the dispatcher is included in the block, charged the base fee, and executes nothing. Every RPC write that once mutated state outside consensus answers with an error; the replacement is a signed transaction to POST /submit.
| Surface | Status | Replacement |
|---|---|---|
SubDelegate (22) | Rejected at ingress and skipped at the dispatcher (XWC-82). | None. |
ZkPrivateTransfer (48) | Skipped at the dispatcher; fee charged (NEW-CRIT-3). | None. |
ZkIdentityProof (49) | Skipped at the dispatcher; fee charged (NEW-CRIT-1). | None. |
PqSignedTransfer (52) | Skipped at the dispatcher; fee charged (NEW-CRIT-4). | None. PqKeyRotate is active. |
QueryCapabilities (30) | No-op in a block. | GET /. |
Legacy SystemInstruction:: | Rejected at ingress and in blocks from slot 0 (LEGACY_). | NativeTransfer. |
execute_, execute_ | Return an error (CRIT-1, CRIT-2). | cancel_, cancel_ recover the escrow. |
RWA distribute, toggle_ | Return an error (contracts-module XWC-69). | None. |
Launchpad vesting_ | Deployment rejected (contracts-module XWC-65). | Unvested launch. |
/ | JSON body with status: 501 under HTTP 200 (NEW-HIGH-7). | None. |
POST / | HTTP 501 (NEW-CRIT-6). | None; staking rewards pay every 900 blocks without a claim. |
POST / | HTTP 501 (NEW-CRIT-6). | Instructions 39 to 41 (§11). No lock or delegate instruction exists. |
POST /, /, / | JSON error under HTTP 200 naming the instruction to submit. | Instructions 0 to 7 via POST /. |
12.3The audit record
CertiK reviewed the node as three separately numbered modules. Each module starts at XWC-01, so an id identifies a finding only with its module name: consensus XWC-22 is reorg signature validation, crypto-module XWC-22 is mempool capacity. Id ranges: consensus to XWC-88, crypto to XWC-22, contracts and network to XWC-81. Xeris has deferred consensus XWC-36 (dependency upgrades) and XWC-86 (fee model, on CertiK's recommendation that gas pricing be redesigned rather than patched).
Every CertiK document the repository answers is titled "Preliminary Comments". No CertiK-authored report is in the repository; CertiK's interim statuses are known as relayed in Xeris's responses. The first comments are dated 2026-05-01 and the latest Xeris response 2026-10-07. This paper cites an id inline where a rule exists because of that finding.
| Module | Files | Finding ids | Comments and Xeris responses | Open |
|---|---|---|---|---|
| Consensus | ledger.rs, pow.rs, poh.rs, main.rs | XWC-01 to XWC-88 | Comments 2026-05-01; responses 2026-05-18, 2026-06-09, 2026-07-27. | XWC-36 dependencies, XWC-86 fee model: deferred. |
| Crypto, Merkle, tx pool | crypto.rs, merkle.rs, tx_pool.rs | XWC-01 to XWC-22 | Responses 2026-06-22, 2026-07-03, 2026-07-27. | None pending; XWC-12 acknowledged: pqcrypto-mldsa replaced pqcrypto-dilithium, pqcrypto-traits remains. |
| Contracts and network | contracts.rs, network.rs, token.rs, genesis.rs | XWC-01 to XWC-81 | Response 2026-09-12; commit-only responses 2026-09-29 and 2026-10-07. | None recorded in the repository. |
XWC-nn, a CertiK finding, module named when ambiguous; AH-n, a consensus issue Xeris disclosed from its own two-node exercise; NEW-CRIT-n, NEW-HIGH-n, CRIT-n, C-n, H-n, L-n, M-n, tags from Xeris's pre-audit internal review that the code still carries. Only XWC ids are CertiK findings.AAppendix A. Constants
Every value below is read from xrs-node 0.1.1 at commit 243046d; paths are src/<file>:<line>. The lamport is the base unit: 1 XRS = 1,000,000,000 lamports (9 decimals). Values the code stores in lamports are shown in XRS.
| Constant | Value | File:line |
|---|---|---|
| SLOT_DURATION_MS | 4,000 ms (a block every 4 s) | main.rs:33 |
| MAX_REORG_DEPTH | 64 blocks (≈ 4.3 min to finality) | ledger.rs:4555 |
| mining_deadline | 3,900 ms per slot | pow.rs:539 |
| NON_LEADER_GRACE_SLOTS | 2 slots (8,000 ms of leader silence) | main.rs:404-405 |
| non_leader_target | base target >> 2 (4× harder) | ledger.rs:407-421 |
| scrypt_params_for_slot | N = 4,096, r = 4, p = 1 (2 MiB per hash) | pow.rs:41-49 |
| SCRYPT_UPGRADE_SLOT | 0 | pow.rs:22 |
| SCRYPT_V2_SLOT | 0 | pow.rs:31 |
| MAX_FUTURE_TIMESTAMP_DRIFT_MS | 2,000 ms | ledger.rs:244 |
| BLOCKHASH_EXPIRY_WINDOW | 150 blocks (≈ 10 min) | ledger.rs:239 |
| MAX_RECENT_BLOCKS | 1,000 blocks in RAM | ledger.rs:36 |
| SNAPSHOT_INTERVAL | 10,000 blocks | ledger.rs:261 |
| MAX_PROCESSED_SIGS | 10,000,000 signatures | ledger.rs:39 |
| MAINNET_INITIAL_SUPPLY | 200,000,000 XRS (treasury) | ledger.rs:27 |
| MAX_EMISSION_SUPPLY | 500,000,000 XRS (mining + staking + attestation) | ledger.rs:65 |
| MAINNET_INITIAL_SUPPLY + MAX_EMISSION_SUPPLY | 700,000,000 XRS | ledger.rs:27, 65 |
| BASE_BLOCK_REWARD | 10 XRS | ledger.rs:70 |
| HALVING_INTERVAL | 25,000,000 blocks (≈ 3.17 years) | ledger.rs:75 |
| BASE_TX_FEE | 0.001 XRS (1,000,000 lamports) | ledger.rs:58 |
| STAKING_APY_NUMERATOR / STAKING_APY_DENOMINATOR | 7 / 100 (7 % per year) | ledger.rs:5144-5145 |
| STAKING_REWARD_INTERVAL | 900 blocks | ledger.rs:5138 |
| BLOCKS_PER_YEAR | 7,884,000 | ledger.rs:5141 |
| MIN_STAKE_TO_MINE | 1,000 XRS | pow.rs:16; ledger.rs:232 |
| staking reward floor | 100 XRS staked | ledger.rs:9399 |
| MIN_ATTESTOR_STAKE | 100 XRS | ledger.rs:1285 |
| ATTESTATION_REWARD | 0.01 XRS | ledger.rs:214 |
| ATTESTATION_SLOT_WINDOW | 200 slots | ledger.rs:218 |
| attestation rate limit | 1 reward per 10 blocks per validator | ledger.rs:6287-6296 |
| UNBONDING_PERIOD_SLOTS | 151,200 slots (7 days) | ledger.rs:201 |
| MAX_UNBONDING_QUEUE | 10,000 entries (10 per account) | ledger.rs:205, 5792 |
| MIN_UNSTAKE_AMOUNT | 1 XRS (partial unstake floor) | ledger.rs:210 |
| slash_amount | 10 % of slashable balance | ledger.rs:8219 |
| reporter_reward | 5 % of the slash; 95 % burned | ledger.rs:8252-8255 |
| MAX_TXS_PER_BLOCK | 40,000 transactions | ledger.rs:78 |
| MAX_BLOCK_SIZE_BYTES | 4 MiB | ledger.rs:196 |
| MAX_IX_PER_TX | 16 instructions | ledger.rs:94 |
| MAX_ACCOUNTS_PER_TX | 64 account keys | ledger.rs:95 |
| MAX_IX_DATA_SIZE | 8 KiB per instruction | ledger.rs:93 |
| MAX_SLASH_IX_DATA_SIZE | 65,535 B (SlashReport only) | ledger.rs:119 |
| MAX_GROTH16_VERIFICATIONS_PER_BLOCK | 64 | ledger.rs:154 |
| MAX_GROTH16_PROOF_BYTES | 512 B | crypto.rs:1041 |
| MAX_GROTH16_VK_BYTES | 16 KiB | crypto.rs:1042 |
| MAX_GROTH16_PUBLIC_INPUTS | 64 field elements | crypto.rs:1043 |
| MAX_MEMPOOL_SIZE | 50,000 entries | tx_pool.rs:160 |
| MAX_MEMPOOL_BYTES | 64 MiB | tx_pool.rs:175 |
| MAX_TX_BYTES | 128 KiB per transaction | tx_pool.rs:183 |
| MAX_TXS_PER_ACCOUNT | 256 per payer | tx_pool.rs:186 |
| MAX_BYTES_PER_ACCOUNT | 4 MiB per payer | tx_pool.rs:197 |
| MAX_UNDERFUNDED_TXS | 5,000 (MAX_MEMPOOL_SIZE / 10) | tx_pool.rs:218 |
| XERIS_CHAIN_ID_MAINNET | xeris-mainnet-v1 | ledger.rs:286 |
| SUPPORTED_PQ_ALGORITHM | dilithium3 (ML-DSA-65, FIPS 204) | crypto.rs:924 |
| dilithium3_public_key_len() | 1,952 B | crypto.rs:1184-1186 |
| dilithium3_signature_len() | 3,309 B | crypto.rs:1178-1180 |
| ML-DSA-65 secret key | 4,032 B | crypto.rs:1164 |
| PQ_KEY_HISTORY_RETENTION_SLOTS | 302,400 slots (14 days) | contracts.rs:1224 |
| FEE_ACTIVATION_SLOT | 1 | ledger.rs:54 |
| STRICT_ADMISSION_ACTIVATION_SLOT | 1 | ledger.rs:1073 |
| MERKLE_FULLTX_ACTIVATION_SLOT | 1 | ledger.rs:1099 |
| LEADER_ENFORCEMENT_ACTIVATION_SLOT | 1 | ledger.rs:1111 |
| HYBRID_SIG_ACTIVATION_SLOT | 1 | ledger.rs:281 |
| POW_PREIMAGE_V2_SLOT | 1 | pow.rs:109 |
| HW_ATTEST_V2_ACTIVATION_SLOT | 1 | ledger.rs:7311 |
| PQ_REGISTRY_BINDING_ACTIVATION_SLOT | 2 | ledger.rs:1137 |
| LEGACY_TRANSFER_SUNSET_SLOT | 0 | ledger.rs:229 |
| MAGIC_BYTES | XRS1 | network.rs:23 |
| MAX_MSG_SIZE | 5 MiB per frame | network.rs:28 |
| MAX_PEERS | 3,000 | network.rs:29 |
| CONN_TIMEOUT | 45 s idle | network.rs:30 |
| MAX_PEERS_PER_SUBNET | 8 per /24 | network.rs:34 |
| MAX_PREAUTH_PER_IP | 4 pre-auth connections per IP | network.rs:40 |
| MAX_CONN_LIFETIME_SECS | 3,600 s | network.rs:46 |
| PEER_SEND_TIMEOUT_SECS | 10 s | network.rs:50 |
| MAX_MSGS_PER_WINDOW | 600 messages per 45 s | network.rs:55 |
| CHALLENGE_TIMEOUT_SECS | 15 s handshake | network.rs:447 |
| GETBLOCKS_MIN_INTERVAL | 2 s | network.rs:560 |
| MAX_SYNC_TURN_BLOCKS | 100 blocks per turn | network.rs:561 |
| MAX_SYNC_TURN_BYTES | 16 MiB per turn | network.rs:562 |
| MAX_RETAINED_SYNC_BYTES | 64 MiB | network.rs:563 |
| MAX_PENDING_HANDSHAKES | 256 | network.rs:3786 |
| MAGIC_TIMEOUT_SECS | 5 s | network.rs:3791 |
| min_proposal_stake | 100 XRS | ledger.rs:8275 |
| MIN_VOTING_PERIOD | 21,600 slots (ingress check) | ledger.rs:8267 |
| MIN_VOTING_PERIOD_SLOTS | 21,600 slots (≈ 1 day) | contracts.rs:5527 |
| MAX_VOTING_PERIOD_SLOTS | 1,296,000 slots (≈ 60 days) | contracts.rs:5528 |
| DEFAULT_PROPOSAL_QUORUM | 5,000 XRS of cast weight | contracts.rs:163 |
| MAX_LIVE_PROPOSALS | 1,000 | contracts.rs:5505 |
| DISPUTE_CHALLENGE_PERIOD_SLOTS | 21,600 slots | contracts.rs:995 |
| DISPUTE_MAX_LIFETIME_SLOTS | 648,000 slots | contracts.rs:1000 |
| arbitration_panel | validators with stake ≥ MIN_STAKE_TO_MINE | ledger.rs:5286-5291 |
| MIN_DEAL_DISPUTE_BOND | 1 XRS | contracts.rs:1008 |
| DEAL_TIMEOUT_SLOTS | 648,000 slots | contracts.rs:1079 |
| MAX_TASK_LIFETIME_SLOTS | 648,000 slots | contracts.rs:916 |
| MAX_TASK_REJECTIONS | 3 | contracts.rs:914 |
| DAILY_SLOTS | 21,600 slots (24 h spend window) | contracts.rs:3439 |
| agents per registry | 50 | contracts.rs:3334 |
| HEARTBEAT_RETENTION_SLOTS | 21,600 slots | contracts.rs:5947 |
| MAX_HEARTBEAT_RECORDS | 10,000 | contracts.rs:5948 |
| stale_threshold | 5,400 slots (≈ 6 h) | contracts.rs:5984 |
| ORDER_STORAGE_BOND | 0.01 XRS | ledger.rs:1290 |
| MAX_ORDER_LIFETIME_SLOTS | 650,000 slots | ledger.rs:1295 |
| MAX_ACTIVE_ORDERS_PER_OWNER | 100 | ledger.rs:1325 |
| MAX_ORACLE_VALUE_AGE_SLOTS | 900 slots | ledger.rs:1300 |
| PRICE_WINDOW_BLOCKS | 61 blocks (median) | ledger.rs:1318 |
| PRICE_MIN_OBSERVATIONS | 20 | ledger.rs:1319 |
| MAX_TRACKED_PRICE_POOLS | 64 | contracts.rs:170 |
| fee_bps (Swap default) | 30 bps | contracts.rs:1396 |
| MINIMUM_LIQUIDITY | 1,000 shares | contracts.rs:6 |
| creator_reward_bps | 100 bps | contracts.rs:1600 |
| XERIS_FEE_BPS | 77 bps | contracts.rs:308 |
| total_supply (Launchpad default) | 1,000,000,000 tokens | contracts.rs:1603-1604 |
| target_liquidity_xrs (default) | 10,000 XRS | contracts.rs:1608-1609 |
| liquidity_bps | 2,000 (clamp 500–4,000) | contracts.rs:1631-1633 |
| MAX_RWA_APPROVED_HOLDERS | 1,024 | token.rs:7 |
| valuation (RealWorldAsset) | USD cents | token.rs:872 |
| challenge_period_slots (state channels) | 1,000 slots | ledger.rs:8308 |
| MAX_CHANNELS | 100,000 | contracts.rs:2105 |
BAppendix B. The instruction set
XerisInstruction has 62 variants. The bincode discriminant is the declaration index, encoded as u32 little-endian, so the index column is the wire tag. Signer rule names the field that must equal the first account key, or the rule the handler applies. Status is what the block dispatcher does with the variant; disabled arms skip the instruction and still charge the fee.
| Index | Variant | Signer rule | Status |
|---|---|---|---|
| 0 | TokenMint | mint authority | active |
| 1 | TokenTransfer | signer == from | active |
| 2 | TokenBurn | signer == from | active |
| 3 | TokenCreate | mint authority | active |
| 4 | ContractCall | per method | active |
| 5 | ContractDeploy | owner | active |
| 6 | TokenCreateRWA | mint authority | active |
| 7 | RWAUpdateStatus | mint authority | active |
| 8 | RWATransfer | signer == from | active |
| 9 | Stake | signer == pubkey | active |
| 10 | Unstake | signer == pubkey | active |
| 11 | NativeTransfer | signer == from | active |
| 12 | ValidatorAttestation | signer == validator | active |
| 13 | WrapXrs | signer | active |
| 14 | UnwrapXrs | signer | active |
| 15 | RegisterAgent | owner | active |
| 16 | UpdateAgent | owner | active |
| 17 | AgentExecute | agent (executes as owner) | active |
| 18 | CreateIdentity | signer == identity_pubkey | active |
| 19 | UpdateIdentity | signer == identity_pubkey | active |
| 20 | AttestReputation | active identity | active |
| 21 | AgentMessage | active identity | active (no state) |
| 22 | SubDelegate | n/a | disabled (XWC-82) |
| 23 | ConditionalOrder | owner | active |
| 24 | CancelConditionalOrder | owner | active |
| 25 | RegisterOracle | owner | active |
| 26 | OracleSubmit | owner | active |
| 27 | HardwareAttest | signer == device_pubkey or bound_identity | active |
| 28 | RegisterCapability | signer == provider_identity | active |
| 29 | UpdateCapability | signer == provider_identity | active |
| 30 | QueryCapabilities | n/a | no-op |
| 31 | PostTask | anyone | active |
| 32 | ClaimTask | signer == claimant_identity | active |
| 33 | ResolveTask | party | active |
| 34 | RegisterModel | signer == identity_pubkey | active |
| 35 | UpdateModel | owner | active |
| 36 | OpenDispute | anyone | active |
| 37 | ResolveDispute | stake ≥ 1,000 XRS or party | active |
| 38 | SlashReport | anyone | active |
| 39 | CreateProposal | stake ≥ 100 XRS | active |
| 40 | CastVote | anyone (weight = stake) | active |
| 41 | ExecuteProposal | anyone | active |
| 42 | OpenChannel | party | active |
| 43 | CloseChannel | party | active |
| 44 | ForceCloseChannel | party | active |
| 45 | AgentHeartbeat | signer == identity_pubkey | active |
| 46 | ZkProofSubmit | anyone | active |
| 47 | ZkProofVerify | anyone | active (read-only) |
| 48 | ZkPrivateTransfer | n/a | disabled (NEW-CRIT-3) |
| 49 | ZkIdentityProof | n/a | disabled (NEW-CRIT-1) |
| 50 | PqKeyRegister | signer == ed25519_pubkey | active |
| 51 | PqKeyRotate | signer == ed25519_pubkey | active |
| 52 | PqSignedTransfer | n/a | disabled (NEW-CRIT-4) |
| 53 | PqAttest | anyone | active (marker) |
| 54 | CreateDeal | party | active |
| 55 | AcceptDeal | party | active |
| 56 | ConfirmDeal | party | active |
| 57 | CancelDeal | party | active |
| 58 | DisputeDeal | party | active |
| 59 | SettleDeal | anyone | active |
| 60 | ReclaimDeal | party | active |
| 61 | ZkVkRegister | stake ≥ 1,000 XRS | active |
CAppendix C. Contract types
ContractType has 23 variants. ContractDeploy resolves contract_type_str through ContractType::from_str, which lower-cases the input, so every alias is case-insensitive. Eight types are protocol-managed: user deploy is refused and the protocol creates each singleton under a fixed xeris_* id on first use.
| Type | Aliases | Deployable | Singleton id | Driven by |
|---|---|---|---|---|
TimeLock | timelock, time_ | yes | — | ContractDeploy, ContractCall |
Escrow | escrow | yes | — | ContractDeploy, ContractCall |
Swap | swap | yes | — | ContractDeploy, ContractCall |
Vesting | vesting | yes | — | ContractDeploy, ContractCall |
MultiSig | multisig, multi_ | yes | — | ContractDeploy, ContractCall |
RealWorldAsset | rwa, real_, realworldasset | mint authority of the RWA token only | — | ContractDeploy, ContractCall |
Launchpad | launchpad, launch_ | yes | — | ContractDeploy, ContractCall |
AgentRegistry | agent_, agent, agents | yes | agent_ per owner | RegisterAgent, UpdateAgent, AgentExecute |
IdentityRegistry | identity, identity_ | yes | xeris_; identity_ per identity | CreateIdentity, UpdateIdentity, AttestReputation |
ConditionalOrderBook | conditional, conditional_, orders | no | xeris_ | ConditionalOrder, CancelConditionalOrder |
LimitOrder | limit, limit_, limit_ | yes | — | ContractDeploy, ContractCall |
DcaOrder | dca, dca_, dollar_ | yes | — | ContractDeploy, ContractCall |
OracleRegistry | oracle, oracle_, oracles | yes | xeris_ | RegisterOracle, OracleSubmit |
DeviceRegistry | device, device_, hardware | no | xeris_ | HardwareAttest |
CapabilityRegistry | capability, capabilities, cap_ | yes | xeris_ | RegisterCapability, UpdateCapability |
TaskBoard | task, tasks, task_, bounty | no | xeris_ | PostTask, ClaimTask, ResolveTask |
ModelRegistry | model, model_, models | yes | xeris_ | RegisterModel, UpdateModel |
DisputeRegistry | dispute, disputes, arbitration | no | xeris_ | OpenDispute, ResolveDispute, DisputeDeal |
Governance | governance, gov, dao | yes; the instructions target xeris_ only | xeris_ | CreateProposal, CastVote, ExecuteProposal |
StateChannelRegistry | channel, channels, state_ | no; also refused inside deploy_ | xeris_ | OpenChannel, CloseChannel, ForceCloseChannel |
ZkVerifierRegistry | zk, zk_, zero_ | no | xeris_ | ZkVkRegister, ZkProofSubmit, ZkProofVerify, PqAttest |
PqKeyRegistry | pq, pq_, post_, quantum | no | xeris_ | PqKeyRegister, PqKeyRotate; slot-1 proposer bootstrap |
DealRegistry | deal, deals, escrow_ | no | xeris_ | CreateDeal … ReclaimDeal (54–60) |
xeris_heartbeats (AgentHeartbeat) and xeris_slashing_registry (SlashReport) are stored with contract_type: IdentityRegistry although their state is Heartbeats; GET /contracts reports them as IdentityRegistry.