Skip to main content

Wallet Screening

NowRamp automatically screens all customer wallets for AML (Anti-Money Laundering) compliance using Chainalysis. This guide explains how wallet screening works and how to handle screening results.

How It Works

When a customer adds a wallet:
  1. Address Validation - Format is validated for the blockchain
  2. Screening Request - Address sent to Chainalysis
  3. Risk Assessment - Chainalysis returns risk score and flags
  4. Status Update - Wallet marked as clear, flagged, or blocked

Adding Wallets

const wallet = await fetch(
  `https://api.nowramp.com/v1/customers/${customerId}/wallets`,
  {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      address: '0x742d35Cc6634C0532925a3b844Bc9e7595f5bE21',
      currency: 'ETH',
      label: 'My Main Wallet',
    }),
  }
);

// Wallet created with screeningStatus: 'pending'
console.log(wallet.data.screeningStatus); // 'pending'

Screening Statuses

StatusDescriptionCan Transact
pendingScreening in progressNo
clearNo risk indicatorsYes
flaggedSome risk factors (review)Depends on policy
blockedHigh risk - blockedNo
errorScreening failedNo

Checking Wallet Status

async function waitForScreening(walletId, maxAttempts = 10) {
  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(
      `https://api.nowramp.com/v1/wallets/${walletId}`,
      { headers: { 'X-API-Key': apiKey } }
    );
    const wallet = await response.json();

    if (wallet.data.screeningStatus !== 'pending') {
      return wallet.data;
    }

    await sleep(2000); // Wait 2 seconds
  }

  throw new Error('Screening timeout');
}

// Usage
const wallet = await waitForScreening('wallet_xyz789');
if (wallet.screeningStatus === 'clear') {
  console.log('Wallet is ready to use');
} else if (wallet.screeningStatus === 'blocked') {
  console.log('Wallet was blocked:', wallet.screeningResult.flags);
}

Handling Screening Results

app.post('/webhooks/ramp', (req, res) => {
  const { type, data } = req.body;

  if (type === 'wallet.screened') {
    handleWalletScreened(data);
  } else if (type === 'wallet.blocked') {
    handleWalletBlocked(data);
  }

  res.status(200).send('OK');
});

async function handleWalletScreened(wallet) {
  if (wallet.screeningStatus === 'clear') {
    // Wallet is ready - update UI
    await notifyUser(wallet.customerId, 'Your wallet is verified!');
  } else if (wallet.screeningStatus === 'flagged') {
    // Review needed
    await createReviewTask(wallet);
  }
}

async function handleWalletBlocked(wallet) {
  // High risk wallet blocked
  await notifyUser(
    wallet.customerId,
    'This wallet cannot be used. Please add a different wallet.'
  );

  // Log for compliance
  await logComplianceEvent({
    type: 'wallet_blocked',
    walletId: wallet.id,
    customerId: wallet.customerId,
    flags: wallet.screeningResult.flags,
  });
}

Risk Scores and Levels

LevelScore RangeAction
low0.0 - 0.3Auto-approve
medium0.3 - 0.7Review required
high0.7 - 1.0Auto-block
function getRiskAction(wallet) {
  const { riskLevel, riskScore } = wallet.screeningResult;

  switch (riskLevel) {
    case 'low':
      return { action: 'approve', requiresReview: false };
    case 'medium':
      return { action: 'review', requiresReview: true };
    case 'high':
      return { action: 'block', requiresReview: false };
  }
}

Risk Flags

Common flags returned by Chainalysis:
FlagDescriptionSeverity
sanctionsAddress on OFAC/sanctions listCritical
ransomwareLinked to ransomwareCritical
darknetDarknet market activityHigh
mixerMixing service usageMedium
gamblingGambling platformLow
exchangeKnown exchange addressInfo
function assessRiskFlags(flags) {
  const criticalFlags = ['sanctions', 'ransomware', 'terrorism'];
  const highFlags = ['darknet', 'fraud', 'scam'];

  if (flags.some(f => criticalFlags.includes(f))) {
    return { action: 'block', reason: 'Critical risk indicator' };
  }

  if (flags.some(f => highFlags.includes(f))) {
    return { action: 'review', reason: 'High risk indicator' };
  }

  return { action: 'approve', reason: 'No significant risk' };
}

Widget Integration

The widget handles wallet screening automatically:
const widget = new RampWidget({
  apiKey: 'your_api_key',
  projectId: 'your_project_id',
  externalUserId: 'user_123',
});

widget.on('walletScreeningStarted', (wallet) => {
  console.log('Screening wallet:', wallet.address);
});

widget.on('walletScreeningComplete', (result) => {
  if (result.status === 'blocked') {
    console.log('Wallet blocked:', result.flags);
  }
});

Manual Review

For flagged wallets, implement a review process:
// Admin endpoint to review flagged wallets
app.post('/admin/wallets/:id/review', async (req, res) => {
  const { id } = req.params;
  const { decision, notes } = req.body;

  // Record the review
  await db.walletReviews.insert({
    walletId: id,
    decision, // 'approve' or 'block'
    notes,
    reviewedBy: req.user.id,
    reviewedAt: new Date(),
  });

  // Update wallet status
  if (decision === 'approve') {
    await updateWalletStatus(id, 'clear');
  } else {
    await updateWalletStatus(id, 'blocked');
  }

  res.json({ success: true });
});

Sandbox Testing

In sandbox, use these test addresses:
Address PatternResult
Ends with ...000Clear (low risk)
Ends with ...111Flagged (medium risk)
Ends with ...999Blocked (high risk)
// Test addresses
const testAddresses = {
  clear: '0x742d35Cc6634C0532925a3b844Bc9e7595000',
  flagged: '0x742d35Cc6634C0532925a3b844Bc9e7595111',
  blocked: '0x742d35Cc6634C0532925a3b844Bc9e7595999',
};