Multi-chain crypto payment system development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Multi-chain crypto payment system development
Complex
~1-2 weeks
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of Multichain Crypto Payments System

Accepting crypto payments "in one network" is long solved problem. But once client wants to accept payments in ETH, USDC, BNB, SOL, USDT on Tron and also Bitcoin — this becomes architectural task. Each network has own address model, transaction finality, double-spend risks, SDKs and node requirements. Gluing all this into single reliable system is nontrivial.

Key Architectural Decisions

Before writing code, need to make three decisions defining entire architecture.

Custodial vs Non-custodial

Custodial — system stores funds until merchant withdrawal. Simpler technically, but requires licensing (in most jurisdictions storing others' crypto assets = financial activity). Requires HSM or MPC for keys, regular audits.

Non-custodial — funds go directly to merchant addresses, system only detects payments. Technically harder (no single hot wallet), but cleaner regulatorily. Most B2B solutions use this approach.

HD Wallet vs Smart Contract Addresses

HD Wallet (BIP32/BIP44) — generate unique deposit address for each payment from one seed. m/44'/60'/0'/0/invoice_id — each invoice gets own address. Works for all EVM networks and Bitcoin. Monitoring: subscribe to events of all generated addresses.

import { HDNodeWallet, Mnemonic } from 'ethers';

function generateDepositAddress(mnemonic: string, invoiceId: number): string {
  const wallet = HDNodeWallet.fromPhrase(
    mnemonic,
    `m/44'/60'/0'/0/${invoiceId}`
  );
  return wallet.address; // same address for all EVM networks
}

Important: Bitcoin and Solana need different derivation paths (BIP44 coin types: 0 for BTC, 501 for SOL, 60 for ETH).

Smart Contract approach (Forward Contract / Payment Splitter) — each merchant gets or is assigned smart contract that automatically forwards funds to main address. Convenient for EVM networks: one address, any tokens, automatic processing. CREATE2 allows computing contract address before deploy — can give client address immediately, deploy contract only on first payment.

// CREATE2 factory for deterministic deposit addresses
contract DepositFactory {
    function getDepositAddress(bytes32 salt) external view returns (address) {
        return Create2.computeAddress(salt, keccak256(type(ForwardDeposit).creationCode));
    }
    
    function deployDeposit(bytes32 salt, address recipient) external returns (address) {
        return address(new ForwardDeposit{salt: salt}(recipient));
    }
}

Confirmation Requirements

Different networks require different confirmation counts for safe finality:

Network Recommended Confirmations Time
Bitcoin 3-6 30-60 min
Ethereum 12-20 3-4 min
BNB Chain 15-20 45-60 sec
Polygon 256 ~8 min
Solana 32 (finalized) ~15 sec
Tron 20 ~1 min
Arbitrum 1 (L2) <1 sec

Polygon PoS has deep reorgs — 256 confirmations for safe finality not exaggeration. Arbitrum inherits finality from Ethereum after settlement.

System Architecture

[Payment Gateway API]
        ↓
[Invoice Service] ← stores invoice state, triggers, webhooks
        ↓
[Address Generator] ← HD wallet or CREATE2 factory
        ↓
[Chain Monitors] ← one process per chain
  ├─ EthereumMonitor   (WebSocket eth_subscribe)
  ├─ BscMonitor        (WebSocket)
  ├─ SolanaMonitor     (WebSocket account subscribe)
  ├─ TronMonitor       (Event API polling)
  └─ BitcoinMonitor    (ZMQ or Electrum)
        ↓
[Confirmation Tracker] ← waits N confirmations
        ↓
[Webhook Dispatcher] ← notifies merchant

Chain Monitors — most critical component. Each monitor must:

  • Survive node connection breaks (auto-reconnect + catch-up)
  • Handle reorgs (invalidate pending confirmations)
  • Detect both native coins and ERC-20/BEP-20/SPL tokens
  • Work independently — one monitor failure shouldn't crash others

EVM Monitoring

import { createPublicClient, webSocket, parseAbiItem } from 'viem';

const client = createPublicClient({
  chain: mainnet,
  transport: webSocket('wss://eth-mainnet.g.alchemy.com/v2/...'),
});

// Monitoring ERC-20 Transfer to our addresses
const unwatch = client.watchEvent({
  event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
  args: { to: monitoredAddresses },
  onLogs: async (logs) => {
    for (const log of logs) {
      await processIncomingTransfer({
        chain: 'ethereum',
        token: log.address,
        from: log.args.from,
        to: log.args.to,
        amount: log.args.value,
        txHash: log.transactionHash,
        blockNumber: log.blockNumber,
      });
    }
  },
});

Solana Monitoring

Solana has different model: tokens stored not directly on user address, but in Associated Token Accounts (ATA). To accept USDC need to know user's ATA address for that token:

import { Connection, PublicKey } from '@solana/web3.js';
import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token';

const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');

async function getUsdcDepositAddress(userPublicKey: PublicKey): Promise<string> {
  const ata = await getAssociatedTokenAddress(USDC_MINT, userPublicKey);
  return ata.toBase58();
}

// Monitoring via WebSocket
const connection = new Connection('wss://api.mainnet-beta.solana.com');
connection.onAccountChange(ataAddress, (accountInfo) => {
  // process balance change
});

Bitcoin Monitoring

For Bitcoin without custom node can use Electrum Protocol or third-party services (BlockCypher API, Tatum). For serious production system recommend own bitcoind + Electrs (Electrum Rust server):

from electrum_client import ElectrumClient

client = ElectrumClient('127.0.0.1', 50001)

# Subscribe to address history
async def monitor_bitcoin_address(address: str):
    script_hash = address_to_scripthash(address)  # sha256(scriptPubKey), reversed
    await client.subscribe_scripthash(script_hash, callback=on_history_change)

Token Processing and Conversion

Token whitelist. Don't accept arbitrary tokens — only pre-approved list. Otherwise attacker can send worthless ERC-20 token which technically is "payment".

Slippage on conversion to stablecoins. If merchant wants USD equivalent, system must convert volatile assets. Use aggregators (1inch) with tight slippage tolerance and minimum conversion amount for profitability.

Underpayment handling. Client paid 99.5 USDC instead of 100. Need policy: acceptable variance (usually 0.5-1%), partial payment (invoice marked as partially paid, requires top-up), or automatic refund. All this should be in business logic, not smart contract.

Webhook Reliability

Merchant notification about payment is critical. Webhook can fail, hang, return 5xx. Pattern for reliable delivery:

class WebhookDispatcher:
    MAX_ATTEMPTS = 5
    RETRY_DELAYS = [30, 120, 600, 3600, 86400]  # sec: 30s, 2m, 10m, 1h, 24h
    
    async def dispatch_with_retry(self, webhook_url: str, payload: dict, attempt: int = 0):
        try:
            async with aiohttp.ClientSession() as session:
                resp = await session.post(
                    webhook_url,
                    json=payload,
                    headers={'X-Signature': self.sign_payload(payload)},
                    timeout=aiohttp.ClientTimeout(total=30)
                )
                if resp.status == 200:
                    await self.mark_delivered(payload['event_id'])
                    return
        except Exception as e:
            log.error(f"Webhook failed attempt {attempt}: {e}")
        
        if attempt < self.MAX_ATTEMPTS:
            await asyncio.sleep(self.RETRY_DELAYS[attempt])
            await self.dispatch_with_retry(webhook_url, payload, attempt + 1)

HMAC signature of payload (X-Signature header) allows merchant to verify notification authenticity.

Infrastructure and Security

HD wallet keys — master seed stored in KMS (AWS KMS or HashiCorp Vault). Address generation service doesn't store seed locally — requests KMS on each operation. Derivation indices stored in DB — loss means inability to find payments.

Rate limiting and abuse. Invoice generation must be rate-limited at API key level. Unused invoices with expired deadline — archive, don't delete (needed for audit).

Reconciliation. Daily check: sum all confirmed payments per our data vs hot wallet balance (if custodial). Discrepancies — alert immediately.

Stack and Timeline

Technology stack:

Layer Choice
API FastAPI (Python) or Fastify (Node.js)
Chain monitoring Python asyncio / Node.js workers, one process per chain
DB PostgreSQL (invoices, transactions) + Redis (pending confirmations, cache)
Queues Redis Streams or RabbitMQ for webhook dispatch
Deploy Kubernetes (High Availability critical)

Development phases:

  • Basic EVM monitoring + invoice flow: 3-4 weeks
  • Add Bitcoin and Solana: +2-3 weeks
  • Conversion to stablecoins: +1-2 weeks
  • Webhook system + merchant dashboard: +2-3 weeks
  • Load testing, security review, production deployment: +2 weeks

Total for full-featured system with 5-6 supported networks: 10-14 weeks.