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 / SDK | Fleet SDK (JS/TS) | Sigma-Rust | Appkit (Scala) |
|---|---|---|---|
| Primary Language | JS/TS | Rust | Scala (JVM) |
| Environment | Node.js, Web | Native, WASM | JVM |
| Strengths | Web integration, ease, UI/backend | Performance, WASM, low-level | Strong typing, JVM, Android |
| Weaknesses | Perf, less type safety | Steep curve, smaller web eco | Not for web front, JVM overhead |
| Ideal Use Cases | Web dApps, scripts, bots | Indexers, bots, WASM libs | Backends, 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
- Design: Define protocol, contracts, and off-chain logic.
- On-Chain Dev: Write and test ErgoScript contracts.
- Off-Chain Dev: Build backend, watcher, or bot with an SDK.
- Testing (Off-Chain): Simulate with SDK test frameworks (Fleet MockChain, Appkit Mockchain, Sigma-Rust test utils).
- Testing (Integrated): Deploy/test on Testnet with real nodes/wallets.
- 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
- SigmaUSD: Oracle and reserve management.
- Oracle Core: Oracle pools, off-chain bots.
- Ergo Explorer Backend: Full-chain indexer.
- DEX Bots (Grid Trading).
- Exle Tx Bot.
- HummingBot, KuPyBot.
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.
Resources
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