Building Off-Chain Services for Ergo dApps

A comprehensive guide to backend patterns, SDKs, wallet integration, deployment, and best practices for robust Ergo dApps.

Why Off-Chain Services?

Off-chain services bridge the gap between users and the blockchain, enabling features impractical or impossible to implement purely on-chain:

  • User Interfaces (UI/UX): Web and mobile frontends.
  • Complex Transaction Construction: Assembling protocol-specific inputs/outputs.
  • State Management: Tracking app state (order books, user data).
  • Event Monitoring: Watch for chain events & trigger actions.
  • Data Indexing: Efficient blockchain queries (Indexing Guide).
  • Wallet Interaction: Fetching addresses, signing (EIP-0012, ErgoPay).
  • Automation: Bots for liquidation/arbitrage.
  • External Integrations: Off-chain data/API connectivity.

Common Off-Chain Patterns

1. Watchers / Monitors

Services that scan new blocks or the mempool for relevant events.

Example: SigmaUSD's reserve monitoring system watches SigRSV/SigUSD token amounts and triggers UI updates.

// Fleet SDK watcher (simplified)
import { ErgoNodeApi } from "@fleet-sdk/common";
const nodeApi = new ErgoNodeApi({ url: "http://your-node-ip:9053" });
let lastHeight = 0;

async function watchForBoxes() {
  try {
    const info = await nodeApi.getNodeInfo();
    const currentHeight = info.fullHeight;
    if (currentHeight > lastHeight) {
      for (let h = lastHeight + 1; h <= currentHeight; h++) {
        const headerIds = await nodeApi.getHeaderIdsAtHeight(h);
        if (!headerIds?.length) continue;
        const block = await nodeApi.getBlockById(headerIds[0]);
        if (!block?.blockTransactions) continue;
        for (const tx of block.blockTransactions) {
          for (const box of tx.outputs) {
            if (box.assets.some(a => a.tokenId === "YOUR_TOKEN_ID")) {
              console.log(`Found box: ${box.boxId} at height ${h}`);
            }
          }
        }
      }
      lastHeight = currentHeight;
    }
  } catch (e) { console.error(e); }
  setTimeout(watchForBoxes, 30000);
}
watchForBoxes();

Cross-chain example: Rosen Bridge Watchers monitor deposit events on other blockchains and relay to Ergo.

2. Bots / Agents

Automated services that construct and submit transactions.

Example: Oracle Pool bots post new price data to the chain.

// Fleet SDK bot (conceptual)
import { OutputBuilder, TransactionBuilder, ErgoNodeApi, ErgoProver } from "@fleet-sdk/core";
const nodeApi = new ErgoNodeApi({ url: "http://your-node-ip:9053" });
const botProver = new ErgoProver(process.env.BOT_SECRET_KEY);
async function updateOraclePrice() {
  try {
    const price = await getExternalPrice("ERG-USD");
    const newPriceLong = BigInt(Math.round(price * 100));
    const oracleBox = await nodeApi.getBoxById("YOUR_ORACLE_POOL_BOX_ID");
    const creationHeight = await nodeApi.getCurrentHeight();
    const unsignedTx = new TransactionBuilder(creationHeight)
      .from([oracleBox])
      .to(new OutputBuilder(oracleBox.value, oracleBox.ergoTree)
        .setAdditionalRegisters({ R4: SLong(newPriceLong).toString() })
        .addTokens(oracleBox.assets))
      .sendChangeTo(botProver.getAddress())
      .payFee(RECOMMENDED_MIN_FEE)
      .build();
    const signedTx = await botProver.sign(unsignedTx);
    await nodeApi.submitTransaction(signedTx);
  } catch (e) { console.error(e); }
  setTimeout(updateOraclePrice, 300000);
}
updateOraclePrice();

See: Oracle Pools V2

3. Backend APIs for dApps

Central API for frontends to handle state, indexing, and wallet orchestration.

Example: Auction House backend provides endpoints for auctions, bids, and transaction construction.

// Express.js backend API with Fleet SDK (conceptual)
import express from 'express';
import { ErgoNodeApi } from '@fleet-sdk/common';
const app = express();
app.use(express.json());
const nodeApi = new ErgoNodeApi({ url: "http://your-node-ip:9053" });
app.post('/api/build-auction-tx', async (req, res) => {
  try {
    const { sellerAddress, tokenId, startPrice, auctionLength } = req.body;
    const currentHeight = await nodeApi.getCurrentHeight();
    const deadline = currentHeight + auctionLength;
    const auctionOutput = {
      value: startPrice.toString(),
      ergoTree: "YOUR_AUCTION_CONTRACT_TREE",
      assets: [{ tokenId, amount: "1" }],
      additionalRegisters: {
        R4: SGroupElement(ErgoAddress.fromBase58(sellerAddress).getPublicKey()).toHex(),
        R5: SLong(BigInt(deadline)).toHex()
      },
      creationHeight: currentHeight
    };
    res.json({ auctionOutput, fee: RECOMMENDED_MIN_FEE.toString(), creationHeight });
  } catch (e) { res.status(500).json({ error: 'Failed to build tx params' }); }
});
app.listen(3000, () => console.log('API running'));

4. Indexer Services

Dedicated services for processing/storing blockchain data efficiently. See: Indexing Guide

SDK Comparison

Feature / SDKFleet SDK (JS/TS)Sigma-RustAppkit (Scala)
Primary LanguageJS/TSRustScala (JVM)
EnvironmentNode.js, WebNative, WASMJVM
StrengthsWeb integration, ease, UI/backendPerformance, WASM, low-levelStrong typing, JVM, Android
WeaknessesPerf, less type safetySteep curve, smaller web ecoNot for web front, JVM overhead
Ideal Use CasesWeb dApps, scripts, botsIndexers, bots, WASM libsBackends, Android, protocol

Core Tasks & Example Code

Connecting to a Node (Fleet SDK, JS/TS)

import { ErgoNodeApi } from "@fleet-sdk/common";
const nodeApi = new ErgoNodeApi({ url: "http://your-node-ip:9053" });
async function getNodeHeight() {
  try {
    const info = await nodeApi.getNodeInfo();
    console.log("Node Height:", info.fullHeight);
  } catch (e) { console.error("Error", e); }
}

Connecting to a Node (Sigma-Rust, Rust)

use ergo_node_interface::NodeInterface;
async fn get_node_height() {
    let node = NodeInterface::new("127.0.0.1", "9053", "your_api_key_hash");
    match node.get_node_info().await {
        Ok(info) => println!("Node Height: {}", info.full_height),
        Err(e) => eprintln!("Error: {:?}", e),
    }
}

Wallet Integration (EIP-0012 & ErgoPay)

  • EIP-0012 (dApp Connector): Browser/mobile wallet standard. Enables UI-initiated address/signing actions.
  • ErgoPay (EIP-0020): QR/deeplink protocol. Enables backends or services to request signing from mobile wallets, even outside browser.

See: ErgoPay Tutorial

Development Workflow

  1. Design: Define protocol, contracts, and off-chain logic.
  2. On-Chain Dev: Write and test ErgoScript contracts.
  3. Off-Chain Dev: Build backend, watcher, or bot with an SDK.
  4. Testing (Off-Chain): Simulate with SDK test frameworks (Fleet MockChain, Appkit Mockchain, Sigma-Rust test utils).
  5. Testing (Integrated): Deploy/test on Testnet with real nodes/wallets.
  6. Deployment: Go to Mainnet with monitoring & alerts.

See also: Learning Ergo 101

Testing Strategies

  • Unit Testing: Test functions (parsing, state, API formatting).
  • Mocking: Use SDK mocks to simulate node/indexer/wallet responses.
  • Integration Testing: Full system flow on Testnet.
  • Component Testing: Test interactions between off-chain system parts.
  • Load Testing: Simulate high traffic for performance checks.

Deployment & Scaling

  • Infrastructure: Choose cloud, VPS, bare metal; add redundancy, backups, firewall.
  • Node Access: Ensure reliable, synced node API. Use load balancing.
  • Database Scaling: Use replicas/sharding if needed. Monitor performance.
  • Service Scaling: Stateless, horizontally scalable backend APIs/indexers.
  • Monitoring: Use Prometheus, Grafana, Datadog, etc. Set alerts.

Common Challenges & Solutions

  • Reorg Handling: Use frameworks like ergoplatform/scanner, track block headers, database rollback.
  • Node Issues: Implement resilient API clients with retries, timeouts, fallback nodes.
  • State Consistency: Use DB transactions, mark data with block height/hash, handle reorgs robustly.
  • Key Management: NEVER store keys in code/config. Use HSM, secret managers, or encrypted envs.
  • Complex Transaction Building: Rely on SDKs, test edge cases thoroughly.
  • Indexer Performance: Optimize writes, selective indexing, consider multiple indexer instances.

Real-World Examples

Best Practices

  • Stay Informed: Follow Ergo news, protocol updates, and new off-chain opportunities.
  • Security: Secure your infra and private keys; never store secrets in code.
  • Community: Engage in forums, Discord, and share experience.
  • Compliance: Follow all relevant legal & regulatory requirements.

Updated Architecture Diagram

graph LR
    subgraph User Interaction
        User[User via Frontend/UI]
        Wallet[(User Wallet)]
    end

    subgraph OffChain Infrastructure
        BackendAPI[dApp Backend API]
        IndexerDB[(Indexer Database)]
        Indexer[Indexer Service]
        Bot[Automated Bot/Agent]
        Node[Ergo Node API]
    end

    User -->|HTTP/WebSocket| BackendAPI;
    User -->|EIP-0012 / Deeplink| Wallet;

    BackendAPI -->|Reads Data| IndexerDB;
    BackendAPI -->|Builds Tx / ErgoPay Payload| BackendAPI;
    BackendAPI -->|ErgoPay QR/Link| User;
    Wallet -->|Signs Tx| BackendAPI; 

    BackendAPI -->|Submits Signed Tx| Node;

    Indexer -->|Fetches Blocks| Node;
    Indexer -->|Writes Data / Handles Reorgs| IndexerDB;

    Bot -->|Reads Data| IndexerDB;
    Bot -->|Reads Data / Events| Node;
    Bot -->|Builds/Signs Tx| Bot;
    Bot -->|Submits Signed Tx| Node;

    style IndexerDB fill:#ccf,stroke:#333,stroke-width:2px
    style Node fill:#f9f,stroke:#333,stroke-width:2px
    style Wallet fill:#cfc,stroke:#333,stroke-width:2px
Building robust off-chain services for Ergo dApps requires careful planning, the right tools, strong security, and extensive testing across scenarios.