One address for all payments with a memo works until users forget to fill in the memo. When 5% of incoming transfers arrive without an identifier and require manual parsing, it becomes an operational problem. We've encountered cases where a client lost up to 8% of payments, and recovery took up to 3 business days. A unique address per payment solves this completely: each order gets its own address, every payment is uniquely identified. Order such a system from us — we guarantee security and transparency. Our specialists have 10+ years of experience in blockchain development and have completed 500+ projects.
Unique Address Generation Solves the Memo Problem
The memo field in a transaction is optional, and users may leave it blank. Under high load, even 1% lost payments means losses and manual labor. A unique address for each order makes identification automatic: by the destination address we immediately know which order the transaction belongs to. Compare the two approaches in the table below:
| Criterion | Memo field | Unique address |
|---|---|---|
| Requires user input | Yes | No |
| Risk of payment loss | 5–8% | 0% |
| Manual processing | Required | Automatic |
| Scalability | Limited | Unlimited (up to 2^32 addresses from one seed) |
| Security | No special features | HD derivation with key isolation |
How HD Derivation Works
The foundation is hierarchical deterministic wallets (BIP-32/BIP-44). From a single master seed, any number of child keys can be derived deterministically. Addresses are reproducible, and the private key of each child address can be recovered from the seed at any time. This is the standard BIP-44, used in most crypto wallets.
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
An important point: address-only derivation does not require the private key. An extended public key (xpub) is sufficient for generating addresses. This allows separating components: the address generation server holds only xpub (compromise reveals addresses, not funds), and the transaction signing server holds xprv in an isolated environment (HSM, offline). This approach has been used in production for several years — we have implemented it in dozens of projects.
What Is Sweeping and How Does It Work?
Funds on child addresses must be periodically collected to a main address (cold wallet or multisig). Sweep should occur after payment confirmation:
async function sweepAddress(index: number): Promise<void> {
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,
});
}
For ERC-20 tokens, sweeping is more complex: you must first ensure the address has ETH for gas (send it from the main address) and then withdraw the tokens. Alternatively, use the Permit/EIP-2612 pattern so that the child address doesn't pay for gas.
Implementation: Generation and Monitoring
import { HDNodeWallet, Mnemonic } from 'ethers';
class PaymentAddressGenerator {
private hdNode: HDNodeWallet;
private currentIndex: number;
constructor(xpub: string, startIndex: number = 0) {
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,
};
}
}
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 Incrementing in Code Is Not Recommended: When horizontally scaling, multiple service instances may get the same index simultaneously. A PostgreSQL sequence is atomic — nextval always returns a unique value. We use this scheme in projects with a load of up to 10,000 addresses per day.
Database and O(1) Lookup
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,
currency VARCHAR(20) NOT NULL,
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),
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';
The index on address is critical for O(1) lookup when receiving a transaction from the blockchain listener. Without it, checking each incoming transfer would require a full table scan. We ensure monitoring works in real time even with 10,000 simultaneously active addresses.
How Is Transaction Monitoring Performed?
A naive approach is to subscribe to all generated addresses. In a large system, this could be thousands of addresses. Better: maintain an in-memory set of active (pending) addresses that updates when payments are created or closed.
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;
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 — the address is identical on Ethereum, BNB Chain, Polygon, Arbitrum. This is convenient: one index in the table can be used to accept payments in different networks on the same address, but monitoring is needed for each network separately.
For non-EVM (TON, Solana, Bitcoin) — separate HD derivations with different master seeds or different derivation paths.
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 of all sweep operations — each transaction is logged with justification.
- Gap limit — BIP-44 recommends not looking deeper than 20 consecutive unused addresses when restoring a wallet.
Implementation Stages (Turnkey)
| Stage | Duration | Cost estimate |
|---|---|---|
| Analysis and design | 3–5 days | Depends on scope |
| Generation module development | 5–7 days | Depends on scope |
| Monitoring and sweeping | 5–7 days | Depends on scope |
| Integration with your system | 3–5 days | Depends on scope |
| Testing and deployment | 3–5 days | Depends on scope |
| Total | 2–3 weeks | Depends on scope |
What's Included
When you order the development of a unique address system, we provide:
- generation and monitoring module (source code under your license);
- PostgreSQL schema with indexes and migrations;
- deployment scripts and HSM/KMS configuration;
- API documentation and operation manual;
- training for your team (2–3 days);
- technical support for 3 months.
Contact us to evaluate your project — we will respond within 1–2 business days. Order development and get a ready-made solution that eliminates payment losses. Our system is 10 times more reliable than manual memo-based processing, reducing payment loss rate from 8% to 0%.







