Розробка системи генерації унікальних адрес для платежів

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка системи генерації унікальних адрес для платежів
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1307
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1219
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1148
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    611
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    886

Розробка системи генерації унікальних адрес для платежів

Одна адреса для всіх платежів з коментарем — працює, доки користувачі не забувають вказувати memo. Але коли 5% платежів приходять без ідентифікатора та потребують ручного розбору — це операційна проблема. Unique address per payment вирішує її повністю: кожне замовлення отримує свою адресу, будь-який вхідний платіж однозначно ідентифікується.

HD Wallet деривація: математика за кулісами

Основа системи — ієрархічні детерміновані гаманці (BIP-32/BIP-44). З одного master seed можна детерміновано вивести скільки завгодно дочірніх ключів — адреси відтворювані, приватний ключ будь-якої дочірної адреси восстанавлюється з seed в будь-який момент.

Master Seed (128/256 біт)
    ↓ BIP-39
Master Mnemonic (12/24 слова)
    ↓ BIP-32 HMAC-SHA512
Master Extended Key (xprv)
    ↓ BIP-44 деривація
m / purpose' / coin_type' / account' / change / index

Для Ethereum (coin_type = 60):

m/44'/60'/0'/0/0   → перша адреса
m/44'/60'/0'/0/1   → друга адреса
m/44'/60'/0'/0/N   → N+1-ша адреса

Важливий момент: деривація address-only не потребує приватного ключа. Extended public key (xpub) достатньо для генерації адрес. Це дозволяє розмежувати компоненти: сервер генерації адрес зберігає тільки xpub (компрометація = витік адрес, не коштів), сервер підписання транзакцій зберігає xprv в ізольованому середовищі (HSM, offline).

Реалізація

import { HDNodeWallet, Mnemonic } from 'ethers';

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

  constructor(xpub: string, startIndex: number = 0) {
    // З xpub — тільки публічна деривація
    this.hdNode = HDNodeWallet.fromExtendedKey(xpub);
    this.currentIndex = startIndex;
  }

  generateAddress(orderId: string): { address: string; index: number } {
    // Використовуємо атомарний лічильник з БД, не локальне стан
    const index = this.currentIndex++;
    const childNode = this.hdNode.deriveChild(index);
    
    return {
      address: childNode.address.toLowerCase(),
      index,
    };
  }
}

// Атомарна генерація: отримуємо наступний index з 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);
}

Чому не просто інкремент у коді? При горизонтальному масштабуванні кілька інстансів сервісу можуть одночасно отримати один і той же index. PostgreSQL sequence атомарний — nextval завжди повертає унікальне значення.

База даних: схема

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';

Індекс по address — критичний для O(1) lookup при отриманні транзакції від blockchain-листенера.

Моніторинг: відстежуємо тільки активні адреси

Наївний підхід — підписатися на все згенеровані адреси. У великій системі це тисячі адрес. Краще: тримати in-memory set активних (pending) адрес, який оновлюється при створенні/закритті платежів.

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; // Не наша адреса

    // Перевірка суми з толерантністю 1%
    const tolerance = payment.expectedAmount * 99n / 100n;
    if (amount >= tolerance) {
      this.confirmPayment(payment, txHash);
    }
  }
}

Multi-network: один index, різні адреси

EVM-сумісні мережі використовують один і той же приватний ключ — адреса одинакова на Ethereum, BNB Chain, Polygon, Arbitrum. Зручно: один index у таблиці можна використовувати для прийняття платежів у різних мережах на один адрес, але моніторинг потрібен окремий для кожної мережи.

Для non-EVM (TON, Solana, Bitcoin) — окремі HD деривації з різними master seed або різними шляхами деривації.

Sweeping: консолідація коштів

Кошти на дочірніх адресах потрібно періодично збирати на основну адресу (холодний гаманець або multisig). Sweep повинен відбуватися після підтвердження платежу:

async function sweepAddress(index: number): Promise<void> {
  // Деривація приватного ключа — тільки на signing сервері
  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; // Нічого не свипати
  
  await wallet.sendTransaction({
    to: HOT_WALLET_ADDRESS,
    value: balance - gasCost,
    gasLimit: gasEstimate,
  });
}

Для ERC-20 токенів sweep складніший: спочатку переконатися, що на адресі є ETH для gas, відправити його з основної адреси, потім забрати токени. Або використовувати паттерн з Permit/EIP-2612 — тоді газ платить основна адреса.

Безпека

  • Master seed — у HSM або AWS KMS. Ніколи в змінних середовища.
  • xpub для генерації адрес — окремо від xprv для підписання.
  • Signing сервіс — ізольований мікросервіс з мінімальними привілеями.
  • Аудит всіх операцій sweep — кожна транзакція логується з обґрунтуванням.
  • Gap limit — BIP-44 рекомендує не дивитися глибше за 20 послідовних невикористаних адрес при восстановленні гаманця.