Custodian Asset Segregation 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
Custodian Asset Segregation 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
    1214
  • 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
    1041
  • 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

Custodian Asset Segregation System Development

Custodians are companies that professionally store crypto assets of clients. Key regulator and client requirement: different clients' assets must be strictly separated. "Common pool" unacceptable: if custodian suffers loss or bankruptcy, client must be able to uniquely identify their assets and get them back. This is segregation.

Regulatory Context

Asset segregation requirements determined by jurisdiction. MiCA (EU) requires separation of client assets from own. FCA (UK) — likewise. SEC for crypto custodians in US — still in development, but NYDFS BitLicense already contains segregation requirements.

Key requirement: ability to audit. Auditor anytime must be able to match on-chain addresses with client records and confirm that client A's assets not mixed with client B's.

Segregation Architectural Models

One Address Per Client (Full Segregation)

Each client gets unique on-chain address (or set — one per supported blockchain). Assets physically separated at blockchain level.

Advantages:

  • Maximum transparency for client and auditor
  • Simple proof of ownership
  • Risk isolation: one client's problem doesn't affect others

Disadvantages:

  • High operational costs with many clients (thousands of addresses, separate gas replenishments for each)
  • Harder to optimize liquidity

Virtual Segregation with Common Pool

Assets stored on common addresses, but internal database keeps precise account of each client's share. Like bank account (you don't know in which physical storage your money lies).

Advantages:

  • Low operational costs
  • Easier liquidity management

Disadvantages:

  • Regulatorily harder to justify as "segregation"
  • On custodian bankruptcy — client assets can be contested by creditors

Hybrid Model

For large clients (institutions) — dedicated addresses. For retail — virtual segregation with option to transfer to dedicated address on request.

Detailed System Architecture

Address Management

Keys stored in HSM. Addresses generated deterministically via HD-derivation:

m/44'/60'/{clientId}'/0/0 → main client address
m/44'/60'/{clientId}'/0/1 → address for specific asset
m/44'/60'/{clientId}'/1/0 → change address

Allows recovering all addresses from master seed without additional storage.

interface ClientWallet {
  clientId: string;
  blockchain: string;
  address: string;
  derivationPath: string;
  createdAt: Date;
  keyStorageLocation: 'hsm_slot_1' | 'hsm_slot_2'; // for geo-redundancy
}

class AddressManager {
  async createClientAddress(
    clientId: string,
    blockchain: string
  ): Promise<ClientWallet> {
    // Atomic operation: check address not created yet
    return this.db.transaction(async (trx) => {
      const existing = await trx('client_wallets')
        .where({ clientId, blockchain })
        .first();
      
      if (existing) return existing;
      
      const index = await this.getNextIndex(trx, blockchain);
      const derivationPath = `m/44'/60'/${clientId}'/0/${index}`;
      const address = await this.hsm.deriveAddress(derivationPath);
      
      return trx('client_wallets').insert({
        clientId,
        blockchain,
        address,
        derivationPath,
        createdAt: new Date(),
      }).returning('*');
    });
  }
}

Accounting (Ledger)

Double-entry bookkeeping mandatory. Every asset movement must balance:

CREATE TABLE ledger_entries (
    id BIGSERIAL PRIMARY KEY,
    entry_type VARCHAR(32) NOT NULL, -- debit/credit
    client_id UUID NOT NULL REFERENCES clients(id),
    asset VARCHAR(64) NOT NULL,    -- ETH, USDC, BTC
    blockchain VARCHAR(32) NOT NULL,
    amount NUMERIC(36, 18) NOT NULL CHECK (amount > 0),
    balance_after NUMERIC(36, 18) NOT NULL,
    reference_type VARCHAR(32), -- deposit, withdrawal, fee, transfer
    reference_id UUID,
    tx_hash VARCHAR(66),
    block_number BIGINT,
    created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
    created_by VARCHAR(64) NOT NULL, -- system, user_id
    CONSTRAINT no_negative_balance CHECK (balance_after >= 0)
);

CREATE TABLE client_balances (
    client_id UUID REFERENCES clients(id),
    asset VARCHAR(64),
    blockchain VARCHAR(32),
    balance NUMERIC(36, 18) NOT NULL DEFAULT 0,
    last_updated_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (client_id, asset, blockchain)
);

Reconciliation. Daily system must reconcile on-chain balances with ledger records:

async function reconcile(blockchain: string, asset: string): Promise<ReconciliationResult> {
  const clientWallets = await db.clientWallets.findAll({ blockchain });
  
  const results = await Promise.all(
    clientWallets.map(async (wallet) => {
      const onChainBalance = await getOnChainBalance(wallet.address, asset);
      const ledgerBalance = await db.clientBalances.getBalance(
        wallet.clientId, asset, blockchain
      );
      
      const discrepancy = onChainBalance - ledgerBalance;
      
      return {
        clientId: wallet.clientId,
        address: wallet.address,
        onChainBalance,
        ledgerBalance,
        discrepancy,
        status: Math.abs(discrepancy) < TOLERANCE ? 'ok' : 'mismatch',
      };
    })
  );
  
  const mismatches = results.filter(r => r.status === 'mismatch');
  if (mismatches.length > 0) {
    await this.alertService.sendAlert('RECONCILIATION_MISMATCH', mismatches);
  }
  
  return { results, mismatches, timestamp: new Date() };
}

Monitoring Incoming Deposits

System must track all incoming transactions to client addresses and auto-credit:

class DepositMonitor {
  async watchAddress(address: string, clientId: string) {
    // WebSocket subscription to new blocks
    this.provider.on('block', async (blockNumber) => {
      const block = await this.provider.getBlock(blockNumber, true);
      
      for (const tx of block.transactions) {
        if (tx.to?.toLowerCase() === address.toLowerCase()) {
          await this.processDeposit({
            clientId,
            txHash: tx.hash,
            amount: tx.value,
            asset: 'ETH',
            blockNumber,
          });
        }
      }
    });
    
    // Also monitor ERC-20 Transfer events
    this.monitorERC20Transfers(address, clientId);
  }
  
  private async processDeposit(deposit: DepositEvent) {
    // Wait for confirmations (usually 12-20 for finality)
    await this.waitForConfirmations(deposit.txHash, REQUIRED_CONFIRMATIONS);
    
    await this.db.transaction(async (trx) => {
      // Check idempotency
      const existing = await trx('deposits').where({ txHash: deposit.txHash }).first();
      if (existing) return;
      
      // Record deposit
      await trx('deposits').insert(deposit);
      
      // Update ledger
      await this.ledger.credit(trx, deposit.clientId, deposit.asset, deposit.amount);
    });
  }
}

Withdrawal (Withdrawal)

Withdrawal always requires approval workflow. After approval:

  1. Check client balance in ledger
  2. Reserve funds (debit pending)
  3. Sign transaction in HSM
  4. Broadcast on-chain
  5. Wait for confirmations
  6. Final ledger debit (or reversal on failure)

Idempotency. Each withdrawal request has unique idempotency key. Repeat request with same key returns existing status, doesn't create new.

Proof of Reserves

For public proof of solvency — Merkle tree based on client balances:

// Each client gets proof their balance included in overall tree
// Without revealing other clients' data
async function generateProofOfReserves(): Promise<ProofOfReserves> {
  const balances = await db.getAllClientBalances();
  
  // Build Merkle tree
  const leaves = balances.map(b => 
    keccak256(encode(['address', 'uint256'], [b.address, b.balance]))
  );
  
  const tree = new MerkleTree(leaves, keccak256, { sort: true });
  
  // Publish root on-chain
  await proofOfReservesContract.updateRoot(tree.getRoot());
  
  return {
    root: tree.getRoot(),
    totalBalance: balances.reduce((sum, b) => sum + b.balance, 0n),
    timestamp: Date.now(),
    proofs: balances.map((b, i) => ({
      clientId: b.clientId,
      proof: tree.getProof(leaves[i]),
    })),
  };
}

Compliance and Audit

Audit trail. All operations with immutable history. Logs stored in append-only storage (AWS QLDB or PostgreSQL with audit triggers).

Reporting. Monthly reports for each client: fund movements, fees, current balances. Quarterly for auditors: reconciliation reports, proof of reserves.

KYT (Know Your Transaction). Integration with Chainalysis, Elliptic, or TRM Labs for checking incoming transactions against sanctioned addresses and mixers.

Stack

Component Technology
HSM AWS CloudHSM or Thales
Database PostgreSQL + AWS QLDB (audit log)
Blockchain monitoring Alchemy/Infura + custom indexer
Reconciliation Cron job + alerting
KYT Chainalysis Reactor API
API Node.js + TypeScript, REST + gRPC
Frontend React (admin dashboard)

Timeline

  • Core ledger + address management: 6-8 weeks
  • Deposit monitoring + withdrawal flow: 4-6 weeks
  • Reconciliation + proof of reserves: 3-4 weeks
  • KYT integration + compliance reporting: 3-4 weeks
  • Security audit: mandatory, +4-8 weeks

Total: 5-6 months to production-ready system.