Unique Payment Address Generation System

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
Unique Payment Address Generation System
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • 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
    1043
  • 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

Developing a Unique Payment Address Generation System

One address for all payments with comment — works until users forget to specify memo. But when 5% of payments arrive without identifier and require manual reconciliation — that's an operational problem. Unique address per payment solves it completely: each order gets its own address, any incoming payment is uniquely identified.

HD Wallet Derivation: The Math Behind

Foundation of the system — hierarchical deterministic wallets (BIP-32/BIP-44). From one master seed you can deterministically derive unlimited child keys — addresses are reproducible, any child key's private key recovers from seed at any time.

Master Seed (128/256 bits)
    ↓ BIP-39
Master Mnemonic (12/24 words)
    ↓ BIP-32 HMAC-SHA512
Master Extended Key (xprv)
    ↓ BIP-44 derivation
m / purpose' / coin_type' / account' / change / index

For Ethereum (coin_type = 60):

m/44'/60'/0'/0/0   → first address
m/44'/60'/0'/0/1   → second address
m/44'/60'/0'/0/N   → (N+1)-th address

Key point: address-only derivation doesn't require private key. Extended public key (xpub) is enough for address generation. Allows separation of concerns: address generation server holds only xpub (compromise = address leak, not funds), transaction signing server holds xprv in isolated environment (HSM, offline).

Implementation

import { HDNodeWallet, Mnemonic } from 'ethers';

class PaymentAddressGenerator {
  private hdNode: HDNodeWallet;
  private currentIndex: number;

  constructor(xpub: string, startIndex: number = 0) {
    // From xpub — public derivation only
    this.hdNode = HDNodeWallet.fromExtendedKey(xpub);
    this.currentIndex = startIndex;
  }

  generateAddress(orderId: string): { address: string; index: number } {
    // Use atomic counter from DB, not local state
    const index = this.currentIndex++;
    const childNode = this.hdNode.deriveChild(index);
    
    return {
      address: childNode.address.toLowerCase(),
      index,
    };
  }
}

// Atomic generation: get next index from PostgreSQL sequence
async function getNextIndex(db: Pool): Promise<number> {
  const result = await db.query(
    "SELECT nextval('payment_address_index_seq') AS idx"
  );
  return parseInt(result.rows[0].idx);
}

Why not just increment in code? With horizontal scaling, multiple service instances can simultaneously get the same index. PostgreSQL sequence is atomic — nextval always returns unique value.

Database: Schema

CREATE SEQUENCE payment_address_index_seq START 1;

CREATE TABLE payment_addresses (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  address       VARCHAR(42) NOT NULL UNIQUE,
  derivation_index INTEGER NOT NULL UNIQUE,
  order_id      UUID NOT NULL REFERENCES orders(id),
  network       VARCHAR(20) NOT NULL, -- 'ethereum', 'bsc', 'polygon'...
  currency      VARCHAR(20) NOT NULL, -- 'ETH', 'USDT', 'USDC'...
  expected_amount NUMERIC(36, 18),
  received_amount NUMERIC(36, 18) DEFAULT 0,
  status        VARCHAR(20) NOT NULL DEFAULT 'pending',
  expires_at    TIMESTAMPTZ NOT NULL,
  confirmed_tx  VARCHAR(66), -- tx hash
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_payment_addresses_address ON payment_addresses(address);
CREATE INDEX idx_payment_addresses_status ON payment_addresses(status) 
  WHERE status = 'pending';

Index by address is critical for O(1) lookup when receiving transaction from blockchain listener.

Monitoring: Track Only Active Addresses

Naive approach — subscribe to all generated addresses. In large system this is thousands of addresses. Better: keep in-memory set of active (pending) addresses, updated on payment creation/closure.

class PaymentAddressMonitor {
  private activeAddresses: Map<string, PaymentAddress> = new Map();

  async loadActiveAddresses(db: Pool): Promise<void> {
    const result = await db.query(
      `SELECT address, order_id, expected_amount, currency, expires_at
       FROM payment_addresses 
       WHERE status = 'pending' AND expires_at > NOW()`
    );
    for (const row of result.rows) {
      this.activeAddresses.set(row.address.toLowerCase(), row);
    }
  }

  onTransactionDetected(toAddress: string, amount: bigint, txHash: string): void {
    const payment = this.activeAddresses.get(toAddress.toLowerCase());
    if (!payment) return; // Not our address

    // Amount check with 1% tolerance
    const tolerance = payment.expectedAmount * 99n / 100n;
    if (amount >= tolerance) {
      this.confirmPayment(payment, txHash);
    }
  }
}

Multi-Network: One Index, Different Addresses

EVM-compatible networks use the same private key — address is identical on Ethereum, BNB Chain, Polygon, Arbitrum. Convenient: one index in table can receive payments in different networks to same address, but monitoring must be separate for each network.

For non-EVM (TON, Solana, Bitcoin) — separate HD derivations with different master seed or different derivation paths.

Sweeping: Funds Consolidation

Funds on child addresses must periodically gather to main address (cold wallet or multisig). Sweep should happen after payment confirmation:

async function sweepAddress(index: number): Promise<void> {
  // Private key derivation — only on signing server
  const privateKey = masterHdNode.deriveChild(index).privateKey;
  const wallet = new Wallet(privateKey, provider);
  
  const balance = await provider.getBalance(wallet.address);
  const gasEstimate = 21000n;
  const gasPrice = await provider.getFeeData().then(d => d.gasPrice!);
  const gasCost = gasEstimate * gasPrice;
  
  if (balance <= gasCost) return; // Nothing to sweep
  
  await wallet.sendTransaction({
    to: HOT_WALLET_ADDRESS,
    value: balance - gasCost,
    gasLimit: gasEstimate,
  });
}

For ERC-20 tokens sweep is more complex: first ensure address has ETH for gas, send it from main address, then withdraw tokens. Or use Permit/EIP-2612 pattern — then gas paid by main address.

Security

  • Master seed — in HSM or AWS KMS. Never in environment variables.
  • xpub for address generation — separate from xprv for signing.
  • Signing service — isolated microservice with minimal privileges.
  • Audit all sweep operations — each transaction logged with justification.
  • Gap limit — BIP-44 recommends not looking deeper than 20 consecutive unused addresses during wallet recovery.