Integrate

Backend Integration

Server-side patterns for integrating with Vara.eth programs.

Backend Integration

Endpoint Availability

The gear-tech.io and vara.network URLs in the examples below were operated by Gear for the launch campaigns and are no longer served. Swap the Ethereum RPC for any Hoodi or Mainnet provider, and the Vara.eth RPC for your own node — see Network Endpoints. The code itself is unchanged.

Backend services connect to Vara.eth for automation, indexing, monitoring, and bridging off-chain systems with on-chain programs.

Method Coverage by Flow

Backend usually orchestrates the full lifecycle, not only reads/events.

Flow StepMethod(s)Notes
Upload codeethexe tx upload ... --watchCanonical path for code submission/validation
Create programrouter.createProgram(...)Standard deployment
Create with ABIrouter.createProgramWithAbiInterface(...)Needed for typed Solidity/Etherscan proxy UX
Top up balancewvara.approve(...) + mirror.executableBalanceTopUp(...)Required before processing user workload
Send message (L1)mirror.sendMessage(...)Standard Ethereum-side path
Send message (fast)api.createInjectedTransaction(...) + sendAndWaitForPromise()Vara.eth-side pre-confirmation path
Read canonical statemirror.stateHash() + api.query.program.readState(...)Hash from L1, body from Vara.eth API
Read computed queryapi.call.program.calculateReplyForHandle(...)Read-only execution without sending tx

Tx Lifecycle Helpers (@vara-eth/api)

Useful when building robust backend workers:

  • ethereumClient.waitForInitialization() — wait until Router/WVARA clients are ready
  • tx.sendAndWaitForReceipt() — reliable write completion point
  • tx.estimateGas() — preflight for operational safety
  • tx.findEvent(...) / tx.getProgramId() — event-based extraction for pipelines

Connection Methods

WebSocket RPC

Use @vara-eth/api for Vara.eth-side operations (recommended):

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

const ethereumClient = new EthereumClient(publicClient, ROUTER_ADDRESS, signerAdapter);
await ethereumClient.waitForInitialization();

const api = new VaraEthApi(
  new WsVaraEthProvider("wss://vara-eth-validator-1.gear-tech.io"),
  ethereumClient
);

// Read program ids
const ids = await api.query.program.getIds();
console.log("programs:", ids.length);

// Read full state by state hash
const state = await api.query.program.readState(stateHash);
console.log("state:", state);

See RPC API for the full method reference.

Ethereum RPC

Monitor Mirror events via standard Ethereum JSON-RPC:

import { ethers } from "ethers";

const provider = new ethers.WebSocketProvider(
  "wss://hoodi-reth-rpc.gear-tech.io/ws"
);

const mirror = new ethers.Contract(mirrorAddress, mirrorAbi, provider);

mirror.on("StateChanged", (stateHash) => {
  console.log(`New state hash: ${stateHash}`);
});

mirror.on("Message", (id, destination, payload, value) => {
  console.log(`Program sent message to ${destination}`);
});

sails-js (Node.js)

Use the typed SDK for server-side operations:

import { SailsProgram } from "sails-js";
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider(
  "https://hoodi-reth-rpc.gear-tech.io"
);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

const program = new SailsProgram({
  idl: programIdl,
  address: mirrorAddress,
  provider,
  signer: wallet,
});

// Read state (free, no tx)
const balance = await program.token.balanceOf(userAddress);

// Send message (requires ETH for gas)
await program.token.transfer(recipient, amount);

Common Backend Patterns

Event Indexer

Index program events into a database for fast queries:

import { ethers } from "ethers";
import { Pool } from "pg";

const db = new Pool({ connectionString: DATABASE_URL });
const provider = new ethers.WebSocketProvider(RPC_URL);
const mirror = new ethers.Contract(mirrorAddress, abi, provider);

// Index all transfer events
mirror.on("Transferred", async (from, to, amount, event) => {
  await db.query(
    `INSERT INTO transfers (from_addr, to_addr, amount, block_number, tx_hash)
     VALUES ($1, $2, $3, $4, $5)`,
    [
      from,
      to,
      amount.toString(),
      event.log.blockNumber,
      event.log.transactionHash,
    ]
  );
});

// Backfill historical events
async function backfill(fromBlock: number) {
  const events = await mirror.queryFilter("Transferred", fromBlock, "latest");
  for (const event of events) {
    // Insert into DB...
  }
}

Automation Bot

Automate actions based on program state or events:

async function monitorAndAct() {
  const program = new SailsProgram({
    idl,
    address: mirrorAddress,
    provider,
    signer: botWallet,
  });

  setInterval(async () => {
    const state = await program.myService.getStatus();

    if (state.needsRebalance) {
      console.log("Triggering rebalance...");
      await program.myService.rebalance();
    }
  }, 30_000);
}

Keeper Service

Execute time-sensitive operations that programs cannot trigger themselves:

// Programs respond to messages — they can't initiate actions on their own.
// A keeper sends periodic messages to trigger scheduled logic.

async function keeper() {
  const program = new SailsProgram({
    idl,
    address: mirrorAddress,
    provider,
    signer: keeperWallet,
  });

  const cron = new CronJob("0 0 * * *", async () => {
    try {
      await program.settlement.processDailyBatch();
      console.log("Daily settlement triggered");
    } catch (err) {
      console.error("Settlement failed:", err);
    }
  });

  cron.start();
}

Message Automation on Vara

On Vara Network, programs can use delayed messages for self-scheduling. On Vara.eth, external keepers fill this role.

API Gateway

Expose program state via REST API:

import express from "express";
import { SailsProgram } from "sails-js";

const app = express();
const program = new SailsProgram({ idl, address: mirrorAddress, provider });

// Read-only endpoints (free, no gas)
app.get("/api/balance/:address", async (req, res) => {
  const balance = await program.token.balanceOf(req.params.address);
  res.json({ balance: balance.toString() });
});

app.get("/api/state", async (req, res) => {
  const state = await program.myService.getFullState();
  res.json(state);
});

// Write endpoints (requires gas)
app.post("/api/action", async (req, res) => {
  const signedProgram = program.connect(serverWallet);
  const result = await signedProgram.myService.doAction(req.body.params);
  res.json({ success: true, result });
});

app.listen(3000);

The Graph Subgraph

For production indexing, use The Graph to create a subgraph:

subgraph.yaml
specVersion: 0.0.5
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum
    name: MyProgram
    network: hoodi
    source:
      address: "0x..." # Mirror address
      abi: Mirror
      startBlock: 1000000
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Transfer
        - StateUpdate
      abis:
        - name: Mirror
          file: ./abis/Mirror.json
      eventHandlers:
        - event: StateChanged(bytes32)
          handler: handleStateChanged
        - event: Transferred(address,address,uint256)
          handler: handleTransfer

Monitoring & Alerting

Health Check

async function healthCheck() {
  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const mirror = new ethers.Contract(mirrorAddress, abi, provider);
  const stateHash = await mirror.stateHash();

  // Check recent activity
  const filter = mirror.filters.StateChanged();
  const events = await mirror.queryFilter(filter, -1000);
  const lastUpdate = events[events.length - 1];

  if (lastUpdate) {
    const block = await provider.getBlock(lastUpdate.blockNumber);
    const age = Date.now() / 1000 - block.timestamp;
    if (age > 3600) {
      alert("Program hasn't updated state in over an hour");
    }
  }
}

Metrics Collection

import { Counter, Gauge } from "prom-client";

const messagesReceived = new Counter({
  name: "vara_messages_received_total",
  help: "Total messages received by program",
});

const executableBalance = new Gauge({
  name: "vara_executable_balance",
  help: "Current executable balance in wVARA",
});

mirror.on("MessageQueueingRequested", () => {
  messagesReceived.inc();
});

setInterval(async () => {
  const balance = await getExecutableBalance(programId);
  executableBalance.set(parseFloat(balance));
}, 60_000);

Security Considerations

  • Private key management: Use environment variables, HSMs, or secret managers (AWS Secrets Manager, HashiCorp Vault) — never hardcode keys.
  • Rate limiting: Vara.eth RPC endpoints may rate-limit. Implement exponential backoff and connection pooling.
  • Idempotency: Design your backend to handle duplicate events. Mirror events can be re-emitted during chain reorgs.
  • Error handling: Always wrap message sends in try/catch. Failed transactions still cost gas.
  • Monitoring gas: Backend wallets need ETH for gas. Monitor balances and set up alerts for low funds.

On this page