Настройка соответствия FATF Travel Rule
FATF Recommendation 16 (Travel Rule) требует, чтобы при переводе виртуальных активов между VASPs передавалась информация об отправителе и получателе. Это самое технически сложное требование в FATF R15-16 пакете: нет единого протокола, несколько конкурирующих решений, и проблема "sunrise issue" когда один VASP compliant, а другой — нет.
Что требует Travel Rule
Originator information (отправитель):
- Имя
- Номер счёта (blockchain адрес или account ID)
- Физический адрес / дата рождения / national ID number (одно из)
Beneficiary information (получатель):
- Имя
- Номер счёта
Порог: FATF рекомендует $1,000/€1,000. Реальность: разные юрисдикции установили разные пороги. EU TFR: €0 (все переводы). США FinCEN: $3,000. Большинство остальных: $1,000.
Архитектурные решения
Провайдеры Travel Rule messaging
Не каждый VASP строит собственную инфраструктуру — есть специализированные messaging networks:
Notabene: наибольшее покрытие (500+ VASP members), SSI-based (Self-Sovereign Identity), RESTful API.
Sygna Bridge: strong в Азии (BiTSO, OKX, Huobi используют), хорошо для Asian market compliance.
Veriscope (Shyft Network): blockchain-based Travel Rule, decentralized.
TRP (Travel Rule Protocol, SWIFT): для крупных банков с крипто arms.
OpenVASP: открытый протокол, peer-to-peer, не требует hub. Меньше adoption, но технически интересен.
Notabene интеграция
import { Notabene } from "@notabene/javascript-sdk";
const notabene = new Notabene({
audience: "https://api.notabene.id",
clientId: NOTABENE_CLIENT_ID,
clientSecret: NOTABENE_CLIENT_SECRET,
vaspDID: MY_VASP_DID,
});
// Создание исходящего Travel Rule transfer
async function createTravelRuleTransfer(withdrawal: Withdrawal): Promise<string> {
const transfer = await notabene.transfers.create({
transactionAsset: withdrawal.asset,
transactionAmount: withdrawal.amount.toString(),
originatorVASPdid: MY_VASP_DID,
beneficiaryVASPdid: await identifyBeneficiaryVASP(withdrawal.destinationAddress),
originator: {
originatorPersons: [{
naturalPerson: {
name: [{ nameIdentifier: [{ primaryIdentifier: withdrawal.userLastName,
secondaryIdentifier: withdrawal.userFirstName }] }],
},
geographicAddress: [{ streetName: withdrawal.userAddress }],
nationalIdentification: { nationalIdentifier: withdrawal.userIdNumber },
}],
accountNumber: [MY_VASP_ADDRESS_MAPPING[withdrawal.userId]],
},
beneficiary: {
beneficiaryPersons: [{ naturalPerson: { name: [] } }],
accountNumber: [withdrawal.destinationAddress],
},
transactionBlockchainInfo: {
origin: withdrawal.fromAddress,
destination: withdrawal.destinationAddress,
},
});
return transfer.id;
}
Идентификация VASP по адресу
Ключевая задача: определить, принадлежит ли адрес назначения другому VASP (hosted wallet) или это unhosted wallet.
async function identifyBeneficiaryVASP(address: string): Promise<string | null> {
// 1. Notabene VASP lookup (база данных VASP адресов)
const vaspLookup = await notabene.addresses.lookup({ address });
if (vaspLookup.vasp) return vaspLookup.vasp.did;
// 2. Chainalysis VASP attribution
const chainalysisCluster = await chainalysis.getCluster(address);
if (chainalysisCluster?.type === "exchange" || chainalysisCluster?.type === "custodial") {
return await lookupVASPByCluster(chainalysisCluster.name);
}
// 3. Если не определили — unhosted wallet
return null;
}
Unhosted Wallet Policy
FATF допускает упрощённый подход для переводов на unhosted wallets (личные кошельки пользователей). Но многие регуляторы (ЕС, Швейцария) требуют дополнительные меры:
async function handleUnhostedWalletWithdrawal(
userId: string,
destinationAddress: string,
amount: number
): Promise<void> {
const usdAmount = await convertToUSD(amount);
if (usdAmount >= UNHOSTED_WALLET_VERIFICATION_THRESHOLD) {
// Требуем proof of wallet ownership
const ownershipProof = await requestWalletOwnershipProof(userId, destinationAddress);
if (!ownershipProof.verified) {
throw new Error("Wallet ownership verification failed");
}
// Записываем в travel rule файл (без отправки — нет receiving VASP)
await db.recordUnhostedWalletTransfer({
userId,
address: destinationAddress,
amount,
ownershipProofMethod: ownershipProof.method,
verifiedAt: new Date(),
});
}
await executeWithdrawal(userId, destinationAddress, amount);
}
// Верификация владения кошельком — message подписание
async function requestWalletOwnershipProof(
userId: string,
address: string
): Promise<OwnershipProof> {
const challenge = crypto.randomBytes(32).toString("hex");
// Сохраняем challenge, ждём подписи от пользователя
await db.storeWalletChallenge(userId, address, challenge);
// Пользователь подписывает challenge своим кошельком
// Верификация происходит в другом endpoint
return { pending: true, challenge };
}
Sunrise Issue
"Sunrise issue" — ситуация когда принимающий VASP не поддерживает Travel Rule. Варианты:
- Не отправлять пока нет подтверждения — строго compliant, плохой UX.
- Best efforts — отправить Travel Rule данные если можем, логировать попытки. Принято большинством регуляторов как temporary measure.
- Delay + retry — держать транзакцию в pending, повторять запрос к receiving VASP через интервалы.
Рекомендация: best efforts подход с полным логированием всех попыток и ответов.
Технический стек
| Компонент | Решение |
|---|---|
| Travel Rule messaging | Notabene SDK |
| VASP identification | Notabene + Chainalysis |
| Wallet ownership proof | EIP-191 message signing |
| Records storage | PostgreSQL + шифрование |
| Compliance dashboard | React admin panel |
Настройка FATF Travel Rule compliance с Notabene интеграцией, unhosted wallet policy и compliance dashboard: 3-5 недель.







