Relayer
dataprism-relayer is a continuously running Node.js service that submits user
meta-transactions on-chain. It polls the API for pending requests, checks
each one for profitability, submits it to DataPrismCloudForwarder, and reports the result
back to the API.
Tech stack
- Language: TypeScript
5.8· Runtime: Node.js - Key dependencies: viem
2.28(chain interaction), nodemailer (email alerts), tslog (logging), pm2 (process management)
Project structure
src/
├── relayer/
│ ├── runner.ts # Entry point: init executor + notifier, start the loop
│ ├── poller.ts # Main polling loop
│ ├── executor.ts # Simulates and submits transactions (retries, gas bumps)
│ ├── profitability.ts # Profitability check via Uniswap V4 price
│ └── gas.ts # Standalone ETH balance monitor
├── api/client.ts # API client (auth + request management)
├── config.ts # Environment variable parsing
├── notification.ts # Email + Telegram notifications
└── abi/DataPrismCloudForwarder.tsPolling loop (poller.ts)
Every POLLING_INTERVAL ms (default 5 s):
- Fetch pending requests via
getPendingRequests()(GET /requests?status=pending). - For each request:
- Atomic claim (
PATCH /requests/{id}→processing), skip if already taken. - Profitability check: skip if not profitable.
- Execution via
executor.execute(req). - Report success (with
txHash) or failure (with the error message) to the API.
- Atomic claim (
Errors are caught per-request, so one failed request never halts the loop.
Profitability check (profitability.ts)
Optional, disabled when UNISWAP_POOL_MANAGER is empty. It compares the relayerFee (in
DPC) against the estimated gas cost:
gasCostInFeeToken = estimateGas × maxFeePerGas × ETH_price_in_DPC
profitable = relayerFee ≥ gasCostInFeeTokenThe ETH/DPC price is read from a configurable Uniswap V4 pool (getSlot0 → sqrtPriceX96),
via a separate price RPC endpoint (PRICE_RPC_URL_0).
On-chain execution (executor.ts)
Calls the *For functions on DataPrismCloudForwarder via viem. For each request:
- Simulation (
simulateContract), a failure throws a non-retriableSimulationError(the contract would revert). - Submission (
writeContract). - Wait for
CONFIRMATIONSconfirmations (with a timeout).
On a network failure (not a simulation error), the relayer retries with progressively
higher gas (INCREASE_GAS_PERCENT × attempt) up to MAX_RETRIES, pausing if fees exceed
MAX_FEE_PER_GAS. Multiple RPCs are supported in fallback mode (RPC_URL_0, RPC_URL_1).
Supported actions: CREATE_PRISM_FOR, WRITE_DATA_FOR, MODIFY_DATA_FOR, DELETE_DATA_FOR,
TRANSFER_PRISM_FOR, ACCEPT_TRANSFER_FOR, DELETE_PRISM_FOR, BATCH_WRITE_DATA_FOR,
BATCH_MODIFY_DATA_FOR, BATCH_DELETE_DATA_FOR, BATCH_MULTI_USER_WRITE_DATA.
For CREATE_PRISM_FOR the executor forwards the request's permit field as the
PermitParams tuple (deadline, v, r, s) argument of createPrismFor (a zero tuple when the
prism is unfunded), so the Forwarder can run token.permit(...) and fund the prism with no
prior approve.
Gas monitor (gas.ts)
A separate PM2 process checks the relayer's ETH balance every GAS_POLLING_INTERVAL ms
(default 3 min) and sends a notification when it drops below GAS_MIN_BALANCE (default 1 ETH).
Configuration
The relayer reads a .env file. The account must be whitelisted in the API before it can
relay.
# Identity & contracts
RELAYER_PRIVATE_KEY=""
DATAPRISM_FORWARDER_ADDRESS="" # DataPrismCloudForwarder
# API
API_BASE_URL=""
CHAIN_NAME="" # optional, filters requests by chain
# RPC (with fallback)
RPC_URL_0=""
RPC_URL_1=""
# Polling & gas
POLLING_INTERVAL="5000"
MAX_FEE_PER_GAS="100000000000" # 100 Gwei
MAX_PRIORITY_FEE_PER_GAS="100000000000"
INCREASE_GAS_PERCENT="20" # gas bump % per retry
MAX_RETRIES="50"
RETRY_DELAY="5000"
CONFIRMATIONS="2"
CONFIRMATIONS_TIMEOUT="30000"
GAS_MIN_BALANCE="1000000000000000000" # 1 ETH (triggers alert)
GAS_POLLING_INTERVAL="180000" # balance check every 3 min
# Profitability via Uniswap V4 (leave empty to disable)
UNISWAP_POOL_MANAGER=""
UNISWAP_CURRENCY0="0x0000000000000000000000000000000000000000" # ETH
UNISWAP_CURRENCY1="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" # USDC (mainnet)
UNISWAP_FEE="500"
UNISWAP_TICK_SPACING="10"
UNISWAP_HOOKS="0x0000000000000000000000000000000000000000"
PRICE_RPC_URL_0="" # mainnet RPC for price reading
PRICE_RPC_URL_1=""
# Notifications (optional)
SMTP_HOST="" SMTP_PORT="465" SMTP_USER="" SMTP_PASS="" SMTP_FROM="" SMTP_TO=""
TELEGRAM_BOT_TOKEN="" TELEGRAM_CHAT_ID=""Install, run, deploy
npm install # Install dependencies
npm run typecheck # tsc --noEmit
npm run dev # Build + run runner.js directly
npm run build # Compile to dist/
npm run deploy # Build + start relayer & gas monitor via PM2
npm run list # pm2 listPM2 (ecosystem.config.js) runs two processes: DataPrism Relayer (runner.js) and
DataPrism Gas Monitor (gas.js). Deploy multiple instances (each with its own key) for
redundancy, the atomic claim makes this safe.
API authentication
The relayer authenticates to the API with an ECDSA signature over the request headers
(X-Relayer-Address, X-Relayer-Signature, X-Relayer-Timestamp). See
API → Authentication for the message format.

