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 Step | Method(s) | Notes |
|---|---|---|
| Upload code | ethexe tx upload ... --watch | Canonical path for code submission/validation |
| Create program | router.createProgram(...) | Standard deployment |
| Create with ABI | router.createProgramWithAbiInterface(...) | Needed for typed Solidity/Etherscan proxy UX |
| Top up balance | wvara.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 state | mirror.stateHash() + api.query.program.readState(...) | Hash from L1, body from Vara.eth API |
| Read computed query | api.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 readytx.sendAndWaitForReceipt()— reliable write completion pointtx.estimateGas()— preflight for operational safetytx.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}`);
});import { ethers } from "ethers";
const provider = new ethers.WebSocketProvider(
"wss://mainnet-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://mainnet-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);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:
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: handleTransferMonitoring & 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.