Reference

RPC API

Vara.eth API — two interaction paths, injected transactions, state queries, and pre-confirmations.

RPC API

Vara.eth provides two interaction paths for communicating with programs:

  1. Ethereum-side — Send messages through Mirror contracts on Ethereum L1. Standard Ethereum transactions, settled on-chain.
  2. Vara.eth-side — Send messages directly to Vara.eth validator nodes, bypassing Ethereum. Faster, cheaper, used for pre-confirmations and state queries.

Both paths are unified in the @vara-eth/api TypeScript library.

SDK: @vara-eth/api

The primary client library for Vara.eth. Built on viem, it provides typed interfaces for both Ethereum-side and Vara.eth-side operations.

npm install @vara-eth/api viem@^2.39.0 [email protected]

Initialization

import { VaraEthApi, WsVaraEthProvider, EthereumClient } from "@vara-eth/api";
import { walletClientToSigner } from "@vara-eth/api/signer";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

// Ethereum clients (viem)
const publicClient = createPublicClient({
  transport: http("https://hoodi-reth-rpc.gear-tech.io"),
});
const account = privateKeyToAccount("0x...");
const walletClient = createWalletClient({
  account,
  transport: http("https://hoodi-reth-rpc.gear-tech.io"),
});

// Convert viem WalletClient to ISigner
const signer = walletClientToSigner(walletClient);

// EthereumClient — wraps Router, Mirror, and wVARA contracts
const ethereumClient = new EthereumClient(
  publicClient,
  routerAddress,
  signer
);
await ethereumClient.waitForInitialization();

// VaraEthApi — connects to Vara.eth validator nodes
const api = new VaraEthApi(
  new WsVaraEthProvider("ws://localhost:9944"),
  ethereumClient
);

// Access contract clients
const router = ethereumClient.router; // RouterClient
const wvara = ethereumClient.wvara;   // WrappedVaraClient

Key Classes

ClassDescription
VaraEthApiMain API for Vara.eth-side operations (queries, injected tx, state reads)
EthereumClientEthereum-side wrapper (Router, Mirror, wVARA contracts)
RouterClientInterface to the Router contract (code validation, program creation)
MirrorClientInterface to Mirror contracts (send messages, top up balance, state hash)
WrappedVaraClientERC-20 wVARA token operations (approve, balanceOf, allowance)

Source contracts: Router.sol, Mirror.sol, WrappedVara.sol


Ethereum-Side Operations

Standard Ethereum transactions through Mirror contracts on L1.

Program Creation

const codeId = "0x..."; // From ethexe CLI upload

// Create program from validated code
const tx = await router.createProgram(codeId);
await tx.sendAndWaitForReceipt();
const programId = await tx.getProgramId();

// Or with Solidity ABI for Etherscan compatibility
const tx2 = await router.createProgramWithAbiInterface(codeId, abiAddress);
await tx2.sendAndWaitForReceipt();

Sending Messages

import { getMirrorClient } from "@vara-eth/api";

const mirror = getMirrorClient(programId, signer, publicClient);

// Send message (payload encoded via sails-js)
const tx = await mirror.sendMessage(payload, 0n);
await tx.send();

// Get message details
const message = await tx.getMessage();
console.log("Message ID:", message.id);

// Wait for reply
const { waitForReply } = await tx.setupReplyListener();
const { payload: replyPayload, replyCode, value } = await waitForReply;

Payload Encoding

Use sails-js to encode payloads and decode replies from your program's IDL. See Sails-JS documentation.

Managing Executable Balance

// Check wVARA balance
const balance = await wvara.balanceOf(ethereumClient.accountAddress);

// Approve program to spend wVARA
const approveTx = await wvara.approve(programId, BigInt(10 * 1e12));
await approveTx.sendAndWaitForReceipt();

// Top up program's Executable Balance
const topUpTx = await mirror.executableBalanceTopUp(BigInt(10 * 1e12));
const { status } = await topUpTx.sendAndWaitForReceipt();

Checking Program State (Ethereum-side)

// Code validation status
const codeState = await router.codeState(codeId);
// 'Validated' | 'Rejected' | 'Unknown'

// Program's code ID
const programCodeId = await router.programCodeId(programId);

// State hash from Mirror
const stateHash = await mirror.stateHash();

// Program nonce
const nonce = await mirror.nonce();

TxManager

All contract write methods return a TxManager that handles the transaction lifecycle:

const tx = await router.createProgram(codeId);

// Send and get hash
const response = await tx.send();
console.log("Tx hash:", response.hash);

// Send and wait for receipt
const receipt = await tx.sendAndWaitForReceipt();

// Estimate gas
const gasEstimate = await tx.estimateGas();

// Find events in receipt
const event = await tx.findEvent("ProgramCreated");

Vara.eth-Side Operations

Direct communication with Vara.eth validator nodes. This is the primary path for pre-confirmations and read-only state queries -- faster and cheaper than going through Ethereum.

Connecting to Vara.eth Nodes

import { VaraEthApi, WsVaraEthProvider, HttpVaraEthProvider } from "@vara-eth/api";

// WebSocket — for subscriptions and real-time updates
const api = new VaraEthApi(
  new WsVaraEthProvider("ws://localhost:9944"),
  ethereumClient
);

// HTTP — for one-time queries
const api = new VaraEthApi(
  new HttpVaraEthProvider("http://localhost:9944"),
  ethereumClient
);

// Disconnect when done
await api.provider.disconnect();

Injected Transactions

Injected transactions are Vara.eth-native transactions sent directly to validators, bypassing Ethereum entirely. They are:

  • Signed with your Ethereum private key but submitted off-chain
  • Cheaper (no Ethereum gas costs)
  • Faster (no L1 block time wait)
  • Reference an Ethereum block for security anchoring

This is the primary mechanism for pre-confirmations.

// Create injected transaction
const injected = await api.createInjectedTransaction({
  destination: programId,
  payload: encodedPayload,
  value: 0n,
  // Auto-populated if not provided:
  // recipient: validator address (auto-selected)
  // referenceBlock: recent Ethereum block hash
  // salt: random salt for uniqueness
});

// Send transaction
const result = await injected.send();

// Send and wait for promise (includes reply)
const promise = await injected.sendAndWaitForPromise();

// Validate the FROST threshold signature
await promise.validateSignature();

Configuring Injected Transactions

const injected = await api.createInjectedTransaction({
  destination: programId,
  payload: encodedPayload,
  value: 1000n,
  referenceBlock: blockHash,    // Specific Ethereum block
  salt: "0x030405",             // Custom salt
  recipient: validatorAddress,  // Specific validator
});

// Modify using fluent API
injected
  .setValue(2000n)
  .setSalt("0x060708");

// Access properties
const messageId = injected.messageId;

// Update fields
await injected.setReferenceBlock();     // Fetch latest block
await injected.setRecipient();          // Auto-select validator
await injected.setRecipient(address);   // Specific validator

Querying Program Data

// List all program IDs
const programIds = await api.query.program.getIds();

// Get program's code ID
const codeId = await api.query.program.codeId(programId);

// Read full program state by state hash
const stateHash = await mirror.stateHash();
const state = await api.query.program.readState(stateHash);

if ("Active" in state.program) {
  console.log("Program is active");
  console.log("Balance:", state.balance);
}

Reading State via calculateReplyForHandle

Perform read-only queries on program state without sending transactions or paying gas. This simulates a message and returns what the program would reply:

// Encode query payload using sails-js
const queryPayload = sails.services.Counter.queries.GetValue.encodePayload();

// Calculate reply (read-only, free)
const reply = await api.call.program.calculateReplyForHandle(
  await ethereumClient.getAccountAddress(), // Source address
  programId,                                 // Program to query
  queryPayload                               // Encoded query
);

// Decode result using sails-js
const value = sails.services.Counter.queries.GetValue.decodeResult(
  reply.payload
);
console.log("Current counter value:", value);

This is useful for:

  • Reading program state without modifying it
  • Testing message payloads before sending
  • Querying computed values from programs
  • Building responsive UIs with instant state reads

Interaction Flow Summary

                    ┌──────────────────────┐
                    │     Your dApp        │
                    └──────┬───────┬───────┘
                           │       │
              Ethereum-side│       │Vara.eth-side
                           │       │
                    ┌──────▼──┐ ┌──▼──────────┐
                    │ Mirror  │ │  VaraEthApi  │
                    │Contract │ │  (direct)    │
                    └──────┬──┘ └──┬───────────┘
                           │       │
                    ┌──────▼───────▼──────────┐
                    │   Vara.eth Validators   │
                    │   (WASM execution)      │
                    └─────────┬───────────────┘

                    ┌─────────▼───────────────┐
                    │   Ethereum L1           │
                    │   (batch settlement)    │
                    └─────────────────────────┘
PathUse CaseCostLatency
Ethereum-side (Mirror)On-chain settlement, value transfers, Solidity integrationETH gas + wVARA~12s (L1 block)
Vara.eth-side (Injected)Pre-confirmations, fast UX, state queriesNo ETH gasSub-second
Read-only (calculateReply)State queries, testing payloadsFreeInstant

JSON-RPC Methods (Low-Level)

For direct JSON-RPC access without the SDK:

Connection

Ethereum RPC (L1): https://hoodi-reth-rpc.gear-tech.io
Vara.eth RPC:      http://127.0.0.1:9944
Vara.eth WS:       ws://127.0.0.1:9944

Standard Ethereum Methods

Use Ethereum RPC for standard eth_* methods (eth_blockNumber, eth_call, eth_sendRawTransaction, eth_getLogs, etc.).

Vara.eth Extension Methods

MethodDescription
program_idsList known program IDs
program_codeIdGet code ID for a program
program_readStateRead full program state by state hash
program_calculateReplyForHandleRead-only simulation of a message call
block_headerGet latest block header (or by hash)
injected_sendTransactionSend an injected transaction to validators
injected_sendTransactionAndWatch / injected_sendTransactionAndWatchUnsubscribeSubscribe to injected tx promise and unsubscribe

Additional node methods (not wrapped by high-level SDK helpers):

MethodDescription
program_readQueue / program_readWaitlist / program_readStash / program_readMailboxInspect internal program queues/storage fragments
program_readFullStateRead extended full state snapshot
program_readPages / program_readPageDataInspect memory pages/page data
block_events / block_outcomeInspect block-level request events and transitions
code_getOriginal / code_getInstrumentedFetch uploaded/or instrumented code blobs

Error Codes

CodeMeaning
-32600Invalid request
-32601Method not found
-32602Invalid params
-32603Internal error
8000Node runtime/database/internal error (ethexe RPC service-specific)

On this page