Pular para o conteúdo

Wallet Screening Flow

Usuário faz onboarding e declara uma carteira. Você quer:

  1. Validar formato do endereço
  2. Detectar chain automaticamente
  3. Fazer screening completo (sanctions + risk score)
  4. Tomar ação automática conforme tier
  5. Audit trail compliance
  • Linguagem: qualquer (exemplos em curl + Node.js)
  • Variáveis: SENTINEX_API_KEY, SENTINEX_TENANT_ID
  1. Validar + detectar chain

    function detectChain(address) {
    if (/^0x[a-fA-F0-9]{40}$/.test(address)) return 'ETHEREUM';
    if (/^bc1[a-z0-9]{39,87}$/.test(address)) return 'BITCOIN';
    if (/^[13][a-km-zA-HJ-NP-Z0-9]{25,34}$/.test(address)) return 'BITCOIN';
    if (/^T[A-HJ-NP-Z0-9]{33}$/.test(address)) return 'TRON';
    if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)) return 'SOLANA';
    return null;
    }
  2. Screening

    // Superfície máquina-a-máquina: POST /vasp/screenings com X-API-Key.
    // (POST /wallets/screen é rota do dashboard, exige JWT + address_hash.)
    const response = await fetch('https://api.crypto.sentinexrisk.com/api/v1/vasp/screenings', {
    method: 'POST',
    headers: {
    'X-API-Key': process.env.SENTINEX_API_KEY,
    'Content-Type': 'application/json',
    },
    body: JSON.stringify({
    direction: 'DEPOSIT',
    asset: 'BTC',
    chain: blockchain.toLowerCase(),
    counterparty_address: address,
    amount: 0,
    }),
    });
    const envelope = await response.json();
    if (!envelope.success) throw new Error(`Screening failed: ${JSON.stringify(envelope.error)}`);
    const result = envelope.data; // decision, risk_score, risk_level, sanctions, rule_hits...
  3. Decisão automática

    function decideAction(result) {
    if (result.metadata?.sanctioned || result.sanctions_exposure > 0.1) {
    return { action: 'BLOCK', reason: `Sanção ${result.metadata?.sanction_program || 'OFAC'}`, sar_required: true };
    }
    if (result.mixer_exposure > 0.7) {
    return { action: 'REVIEW', reason: `Alta exposição a mixer (${(result.mixer_exposure * 100).toFixed(0)}%)` };
    }
    switch (result.risk_tier) {
    case 'GREEN': return { action: 'APPROVE' };
    case 'AMBER': return { action: 'APPROVE_WITH_MONITORING' };
    case 'RED': return { action: 'REVIEW', reason: `Risk score ${result.risk_score}` };
    case 'CRITICAL': return { action: 'BLOCK', reason: 'Risk crítico', sar_required: true };
    default: return { action: 'REVIEW', reason: 'Tier desconhecido' };
    }
    }
  4. Aplicar ação: BLOCK

    await fetch(`https://api.crypto.sentinexrisk.com/api/v1/wallets/${result.wallet_id}/blocklist`, {
    method: 'POST',
    headers: { /* ... */ },
    body: JSON.stringify({ reason: decision.reason }),
    });

    Backend faz audit trail FAIL-CLOSED (PR #173): se audit não persistir, blocklist é revertido via optimistic locking.

  5. SAR (se decision.sar_required)

    await fetch('https://api.crypto.sentinexrisk.com/api/v1/agent/sar-draft', {
    method: 'POST',
    headers: { /* ... */ },
    body: JSON.stringify({
    subject_type: 'kyt_alert_cluster',
    subject_id: result.wallet_id,
    }),
    });
  6. Audit no seu lado

    await yourAuditService.append({
    action: 'wallet_screening',
    resource: result.wallet_id,
    decision: decision.action,
    user: currentUser.id,
    raw_response: result,
    });
async function onboardWallet(rawAddress) {
const blockchain = detectChain(rawAddress);
if (!blockchain) throw new Error('Invalid address format');
const result = await screen(rawAddress, blockchain);
const decision = decideAction(result);
if (decision.action === 'BLOCK') {
await blockWallet(result.wallet_id, decision.reason);
}
if (decision.sar_required) {
await draftSAR(result.wallet_id);
}
await audit({ action: 'wallet_screening', wallet: result.wallet_id, decision });
return decision;
}

Em vez de polling, configure webhook em Settings → Webhooks. Eventos:

  • wallet.risk_changed: score mudou (ex: nova sanção apareceu)
  • wallet.blocklisted: outra fonte adicionou ao blocklist
  • alert.created: novo alerta KYT envolvendo wallet conhecida