Integrate

Backend Integration

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

Backend Integration

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:9944"),
  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