DEV Community

Neil Yan
Neil Yan

Posted on

Build Your Own Crypto Payment Gateway with XPayLabs (xpay): Architecture, SDKs, and Production Deployment

Build Your Own Crypto Payment Gateway with XPayLabs (xpay): Architecture, SDKs, and Production Deployment

Accepting crypto payments doesn't have to mean paying 1% to BitPay or trusting Coinbase Commerce with your private keys. With modern tools, you can self-host a production-grade gateway that supports USDT, USDC, DAI, and BUSD across TRON, Ethereum, BNB Chain, Polygon, Arbitrum, Optimism, Avalanche, Base, and SUI — with zero transaction fees.

This article breaks down the architecture, SDK integration patterns, and production deployment of XPayLabs (often shortened to xpay), a self-hosted gateway with open-source SDKs and tools.


Architecture Overview

┌─────────────────────────────────────────┐
│           Docker Compose Stack          │
│  ┌────────┐  ┌────────┐  ┌──────────┐  │
│  │ MySQL  │  │ Redis  │  │ Gateway  │  │
│  │        │  │        │  │ (Java 17 │  │
│  │        │  │        │  │ Spring)  │  │
│  └────────┘  └────────┘  └────┬─────┘  │
│                                │        │
│  ┌─────────────────────────────┘        │
│  │  ┌──────────────┐ ┌──────────────┐  │
│  │  │  Scanner:    │ │  Scanner:    │  │
│  │  │  TRON        │ │  EVM Chains  │  │
│  │  └──────────────┘ └──────────────┘  │
│  │  ┌──────────────┐ ┌──────────────┐  │
│  │  │  Scanner:    │ │  Webhook     │  │
│  │  │  SUI         │ │  Dispatcher  │  │
│  │  └──────────────┘ └──────────────┘  │
└─────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The gateway runs as multiple services in a Docker Compose stack:

  • Gateway API (Spring Boot 3.4+): REST API for order creation, status queries, and balance checks
  • Blockchain Scanners: Concurrent per-chain workers that monitor mempool and blocks
  • Webhook Dispatcher: Queue-backed delivery with exponential backoff
  • MySQL: Persistent storage for orders, addresses, and transactions
  • Redis: Caching, job queues, and nonce deduplication

1. HD Wallet Key Management

The gateway uses BIP-39 + BIP-44 hierarchical deterministic wallet derivation. One master seed generates every deposit address.

How It Works

// Conceptual example — HD wallet derivation
import { generateMnemonic, mnemonicToSeedSync } from 'bip39';
import { HDKey } from 'micro-bip32';

// Step 1: Generate or load a mnemonic (12 words)
const mnemonic = generateMnemonic(); // or load from env

// Step 2: Derive master key from seed
const seed = mnemonicToSeedSync(mnemonic);
const masterKey = HDKey.fromMasterSeed(seed);

// Step 3: Derive child key for a specific chain + index
// Path: m / 44' / coin_type' / 0' / 0 / address_index
// TRON coin_type = 195, EVM = 60, SUI = 9006
const childKey = masterKey.derive(`m/44'/195'/0'/0/${addressIndex}`);
const depositAddress = childKey.publicKey; // → TXYZ1234...
Enter fullscreen mode Exit fullscreen mode

Security Model

  • The master seed is injected via XPAY_MASTER_SEED environment variable — never stored in the database
  • Only derivation paths (integers) are stored in MySQL
  • If the database is compromised, an attacker sees which addresses belong to you but cannot derive private keys
  • The seed should be generated offline and backed up physically

2. Multi-Chain Transaction Detection

Each blockchain scanner runs as an independent worker with chain-specific logic:

// ChainAdapter interface — implemented per blockchain
interface ChainAdapter {
  generateAddress(derivationPath: string): string;
  scanTransactions(
    addresses: string[],
    fromBlock: number,
    toBlock: number
  ): Promise<DetectedTransaction[]>;
  getConfirmationCount(txHash: string): Promise<number>;
  getLatestBlock(): Promise<number>;
}
Enter fullscreen mode Exit fullscreen mode

TRON (TRC20) Scanner

TRON accounts for the majority of stablecoin volume globally. The scanner:

  1. Polls TRON Grid API or a local node every 3 seconds
  2. Uses getTransactionsToThis for tracked addresses
  3. Filters by USDT/USDC contract address
  4. Matches amounts with pending invoices (with small tolerance for gas deductions)
// TRON scanner pseudocode
async function scanTron(pendingAddresses: string[]) {
  const txs = await tronGrid.getTransactionsToThis(pendingAddresses, {
    onlyConfirmed: false, // mempool detection
    minTimestamp: Date.now() - 120_000 // last 2 minutes
  });

  for (const tx of txs) {
    if (tx.contractType === 'Transfer' &&
        tx.contractData.contractAddress === USDT_CONTRACT) {
      await processDetection('tron', tx);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

EVM Scanner (Ethereum, BSC, Polygon, Arbitrum, etc.)

For EVM chains, we use eth_getLogs with the Transfer event signature:

event Transfer(address indexed from, address indexed to, uint256 value);
Enter fullscreen mode Exit fullscreen mode
// EVM scanner pseudocode
async function scanEvm(chain: string, rpc: string, pendingAddresses: string[]) {
  const latestBlock = await getBlockNumber(rpc);
  const fromBlock = latestBlock - 200; // look back 200 blocks

  const logs = await ethGetLogs(rpc, {
    fromBlock: toHex(fromBlock),
    toBlock: toHex(latestBlock),
    address: USDT_CONTRACTS[chain],
    topics: [TRANSFER_EVENT_SIG, null, pendingAddresses.map(toPaddedHex)]
  });

  for (const log of logs) {
    const tx = decodeTransferLog(log);
    await processDetection(chain, tx);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. SDK Integration Patterns

XPayLabs (xpay) provides first-party SDKs in Node.js/TypeScript and Java with identical API surfaces.

Node.js SDK Installation

npm install @xpaylabs/node-sdk
Enter fullscreen mode Exit fullscreen mode
import { XPay } from '@xpaylabs/node-sdk';

const xpay = new XPay({
  apiKey: process.env.XPAY_API_KEY,
  apiSecret: process.env.XPAY_API_SECRET,
  baseUrl: 'https://payments.example.com'
});
Enter fullscreen mode Exit fullscreen mode

Create a Collection (Receive Payment)

const collection = await xpay.createCollection({
  amount: '100.00',
  symbol: 'USDT',
  chain: 'tron',
  orderId: 'order_20260601_001',
});


// collection.address: deposit address for customer
// collection.orderId: unique gateway order ID
// collection.expireAt: ISO timestamp after which the order expires
Enter fullscreen mode Exit fullscreen mode

Create a Payout (Send Payment)

const payout = await xpay.createPayout({
  amount: '50.00',
  symbol: 'USDT',
  chain: 'tron',
  receiveAddress: 'TXYZ...recipient...',
  orderId: 'payout_20260601_001'
});
Enter fullscreen mode Exit fullscreen mode

Webhook Verification

import express from 'express';

const app = express();
app.post('/webhooks/xpay', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = xpay.parseWebhook(
      req.body,
      req.headers['x-webhook-signature'] as string
    );

    switch (event.type) {
      case 'ORDER_SUCCESS':
        console.log(`Payment received: ${event.data.orderId}`);
        // Fulfill order, update database, send email
        break;
      case 'ORDER_FAILED':
        console.log(`Payment failed: ${event.data.orderId}`);
        // Notify customer, release inventory
        break;
    }

    res.status(200).json({ received: true });
  } catch (err) {
    res.status(401).json({ error: 'Invalid signature' });
  }
});
Enter fullscreen mode Exit fullscreen mode

Java/Spring Boot SDK

<dependency>
    <groupId>io.xpay</groupId>
    <artifactId>xpay-java-sdk</artifactId>
    <version>0.1.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode
@Configuration
public class PaymentConfig {

    @Bean
    public XPay xPay(@Value("${xpay.api-key}") String apiKey,
                     @Value("${xpay.api-secret}") String apiSecret,
                     @Value("${xpay.base-url}") String baseUrl) {
        return new XPay(XPayConfig.builder()
            .apiKey(apiKey)
            .apiSecret(apiSecret)
            .baseUrl(baseUrl)
            .build());
    }
}

@Service
public class PaymentService {

    private final XPay xpay;

    public PaymentService(XPay xpay) {
        this.xpay = xpay;
    }

    public String createPayment(String orderId, BigDecimal amount) {
        CreateCollectionRequest request = CreateCollectionRequest.builder()
            .amount(amount.toPlainString())
            .symbol("USDT")
            .chain("tron")
            .orderId(orderId)
            .build();

        ApiResponse<CollectionData> response = xpay.createCollection(request);
        return response.getData().getAddress();
    }
}
Enter fullscreen mode Exit fullscreen mode

4. HMAC-SHA256 Request Signing (Under the Hood)

One of the most important security features is how API requests are signed. Instead of sending a bearer token, every request includes a cryptographic signature:

function generateSignature(
  params: Record<string, string>,
  data: Record<string, string>,
  secret: string
): string {
  // 1. Serialize data as data={key=value,...} sorted by key
  const dataStr = 'data={' + Object.keys(data)
    .sort()
    .map(k => `${k}=${data[k]}`)
    .join(',') + '}';

  // 2. Append nonce and timestamp
  const allParams = { ...params, data: dataStr };
  const sorted = Object.keys(allParams)
    .sort()
    .map(k => `${k}=${allParams[k]}`)
    .join('&');

  // 3. HMAC-SHA256
  return crypto
    .createHmac('sha256', secret)
    .update(sorted)
    .digest('hex');
}

// Usage
const data = { amount: '100.00', symbol: 'USDT', chain: 'tron' };
const params = { timestamp: Date.now().toString(), nonce: crypto.randomUUID() };
};

const sign = generateSignature(params, apiSecret);

// POST /v1/order/createCollection
// Body: { sign, timestamp: params.timestamp, nonce: params.nonce, data: {...} }
Enter fullscreen mode Exit fullscreen mode

The gateway validates:

  1. Signature: Recomputes HMAC on the server side
  2. Timestamp: Rejects if more than 30 seconds old
  3. Nonce: Rejects if already seen (Redis-backed deduplication)

5. Docker Compose Production Deployment

services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: xpay_gateway
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

  gateway:
    image: ghcr.io/xpaylabs/gateway:latest
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/xpay_gateway
      SPRING_DATASOURCE_USERNAME: xpay
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
      SPRING_REDIS_PASSWORD: ${REDIS_PASSWORD}
      XPAY_MASTER_SEED: ${XPAY_MASTER_SEED}
      XPAY_WEBHOOK_SECRET: ${XPAY_WEBHOOK_SECRET}
      XPAY_API_KEY: ${XPAY_API_KEY}
      XPAY_API_SECRET: ${XPAY_API_SECRET}
      XPAY_CHAINS: tron,eth,bsc,polygon,arbitrum,base
    depends_on:
      mysql: { condition: service_healthy }
      redis: { condition: service_healthy }

volumes:
  mysql_data:
  redis_data:
Enter fullscreen mode Exit fullscreen mode

Environment Variables

Variable Description
XPAY_MASTER_SEED BIP-39 mnemonic (generate offline!)
XPAY_API_KEY Public API identifier
XPAY_API_SECRET HMAC signing secret
XPAY_WEBHOOK_SECRET HMAC secret for webhook payloads
XPAY_CHAINS Comma-separated chain names to activate

Startup

# One command to deploy
docker compose up -d

# Verify
curl http://localhost:8080/v1/symbol/supportSymbols
Enter fullscreen mode Exit fullscreen mode

Cost Comparison

BitPay Coinbase Commerce Self-Hosted
Setup fee $0–$300 $0 $0 (Docker)
Per-transaction 0.5–1% 1% $0 (gas only)
Monthly (100k volume) $1,000+ $1,000+ ~$30–50 server
Key custody Theirs Theirs Yours
White-label Premium No Yes
Annual savings vs BitPay $360–$3,600 $11,500+

Summary

Self-hosting a crypto payment gateway is more accessible than most developers think. The key components are:

  1. HD wallet derivation for deterministic, scalable address generation
  2. Per-chain scanners with chain-specific detection logic
  3. HMAC-signed API with SDKs in Node.js and Java
  4. Docker Compose deployment for operational simplicity

xpay SDKs (Node.js and Java), checkout UI, and documentation are open-source under MIT at github.com/yan253319066/XPayLabs. The core gateway engine is source-available under the XPay Enterprise License.

SDK packages:

If you're processing $5,000+/month in crypto, the self-hosted approach will save you significant fees — and you'll never have to trust a third party with your keys.

Top comments (1)

Collapse
 
dappfort profile image
DappFort

Great insights! One thing many businesses overlook when choosing a cryptocurrency exchange development company is long-term scalability. Beyond launch-ready features, it's important to evaluate the company's ability to provide ongoing support, security updates, liquidity solutions, and compliance integrations as regulations evolve. We, Dappfort focus on building secure, high-performance crypto exchanges with advanced trading features, strong security architecture, and post-launch technical assistance, helping startups and enterprises launch confidently in the competitive crypto market. The right development partner should grow alongside your business, not just deliver a product.