Skip to main content

Cross-VM Interoperability

QoreChain's triple-VM architecture (EVM, QoreChain Native, SVM) is designed to let smart contracts on any virtual machine communicate with contracts on any other VM. The x/crossvm module provides a call message with a same-transaction path and a queued path.

What works today

Cross-VM calls are only as available as their target lane:

  • MsgCrossVMCall is switched off on mainnet by the circuit breaker (qorechaind query circuit disabled-list); it is enabled on the testnet.
  • The SVM lane is disabled for transactions on both networks, so an SVM leg cannot run.
  • QoreChain Native upload is permissioned on both networks and instantiation is permissioned on mainnet, which has no stored contract code, so there is no QoreChain Native contract to call on mainnet yet. On the testnet, governance stored a few reviewed contracts that any account can instantiate (see QoreChain Native Development), so a QoreChain Native target can exist there.
  • The synchronous EVM precompile is not active on mainnet and is active on the testnet with an allowlist of reviewed QoreChain Native contracts (read 7 November 2026; a read-only call through it was run after the v3.2.7 upgrade, see below). A hardened version has been in the binary since v3.2.3. (On 27 October 2026 testnet governance activated three other precompiles, PQC Verify, PQC Key Status and PRISM Consensus Params, and the CrossVM bridge joined the testnet list by 4 November; mainnet has none active.)

On mainnet the module's REST routes (/qorechain/crossvm/v1/...) are not served by the public gateway (501); read messages with the CLI or a gRPC/ABCI query client.

note

The endpoints below default to a local node. On mainnet, use the qorechain-vladi RPC endpoints (Cosmos RPC 26657, EVM JSON-RPC 8545); the testnet is qorechain-diana.


Architecture Overview​

EVM (Solidity) QoreChain Native (Rust/Wasm) SVM (BPF)
| | |
|--- sync (precompile) ->| |
| | |
|<-- async (EndBlocker) -|-- async (EndBlocker) ->|
| | |
|<------------ async (EndBlocker) ----------------|
PathDirectionTimingMechanism
SynchronousEVM to QoreChain NativeSame transactionPrecompile at 0x0000...0901 (active on the testnet, not on mainnet; see below)
Message, defaultAny VM to any other VMSame transactionMsgCrossVMCall; the call executes immediately and a failure reverts the whole transaction
Message, queuedAny VM to any other VMNext block (or later)MsgCrossVMCall with async set; the message waits in the queue

Synchronous Path (EVM to QoreChain Native)​

The synchronous path uses an EVM precompile at address 0x0000000000000000000000000000000000000901. It is designed to let Solidity contracts call QoreChain Native contracts and receive a response within the same transaction.

Not active on mainnet; active on the testnet with an allowlist

The CrossVM bridge precompile is not active on qorechain-vladi: a call to 0x…0901 there succeeds and returns empty data. On qorechain-diana the active_static_precompiles list holds 0x…0901 (read 7 November 2026); a read-only eth_call through it was run on 7 November 2026, after the v3.2.7 upgrade (results below). The v3.2.3 binary carries a hardened version (it refuses a STATICCALL, meters its nested call against the calling frame's gas and journals it so an EVM revert also undoes the QoreChain Native side, reaches only allowlisted QoreChain Native targets, and honours the cross-VM circuit breaker), but it only runs where a governance-approved proposal has added it to the active list (the testnet so far, not mainnet). On mainnet the precompile and cross-VM calls stay off, even after a v3.2.7 upgrade (which has no date yet), until a separate governance decision. The example below is the layout the node decodes; it cannot run on mainnet until the precompile is active. On the testnet it can be called, within the allowlist (next section). See EVM Precompiles: Network status.

Solidity Example​

The node decodes the precompile's input as a 4-byte selector followed by the ABI-encoded arguments of executeCrossVMCall(uint8 targetVM, string targetContract, bytes payload) (selector 0x3c11078c). targetVM is 1 for a QoreChain Native contract (QoreChain Native is the CosmWasm runtime; 0 is the EVM, which the hardened bridge refuses as a target). targetContract is the contract's bech32 address as a string, and payload is the contract's execute message as UTF-8 JSON bytes. The precompile returns the target's raw response bytes.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract CrossVMCaller {
address constant CROSSVM = 0x0000000000000000000000000000000000000901;
uint8 constant TARGET_QORECHAIN_NATIVE = 1;

function callQoreChainNative(string calldata contractAddr, bytes calldata executeMsg)
external
returns (bytes memory)
{
bytes memory data = abi.encodeWithSignature(
"executeCrossVMCall(uint8,string,bytes)",
TARGET_QORECHAIN_NATIVE,
contractAddr,
executeMsg
);
(bool ok, bytes memory ret) = CROSSVM.call(data);
require(ok, "cross-VM call failed");
// While the precompile is not active, the call succeeds with empty data
// and nothing executes; treat an empty reply as a failure.
require(ret.length > 0, "cross-VM precompile not active or empty reply");
return ret;
}
}

This is the layout the node decodes; it is the same layout the @qorechain/rdk encoder builds, and the contract above matches the starter in Multi-VM. Funds cannot be attached through the precompile (the MsgCrossVMCall message path takes --funds). The sample cannot do anything on mainnet today, because the precompile is not active there. On the testnet it works within the allowlist described next; the sample contract itself was not deployed for this page, the call below was made directly with eth_call.

Where the precompile is active, it executes the QoreChain Native contract immediately and returns the result. (The @qorechain/rdk helper encodeCrossVmCalldata, available from 0.6.0 and in the current 0.8.0, builds the calldata; see Multi-VM.) Gas cost: 50,000 base + 4 per input byte + execution cost.

Testnet behaviour (allowlist, checked 7 November 2026)​

On qorechain-diana (node release v3.2.7 since 6 November 2026) the precompile reaches only QoreChain Native contracts on the bridge allowlist. GET /qorechain/crossvm/v1/params lists them in bridge_allowed_targets (three entries on 7 November 2026, written cosmwasm:qor1...), next to enabled true, max_message_size 65536, max_queue_size 1000 and queue_timeout_blocks 100. Two read-only eth_call requests to 0x…0901 with executeCrossVMCall(1, target, 0x7b7d) were run on 7 November 2026:

  • Target outside the allowlist: reverts with cross-VM bridge call refused: target cosmwasm:qor1... is not allowlisted.
  • Target on the allowlist: the call reaches the contract (the node reports the contract's own error for the empty {} message: Error parsing into type cw20::msg::Cw20ExecuteMsg). That shows the route works; no state-changing cross-VM transaction through the precompile was sent for this page.

On mainnet the precompile (0x…0901) and cross-VM calls stay off (circuit breaker) even after the v3.2.7 upgrade, until a separate governance decision.

What v3.2.7 changes for cross-VM calls (testnet only for now)​

These apply on the testnet since 6 November 2026 and are not on mainnet yet (the upgrade has no date):

  • A call that fails in the target VM now fails cleanly: it is that call's failure and the queue goes on with the next message.
  • Malformed targets are refused at submit time with code 5 in codespace crossvm.
  • A cross-VM call can no longer target an EVM precompile address.

Message Path (MsgCrossVMCall)​

Other cross-VM calls use the MsgCrossVMCall transaction. The chain works out the source lane from the sender, so the message carries only the target VM, the target contract, the payload and optional funds. By default the call executes immediately within the transaction, and the whole transaction reverts if the target fails. With the async flag the message is queued instead and handled by the module's end-block queue pass in a later block (see the lifecycle below). As noted above, this message is switched off on mainnet.

CLI​

tx crossvm call takes the target VM (evm, cosmwasm or svm) and the target contract as positional arguments, with the payload as hex. The CLI does not ask for the source VM: the chain derives the caller's lane from state and ignores the declared field (the CLI fills in a placeholder so the message passes its own validation):

qorechaind tx crossvm call evm 0x1234567890123456789012345678901234567890 \
--payload 0a0b0c --from mykey --generate-only > tx.json

Other flags: --funds <coins> (QoreChain Native targets only) and --async to queue the call instead of executing it now. Sign and broadcast tx.json with tx pqc cosign (see Post-Quantum Signing); the fee floor is 0.1 uqor per gas. A call into the caller's own lane is refused (since v3.2.3).

Disabled on mainnet

MsgCrossVMCall is switched off on mainnet by the circuit breaker, so the command cannot succeed there today. (Release binaries before v3.2.3 also built a message that failed its own validation because the source VM was empty; the v3.2.3 binary fixes that.) On the testnet the message is enabled, but the SVM lane is disabled, so an SVM target cannot be reached; an EVM target, and a QoreChain Native target once a contract has been instantiated there, can.


Message Lifecycle​

Every queued cross-VM message transitions through a defined set of states:

Submitted --> Pending --> Executed
|
+--> Failed
|
+--> Timed Out
StateDescription
SubmittedMessage accepted into the queue
PendingAwaiting execution in the next EndBlocker pass
ExecutedTarget contract called successfully; response recorded
FailedTarget contract execution reverted; error recorded
Timed OutMessage exceeded queue_timeout_blocks without execution

Since v3.2.3 a queued call that fails is rolled back as a whole: only its Failed record remains, and the funds attached to a failed QoreChain Native call are no longer moved into the contract. While the module is disabled, or MsgCrossVMCall is switched off by the circuit breaker, the queue is not executed: queued messages stay pending and expire at their timeout unless the switch is lifted first.


Parameters​

ParameterValueDescription
enabledtrueModule-level switch (the call message itself is separately switched off on mainnet)
max_message_size65,536 bytesMaximum payload size per message
max_queue_size1,000Maximum pending messages in the queue
queue_timeout_blocks100Blocks before an unprocessed message times out

Events​

The x/crossvm module emits the following events:

EventAttributesDescription
crossvm_requestmessage_id, source_vm, target_vm, target_contract, senderNew cross-VM message submitted
crossvm_responsemessage_id, status, resultMessage executed (success or failure)
crossvm_timeoutmessage_id, source_vm, target_vmMessage expired without execution

Subscribe to events via WebSocket:

wscat -c ws://localhost:26657/websocket
> {"jsonrpc":"2.0","method":"subscribe","params":["tm.event='Tx' AND crossvm_request.message_id EXISTS"],"id":1}

Querying Messages​

CLI​

# Query a specific message by ID
qorechaind query crossvm message <message-id>

# List all pending messages
qorechaind query crossvm pending

# Module parameters
qorechaind query crossvm params

JSON-RPC​

Not served yet: no released node build serves the qor_ JSON-RPC namespace, so this call returns -32601 (method not found) on every public endpoint today. See the qor_ namespace.

curl -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "qor_getCrossVMMessage",
"params": ["<message-id>"],
"id": 1
}'

Response Format​

{
"message_id": "crossvm-00000042",
"source_vm": "cosmwasm",
"target_vm": "evm",
"target_contract": "0x1234...abcd",
"sender": "qor1sender...",
"payload": "...",
"status": "executed",
"result": "0x...",
"submitted_height": 12345,
"executed_height": 12346
}

Design Considerations​

Atomicity: Synchronous calls (EVM to QoreChain Native via precompile) are designed to be atomic, so that if either side reverts the entire transaction reverts. The hardened precompile journals the nested call, so a revert in the calling contract also undoes the QoreChain Native side; it is active on the testnet only. Asynchronous calls are not atomic across blocks; design your contracts to handle the Failed and Timed Out states gracefully.

Ordering: Messages in the queue are processed FIFO within each EndBlocker pass. There is no guaranteed ordering across different source VMs.

Payload encoding: The payload format depends on the target VM:

  • EVM targets: ABI-encoded function calls
  • QoreChain Native targets: JSON-encoded execute messages
  • SVM targets: Hex-encoded BPF instruction data

Next Steps​