> ## Documentation Index
> Fetch the complete documentation index at: https://docs.webacy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Consumer Wallet Security

> Build secure consumer wallets with address poisoning detection, transaction preview, EIP-712 signature analysis, and phishing URL screening under 500ms.

Your user pasted an address from their clipboard. It looked right—but it wasn't. The first four characters matched. The last four matched. But in the middle, a scammer had swapped characters. \$68 million in WBTC, gone in one transaction. This guide shows you how to protect your users from these attacks before they happen.

## Why Wallets Need Security

<CardGroup cols={3}>
  <Card title="Send Protection" icon="paper-plane">
    Verify addresses before your users send funds to scammers
  </Card>

  <Card title="Transaction Preview" icon="eye">
    Show users what they're signing before they sign it
  </Card>

  <Card title="Phishing Defense" icon="shield-virus">
    Block malicious dApps before they drain wallets
  </Card>
</CardGroup>

**Why wallet developers choose Webacy:**

* **Address poisoning detection** — The attack that stole \$68M in one transaction
* **Transaction simulation** — Show users exactly what will happen before signing
* **EIP-712 signature analysis** — Catch permit phishing attacks
* **URL screening** — Block known phishing sites in your dApp browser
* **Sub-500ms response times** — Security without blocking the user experience

***

## Prerequisites

Before implementing wallet security, ensure you have:

* A Webacy API key ([sign up here](https://developers.webacy.co/billing))
* Basic familiarity with REST APIs or the [Webacy SDK](../sdk/installation)
* Your wallet's send flow and transaction signing identified for integration points

***

## Send Flow Protection

The send flow is where users are most vulnerable. A simple address check can save them from losing everything.

### Quick Profile Before Sending

Before showing the send confirmation, check the destination address.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.webacy.com/addresses/0xd9A1C3788D81257612E2581A6ea0aDa244853a91/quick-profile?chain=eth" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  import { ThreatClient, Chain } from '@webacy-xyz/sdk';

  const client = new ThreatClient({ apiKey: process.env.WEBACY_API_KEY });

  const profile = await client.addresses.getQuickProfile(
    '0xd9A1C3788D81257612E2581A6ea0aDa244853a91',
    { chain: Chain.ETH }
  );

  console.log(`Risk score: ${profile.riskScore}`);
  console.log(`Risk level: ${profile.riskLevel}`);
  console.log(`Account age: ${profile.accountAge} days`);
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api.webacy.com/addresses/0xd9A1C3788D81257612E2581A6ea0aDa244853a91/quick-profile",
      params={"chain": "eth"},
      headers={"x-api-key": "YOUR_API_KEY"}
  )
  profile = response.json()
  ```
</CodeGroup>

**Use the response to guide users:**

| Risk Level | Action |
| - | - |
| `low` | Proceed normally |
| `medium` | Show caution banner |
| `high` | Require explicit confirmation |
| `critical` | Block and explain why |

### Address Poisoning Detection

Address poisoning attacks are devastating because they exploit user trust. Scammers create addresses that look nearly identical to legitimate ones.

<Warning>
  **Real Case**: In May 2024, a victim copied what they thought was their own address from transaction history. The attacker's address matched the first and last characters perfectly. \$68 million in WBTC, gone.
</Warning>

**Can you spot the difference?**

| | Address |
| - | - |
| ✅ Legitimate | `0xd9A1b0B1e1aE382DbDc898Ea68012FfcB2853a91` |
| ❌ Attacker | `0xd9A1C3788D81257612E2581A6ea0aDa244853a91` |

Both start with `0xd9A1`. Both end with `53a91`. The scammer generated a vanity address specifically to match the victim's transaction history.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.webacy.com/addresses/0xd9A1C3788D81257612E2581A6ea0aDa244853a91/poisoning?chain=eth" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const poisoning = await client.addresses.checkPoisoning(
    '0xd9A1C3788D81257612E2581A6ea0aDa244853a91',
    { chain: Chain.ETH }
  );

  if (poisoning.is_poisoned) {
    // Block the transaction and show warning
    showPoisoningWarning({
      message: 'This address appears to be part of an address poisoning attack.',
      similarAddresses: poisoning.similar_addresses,
      recommendation: 'Verify the full address character by character.'
    });
  }
  ```

  ```python Python theme={null}
  response = requests.get(
      "https://api.webacy.com/addresses/0xd9A1C3788D81257612E2581A6ea0aDa244853a91/poisoning",
      params={"chain": "eth"},
      headers={"x-api-key": "YOUR_API_KEY"}
  )
  poisoning = response.json()
  ```
</CodeGroup>

<Tip>
  **Integration tip**: Run this check whenever a user pastes an address. The 300ms latency is invisible to users—but catching a poisoning attack is priceless.
</Tip>

### Sanctions Screening

Prevent users from sending to sanctioned addresses and protect your platform from regulatory risk.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.webacy.com/addresses/sanctioned/0x098B716B8Aaf21512996dC57EB0615e2383E2f96?chain=eth" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const sanctions = await client.addresses.checkSanctioned(
    '0x098B716B8Aaf21512996dC57EB0615e2383E2f96',
    { chain: Chain.ETH }
  );

  if (sanctions.is_sanctioned) {
    // Block transaction to sanctioned address
    blockTransaction({
      reason: 'This address is on the OFAC sanctions list.',
      canOverride: false
    });
  } else if (sanctions.sanctions_status === 'unknown') {
    requireManualReview({
      reason: 'Sanctions screening could not be completed.'
    });
  }
  ```

  ```python Python theme={null}
  response = requests.get(
      "https://api.webacy.com/addresses/sanctioned/0x098B716B8Aaf21512996dC57EB0615e2383E2f96",
      params={"chain": "eth"},
      headers={"x-api-key": "YOUR_API_KEY"}
  )
  ```
</CodeGroup>

***

## Transaction Preview

Show users exactly what they're about to sign. Transaction simulation catches drainers, malicious approvals, and unexpected asset transfers.

### Pre-Signing Simulation

Simulate the transaction before the user signs to reveal what will actually happen.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.webacy.com/scan/0xUserAddress.../transactions" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "tx": {
        "from": "0xUserAddress...",
        "raw": "0x02f00180843b9aca008506fc23ac00830186a094d8da6bf26964af9d7eed9e03e53415d37aa96045872386f26fc1000080c0"
      },
      "chain": 1,
      "domain": "app.example.com"
    }'
  ```

  ```typescript TypeScript theme={null}
  import { Transaction } from 'ethers';

  // `raw` is the serialized unsigned transaction the user is about to sign
  const raw = Transaction.from({
    type: 2,
    chainId: 1,
    to: '0xContractAddress...',
    data: '0xTransactionData...',
    value: 0n,
    nonce: 12,
    gasLimit: 100_000n,
    maxFeePerGas: 30_000_000_000n,
    maxPriorityFeePerGas: 1_000_000_000n,
  }).unsignedSerialized;

  const result = await client.scan.scanTransaction('0xUserAddress...', {
    tx: { from: '0xUserAddress...', raw },
    chain: 1, // Ethereum mainnet
    domain: 'app.example.com',
  });

  // One item per simulated asset movement or approval
  const { simulation } = result as unknown as {
    simulation: Array<{
      txData: { changeType: 'TRANSFER' | 'APPROVE'; rawAmount?: string; symbol?: string; assetType?: string; counterpartyAddress: string };
      counterpartyRisk: { high?: number; overallRisk?: number; issues?: Array<{ tags: Array<{ name: string }> }> };
      assetRisk: { high?: number };
    }>;
  };

  for (const item of simulation) {
    const { txData, counterpartyRisk, assetRisk } = item;
    console.log(`${txData.changeType}: ${txData.rawAmount ?? ''} ${txData.symbol ?? txData.assetType} → ${txData.counterpartyAddress}`);
    if ((counterpartyRisk.high ?? 0) > 0 || (assetRisk.high ?? 0) > 0) {
      console.warn('Flagged:', counterpartyRisk.issues?.flatMap((i) => i.tags.map((t) => t.name)));
    }
  }
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.webacy.com/scan/0xUserAddress.../transactions",
      headers={
          "x-api-key": "YOUR_API_KEY",
          "Content-Type": "application/json"
      },
      json={
          "tx": {
              "from": "0xUserAddress...",
              "raw": "0x02f00180843b9aca008506fc23ac00830186a094d8da6bf26964af9d7eed9e03e53415d37aa96045872386f26fc1000080c0"
          },
          "chain": 1,
          "domain": "app.example.com"
      }
  )
  simulation = response.json()
  ```
</CodeGroup>

**Key response fields:**

| Field | Description |
| - | - |
| `simulation[].txData` | Each asset movement (`TRANSFER`) or approval (`APPROVE`) the transaction would cause: amount, token, recipient / spender |
| `simulation[].counterpartyRisk` | Risk profile of the recipient / spender — `high > 0` is a known drainer, hacker or sanctioned address |
| `simulation[].assetRisk` | Risk profile of the token contract (`address` is `null` for ETH) |
| `simulation[0].functionRisk` | Present when the calldata calls a known-risky function |
| `descriptor` | Signed TLV descriptor for hardware-wallet integrations |

An empty `simulation` array, or a party with only `address` and no score, is *inconclusive* — never treat it as safe.

### EIP-712 Signature Analysis

Permit signatures are the most dangerous attack vector. A user can lose everything without sending a single transaction—just by signing a message.

<Warning>
  **How Permit Phishing Works**: The user signs what looks like a simple message. In reality, they've authorized a contract to spend all their tokens. Hours or days later, the attacker drains everything.
</Warning>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.webacy.com/scan/0xUserAddress.../eip712" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "msg": {
        "from": "0xUserAddress...",
        "data": {
          "types": {
            "EIP712Domain": [
              {"name": "name", "type": "string"},
              {"name": "version", "type": "string"},
              {"name": "chainId", "type": "uint256"},
              {"name": "verifyingContract", "type": "address"}
            ],
            "Permit": [
              {"name": "owner", "type": "address"},
              {"name": "spender", "type": "address"},
              {"name": "value", "type": "uint256"},
              {"name": "nonce", "type": "uint256"},
              {"name": "deadline", "type": "uint256"}
            ]
          },
          "primaryType": "Permit",
          "domain": {
            "name": "USD Coin",
            "version": "2",
            "chainId": 1,
            "verifyingContract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
          },
          "message": {
            "owner": "0xUserAddress...",
            "spender": "0xSuspiciousAddress...",
            "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
            "nonce": 0,
            "deadline": 1893456000
          }
        }
      },
      "domain": "suspicious-site.com"
    }'
  ```

  ```typescript TypeScript theme={null}
  const result = await client.scan.scanEip712(
    '0xUserAddress...',
    {
      msg: {
        from: '0xUserAddress...',
        data: {
          types: {
            EIP712Domain: [
              { name: 'name', type: 'string' },
              { name: 'version', type: 'string' },
              { name: 'chainId', type: 'uint256' },
              { name: 'verifyingContract', type: 'address' },
            ],
            Permit: [
              { name: 'owner', type: 'address' },
              { name: 'spender', type: 'address' },
              { name: 'value', type: 'uint256' },
              { name: 'nonce', type: 'uint256' },
              { name: 'deadline', type: 'uint256' },
            ],
          },
          primaryType: 'Permit',
          domain: {
            name: 'USD Coin',
            version: '2',
            chainId: 1, // Ethereum mainnet only
            verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
          },
          message: {
            owner: '0xUserAddress...',
            spender: '0xSuspiciousAddress...',
            value: '115792089237316195423570985008687907853269984665640564039457584007913129639935',
            nonce: 0,
            deadline: 1893456000,
          },
        },
      },
      domain: 'suspicious-site.com',
    }
  );

  // The spender named in the permit is scored as the counterparty; the dApp
  // domain you passed comes back as `domainRisk`.
  const { simulation } = result as unknown as {
    simulation: {
      counterpartyRisk?: { address: string; high?: number; overallRisk?: number; issues?: Array<{ tags: Array<{ name: string }> }> };
      domainRisk?: { riskLevel: 'low' | 'medium' | 'high' | 'unknown' };
    };
  };

  if ((simulation.counterpartyRisk?.high ?? 0) > 0 || simulation.domainRisk?.riskLevel === 'high') {
    // Block signature and warn user
    showSignatureWarning({
      spender: simulation.counterpartyRisk?.address,
      tags: simulation.counterpartyRisk?.issues?.flatMap((i) => i.tags.map((t) => t.name)),
      message: 'This signature could give unlimited access to your tokens.'
    });
  }
  ```

  ```python Python theme={null}
  response = requests.post(
      "https://api.webacy.com/scan/0xUserAddress.../eip712",
      headers={
          "x-api-key": "YOUR_API_KEY",
          "Content-Type": "application/json"
      },
      json={
          "msg": {
              "from": "0xUserAddress...",
              "data": {
                  "types": {
                      "EIP712Domain": [
                          {"name": "name", "type": "string"},
                          {"name": "version", "type": "string"},
                          {"name": "chainId", "type": "uint256"},
                          {"name": "verifyingContract", "type": "address"}
                      ],
                      "Permit": [
                          {"name": "owner", "type": "address"},
                          {"name": "spender", "type": "address"},
                          {"name": "value", "type": "uint256"},
                          {"name": "nonce", "type": "uint256"},
                          {"name": "deadline", "type": "uint256"}
                      ]
                  },
                  "primaryType": "Permit",
                  "domain": {
                      "name": "USD Coin",
                      "version": "2",
                      "chainId": 1,
                      "verifyingContract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
                  },
                  "message": {
                      "owner": "0xUserAddress...",
                      "spender": "0xSuspiciousAddress...",
                      "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
                      "nonce": 0,
                      "deadline": 1893456000
                  }
              }
          },
          "domain": "suspicious-site.com"
      }
  )
  result = response.json()
  spender = result["simulation"].get("counterpartyRisk") or {}
  if spender.get("high", 0) > 0 or (result["simulation"].get("domainRisk") or {}).get("riskLevel") == "high":
      print("DANGEROUS SIGNATURE:", [t["name"] for i in spender.get("issues", []) for t in i["tags"]])
  ```
</CodeGroup>

<Tip>
  **The max uint256 red flag**: If you see `value: "115792089237316195423570985008687907853269984665640564039457584007913129639935"`, that's unlimited approval. Always flag this for users.
</Tip>

***

## Phishing Protection

If your wallet has a dApp browser, you need URL screening.

### URL Scanning

Check URLs before allowing users to connect their wallet.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.webacy.com/url" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"url": "https://uniswap-claim.com"}'
  ```

  ```typescript TypeScript theme={null}
  const result = await client.url.check('https://uniswap-claim.com');

  // riskLevel: 'high' (known phishing / blocklisted), 'medium' (looks like a
  // known project's domain), 'low' (known project site), 'unknown' (no verdict)
  if (result.riskLevel === 'high') {
    // Block the connection
    blockDappConnection({
      url: 'https://uniswap-claim.com',
      reason: result.description,
    });
  }
  ```

  ```python Python theme={null}
  response = requests.post(
      "https://api.webacy.com/url",
      headers={
          "x-api-key": "YOUR_API_KEY",
          "Content-Type": "application/json"
      },
      json={"url": "https://uniswap-claim.com"}
  )
  ```
</CodeGroup>

The response is `{ "riskLevel", "description", "message" }`. See the [dApp browser guide](/apps/dapp-browser-security#phishing-detection) for what each `riskLevel` means. `unknown` means no verdict, not safe.

***

## Approval Management

Help users understand and manage their existing token approvals.

### Get Risky Approvals

Show users their current approvals and highlight dangerous ones.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.webacy.com/wallets/0xUserAddress.../approvals?chain=eth" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const approvals = await client.wallets.getApprovals(
    '0xUserAddress...',
    { chain: Chain.ETH }
  );

  // Categorize approvals by risk
  const riskyApprovals = approvals.approvals.filter(
    a => a.isUnlimited || a.spenderRisk === 'high'
  );

  console.log(`Found ${riskyApprovals.length} risky approvals`);

  for (const approval of riskyApprovals) {
    console.log(`${approval.symbol} approved to ${approval.spenderName || approval.spender}`);
    console.log(`  Amount: ${approval.isUnlimited ? 'UNLIMITED' : approval.amount}`);
  }
  ```

  ```python Python theme={null}
  response = requests.get(
      "https://api.webacy.com/wallets/0xUserAddress.../approvals",
      params={"chain": "eth"},
      headers={"x-api-key": "YOUR_API_KEY"}
  )
  ```
</CodeGroup>

**Help users clean up:**

* Show unlimited approvals prominently
* Flag approvals to contracts that no longer exist
* Identify approvals to known drainer contracts
* Provide easy revoke functionality

***

## Complete Integration Workflow

### Send Flow

```mermaid theme={null}
flowchart TD
    A[User Enters Address] --> B{Address Poisoning Check}
    B -->|Poisoned| C["BLOCK + Show Warning"]
    B -->|Clear| D{Sanctions Check}
    D -->|Sanctioned| E["BLOCK + Compliance Notice"]
    D -->|Clear| F{Quick Profile}
    F -->|High Risk| G["WARN + Require Confirmation"]
    F -->|Medium Risk| H["Show Caution Banner"]
    F -->|Low Risk| I[Proceed to Confirmation]
    G --> I
    H --> I
```

### Transaction Signing Flow

```mermaid theme={null}
flowchart TD
    A[Transaction Request] --> B{Is EIP-712 Signature?}
    B -->|Yes| C[Scan EIP-712]
    B -->|No| D[Simulate Transaction]
    C --> E{Risk Level?}
    D --> E
    E -->|Critical| F["BLOCK Transaction"]
    E -->|High| G["Show Strong Warning"]
    E -->|Medium| H["Show Asset Changes + Caution"]
    E -->|Low| I["Show Asset Changes"]
    G --> J{User Confirms?}
    H --> J
    I --> K[Sign Transaction]
    J -->|Yes| K
    J -->|No| L[Cancel]
```

### Full TypeScript Implementation

<Accordion title="Complete Wallet Security Module">
  ```typescript theme={null}
  import { ThreatClient, Chain, type ScanChainId, type ScanEIP712TypedData } from '@webacy-xyz/sdk';

  const client = new ThreatClient({
    apiKey: process.env.WEBACY_API_KEY,
    defaultChain: Chain.ETH,
  });

  // Send flow protection
  async function validateSendAddress(address: string, chain: Chain) {
    // 1. Address poisoning (most critical)
    const poisoning = await client.addresses.checkPoisoning(address, { chain });
    if (poisoning.is_poisoned) {
      return {
        allowed: false,
        severity: 'critical',
        reason: 'Address poisoning attack detected',
        details: 'This address looks similar to legitimate addresses and may be part of a scam.'
      };
    }

    // 2. Sanctions check (compliance)
    const sanctions = await client.addresses.checkSanctioned(address, { chain });
    if (sanctions.is_sanctioned) {
      return {
        allowed: false,
        severity: 'critical',
        reason: 'Sanctioned address',
        details: 'This address is on the OFAC sanctions list.'
      };
    }
    if (sanctions.sanctions_status !== 'clean') {
      return {
        allowed: false,
        severity: 'high',
        reason: 'Sanctions screening unavailable',
        details: 'The address could not be authoritatively screened and requires manual review.'
      };
    }

    // 3. Quick profile (risk assessment)
    const profile = await client.addresses.getQuickProfile(address, { chain });

    if (profile.riskLevel === 'critical') {
      return {
        allowed: false,
        severity: 'critical',
        reason: 'Extremely high risk address',
        riskScore: profile.riskScore
      };
    }

    if (profile.riskLevel === 'high') {
      return {
        allowed: true,
        severity: 'high',
        requireConfirmation: true,
        reason: 'High risk address detected',
        riskScore: profile.riskScore
      };
    }

    return {
      allowed: true,
      severity: profile.riskLevel,
      riskScore: profile.riskScore
    };
  }

  // Shape of the scan responses (a party that could not be scored carries only `address`)
  interface AddressRisk {
    address: string;
    overallRisk?: number;
    high?: number;
    medium?: number;
    issues?: Array<{ tags: Array<{ key: string; name: string }> }>;
  }
  interface TxScanResponse {
    simulation: Array<{
      counterpartyRisk: AddressRisk;
      assetRisk: AddressRisk;
      txData: { changeType: 'TRANSFER' | 'APPROVE'; assetType?: string; rawAmount?: string; symbol?: string; counterpartyAddress: string };
      functionRisk?: { functionName: string; description: string };
    }>;
  }
  interface Eip712ScanResponse {
    simulation: {
      counterpartyRisk?: AddressRisk;
      domainRisk?: { riskLevel: 'low' | 'medium' | 'high' | 'unknown' };
      functionRisk?: { functionName: string; risks: string[] };
    };
  }
  const isFlagged = (r?: AddressRisk) => (r?.high ?? 0) > 0;
  const isUnscored = (r?: AddressRisk) => !!r && r.overallRisk === undefined;

  // Transaction preview — `raw` is the serialized unsigned transaction
  // (ethers `Transaction.from({...}).unsignedSerialized`, viem `serializeTransaction`)
  async function previewTransaction(
    userAddress: string,
    raw: string,
    chain: ScanChainId,
    dappDomain?: string
  ) {
    const result = (await client.scan.scanTransaction(userAddress, {
      tx: { from: userAddress, raw },
      chain,
      domain: dappDomain,
    })) as unknown as TxScanResponse;

    const flagged = result.simulation.some((i) => isFlagged(i.counterpartyRisk) || isFlagged(i.assetRisk));
    const unscored = result.simulation.some((i) => isUnscored(i.counterpartyRisk));
    const warnings = result.simulation.flatMap((i) => [
      ...(i.counterpartyRisk.issues?.flatMap((issue) => issue.tags.map((t) => t.name)) ?? []),
      ...(i.functionRisk ? [`Risky function: ${i.functionRisk.functionName}`] : []),
    ]);

    return {
      assetChanges: result.simulation.map((i) => i.txData),
      warnings,
      // An empty simulation or an unscored recipient is inconclusive, not safe
      shouldBlock: flagged,
      requiresExtraConfirmation: !flagged && (unscored || result.simulation.length === 0),
    };
  }

  // Signature analysis
  async function analyzeSignature(
    userAddress: string,
    typedData: ScanEIP712TypedData,
    dappDomain: string
  ) {
    const result = (await client.scan.scanEip712(userAddress, {
      msg: {
        from: userAddress,
        data: typedData,
      },
      domain: dappDomain,
    })) as unknown as Eip712ScanResponse;

    const { counterpartyRisk, domainRisk, functionRisk } = result.simulation;
    return {
      spender: counterpartyRisk?.address,
      spenderTags: counterpartyRisk?.issues?.flatMap((i) => i.tags.map((t) => t.name)) ?? [],
      shouldBlock: isFlagged(counterpartyRisk) || domainRisk?.riskLevel === 'high',
      isPermit: typedData.primaryType.toLowerCase().includes('permit'),
      riskyFunction: functionRisk?.functionName,
    };
  }

  // URL screening for dApp browser
  async function checkDappUrl(url: string) {
    const result = await client.url.check(url);

    return {
      // Only a known project site is safe; 'unknown' is unverified.
      safe: result.riskLevel === 'low',
      block: result.riskLevel === 'high',
      riskLevel: result.riskLevel,
      reason: result.description,
    };
  }
  ```
</Accordion>

***

## Example Addresses for Testing

### Address Poisoning

| Address | Chain | Description |
| - | - | - |
| `0xd9A1C3788D81257612E2581A6ea0aDa244853a91` | ETH | Known poisoned address (\$68M WBTC attack) |
| `0x5f90e59d0a03fd2f8c56b8cc896c5b42594eb3a0` | ETH | \$50M address poisoning drain |

### Sanctioned Addresses

| Address | Chain | Attribution |
| - | - | - |
| `0x098B716B8Aaf21512996dC57EB0615e2383E2f96` | ETH | Lazarus Group (Ronin Bridge) |
| `0x566f827a4988d4a3eb9da469d8d3d0b536da196e` | ETH | OFAC SDN List |

### Phishing & Drainers

| Address | Chain | Attribution |
| - | - | - |
| `0x84672cc56b6dad30cfa5f9751d9ccae6c39e29cd` | ETH | Permit phishing drainer |
| `0xe7d13137923142a0424771e1778865b88752b3c7` | ETH | WalletConnect phishing campaign |
| `0x624Fc3Dc249E37E8BFd3e834C4dF81Ff2dA1D0Ca` | BSC | Malicious Permit scammer |

### Clean Addresses (for comparison)

| Address | Chain | Description |
| - | - | - |
| `0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045` | ETH | Vitalik's wallet (low risk) |

***

## API Quick Reference

| Endpoint | Use Case | Response Time |
| - | - | - |
| `GET /addresses/{address}/quick-profile` | Fast risk assessment | \~200ms |
| `GET /addresses/{address}/poisoning` | Address poisoning check | \~300ms |
| `GET /addresses/sanctioned/{address}` | Sanctions screening | \~100ms |
| `POST /scan/{fromAddress}/transactions` | Transaction simulation | \~1–3s (cold), \~50ms (cached) |
| `POST /scan/{fromAddress}/eip712` | Signature analysis | \~1–3s (cold), \~50ms (cached) |
| `POST /url` | URL screening | \~0.5s (p95 \~1s) |
| `GET /wallets/{address}/approvals` | Token approvals | \~400ms |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Get Your API Key" icon="key" href="https://developers.webacy.co/billing">
    Start protecting users today
  </Card>

  <Card title="API Reference" icon="book" href="../api-reference/introduction">
    Complete endpoint documentation
  </Card>

  <Card title="Install the SDK" icon="download" href="../sdk/installation">
    TypeScript SDK for faster integration
  </Card>

  <Card title="Risk Tags Reference" icon="tags" href="../glossary/threats-and-labels">
    Understand what each risk means
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.