Разработка системы сегрегации активов для кастодиана
Кастодианы — компании, которые профессионально хранят крипто-активы клиентов. Ключевое требование регуляторов и клиентов: активы разных клиентов должны быть строго разделены. «Общий пул» неприемлем: если кастодиан несёт убытки или банкротится, клиент должен иметь возможность однозначно идентифицировать свои активы и получить их обратно. Это и есть сегрегация.
Регуляторный контекст
Требования к сегрегации активов определяются юрисдикцией. MiCA (EU) требует отделения клиентских активов от собственных. FCA (UK) — аналогично. SEC для крипто-кастодианов в США — пока в разработке, но NYDFS BitLicense уже содержит требования к сегрегации.
Ключевое требование: возможность аудита. Аудитор в любой момент должен иметь возможность сопоставить on-chain адреса с записями о клиентах и подтвердить, что активы клиента A не перемешаны с активами клиента B.
Архитектурные модели сегрегации
Один адрес на клиента (Full Segregation)
Каждый клиент получает уникальный on-chain адрес (или набор адресов — по одному на каждый поддерживаемый blockchain). Активы физически разделены на уровне блокчейна.
Преимущества:
- Максимальная прозрачность для клиента и аудитора
- Простое доказательство права собственности
- Изоляция рисков: проблема одного клиента не затрагивает других
Недостатки:
- Высокие operational costs при большом числе клиентов (тысячи адресов, отдельные gas пополнения для каждого)
- Сложнее оптимизировать ликвидность
Виртуальная сегрегация с общим пулом
Активы хранятся на общих адресах, но внутренняя база данных ведёт точный учёт доли каждого клиента. Аналог: банковский счёт (вы не знаете в каком физическом хранилище лежат ваши деньги).
Преимущества:
- Низкие operational costs
- Проще управлять ликвидностью
Недостатки:
- Регуляторно сложнее обосновать как «сегрегацию»
- При банкротстве кастодиана — активы клиентов могут быть оспорены кредиторами
Гибридная модель
Для крупных клиентов (институты) — выделенные адреса. Для розничных — виртуальная сегрегация с возможностью перевода на dedicated address по запросу.
Детальная архитектура системы
Управление адресами
Ключи хранятся в HSM. Адреса генерируются детерминированно через HD-derivation:
m/44'/60'/{clientId}'/0/0 → основной адрес клиента
m/44'/60'/{clientId}'/0/1 → адрес для конкретного актива
m/44'/60'/{clientId}'/1/0 → change адрес
Это позволяет восстановить все адреса из master seed без дополнительного хранилища.
interface ClientWallet {
clientId: string;
blockchain: string;
address: string;
derivationPath: string;
createdAt: Date;
keyStorageLocation: 'hsm_slot_1' | 'hsm_slot_2'; // для geo-redundancy
}
class AddressManager {
async createClientAddress(
clientId: string,
blockchain: string
): Promise<ClientWallet> {
// Атомарная операция: проверяем что адрес ещё не создан
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('*');
});
}
}
Бухгалтерский учёт (Ledger)
Двойная запись обязательна. Каждое движение активов должно балансироваться:
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. Ежедневно система должна сверять on-chain балансы с записями в ledger:
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() };
}
Мониторинг входящих депозитов
Система должна отслеживать все входящие транзакции на адреса клиентов и автоматически зачислять:
class DepositMonitor {
async watchAddress(address: string, clientId: string) {
// Подписка на новые блоки через WebSocket
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,
});
}
}
});
// Также отслеживаем ERC-20 Transfer события
this.monitorERC20Transfers(address, clientId);
}
private async processDeposit(deposit: DepositEvent) {
// Ждём confirmations (обычно 12-20 для finality)
await this.waitForConfirmations(deposit.txHash, REQUIRED_CONFIRMATIONS);
await this.db.transaction(async (trx) => {
// Проверяем идемпотентность
const existing = await trx('deposits').where({ txHash: deposit.txHash }).first();
if (existing) return;
// Записываем депозит
await trx('deposits').insert(deposit);
// Обновляем ledger
await this.ledger.credit(trx, deposit.clientId, deposit.asset, deposit.amount);
});
}
}
Вывод средств (Withdrawal)
Вывод всегда требует approval workflow (см. multi-approval систему). После approval:
- Проверка баланса клиента в ledger
- Резервирование средств (debit pending)
- Подпись транзакции в HSM
- Broadcast on-chain
- Ожидание confirmations
- Финальное списание из ledger (или reversal при failure)
Idempotency. Каждый withdrawal request имеет уникальный idempotency key. Повторный запрос с тем же ключом возвращает статус существующего, не создаёт новый.
Proof of Reserves
Для публичного подтверждения платёжеспособности — Merkle tree на основе client balances:
// Каждый клиент получает proof что его баланс включён в общее дерево
// Без раскрытия данных других клиентов
async function generateProofOfReserves(): Promise<ProofOfReserves> {
const balances = await db.getAllClientBalances();
// Строим Merkle tree
const leaves = balances.map(b =>
keccak256(encode(['address', 'uint256'], [b.address, b.balance]))
);
const tree = new MerkleTree(leaves, keccak256, { sort: true });
// Публикуем 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 и аудит
Audit trail. Все операции с неизменяемой историей. Логи хранятся в append-only хранилище (AWS QLDB или PostgreSQL с audit triggers).
Reporting. Ежемесячные отчёты для каждого клиента: движение средств, комиссии, текущие балансы. Ежеквартальные для аудиторов: reconciliation reports, proof of reserves.
KYT (Know Your Transaction). Интеграция с Chainalysis, Elliptic или TRM Labs для проверки входящих транзакций на связь с санкционными адресами и mixer-ами.
Стек
| Компонент | Технология |
|---|---|
| HSM | AWS CloudHSM или Thales |
| Database | PostgreSQL + AWS QLDB (audit log) |
| Blockchain monitoring | Alchemy/Infura + собственный indexer |
| Reconciliation | Cron job + alerting |
| KYT | Chainalysis Reactor API |
| API | Node.js + TypeScript, REST + gRPC |
| Frontend | React (admin dashboard) |
Сроки
- Core ledger + address management: 6-8 недель
- Deposit monitoring + withdrawal flow: 4-6 недель
- Reconciliation + proof of reserves: 3-4 недели
- KYT интеграция + compliance reporting: 3-4 недели
- Security audit: обязателен, +4-8 недель
Итого: 5-6 месяцев до production-ready системы.







