XChain Platform Action - XCALL
This action lets a smart contract on one chain call a method on a contract deployed on another chain, then receive the outcome through a callback. The relay between chains is performed by the validator federation with no per-call on-chain transaction. See Cross-Chain Contract Calls for the full architecture, trust model, and latency.
PARAMS
| Name | Type | Description |
|---|---|---|
VERSION |
String | Format Version |
CALL_ID |
String | 64-hex call identifier, derived from the emission (see Rules) |
TARGET_CHAIN |
String | COIN network the target contract lives on (BTC, LTC, DOGE), not the calling chain |
TARGET_CONTRACT_INDEX |
Integer | ACTION_INDEX of the target contract’s DEPLOY on TARGET_CHAIN |
METHOD |
String | Method to call; must be in the target contract’s crossCallable allowlist |
PARAMS_JSON |
String | JSON array of up to 32 strings (each ≤ 1024 bytes) passed to METHOD |
GAS_LIMIT |
Integer | Target-side execution ceiling, pre-paid by the caller |
CALLBACK_METHOD |
String | Method on the calling contract that receives the result (required) |
CALLBACK_PARAMS |
String | JSON array of strings echoed back to the callback |
DEADLINE_BLOCKS |
Integer | Source-chain blocks to wait before the call expires |
CROSS_HOPS |
Integer | Host-derived hop counter, capped at XCALL_MAX_HOPS |
Formats
Version 0 - Request (VM-emitted)
VERSION|CALL_ID|TARGET_CHAIN|TARGET_CONTRACT_INDEX|METHOD|PARAMS_JSON|GAS_LIMIT|CALLBACK_METHOD|CALLBACK_PARAMS|DEADLINE_BLOCKS|CROSS_HOPS
Version 2 - Expire (system-synthesized)
VERSION|CALL_ID
Examples
XCALL|0|3f2a9c...e91|LTC|8821|setPrice|["WOWCOIN","100"]|50000|onPriceSet|["job-7"]|200|1
A contract on BTC calls 'setPrice' on contract 8821 over on LTC, pre-paying 50000 gas, and asks for the result back via its own 'onPriceSet' method within 200 blocks
XCALL|2|3f2a9c...e91
System-synthesized when the deadline passes with no result; flips the request to expired and fires the callback with status 'expired'
Rules
XCALLis never broadcast by users. Version0is emitted only from inside a contract viaxchain.emit.crossExecute(...); Version2is synthesized independently by every indexer once the deadline passes.XCALLemissions are disallowed from aDEPLOYconstructorCALL_IDmust equalsha256(network:source_chain:tx_hash:root_action_index:contract_index:emitter_path:emitter_position:target_chain)(colon-delimited) and is re-derived and verified by the indexer. Network and both chains are bound in because BTC-family chains share tx-hash space. Theroot_action_index(the root on-chain action) plus theemitter_path(the>-joined per-execution call path, empty for a root action) andemitter_positionkeep the id deterministic across nodes while disambiguating nested emissions; the emitting sub-action’s own per-emissionaction_indexis not bound inTARGET_CHAINmust be a supported coin network other than the calling chainMETHODis ≤ 64 bytes and must be exported in the target contract’scrossCallableallowlist; the target contract’s existence is checked onTARGET_CHAIN, not the calling chainPARAMS_JSONis a JSON array of ≤ 32 strings, each ≤ 1024 bytes, with no|GAS_LIMITis an integer in[XCALL_MIN_GAS, XCALL_MAX_GAS](5,000 to 200,000), pre-paid by the callerCALLBACK_METHODis required: every call ends in exactly one callbackDEADLINE_BLOCKSis an integer in[10, 4000]source-chain blocksCROSS_HOPSis derived by the host (the caller’s hop count plus one), capped atXCALL_MAX_HOPS(2), and is never trusted from the VM- Effective dispatches apply in
(snapshot_block, call_id)order, up toXCALL_MAX_CALLS_PER_BLOCK(25) per block; any overflow carries forward to the next block - Exactly-once callback: result delivery and deadline expiry share the request’s status column, so whichever reaches a terminal state first wins and the loser records itself as skipped. Both are driven by block height, never wall-clock time
Result statuses
The callback receives exactly one status: ok, reverted, out_of_gas, no_contract, not_callable, payload_too_large, error, or expired (expiry path only).
The callback is invoked using the ATTEST-callback convention:
CALLBACK_METHOD(call_id, target_chain, status, return_payload, ...CALLBACK_PARAMS)
return_payload is the target method’s JSON-serialized return value (≤ XCALL_MAX_RETURN_BYTES, 1,024 bytes). An oversized return yields payload_too_large with an empty payload; the target-side state changes still stand.
Gas
Charged at emit time, entirely from the caller’s budget:
VM_EMISSION (500) + VM_XCALL_REQUEST (2,000) + GAS_LIMIT + VM_XCALL_CALLBACK (20,000)
Unused target-side gas is not refunded in v1. The callback runs against the fixed VM_XCALL_CALLBACK ceiling.
Notes
-
There is no on-chain Version
1. The result comes back as a quorum-signed hub-mirror row (cross_chain_calls, phaseresult), and the callback is delivered as a system-injectedEXECUTE, the same pattern used for attestation callbacks (seeEXECUTEandATTEST) -
Both relay legs travel as immutable
cross_chain_callsrows (UNIQUE(call_id, phase)), signed 2f+1 by thecross_chaincapability set and verified by every indexer against the mirrored capability snapshot at the row’ssnapshot_blockbefore any effect is applied. The canonical signing strings are:Dispatch: XCALL|DISPATCH|call_id|snapshot_block|network|source_chain|source_action_index|source_contract_index|target_chain|target_contract_index|method|sha256(params_json)|gas_limit|cross_hops|effective_time Result: XCALL|RESULT|call_id|snapshot_block|network|target_chain|result_status|sha256(return_payload_b64)|effective_timeVariable-length fields enter as a
sha256digest so the canonical string stays fixed-arity and|-safe -
Target-side execution (
XEXEC) is an internal action: a depth-0EXECUTEundergasCeiling = GAS_LIMIT, with a synthetic chain/network-namespacedTX_HASH, thecrossCallableallowlist enforced, and its own savepoint. A failed run rolls its state back and that failure becomes the relayed result. It is idempotent and reorg-safe viacross_chain_call_executions -
Lifecycle (source-chain request status): a request starts
pending. The federation waits for source-chain confirmation depth, then signs the dispatch row; the target chain verifies signatures and injectsXEXECat the first block at or aftereffective_time, then the federation waits for target-chain depth and signs the result row. The request becomescompletedwhen a verified result arrives, orexpiredonceDEADLINE_BLOCKSpasses with no resultstateDiagram-v2 [*] --> Pending state Pending { [*] --> AwaitingDispatch AwaitingDispatch --> DispatchSigned: source confirmation depth reached,<br>federation signs dispatch row DispatchSigned --> XEXECInjected: target chain verifies signatures,<br>injects XEXEC at first block at or after effective_time XEXECInjected --> ResultSigned: target-chain depth reached,<br>federation signs result row } Pending --> Completed: verified result arrives Pending --> Expired: DEADLINE_BLOCKS passes with no result Completed --> [*] Expired --> [*] -
Recoverability: the Version
0request is reproducible from a pure chain parse, and both relay phases are included in the ANCHOR v1 archive and rebuilt and signature-verified byxchain-indexer/src/recovery.js, so a from-genesis reindex re-derives identical injected executions and callbacks
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.