XChain Platform Action - ATTEST
This action covers the external-data attestation lifecycle in five version-discriminated phases: v0 (VM-emitted request), v1 (validator-broadcast response), v2 (system-synthesized expiry), and the two cross-chain relay legs v3 (a request materialized onto BTC) and v4 (the response relayed back to the origin chain).
All attestation capability stake lives on BTC, so a request emitted by an LTC or DOGE contract has no responsible set where it landed and cannot be fulfilled there. v3 materializes such a request onto BTC, giving it a real BTC block_index; from that point the ordinary v0/v1 machinery services it. v4 carries the outcome back so the origin chain fires the contract callback. Both legs are gated (see the Formats section) and inert until then.
PARAMS
| Name | Type | Description |
|---|---|---|
VERSION |
Integer | Format version (0=request, 1=response, 2=expire, 3=relay request, 4=relay response) |
ORIGIN_CHAIN |
String | Chain a relayed request was emitted on (LTC or DOGE); v3 only |
ORIGIN_ACTION_INDEX |
Integer | The origin chain’s v0 action_index, the relay correlation key; v3 only |
HOME_RESPONSE_ACTION_INDEX |
Integer | The BTC v1 action_index whose outcome is being relayed; v4 only |
SNAPSHOT_BLOCK |
Integer | BTC-anchored block the cross_chain signer set is pinned at, and the plane both relay gates resolve on; v3 and v4 |
REQUEST_ID |
String | 64-hex SHA-256 over tx_hash:root_action_index:emitter_path:contract_index:emitter_position (colon-delimited) |
PROVIDER_ID |
String | Governance-registered provider (http_get, llm, etc.); present in v0 and v1 |
REQUEST_PAYLOAD |
String | Provider-specific payload (URL for http_get, JSON envelope for llm); v0 only |
CALLBACK_METHOD |
String | Contract method to invoke on response (max 64 chars); v0 only |
CALLBACK_PARAMS |
String | JSON array of developer-supplied params echoed back to the callback; v0 only |
REDUNDANCY |
Integer | Required validator signatures (1, 3, or 5); v0 only |
DEADLINE_BLOCKS |
Integer | Blocks until the request auto-expires (capped by provider’s deadline_window_blocks); v0 only |
FEE_TICK |
String | (optional) Tick the attestation fee is paid in; only XCHAIN accepted; v0 only |
FEE_AMOUNT |
String | (optional) Attestation fee, precision no finer than the GAS tick’s own decimals; v0 only |
RESPONSE_PAYLOAD |
String | Response body (base64-encoded on the wire, decoded to UTF-8 for storage and callback); v1 only |
STATUS |
String | ok, timeout, no_quorum, provider_error, or expired; v1 only |
META |
String | Provider-defined metadata (HTTP status code for http_get; model ID for llm); v1 only |
SIG_COUNT |
Integer | Number of (pubkey, sig) pairs that follow; v1 only |
PUBKEY_n |
String | 64-hex Ed25519 pubkey, qualified for attestation at the request block; v1 only |
SIG_n |
String | 128-hex Ed25519 signature over the canonical message; v1 only |
Formats
Version 0 - Request (VM-emitted)
ATTEST|0|REQUEST_ID|PROVIDER_ID|REQUEST_PAYLOAD|CALLBACK_METHOD|CALLBACK_PARAMS|REDUNDANCY|DEADLINE_BLOCKS[|FEE_TICK|FEE_AMOUNT]
The trailing FEE_TICK|FEE_AMOUNT pair is optional. A feeless request omits them entirely (the SDK serializer trims trailing empties), so feeless v0 wire strings are byte-identical to the pre-fee format with no migration needed.
Version 1 - Response (validator-broadcast, variable-length signature list)
ATTEST|1|REQUEST_ID|PROVIDER_ID|RESPONSE_PAYLOAD|STATUS|META|SIG_COUNT|PUBKEY1|SIG1|PUBKEY2|SIG2|...
Version 2 - Expire (system-synthesized; never user-broadcast)
ATTEST|2|REQUEST_ID
Version 3 - Relay request (cross-chain, BTC only, flag-day gated)
ATTEST|3|REQUEST_ID|ORIGIN_CHAIN|ORIGIN_ACTION_INDEX|PROVIDER_ID|REQUEST_PAYLOAD|REDUNDANCY|DEADLINE_BLOCKS|SNAPSHOT_BLOCK|SIG_COUNT|PUBKEY1|SIG1|...
Version 4 - Relay response (cross-chain, origin chain only, flag-day gated)
ATTEST|4|REQUEST_ID|HOME_RESPONSE_ACTION_INDEX|RESPONSE_PAYLOAD|STATUS|META|SNAPSHOT_BLOCK|SIG_COUNT|PUBKEY1|SIG1|...
Both relay versions activate at ATTEST_RELAY_ACTIVATION (BTC 969500 on mainnet, genesis on testnet and regtest). Below the height they are rejected as an unknown VERSION and persist nothing. Every indexer and hub must be deployed before that height.
Examples
ATTEST|0|abc...def|http_get|https://example.com/v1/score/42|handleResponse|["ctx-42"]|1|10
VM-emitted request for an off-chain HTTP GET, single-validator redundancy, 10-block deadline, feeless
ATTEST|0|abc...def|http_get|https://example.com/v1/score/42|handleResponse|["ctx-42"]|1|10|XCHAIN|0.5
Same request carrying a 0.5 XCHAIN attestation fee (escrowed from FEE_PAYER at request time)
ATTEST|1|abc...def|http_get|{"score":7}|ok|200|1|a1b2...|c3d4...
Validator-broadcast response with one Ed25519 signature
ATTEST|2|abc...def
System-synthesized expiry for request abc...def
Rules
Version 0 (request)
- VM emission only; the
IS_EMISSIONflag must be set byexecute.processEmission. User-broadcast v0 is rejected. PROVIDER_IDmust be governance-registered (indexer validates against its provider registry).REDUNDANCYmust appear in the provider’sallowed_redundancylist.REQUEST_PAYLOADsize must be no larger than the provider’smax_request_bytes.DEADLINE_BLOCKSmust be greater than 0 and no larger than the provider’sdeadline_window_blocks.CONTRACT_INDEX(carried viaEMITTER) must reference an existing contract.REQUEST_IDis verified by re-deriving fromtx_hash:root_action_index:emitter_path:contract_index:emitter_position(colon-delimited; defends against compromised VM).- Admission flag-day (
ATTEST_ADMISSION_ACTIVATIONinprotocol/constants.js; mainnet 961000, testnet/regtest genesis): at/above the height, a request whose responsible set at its own block is smaller thanREDUNDANCY(e.g. after the stake-weighted-quorum source-dedupe) is rejected at admission, since the v1 path can never collectREDUNDANCYsignatures from a smaller set. Below the height the request is accepted and expires atDEADLINE_BLOCKunchanged (replay bit-identical).
Fee fields (v0, optional)
FEE_TICK, when present, must equal the GAS tick (XCHAIN); any other value producesinvalid: FEE_TICK (only XCHAIN accepted). Arbitrary fee ticks are a post-launch rule loosening; the wire carries the tick now so no future format change is needed.FEE_AMOUNTmust parse to a precision no finer than the GAS tick’s own decimals; a finer value producesinvalid: FEE_AMOUNT (precision > N dp)where N =min(8, gasDecimals). The escrow/debit/credit ledger rows round to the tick’s decimals, so a finer fee would be charged rounded whileattests.fee_amountkept the unrounded string, desyncing the reward split from the escrow. The production XCHAIN genesis issuance is pinned to 8 decimals, so in production the cap is 8 dp; on a decimals-0 regtest GAS tick the cap is 0 (integer fees only). The VM gateway (xchain.attestation.request) additionally rejects values above 8 dp at emit time as an outer sanity bound.FEE_AMOUNT > 0requires a non-nullFEE_TICK; absent tick producesinvalid: FEE_TICK (required when FEE_AMOUNT > 0).FEE_PAYER(the contract address emitting the request) must hold at leastFEE_AMOUNTof the GAS tick; insufficient balance producesinvalid: insufficient funds (FEE_AMOUNT). As with any failed emission validation, this fails the whole enclosing EXECUTE.- A valid
FEE_AMOUNT > 0debitsFEE_PAYERand writes an escrow row at the v0action_index. Absent or zero value means feeless with no ledger movement.
Version 1 (response)
- Indexer rejects if
REQUEST_IDdoes not match apendingrow from a prior v0. PROVIDER_IDmust equal the request’s provider.- Indexer’s
BLOCK_INDEXmust be no greater than the request’sDEADLINE_BLOCK. - Each
PUBKEY_nis checked against theattestationcapability snapshot at the request’sblock_index(not the response’s; every hub must compute the same set). - Each
SIG_nmust Ed25519-verify against the canonical message underPUBKEY_n. - Valid signature count must be at least
REDUNDANCY(the request’sREDUNDANCYparameter). Sub-quorum responses are rejected and the request remains pending.
Version 2 (expire)
- Never user-broadcast:
VALID_ACTION_NAMESacceptsATTESTfor the decoder’s v0/v1 paths, but v2 is rejected if it appears in a user transaction. - Synthesized once per stale pending request: indexer queries
SELECT * FROM attests WHERE version=0 AND request_status='pending' AND deadline_block < <current_block>and synthesizes one v2 per row. REQUEST_IDmust match an existingpendingrow.
Version 3 (relay request, BTC only)
- Accepted only on BTC, and only at/above
ATTEST_RELAY_ACTIVATIONresolved on this action’s own BTCblock_index. Below the height, and on any other chain, it is treated exactly as an unknown VERSION: nothing is persisted. ORIGIN_CHAINmust beLTCorDOGE.ORIGIN_ACTION_INDEXmust be a positive integer.SNAPSHOT_BLOCKmust not exceed this action’sblock_index, so a broadcaster cannot pin a future signer set.PROVIDER_ID,REDUNDANCY,REQUEST_PAYLOADsize and the derived deadline are validated exactly as for v0.REQUEST_IDmust not already exist on this chain; one request materializes once.- The signature list must meet the
cross_chainfederation quorum atSNAPSHOT_BLOCK: stake-weighted (source-deduped) at/aboveSTAKE_WEIGHTED_QUORUM_ACTIVATION, otherwise the legacy 2f+1 signer count. This is the same rule the XCALL dispatch leg applies. - The stored request row is feeless, carries no callback and no
contract_index(the contract is on the origin chain), and pins its responsible set at this action’s BTCblock_index. A v1 fulfilling it closes the request but fires no local callback.
Version 4 (relay response, origin chain only)
- Accepted only off BTC, and only at/above
ATTEST_RELAY_ACTIVATIONresolved on theSNAPSHOT_BLOCKthe action carries. The gate is deliberately NOT resolved on the localblock_index: a BTC-derived height is already exceeded by LTC and DOGE local heights, so gating there would activate the leg immediately. STATUSmust beokorexpired. The retryable statuses (no_quorum,timeout,provider_error) are refused, because the home chain may still fulfill the request.REQUEST_IDmust name apendingrequest on this chain whoseorigin_chainis this chain, i.e. one this chain admitted for relay. A native request cannot be closed by a v4.- The signature list must meet the same
cross_chainquorum as v3, over the relay-response canonical. - On acceptance the request goes
fulfilled(ok) orerrored(expired), its v0 fee escrow settles, and the contract callback is injected with the identical argument shape a locally serviced attestation produces.
Canonical signing messages (v3/v4)
The relay legs sign pipe-joined field lists, with free-form payloads folded in as SHA-256 digests so the signed bytes stay bounded:
ATTEST|RELAY_REQUEST|request_id|snapshot_block|network|origin_chain|origin_action_index|provider_id|sha256(request_payload)|redundancy|deadline_blocks
ATTEST|RELAY_RESPONSE|request_id|snapshot_block|network|origin_chain|home_response_action_index|provider_id|sha256(response_body)|status|meta
At/above EQUIV_HEADER_ACTIVATION (resolved on SNAPSHOT_BLOCK) each is wrapped in the uniform equivocation header with TAG=XATTEST and a phase-specific ROUND_ID, so the request and response legs of one request_id never share a round key.
Canonical signing message (v1)
Each SIG_n covers the canonical bytes:
request_id || provider_id || sha256(response_payload) || status || meta
Where sha256(response_payload) is the lowercase hex digest of the raw response bytes (after base64-decoding the wire field).
Lifecycle
- VM EXECUTE emits ATTEST v0; indexer stores a v0 row in the consolidated
atteststable (version=0) withrequest_status='pending'. - Validators staked for the
attestationcapability detect the request via the hub’sAttestationRoundpolling. - Top-
REDUNDANCYvalidators (deterministic leader sort bySHA-256(request_id || pubkey)) fetch via the provider and gossipATTEST_PROPOSE. - Leader publishes ATTEST v1 on-chain with
REDUNDANCYEd25519 signatures. - On a terminal v1 the indexer flips the request to
fulfilled(STATUS=ok) orerrored(a genuinely terminal failure such asexpired) and injects a system EXECUTE invoking the callback. A retryable v1 (STATUSofno_quorum,timeout, orprovider_error) is recorded but leavesrequest_status='pending', so the responsible set can attempt another round before the deadline; no callback fires yet. - If
DEADLINE_BLOCKpasses while stillpending(no terminal v1, or only retryable rounds), the indexer’s per-block expiry pipeline synthesizes ATTEST v2 (flips status toexpired, fires the callback withstatus='expired').
sequenceDiagram
participant VM
participant Indexer
participant Validators
participant Leader
VM->>Indexer: ATTEST v0, request emitted
Note over Indexer: store v0 row, request_status=pending
Validators->>Indexer: poll for pending requests, AttestationRound
Validators->>Validators: fetch via provider, gossip ATTEST_PROPOSE
Leader->>Indexer: ATTEST v1, REDUNDANCY signatures
alt terminal status, ok or errored
Indexer->>Indexer: flip request_status, inject callback EXECUTE
else retryable, no_quorum or timeout or provider_error
Indexer->>Indexer: record v1, request_status stays pending
end
alt DEADLINE_BLOCK passes while still pending
Indexer->>Indexer: synthesize ATTEST v2, flip to expired, fire callback
end
stateDiagram-v2
[*] --> pending: ATTEST v0 emitted
pending --> fulfilled: v1 terminal, STATUS=ok
pending --> errored: v1 terminal non-ok, e.g. expired
pending --> expired: DEADLINE_BLOCK passes while still pending, v2 synthesized
fulfilled --> [*]
errored --> [*]
expired --> [*]
note right of pending: retryable v1, no_quorum, timeout, or provider_error, leaves request_status pending
Effects on v1 with valid signatures
- Persists a v1 row into the
atteststable (version=1) with the agreed body and the verified federation sigs inlined as a JSON array invalidator_signatures(always, including retryable rounds, for audit). A v0 request and its v1 response(s) are separate rows correlated byrequest_id. - Terminal statuses flip the matching v0
attestsrow:fulfilled(STATUS=ok) orerrored(a terminal failure such asexpired). - Retryable statuses (
no_quorum,timeout,provider_error) leaverequest_status='pending'untouched so a later round can still reach quorum before the deadline (or the v2 expiry path takes over). No status flip and no callback for these. - On a terminal status only, synthesizes an EXECUTE injecting the callback with params
[request_id, provider_id, status, response_payload, ...original_callback_params]. - Every
original_callback_paramselement is coerced to a string before injection (the VM parameter bus is string-typed), so a request that supplied[42, true, null]reaches the callback as['42', 'true', 'null']. Contracts must re-parse numeric or boolean context withparseInt,parseFloat, orJSON.parseas needed. SOURCEis set tocontract_addresssoxchain.getSourceAddress() === xchain.getContractAddress()inside the callback.- Callback is wrapped in a savepoint; a callback failure does NOT roll back the response row.
Effects on v2 (expire)
- Creates an entry in the
actionstable (gets a newaction_indexso the synthetic event is replay-deterministic and rollback-correct). - Flips the matching v0
attests.request_statusfrompendingtoexpired(v2 writes no row of its own). - Synthesizes an EXECUTE injecting the contract’s callback with params
[request_id, provider_id, 'expired', '', ...original_callback_params]. - As on the v1 path, every
original_callback_paramselement is coerced to a string before injection; re-parse typed context inside the callback. SOURCEis set tocontract_address(matches the v1 callback convention).- Callback is wrapped in a savepoint; a callback failure does not roll back the status flip.
Fee flow
When a v0 request carries FEE_AMOUNT > 0, the fee is escrowed from FEE_PAYER at request time and disposed of when the request reaches a terminal state. All movements are GAS-denominated (XCHAIN). Settlement is deterministic across validators (bcmulfloor to GAS decimals; remainder dust stays in the REWARD pool).
| Event | Fee movement |
|---|---|
v0 valid, FEE_AMOUNT > 0 |
Debit FEE_PAYER and write escrow row (at the v0 action_index). |
v1 → fulfilled (STATUS=ok) |
Release escrow and credit the REWARD pool; one validator_rewards row per responsible-set pubkey (reward_type='attest_fee', round_reference= request action_index), each bcmulfloor(bcdiv(fee, N, 18), '1', 8). Floor dust stays in the pool. Empty responsible set means full fee stays in the pool. |
v1 → errored (terminal non-ok, e.g. expired) |
Release escrow and refund FEE_PAYER (service not rendered). |
v1 retryable (no_quorum / timeout / provider_error) |
No movement: escrow stays locked, request stays pending. |
| v2 expiry (synthesized) | Release escrow and refund FEE_PAYER. |
validator_rewards rows are paid out to stakers via COLLECT (the same path as protocol rewards, hence the XCHAIN-only constraint, since that chain has no per-row tick column). A reorg mid-fulfillment rolls back the release and reward rows generically (credits/debits/escrows by action_index, rewards by block_index) and resets request_status to pending; the earlier v0 escrow row survives.
Notes
REQUEST_IDis the cross-version foreign key; every v1 and v2 must reference an existing v0.- The positional label in the indexer’s internal format string for v0 is
CALLBACK_PARAMS_JSON(so named to signal that the field carries a JSON array). The data object key used throughout the handler and the stored column name isCALLBACK_PARAMS. The name in this PARAMS table (CALLBACK_PARAMS) is the canonical user-facing name and matches the stored key; the_JSONsuffix in the format string is an implementor hint, not a separate field. - Storage is consolidated into a single
atteststable: v0 (request) and v1 (response) rows are version-discriminated and correlated byrequest_id, mirroring howmessagesholds every MESSAGE variant in one table. v2 (expire) writes no row, it only flips the v0 row’srequest_status. Validator sigs live inline as a JSON array in the response row’svalidator_signaturescolumn; per-validator accountability tallies live inattest_validator_stats. - The optional
FEE_TICK/FEE_AMOUNTrequest fee is live. The separategas_escrow(callback-gas) field remains stubbed at'0'; real callback-gas escrow is Phase 3 economic work, independent of the request fee. - See
EXECUTE.mdfor the system-synthesized EXECUTE that delivers attestation callbacks and for the cross-contract call mechanics that share the same emission and savepoint patterns.
Copyright © 2025–2026 Dankest, LLC
Based on XChain Platform by Dankest, LLC – https://dankest.llc
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-or-later) with a commercial license available for proprietary use.
You may use, modify, and distribute this material under the terms of the License. See LICENSE and NOTICE for full terms. See the licensing overview.