Wallet Screening Flow
Cenário
Seção intitulada “Cenário”Usuário faz onboarding e declara uma carteira. Você quer:
- Validar formato do endereço
- Detectar chain automaticamente
- Fazer screening completo (sanctions + risk score)
- Tomar ação automática conforme tier
- Audit trail compliance
Stack mínima
Seção intitulada “Stack mínima”- Linguagem: qualquer (exemplos em curl + Node.js)
- Variáveis:
SENTINEX_API_KEY,SENTINEX_TENANT_ID
-
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;} -
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... -
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' };}} -
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.
-
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,}),}); -
Audit no seu lado
await yourAuditService.append({action: 'wallet_screening',resource: result.wallet_id,decision: decision.action,user: currentUser.id,raw_response: result,});
Padrão completo
Seção intitulada “Padrão completo”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;}Webhooks (opcional)
Seção intitulada “Webhooks (opcional)”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 blocklistalert.created: novo alerta KYT envolvendo wallet conhecida