Skip to main content

Address transfer monitoring — User guide

Version: 2.0.0 | Audience: integrator developers


Table of contents


1. Product overview

Introduction

Address transfer monitoring is designed for business systems that need to track on-chain transfer activity for wallet addresses. The service continuously receives and parses multi-chain transaction data, persists normalized transfer records, and exposes the results through HTTP queries and WebSocket pushes.

The goal is to let integrators monitor selected addresses without operating their own nodes or building a full transaction parsing pipeline. Integrators only need to maintain their monitored address list and keep their delivery connection available.


Core capabilities

CapabilityDescription
Multi-chain transaction monitoringUnified ingestion for EVM-compatible chains, Solana, Tron, Bitcoin, and other supported networks.
Real-time pushPushes on-chain transactions to subscribed clients through WebSocket.
Historical queryQueries historical transactions for addresses through HTTP APIs.
Address subscription managementAdds and removes monitored addresses dynamically, with business isolation based on the public API key.
Webhook notificationDelivers transaction data to an HTTP callback configured by the service side.

Supported chains

EVM-compatible chains

Chainchain_typeMainnet chain_id
Ethereumethereum1
BNB Smart Chainbsc56
Polygonpolygon137
Arbitrum Onearbitrum42161
Optimismoptimism10
Basebase8453

Non-EVM chains

Chainchain_typeMainnet chain_id
Solanasolana1
Trontron1
Bitcoinbitcoin1

chain_type is case-insensitive in requests. The service normalizes it to lowercase.


End-to-end data flow

flowchart TD
UPSTREAM[On-chain data push<br/>Stream / Webhook]
API[Integrator system]
CL_STREAM[POST /stream<br/>POST /webhook]
PROCESSOR[Chain processors<br/>EVM / Solana / Tron / Bitcoin]
DB[(Database)]
WS_DO[SubscriptionManager<br/>Durable Object]
WS_CLIENT[WebSocket client]
WEBHOOK_OUT[Outbound Webhook callback]
CRON[Cron job<br/>Every minute]

UPSTREAM --> CL_STREAM
CL_STREAM --> PROCESSOR
PROCESSOR --> DB
PROCESSOR --> WS_DO
WS_DO -->|Real-time push| WS_CLIENT
WS_DO -->|Webhook notification| WEBHOOK_OUT
API -->|POST /api/v1/transfer| DB
API -->|GET /transaction/transaction/tx| DB
CRON -->|Compensate pending transactions| DB

Data flow notes:

  1. Configured upstream data pushes deliver raw payloads to /stream or /webhook. The service provider deploys and authorizes upstream ingestion, so ordinary integrators do not need to connect to upstream infrastructure directly.
  2. The service dispatches each payload to the corresponding chain processor by chain type.
  3. Parsed transaction data is written to the database and triggers subscription notifications.
  4. Transactions that match monitored addresses are pushed to connected WebSocket clients in real time.
  5. Integrators can query historical transactions through HTTP APIs.
  6. A cron job runs once per minute to compensate unfinished pending transactions.

Core concepts

Chain identity: chain_type and chain_id

Each chain is identified by two fields:

  • chain_type: the chain type name, such as ethereum, solana, or tron.
  • chain_id: the chain ID used to distinguish mainnets and testnets, such as 1 for Ethereum Mainnet and 11155111 for Sepolia.

These two fields are used by every API that involves chain-specific data.

Public authentication: X-API-Key

The public APIs currently exposed by the service use the X-API-Key request header for API authentication and billing identification. Address management, transaction query, and WebSocket connections should all carry this header.

X-API-Key: your-api-key

Standard response format

Most APIs follow this JSON response structure:

{
"code": 0,
"message": "ok",
"data": {}
}
FieldTypeDescription
codeintegerBusiness status code. 0 means success.
messagestringResponse message. Successful responses usually use ok.
dataanyBusiness data. Failed responses usually return null.

Some legacy APIs use msg instead of message. Follow the specific API documentation for each endpoint.

Error response format

Failed requests return a structure similar to:

{
"code": 400,
"msg": "Invalid request",
"data": null
}

Common HTTP status codes:

StatusMeaning
400Request parameter validation failed.
401Authentication failed for the stream secret.
403No permission because the API key is invalid.
504Request timed out after 15 seconds.
500Internal service error.

Authentication

All public APIs use the X-API-Key request header. The key is used for API authentication, caller identification, and billing statistics.

Public API key (X-API-Key)

All public API requests should carry:

X-API-Key: your-api-key

Internal stream secret (Content-Secret-X)

The /stream ingestion entry is used by the service provider or forwarding layer, and is not a public entry for ordinary integrators. The related secret is configured internally by the service provider.


Supported network identifiers (x-chain-net)

When calling the on-chain JSON-RPC proxy API (/api/v1/quicknode), specify the target network through the x-chain-net request header:

Networkx-chain-net value
Ethereum Mainnetethereum-mainnet
Ethereum Sepoliaethereum-sepolia
BNB Mainnetbsc-mainnet
BNB Testnetbsc-testnet
Polygon Mainnetpolygon-mainnet
Polygon Amoypolygon-amoy
Solana Mainnetsolana-mainnet
Solana Devnetsolana-devnet
Tron Mainnettron-mainnet
Bitcoin Mainnetbitcoin-mainnet
Arbitrum Mainnetarbitrum-mainnet
Optimism Mainnetoptimism-mainnet
Base Mainnetbase-mainnet

2. Quick start

This section helps you complete a basic integration in about 10 minutes, including real-time notifications and historical queries for monitored addresses.

Prerequisites

Before starting, obtain the following credential from the service provider:

CredentialPurposeExample
API keyAPI authentication, caller identification, and billing statistics.sk-xxx

Store the service base URL. All examples below use BASE_URL.

BASE_URL=https://api.gelabs.org
API_KEY=your-api-key

Typical integration flow

flowchart LR
A[Add monitored addresses] --> B[Establish WebSocket connection]
B --> C[Receive real-time transaction pushes]
A --> D[Query historical transactions]

Step 1: Add monitored addresses

Add wallet addresses that you want to monitor to your application's address list. The example below adds one Ethereum address:

curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"wallets": [
{
"chain_type": "ethereum",
"address": ["0x1234567890abcdef1234567890abcdef12345678"]
}
]
}'

Expected response:

{
"code": 0,
"message": "ok",
"data": "success"
}

You can add addresses for multiple chains in one request:

curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"wallets": [
{
"chain_type": "ethereum",
"address": ["0xabc...001", "0xabc...002"]
},
{
"chain_type": "solana",
"address": ["So1ana...address"]
}
]
}'

Step 2: Establish a WebSocket connection and receive real-time pushes

After the address is added successfully, establish a WebSocket connection to receive real-time transactions.

Quick test with wscat

# Install wscat if it is not installed yet.
npm install -g wscat

# Establish the connection.
wscat -c "$BASE_URL/transaction/transaction/ws" \
-H "X-API-Key: $API_KEY"

After the connection succeeds, the server immediately sends a confirmation message:

{
"id": "msg-xxx",
"time": 1745000000000,
"type": "connected",
"code": 0,
"data": {
"clientId": "client-abc123",
"appId": "current-business-identity"
},
"msg": "success"
}

Receive real-time transactions

When a monitored address has an on-chain transaction, the server pushes:

{
"id": "msg-tx-001",
"time": 1745000000000,
"type": "transaction",
"code": 0,
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"txhash": "0xabc123...",
"sender": "0x1234...",
"receiver": "0x5678...",
"amount": "1000000000000000000",
"symbol": "ETH",
"decimals": 18,
"txStatus": "success",
"listenAddress": ["0x1234..."],
"is_reorg_resend": false
},
"msg": "success"
}

After receiving a transaction message, send an ACK to avoid server retries:

{
"id": "ack-001",
"type": "transactionACK",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"txHash": "0xabc123..."
}
}

Step 3: Query historical transactions

In addition to real-time pushes, you can query historical transactions for an address through HTTP:

curl -X POST "$BASE_URL/api/v1/transfer" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"chain_type": "ethereum",
"chain_id": 1,
"address": "0x1234567890abcdef1234567890abcdef12345678",
"limit": 10
}'

Expected response, shortened:

{
"code": 1,
"msg": "success",
"data": [
{
"chain_type": "ethereum",
"chain_id": 1,
"tx_hash": "0xabc123...",
"sender": "0x1234...",
"receiver": "0x5678...",
"amount": "1000000000000000000",
"symbol": "ETH",
"tx_status": "success"
}
]
}

Complete integration example (Node.js)

const WebSocket = require('ws');

const BASE_URL = 'https://api.gelabs.org';
const API_KEY = 'your-api-key';
const ADDRESS = '0x1234567890abcdef1234567890abcdef12345678';

async function addAddress() {
const res = await fetch(`${BASE_URL}/api/v1/address/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
},
body: JSON.stringify({
wallets: [{ chain_type: 'ethereum', address: [ADDRESS] }],
}),
});
const data = await res.json();
console.log('Add address result:', data);
}

function connectWebSocket() {
const ws = new WebSocket(
`${BASE_URL.replace('https', 'wss')}/transaction/transaction/ws`,
{ headers: { 'X-API-Key': API_KEY } }
);

ws.on('open', () => {
console.log('WebSocket connection established');
setInterval(() => {
ws.send(JSON.stringify({ id: Date.now().toString(), type: 'ping', data: {} }));
}, 30000);
});

ws.on('message', (raw) => {
const msg = JSON.parse(raw);
switch (msg.type) {
case 'connected':
console.log('Connection confirmed, clientId:', msg.data.clientId);
break;
case 'transaction':
console.log('Transaction received:', msg.data.txhash);
ws.send(JSON.stringify({
id: `ack-${msg.id}`,
type: 'transactionACK',
data: { chain_type: msg.data.chain_type, chain_id: msg.data.chain_id, txHash: msg.data.txhash },
}));
break;
case 'pong':
break;
}
});

ws.on('close', () => {
console.log('Connection closed, reconnecting in 5 seconds...');
setTimeout(connectWebSocket, 5000);
});

ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
});
}

async function main() {
await addAddress();
connectWebSocket();
}

main();

3. Data ingestion module

The data ingestion module receives authorized raw on-chain data pushed to this service through Stream or Webhook entries, then dispatches it to the corresponding chain processor for parsing and persistence. The platform maintains the push-side network and providers. Integrators only need to use the public APIs documented here.

API list

MethodPathDescriptionAuthentication
POST/streamSigned stream ingestion entry, recommended for productionYes, stream secret
POST/webhookCompatibility Webhook entryNo
POST/api/v1/quicknodeOn-chain JSON-RPC proxyNo, target chain network header required

Data processing flow

flowchart LR
IN[Stream request] --> PARSE[Parse batch metadata<br/>Chain type / Network]
PARSE --> PROC[Chain processor\nEVM / Solana / Tron / Bitcoin]
PROC --> DB[(Database write)]
PROC --> NOTIFY[Trigger subscription notification]
NOTIFY --> WS[WebSocket push]
NOTIFY --> WH[Webhook callback]

Processing is asynchronous. After receiving a stream request, the service returns a response immediately. Actual parsing and database writes run in the background.


Stream ingestion

API path: POST /stream

Purpose

Receives authorized raw on-chain data payloads. This is one of the main production entries for on-chain data.

Authentication

Authentication required: the request must carry the stream secret header, otherwise 401 is returned.

Content-Secret-X: your-stream-secret

Chain network identification

The service identifies the source chain network through the following request headers, in priority order:

  1. cl-stream-network
  2. stream-network

Example

curl -X POST "$BASE_URL/stream" \
-H "Content-Secret-X: your-stream-secret" \
-H "cl-stream-network: ethereum-mainnet" \
-H "Content-Type: application/octet-stream" \
--data-binary @payload.bin

Response

{
"code": 0,
"message": "ok",
"data": null
}

Webhook ingestion

API path: POST /webhook

Purpose

A legacy-compatible Webhook entry with the same processing logic as /stream. Main differences from /stream:

  • No stream secret validation: the current version does not validate Content-Secret-X by default.
  • Use cases: legacy-compatible Webhooks or internal test scenarios.

Production environments should prefer /stream with secret validation to avoid unauthorized data injection.

Example

curl -X POST "$BASE_URL/webhook" \
-H "cl-stream-network: ethereum-mainnet" \
-H "Content-Type: application/octet-stream" \
--data-binary @payload.bin

Response

{
"code": 0,
"message": "ok",
"data": null
}

On-chain JSON-RPC proxy

API path: POST /api/v1/quicknode

Purpose

Proxies JSON-RPC requests to the corresponding on-chain RPC endpoint based on the selected network. It can be used to query blocks, balances, and other standard methods. The quicknode segment in the path is a historical compatibility name and is not tied to a specific provider.

Required headers

Target network required: the x-chain-net request header must be present. Otherwise, the service returns a 500 error.

x-chain-net: ethereum-mainnet

Request body fields

FieldTypeRequiredDefaultDescription
methodstringNoeth_blockNumberJSON-RPC method name.
paramsarrayNo[]JSON-RPC parameter array.

Example

# Query the latest block number.
curl -X POST "$BASE_URL/api/v1/quicknode" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "x-chain-net: ethereum-mainnet" \
-d '{"method": "eth_blockNumber", "params": []}'

# Query an account balance.
curl -X POST "$BASE_URL/api/v1/quicknode" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "x-chain-net: ethereum-mainnet" \
-d '{"method": "eth_getBalance", "params": ["0x1234...", "latest"]}'

Response

{
"code": 1,
"msg": "success",
"data": "0x14a5b8c"
}

This API returns code: 1 on success, which differs from APIs that use 0. Handle it explicitly during integration.


Supported network list

Networkx-chain-net / cl-stream-network value
Ethereum Mainnetethereum-mainnet
Ethereum Sepolia Testnetethereum-sepolia
BNB Smart Chain Mainnetbsc-mainnet
BNB Smart Chain Testnetbsc-testnet
Polygon Mainnetpolygon-mainnet
Polygon Amoy Testnetpolygon-amoy
Solana Mainnetsolana-mainnet
Solana Devnetsolana-devnet
Tron Mainnettron-mainnet
Bitcoin Mainnetbitcoin-mainnet
Arbitrum One Mainnetarbitrum-mainnet
Optimism Mainnetoptimism-mainnet
Base Mainnetbase-mainnet

4. Transaction query module

The transaction query module provides multi-dimensional queries for on-chain transaction data, including historical lists by address, batch transaction detail queries, and cross-chain aggregate queries.

API list

MethodPathDescription
POST/api/v1/transferQuery transaction list by address with cursor pagination
POST/api/v1/transfer/multipleMulti-chain and multi-address aggregate query
POST/api/v1/transfer/detailsBatch query details by transaction hash
GET/transaction/transaction/txQuery single transaction detail, V2

Address transaction query

API path: POST /api/v1/transfer

Purpose

Queries the historical transaction list for one address on a specified chain. It supports filtering by transaction type and token, and uses cursor-based pagination.

Request body fields

FieldTypeRequiredDefaultDescription
addressstringYes-Wallet address to query.
chain_typestringNo-Chain type. If omitted, the service performs a cross-chain query.
chain_idintegerNo-Chain ID.
tx_typestringNo""Transaction type filter. Empty string means no filtering.
tokenstringNo-Token contract address used to filter a specific token transfer.
limitintegerNo20Maximum number of returned records. Range: 1~100.
idintegerNo-Cursor ID for pagination continuation.

Example

# Basic query.
curl -X POST "$BASE_URL/api/v1/transfer" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"chain_type": "ethereum",
"chain_id": 1,
"address": "0x1234567890abcdef1234567890abcdef12345678",
"limit": 20
}'

# Filter by token to query USDT transfers.
curl -X POST "$BASE_URL/api/v1/transfer" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"chain_type": "ethereum",
"chain_id": 1,
"address": "0x1234...",
"token": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
}'

Response

{
"code": 1,
"msg": "success",
"data": [
{
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"tx_time": 1745000000,
"tx_hash": "0xabc123...",
"sender": "0x1234...",
"receiver": "0x5678...",
"amount": "1000000000000000000",
"symbol": "ETH",
"decimals": 18,
"tx_status": "success",
"transfer_type": "native",
"token_transfers": [],
"from_details": [],
"to_details": [],
"internal_transactions": []
}
]
}

This API returns code: 1 on success and uses msg, which differs from some other APIs.


Multi-address aggregate query

API path: POST /api/v1/transfer/multiple

Purpose

Queries transaction records for multiple chains and multiple addresses and returns the aggregated result.

Request body fields

FieldTypeRequiredDefaultDescription
addressesarrayYes-Multi-chain address groups. At least 1 group and at most 10 groups.
addresses[].chain_typestringYes-Chain type.
addresses[].chain_idintegerYes-Chain ID.
addresses[].addressstring[]Yes-Address array for this chain. At most 20 addresses per group.
limitintegerNo20Maximum returned records. Range: 1~100.
idintegerNo-Cursor ID for pagination continuation.

Example

curl -X POST "$BASE_URL/api/v1/transfer/multiple" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"addresses": [
{"chain_type": "ethereum", "chain_id": 1, "address": ["0xabc...001", "0xabc...002"]},
{"chain_type": "bsc", "chain_id": 56, "address": ["0xdef...003"]},
{"chain_type": "solana", "chain_id": 1, "address": ["SolanaAddress..."]}
],
"limit": 20
}'

Response

The response format is the same as /api/v1/transfer. data is the aggregated transaction record array.


Batch transaction detail query

API path: POST /api/v1/transfer/details

Purpose

Batch queries transaction details by transaction hash.

Request body fields

FieldTypeRequiredDescription
chain_typestringYesChain type.
chain_idintegerYesChain ID.
tx_hashstring or string[]YesA single transaction hash or an array of hashes.

Example

# Query one transaction.
curl -X POST "$BASE_URL/api/v1/transfer/details" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{"chain_type": "ethereum", "chain_id": 1, "tx_hash": "0xabc123..."}'

# Batch query multiple transactions.
curl -X POST "$BASE_URL/api/v1/transfer/details" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{"chain_type": "ethereum", "chain_id": 1, "tx_hash": ["0xabc123...", "0xdef456..."]}'

Single transaction detail query

API path: GET /transaction/transaction/tx

Purpose

Queries the full details of one transaction, V2. Main differences from /api/v1/transfer/details:

  • Uses GET with query string parameters.
  • Supports no_cache to force an origin fetch. If there is no local database record, the service can fetch by chain and write the result into cache.
  • Writes the data into the database cache after a successful origin fetch.
  • Returns a mixed field naming structure for historical compatibility. Some fields are camelCase.

Request parameters

NameLocationTypeRequiredDescription
chain_typequerystringYesChain type.
chain_idqueryintegerYesChain ID.
tx_hashquerystringYesTransaction hash, with or without the 0x prefix.
no_cachequerystringNoPass true or 1 to skip local cache and fetch from origin.

Example

# Normal query.
curl "$BASE_URL/transaction/transaction/tx?chain_type=ethereum&chain_id=1&tx_hash=0xabc123..."

# Force origin fetch.
curl "$BASE_URL/transaction/transaction/tx?chain_type=ethereum&chain_id=1&tx_hash=0xabc123...&no_cache=true"

Response when the transaction is found

{
"code": 0,
"msg": "success",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"txTime": 1745000000000,
"txhash": "0xabc123...",
"sender": "0x1234...",
"fee_payer": "0x1234...",
"receiver": "0x5678...",
"transfer_type": "native",
"gasLimit": "21000",
"gasUsed": "21000",
"gasPrice": "20000000000",
"max_fee_per_gas": "30000000000",
"max_priority_fee_per_gas": "1000000000",
"txFee": "420000000000000",
"nonce": "42",
"symbol": "ETH",
"decimals": 18,
"amount": "1000000000000000000",
"txStatus": "success",
"methodId": "",
"methodCall": "",
"l1OriginHash": "",
"fromDetails": [],
"toDetails": [],
"internalTransactionDetails": [],
"tokenTransferDetails": [],
"listenAddress": null,
"is_reorg_resend": false
}
}

Response when the transaction does not exist

{
"code": 0,
"msg": "success",
"data": null
}

Transaction data structure

TransactionDetail returned by list queries

The /api/v1/transfer, /api/v1/transfer/multiple, and /api/v1/transfer/details APIs return this transaction format.

Main fields:

FieldTypeDescription
chain_typestringNormalized lowercase chain type.
chain_idintegerChain ID.
heightinteger | nullBlock height.
tx_timeinteger | nullTransaction time as a second-level Unix timestamp.
tx_hashstringTransaction hash.
tx_versioninteger | nullTransaction version, used by chains such as Solana.
senderstring | nullSender address.
fee_payerstring | nullFee payer address, mainly for Solana.
receiverstring | nullReceiver address.
transfer_typestring | nullTransaction type, such as native or token.
amountstring | nullTransaction amount as a string to avoid precision loss.
symbolstring | nullNative currency or token symbol, such as ETH or BNB.
decimalsinteger | nullDecimal places, such as 18.
tx_statusstring | nullTransaction status: success, failed, or pending.
tx_feestring | nullTransaction fee.
gas_limitstring | nullGas limit for EVM chains.
gas_usedstring | nullActual gas used for EVM chains.
gas_pricestring | nullGas price for EVM chains.
max_fee_per_gasstring | nullEIP-1559 max fee per gas.
max_priority_fee_per_gasstring | nullEIP-1559 max priority fee per gas.
noncestring | nullAccount nonce for EVM chains.
method_idstring | nullContract method selector for EVM calls.
method_callstring | nullContract method name or call summary.
l1_origin_hashstring | nullOriginal L1 transaction hash for L2 chains.
token_transfersarrayToken transfer details.
from_detailsarrayInput address details for UTXO-style data.
to_detailsarrayOutput address details for UTXO-style data.
internal_transactionsarrayInternal transactions or Solana instruction details.

token_transfers:

FieldTypeDescription
from_addressstring | nullToken source address.
to_addressstring | nullToken destination address.
token_contract_addressstring | nullToken contract address.
symbolstring | nullToken symbol.
amountstring | nullTransfer amount.
decimalsinteger | nullToken decimals.
is_from_contractbooleanWhether the source address is a contract.
is_to_contractbooleanWhether the destination address is a contract.
from_token_addressstring | nullSource token account address for Solana.
to_token_addressstring | nullDestination token account address for Solana.
mintstring | nullSolana mint address.
program_idstring | nullSolana program ID.

from_details, input details for UTXO-style data:

FieldTypeDescription
addressstring | nullInput address.
amountstring | nullInput amount.
is_contractbooleanWhether the address is a contract.
vin_indexstring | nullUTXO input index.
pre_vout_indexstring | nullPrevious transaction output index.
ref_tx_hashstring | nullReferenced previous transaction hash.

to_details, output details for UTXO-style data:

FieldTypeDescription
addressstring | nullOutput address.
amountstring | nullOutput amount.
is_contractbooleanWhether the address is a contract.
vout_indexstring | nullUTXO output index.

internal_transactions, internal calls or Solana instructions:

FieldTypeDescription
from_addressstring | nullInternal call sender.
to_addressstring | nullInternal call receiver.
amountstring | nullInternal call amount.
tx_statusstring | nullInternal call status.
program_idstring | nullSolana program ID.
instr_indexinteger | nullSolana instruction index.
depthinteger | nullSolana call depth.
instructionstring | nullSolana instruction name.
descriptionstring | nullInstruction description.

V2 field naming differences

The /transaction/transaction/tx API returns a mixed naming style for historical compatibility:

V2 fieldMatching list fieldMain difference
txhashtx_hashDifferent field name.
txTimetx_timecamelCase and millisecond timestamp.
txStatustx_statuscamelCase.
txFeetx_feecamelCase.
gasLimitgas_limitcamelCase.
gasUsedgas_usedcamelCase.
gasPricegas_pricecamelCase.
methodIdmethod_idcamelCase.
methodCallmethod_callcamelCase.
l1OriginHashl1_origin_hashcamelCase.
fromDetailsfrom_detailscamelCase.
toDetailsto_detailscamelCase.
internalTransactionDetailsinternal_transactionsDifferent field name.
tokenTransferDetailstoken_transfersDifferent field name.

Pagination

Transaction list queries use cursor pagination instead of page numbers:

  1. Do not pass id in the first request. The service returns the latest N records.
  2. If the returned record count equals limit, there may be more data.
  3. Use the database internal id of the last returned record as the next request's id.
  4. Continue until the returned record count is smaller than limit.

Cursor pagination does not support jumping to a page. It can only move forward sequentially.


Amount handling recommendations

All amount fields are returned as strings to avoid large-number precision loss. Convert display values with the decimals field:

function formatAmount(amount, decimals) {
if (!amount || decimals === null) return '0';
const bigAmount = BigInt(amount);
const divisor = BigInt(10 ** decimals);
const intPart = bigAmount / divisor;
const fracPart = bigAmount % divisor;
return `${intPart}.${fracPart.toString().padStart(decimals, '0')}`;
}

// Example: 1000000000000000000 with decimals=18 becomes "1.000000000000000000".
console.log(formatAmount('1000000000000000000', 18));

5. Address management module

The address management module maintains the monitored address list for each business integration. Only addresses added to the list trigger transaction pushes to the corresponding WebSocket subscribers or Webhook callback.

API list

MethodPathDescription
POST/api/v1/addressPatch mode for adding and removing addresses in one request
POST/api/v1/address/addBatch add addresses
POST/api/v1/address/removeBatch remove addresses
POST/api/v1/address/containsQuery whether an address exists in the list

Public authentication notes

All protected address APIs identify the caller through X-API-Key. The service uses this key for API authentication, business ownership identification, and billing statistics.

MethodDescriptionExample
Request headerPublic API keyX-API-Key: your-api-key

Public access only requires X-API-Key. No additional application identity header or query parameter is required.


Add monitored addresses API

API path: POST /api/v1/address/add

Purpose

Batch adds addresses to the monitored address list for the current business identity. Multiple chains can be added in one request.

Chain-specific behavior

  • Tron: Tron-format addresses starting with T are automatically converted to equivalent EVM-format 0x... addresses for storage.
  • Other non-EVM chains: the service attempts to sync addresses to an internal platform monitoring component. Sync failures do not block local database writes.

Request body fields

FieldTypeRequiredDescription
walletsarrayYesAddress list grouped by chain. At least one item is required.
wallets[].chain_typestringYesChain type, such as ethereum, solana, or tron.
wallets[].addressstring[]YesAddress array for the chain. At least one address is required.

Example

# Add Ethereum addresses.
curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"wallets": [
{
"chain_type": "ethereum",
"address": ["0x1234...", "0xabcd..."]
}
]
}'

# Add addresses for multiple chains.
curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"wallets": [
{"chain_type": "ethereum", "address": ["0x1234..."]},
{"chain_type": "tron", "address": ["TJDENsfBJs4RFETt1X1uyT9pBxdnAbFnbm"]},
{"chain_type": "solana", "address": ["SolanaAddress..."]}
]
}'

Response

{
"code": 0,
"message": "ok",
"data": "success"
}

Remove monitored addresses

API path: POST /api/v1/address/remove

Purpose

Batch removes addresses from the monitored address list. After removal, new transactions for the address are no longer pushed to the current business identity.

Request body fields

Same as /api/v1/address/add: wallets[].chain_type plus wallets[].address.

Example

curl -X POST "$BASE_URL/api/v1/address/remove" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"wallets": [
{"chain_type": "ethereum", "address": ["0x1234..."]}
]
}'

Response

{
"code": 0,
"message": "ok",
"data": "success"
}

Patch address list

API path: POST /api/v1/address

Purpose

Uses patch mode to add and remove addresses for one chain in a single request.

Request body fields

FieldTypeRequiredDefaultDescription
chain_typestringYes-Chain type. This API operates on one chain at a time.
addsstring[]No[]Addresses to add to the list.
removesstring[]No[]Addresses to remove from the list.

Example

curl -X POST "$BASE_URL/api/v1/address" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"chain_type": "ethereum",
"adds": ["0xNewAddress001...", "0xNewAddress002..."],
"removes": ["0xOldAddress001..."]
}'

Response

{
"code": 0,
"message": "ok",
"data": "success"
}

Address existence query

API path: POST /api/v1/address/contains

Purpose

Queries whether an address already exists in the address list for a specified chain.

In the current version, this API only checks whether the address exists in the global address table. It does not filter by API key isolation.

Request body fields

FieldTypeRequiredDescription
chain_typestringYesChain type.
addressstringYesAddress to query.

Example

curl -X POST "$BASE_URL/api/v1/address/contains" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"chain_type": "ethereum", "address": "0x1234..."}'

Response

{
"code": 0,
"message": "ok",
"data": {
"is_exist": true
}
}

Address management best practices

Address format guidelines:

  • EVM chains: use hex addresses starting with 0x. EIP-55 checksum format is recommended.
  • Solana: use Base58 addresses.
  • Tron: use Base58Check addresses starting with T; the service automatically converts them to 0x format for storage.
  • Bitcoin: use standard Bitcoin address formats, such as P2PKH, P2SH, and Bech32.

Relationship between the address list and WebSocket subscriptions:

  • Addresses added through /api/v1/address/add are persisted in the database and remain effective after service restarts.
  • After a WebSocket connection is established, the service automatically monitors all addresses in the business identity's address list.
  • You can also use the WebSocket subscribe command to add temporary monitoring after connection establishment, but this is only effective for the current connection and is not restored after disconnection.

For regular business monitoring, use HTTP APIs to manage the address list. Use WebSocket subscribe and unsubscribe only for temporary fine-grained control.


6. WebSocket real-time push

The WebSocket module provides real-time on-chain transaction push. After a client establishes a WebSocket connection, the service immediately pushes messages when monitored addresses produce on-chain transactions.

Connection endpoint:

GET /transaction/transaction/ws

Establishing a connection

Request notes

The WebSocket handshake requires these request headers:

HeaderTypeRequiredDescription
UpgradestringYesFixed value: websocket.
X-API-KeystringYesPublic API key for authentication and billing identification.
X-ResendDurationstringNoHistorical message resend window in seconds. Default: 180.

If X-API-Key is missing, the connection request returns 400 or an authentication failure.

Connection example

# Use wscat.
wscat -c "wss://api.gelabs.org/transaction/transaction/ws" \
-H "X-API-Key: your-api-key"

Successful connection message

After the connection is established, the server immediately pushes:

{
"id": "msg-uuid-001",
"time": 1745000000000,
"type": "connected",
"code": 0,
"data": {
"clientId": "client-abc123",
"appId": "current-business-identity"
},
"msg": "success"
}
FieldDescription
data.clientIdUnique server-side identifier of the current connection. It changes after reconnecting.
data.appIdCaller identity bound to the current connection.

Connection failure

HTTP statusReasonRecommendation
400API key not provided.Confirm that X-API-Key is passed correctly.
426Upgrade: websocket header is missing.Confirm that the client connects through the WebSocket protocol.

Message format

All WebSocket messages use JSON and follow this envelope:

{
"id": "message unique ID",
"time": 1745000000000,
"type": "message type",
"code": 0,
"data": {},
"msg": "success"
}
FieldTypeDescription
idstringUnique message ID generated by the sender.
timeintegerMessage timestamp as a millisecond Unix timestamp.
typestringMessage type that determines business meaning.
codeintegerBusiness status code. 0 means success and non-zero means error.
dataobject | nullBusiness data.
msgstringText message. Successful responses usually use success.

Client commands

Clients can send the commands below to the server. Each command needs a unique id, and the server response uses the same id.

ping (heartbeat)

Sends a heartbeat to keep the connection active.

Send:

{
"id": "ping-001",
"type": "ping",
"data": {}
}

Server response:

{
"id": "ping-001",
"time": 1745000000000,
"type": "pong",
"code": 0,
"data": null,
"msg": "success"
}

subscribe (subscribe to an address)

Subscribes to real-time transaction pushes for a specified address on a specified chain.

Addresses subscribed through this command are only effective for the current connection and are not saved after disconnection. For persistent address subscription, add the address through the address management module in advance.

Send:

{
"id": "cmd-001",
"type": "subscribe",
"data": {
"address": "0x1234567890abcdef1234567890abcdef12345678",
"chain_type": "ethereum"
}
}

Command parameters:

FieldTypeRequiredDescription
data.addressstringYesAddress to subscribe.
data.chain_typestringYesChain type. The server converts it to lowercase.

Success response:

{
"id": "cmd-001",
"type": "subscribe",
"code": 0,
"data": {"API Key": "current-business-identity", "address": "0x1234...", "chain_type": "ethereum"},
"msg": "success"
}

Error response:

{
"id": "cmd-001",
"type": "subscribe",
"code": 400,
"data": null,
"msg": "address and chain_type required"
}

unsubscribe (unsubscribe)

Cancels the real-time transaction subscription for an address.

Send for a specific chain:

{
"id": "cmd-002",
"type": "unsubscribe",
"data": {
"address": "0x1234...",
"chain_type": "ethereum"
}
}

Send for all chains by omitting chain_type:

{
"id": "cmd-002",
"type": "unsubscribe",
"data": {
"address": "0x1234..."
}
}

Command parameters:

FieldTypeRequiredDescription
data.addressstringYesAddress to unsubscribe.
data.chain_typestringNoChain type. If omitted, all chain subscriptions for this address are canceled.

Success response:

{
"id": "cmd-002",
"type": "unsubscribe",
"code": 0,
"data": {"address": "0x1234...", "chainType": "ethereum"},
"msg": "success"
}

When all chains are canceled, chainType is null.


getTxHash (query transaction details)

Queries the full details of one transaction through the WebSocket connection.

Send:

{
"id": "cmd-003",
"type": "getTxHash",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"txHash": "0xabc123..."
}
}

Command parameters:

FieldTypeRequiredDescription
data.chain_typestringYesChain type.
data.chain_idintegerYesChain ID.
data.txHashstringNoTransaction hash.
data.messageIdstringNoUnique ID of the real-time push message. Prefer this field when available.

Prefer messageId, the id value of the push message, so the server can locate the record more accurately.

Error response when the transaction does not exist:

{
"id": "cmd-003",
"type": "getTxHash",
"code": 404,
"data": null,
"msg": "transaction not found"
}

transactionACK (confirm processed transaction)

After receiving and processing a transaction message, the client should send an ACK. After receiving the ACK, the server stops retrying that transaction.

If the client does not send ACK for a long time, the server may resend unacknowledged transactions after the client reconnects to prevent message loss.

Send:

{
"id": "ack-001",
"type": "transactionACK",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"txHash": "0xabc123..."
}
}

Success response:

{
"id": "ack-001",
"type": "transactionACK",
"code": 0,
"data": {"txHash": "0xabc123..."},
"msg": "success"
}

Server push messages

transaction (real-time transaction push)

When a monitored address has an on-chain transaction, the server pushes:

{
"id": "msg-tx-uuid-001",
"time": 1745000000000,
"type": "transaction",
"code": 0,
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"txTime": 1745000000000,
"txhash": "0xabc123...",
"sender": "0x1234...",
"fee_payer": "0x1234...",
"receiver": "0x5678...",
"transfer_type": "native",
"gasLimit": "21000",
"gasUsed": "21000",
"gasPrice": "20000000000",
"max_fee_per_gas": "30000000000",
"max_priority_fee_per_gas": "1000000000",
"txFee": "420000000000000",
"nonce": "42",
"symbol": "ETH",
"decimals": 18,
"amount": "1000000000000000000",
"txStatus": "success",
"methodId": "",
"methodCall": "",
"fromDetails": [],
"toDetails": [],
"internalTransactionDetails": null,
"tokenTransferDetails": null,
"listenAddress": ["0x1234..."],
"is_reorg_resend": false
},
"msg": "success"
}

Key transaction message fields:

FieldTypeDescription
data.listenAddressstring[]Addresses that matched monitoring rules. One transaction may match multiple addresses.
data.is_reorg_resendbooleanWhether this is a resend after a chain reorganization. If true, check local records.
data.tokenTransferDetailsarray | nullToken transfer details. Present for ERC-20 transfers.
data.txTimeintegerTransaction timestamp in milliseconds.

is_reorg_resend notes:

When the value is true, the transaction is resent because of a chain reorganization. Check whether the local record for the same txhash already exists and update fields such as block height when needed.

ERC-20 token transfer message example:

{
"id": "msg-tx-002",
"type": "transaction",
"code": 0,
"data": {
"chain_type": "ethereum",
"txhash": "0xdef456...",
"transfer_type": "token",
"tokenTransferDetails": [
{
"from": "0x1234...",
"to": "0x5678...",
"isFromContract": false,
"isToContract": false,
"tokenContractAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"amount": "1000000000",
"decimals": 6,
"fromTokenAddress": "",
"toTokenAddress": "",
"mint": "",
"programId": ""
}
],
"listenAddress": ["0x1234..."],
"is_reorg_resend": false
},
"msg": "success"
}

HTTP helper APIs

WebSocket connection status query

API path: GET /transaction/transaction/ws/status

Queries the WebSocket connection status for the current business identity.

curl "$BASE_URL/transaction/transaction/ws/status" \
-H "X-API-Key: your-api-key"

The caller identity is identified by X-API-Key. Public access does not require an additional application identity query parameter.

Response:

{
"code": 0,
"message": "ok",
"data": {
"appId": "current-business-identity",
"connectedClients": 2,
"clients": ["client-abc123", "client-def456"],
"details": [
{
"clientId": "client-abc123",
"connectedAt": 1745000000000,
"uptime": 3600,
"pendingTxCount": 0
}
]
}
}
FieldTypeDescription
connectedClientsintegerNumber of online clients.
details[].uptimeintegerLifetime in seconds.
details[].pendingTxCountintegerNumber of pending transactions that have not received ACK.

WebSocket message replay

API path: POST /transaction/transaction/ws/replay

Manually replays a missed transaction message. The server will:

  1. Idempotently write the message into the database.
  2. Push it to currently online WebSocket subscribers.
  3. Trigger Webhook callbacks in parallel.

The request body is a complete WsTransactionMessage, the same format as the server-pushed transaction message.

Response:

{
"code": 0,
"message": "ok",
"data": {
"processed": true,
"txhash": "0xabc123..."
}
}

Error handling

WebSocket command errors use the same JSON envelope and set code to a non-zero value:

{
"id": "original command id",
"type": "command type",
"code": 400,
"data": null,
"msg": "error description"
}

Common errors:

Commandcodemsg
subscribe400address and chain_type required
unsubscribe400address required
getTxHash400chain_type, chain_id, txHash required
getTxHash404transaction not found
transactionACK400chain_type and messageId/txHash required

Best practices

Heartbeat

Send a ping command every 30 seconds:

setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ id: `ping-${Date.now()}`, type: 'ping', data: {} }));
}
}, 30000);

Reconnect after disconnection

function connect() {
const ws = new WebSocket(WS_URL, { headers: { 'X-API-Key': API_KEY } });
ws.on('close', () => {
console.log('Connection closed, reconnecting in 5 seconds...');
setTimeout(connect, 5000);
});
return ws;
}

ACK strategy

  • ACK promptly: after receiving and processing a transaction message, send transactionACK immediately.
  • Avoid missing ACK: if business processing is slow, consider sending ACK first and processing asynchronously.
  • Deduplicate ACKs: use txhash + chain_type + chain_id as the business idempotency key.

Address subscription strategy

MethodPersistenceUse case
HTTP address management (/api/v1/address/add)Persistent, effective after restartLong-term monitoring of regular business addresses
WebSocket subscribe commandTemporary, only effective for the current connectionTemporary debugging or dynamic fine-grained control

Concurrent connections

The business identity represented by the same API key supports multiple clients connected at the same time. The server broadcasts transaction messages to all online connections. This is suitable for horizontal scaling or active-standby switching, as long as the business layer handles messages idempotently.


Complete integration example

const WebSocket = require('ws');

const WS_URL = 'wss://api.gelabs.org/transaction/transaction/ws';
const API_KEY = 'your-api-key';

const processedTxHashes = new Set(); // Idempotency tracking.

function connect() {
const ws = new WebSocket(WS_URL, {
headers: { 'X-API-Key': API_KEY }
});

let pingInterval;

ws.on('open', () => {
console.log('WebSocket connection established');
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ id: `ping-${Date.now()}`, type: 'ping', data: {} }));
}
}, 30000);
});

ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString());

switch (msg.type) {
case 'connected':
console.log(`Connection confirmed: appId=${msg.data.appId}, clientId=${msg.data.clientId}`);
break;

case 'pong':
break;

case 'transaction': {
if (msg.code !== 0) {
console.error('Error message received:', msg.msg);
break;
}
const tx = msg.data;
const txKey = `${tx.chain_type}-${tx.chain_id}-${tx.txhash}`;

if (!processedTxHashes.has(txKey)) {
processedTxHashes.add(txKey);
console.log(`New transaction: chain=${tx.chain_type}, hash=${tx.txhash}, amount=${tx.amount} ${tx.symbol}`);

if (tx.is_reorg_resend) {
console.warn(`Reorg resend: ${tx.txhash}. Check whether local records need updates.`);
}

if (tx.tokenTransferDetails && tx.tokenTransferDetails.length > 0) {
tx.tokenTransferDetails.forEach((t) => {
console.log(` Token transfer: ${t.amount} ${t.symbol} from ${t.from} to ${t.to}`);
});
}
}

// Send ACK after receiving the transaction.
ws.send(JSON.stringify({
id: `ack-${msg.id}`,
type: 'transactionACK',
data: { chain_type: tx.chain_type, chain_id: tx.chain_id, txHash: tx.txhash },
}));
break;
}

default:
console.log(`Unknown message type: ${msg.type}`);
}
});

ws.on('close', (code) => {
clearInterval(pingInterval);
console.log(`Connection closed (code=${code}), reconnecting in 5 seconds...`);
setTimeout(connect, 5000);
});

ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
});
}

connect();