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

# How to: Monitor Blocklist Compliance

> Handle Arc's USDC blocklist enforcement in your compliance monitoring, including Memo and Multicall3From contract attribution.

Arc enforces USDC blocklist restrictions at three levels: pre-mempool rejection,
post-mempool runtime revert, and per-transfer checks. The Memo and
Multicall3From contracts route transactions while preserving the original
`msg.sender` via the CallFrom precompile—your monitoring must attribute these to
the original sender. Subscribe to `Blocklisted` and `UnBlocklisted` events to
maintain a local copy of the blocklist.

## Prerequisites

Before you begin:

* Access to an Arc RPC endpoint (`https://rpc.testnet.arc.network`) or WebSocket
  (`wss://rpc.testnet.arc.network`)
* Familiarity with Ethereum event log filtering and transaction tracing
* A local database or cache for storing blocklisted addresses
* Understanding of your regulatory obligations (AML/CFT screening requirements)

## Contracts and addresses

| Contract       | Address                                      | Purpose                                                |
| -------------- | -------------------------------------------- | ------------------------------------------------------ |
| USDC           | `0x3600000000000000000000000000000000000000` | Native stablecoin with built-in blocklist              |
| Memo           | `0x5294E9927c3306DcBaDb03fe70b92e01cCede505` | Attaches metadata to transfers; preserves `msg.sender` |
| Multicall3From | `0x522fAf9A91c41c443c66765030741e4AaCe147D0` | Batches multiple calls; preserves `msg.sender`         |

## Steps

### Step 1. Understand the three enforcement stages

Arc enforces the USDC blocklist at every point in a transaction's lifecycle:

| Stage                  | When it applies                             | Behavior                                                                                         |
| ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Pre-mempool            | Transaction submitted to RPC node           | If the sender is blocklisted, the RPC node rejects the transaction. It never enters the mempool. |
| Post-mempool runtime   | Transaction executes after entering mempool | If the sender becomes blocklisted between submission and execution, the transaction reverts.     |
| Runtime transfer check | `transfer` or `transferFrom` is called      | If either the `from` or `to` address is blocklisted, the call reverts.                           |

<Note>
  The pre-mempool check means blocklisted addresses cannot submit any
  transaction to Arc, not just USDC transfers. The runtime checks provide
  defense-in-depth for edge cases where blocklist state changes between
  submission and execution.
</Note>

### Step 2. Monitor blocklist events

Subscribe to the `Blocklisted` and `UnBlocklisted` events on the USDC contract
to maintain a real-time view of restricted addresses.

```typescript theme={null}
import { Contract, JsonRpcProvider } from "ethers";

const USDC_ADDRESS = "0x3600000000000000000000000000000000000000";
const provider = new JsonRpcProvider("https://rpc.testnet.arc.network");

const usdc = new Contract(
  USDC_ADDRESS,
  [
    "event Blocklisted(address indexed account)",
    "event UnBlocklisted(address indexed account)",
  ],
  provider,
);

// Subscribe to blocklist changes
usdc.on("Blocklisted", (account: string) => {
  console.log(`Address blocklisted: ${account}`);
  addToLocalBlocklist(account);
});

usdc.on("UnBlocklisted", (account: string) => {
  console.log(`Address unblocklisted: ${account}`);
  removeFromLocalBlocklist(account);
});
```

### Step 3. Include Memo and Multicall3From in your monitoring scope

The Memo and Multicall3From contracts use the CallFrom precompile to execute
calls on behalf of the original sender. The blocklist is still enforced (the
CallFrom precompile checks the original sender's blocklist status), but
compliance monitors must attribute activity correctly.

<Warning>
  If you only monitor direct `from` addresses in transaction receipts, you will
  miss the true sender for transactions routed through Memo or Multicall3From.
  You must inspect calls to these contracts and attribute them to the original
  `msg.sender`.
</Warning>

```typescript theme={null}
import { Interface, JsonRpcProvider, Log } from "ethers";

const MEMO_ADDRESS = "0x5294E9927c3306DcBaDb03fe70b92e01cCede505";
const MULTICALL3FROM_ADDRESS = "0x522fAf9A91c41c443c66765030741e4AaCe147D0";
const USDC_ADDRESS = "0x3600000000000000000000000000000000000000";
const TRANSFER_TOPIC =
  "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";

const provider = new JsonRpcProvider("https://rpc.testnet.arc.network");
const erc20Interface = new Interface([
  "event Transfer(address indexed from, address indexed to, uint256 value)",
]);

async function checkTransactionCompliance(txHash: string): Promise<void> {
  const tx = await provider.getTransaction(txHash);
  if (!tx) return;

  const receipt = await provider.getTransactionReceipt(txHash);
  if (!receipt) return;

  const originalSender = tx.from;

  // Flag if the transaction is routed through Memo or Multicall3From
  const isRoutedTransaction =
    tx.to?.toLowerCase() === MEMO_ADDRESS.toLowerCase() ||
    tx.to?.toLowerCase() === MULTICALL3FROM_ADDRESS.toLowerCase();

  if (isRoutedTransaction) {
    // Attribute all Transfer events in this transaction to the original sender
    const transfers = receipt.logs.filter(
      (log: Log) =>
        log.address.toLowerCase() === USDC_ADDRESS.toLowerCase() &&
        log.topics[0] === TRANSFER_TOPIC,
    );

    for (const log of transfers) {
      const parsed = erc20Interface.parseLog({
        topics: log.topics as string[],
        data: log.data,
      });
      if (!parsed) continue;

      // Screen the original sender, not the contract address
      await screenAddress(originalSender, txHash);
      await screenAddress(parsed.args.to as string, txHash);
    }
  }
}
```

### Step 4. Build a transaction decision tree

Use the following logic to determine whether a transaction involves a
blocklisted address:

| Check                           | Condition                                                               | Action                                                               |
| ------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------- |
| 1. Direct sender                | `tx.from` is in blocklist                                               | Flag transaction—will be rejected pre-mempool or reverted at runtime |
| 2. Transfer recipient           | `Transfer` event `to` is in blocklist                                   | Flag transaction—`transfer`/`transferFrom` will revert               |
| 3. Memo-routed sender           | `tx.to` is Memo contract AND `tx.from` is in blocklist                  | Flag—CallFrom precompile will reject                                 |
| 4. Multicall3From-routed sender | `tx.to` is Multicall3From AND `tx.from` is in blocklist                 | Flag—CallFrom precompile will reject                                 |
| 5. Routed transfer recipient    | `tx.to` is Memo or Multicall3From AND any Transfer `to` is in blocklist | Flag—runtime transfer check will revert                              |

```typescript theme={null}
interface ComplianceResult {
  flagged: boolean;
  reason?: string;
}

async function evaluateTransaction(txHash: string): Promise<ComplianceResult> {
  const tx = await provider.getTransaction(txHash);
  if (!tx) return { flagged: false };

  // Check 1: Direct sender
  if (await isBlocklisted(tx.from)) {
    return { flagged: true, reason: "Sender is blocklisted" };
  }

  const receipt = await provider.getTransactionReceipt(txHash);
  if (!receipt) return { flagged: false };

  // Check 2-5: Inspect Transfer events for blocklisted recipients
  const transfers = receipt.logs.filter(
    (log: Log) =>
      log.address.toLowerCase() === USDC_ADDRESS.toLowerCase() &&
      log.topics[0] === TRANSFER_TOPIC,
  );

  for (const log of transfers) {
    const parsed = erc20Interface.parseLog({
      topics: log.topics as string[],
      data: log.data,
    });
    if (!parsed) continue;

    const to = parsed.args.to as string;
    if (await isBlocklisted(to)) {
      return { flagged: true, reason: `Recipient ${to} is blocklisted` };
    }
  }

  return { flagged: false };
}
```

### Step 5. Integrate compliance vendor APIs

Connect your monitoring pipeline to Elliptic or TRM Labs for automated risk
scoring and sanctions screening. These vendors provide Arc-compatible APIs for
real-time transaction analysis.

```typescript theme={null}
// Example: screen an address against a compliance vendor API
async function screenAddress(
  address: string,
  txHash: string,
): Promise<boolean> {
  const response = await fetch(
    "https://api.your-compliance-vendor.com/screen",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.COMPLIANCE_API_KEY}`,
      },
      body: JSON.stringify({
        address,
        chain: "arc",
        transactionHash: txHash,
      }),
    },
  );

  const result = await response.json();
  return result.risk_level === "high";
}
```

For vendor-specific integration details, see
[Compliance vendors](/arc/tools/compliance-vendors).

## See also

* [Compliance vendors](/arc/tools/compliance-vendors)—Elliptic and TRM Labs
  integration details
* [Infrastructure overview](/integrate/infrastructure)—Arc architectural
  differences relevant to compliance monitoring
