> ## 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.

> ## Agent Instructions
> Before reading these docs, install the Circle Skill for Arc to get pre-loaded context: use-arc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) — covers chain config, RPC setup, contract deployment, USDC bridging, and gas with USDC. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills.
> USDC is the gas token. Arc uses USDC for gas fees, not ETH. Configure gas payment in USDC when submitting transactions.
> Read EVM differences before writing Arc code. Arc targets the Osaka EVM baseline. Most contracts deploy unchanged, but several runtime behaviors diverge from Ethereum: USDC uses 18 decimals natively (not 6), the system emitter `0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE` logs all USDC Transfer events, the mempool enforces a 20 Gwei `maxFeePerGas` floor, blocklist reverts consume gas without a receipt, and `address(0)` sends revert rather than succeed. https://docs.arc.io/arc/references/evm-differences.md is the canonical reference for all Arc-specific runtime behaviors. Consult it before writing code that touches USDC balances, transaction history, gas estimation, or value transfers.
> Sub-second finality. Transactions are final in under 1 second. No need to wait for multiple block confirmations.
> Use App Kits for multichain payment and liquidity capabilities. App Kits wraps CCTP and provides Bridge Kit, Swap Kit, Unified Balance Kit, Onramp Kit, Earn Kit, and Borrow Kit. Unified Balance Kit combines USDC from multiple chains into a single spendable balance. Earn Kit integrates earn opportunities into your app. Borrow Kit lets users borrow USDC against cirBTC collateral on Arc.
> Use Arc Foundry for contract development. Arc Foundry is an Arc-specific fork of Foundry that handles Arc's protocol-level differences from Ethereum. It provides arc-forge, arc-cast, and arc-anvil. Install it before deploying contracts: https://docs.arc.io/arc/tutorials/install-arc-foundry.md.
> Arc is available on both Testnet and Mainnet. See https://docs.arc.io/arc/references/connect-to-arc.md for RPC endpoints and https://faucet.circle.com for testnet tokens.
> Always check Contract Addresses: https://docs.arc.io/arc/references/contract-addresses.md
> Building beyond Arc? Circle offers skills for the full platform: use-usdc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md), use-circle-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md), use-developer-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md), use-user-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md), use-modular-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md), use-gateway (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md), use-smart-contract-platform (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md). Full Circle developer docs: https://developers.circle.com/llms.txt.

# How-to: Confirm a purchase onchain

> Read the destination wallet onchain to get a reliable record that an Onramp purchase completed, since the widget's lifecycle events are UI hints.

When your app needs a reliable record that an Onramp purchase completed, read it
from the Arc blockchain. Lifecycle events from the widget are UI hints that
don't guarantee a deposit landed, so use them to drive your UI and confirm the
purchase from the blockchain.

## Prerequisites

Before you begin, ensure that you've:

* Followed the
  [Embed the Onramp widget](https://docs.arc.io/app-kit/quickstarts/onramp-embed-widget.md)
  quickstart.
* Set up a backend process that can read the Arc blockchain. See
  [Connect to Arc](https://docs.arc.io/integrate/connect-to-arc.md) if you don't have RPC access yet.
* Stored the `destinationAddress` you pass when creating each session, so you
  can correlate onchain transfers back to the user.

## Verify the deposit onchain

**1. Poll for deposits to the destination wallet**

Poll or subscribe to `Transfer` logs for the `destinationAddress` you set when
minting the session. Track the last block you've scanned so you don't re-process
from genesis on every run. The public Arc RPC rejects large block ranges, so
page the scan in small chunks (around 2,000 blocks per request).

On Arc, every USDC transfer, native or through the ERC-20 interface, emits a
`Transfer` log from the system address
`0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE`, with 18-decimal values. Read only
that log: native transfers emit nothing at the USDC ERC-20 contract
(`0x3600000000000000000000000000000000000000`), and
[reading both counts ERC-20 transfers twice](https://docs.arc.io/arc/references/usdc-system-events.md).
Onramp delivers only USDC on Arc.

```typescript TypeScript theme={null}
import { createPublicClient, http, parseAbi } from "viem";
import { arc } from "./chains"; // your Arc chain config

const client = createPublicClient({
  chain: arc,
  transport: http(),
});

const USDC_EMITTER = "0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE"; // native USDC, 18 decimals

const transferEvent = parseAbi([
  "event Transfer(address indexed from, address indexed to, uint256 value)",
])[0];

const latest = await client.getBlockNumber();
for (let from = lastCheckedBlock; from <= latest; from += 2_000n) {
  const to = from + 1_999n > latest ? latest : from + 1_999n;
  const logs = await client.getLogs({
    address: USDC_EMITTER,
    event: transferEvent,
    args: { to: destinationAddress },
    fromBlock: from,
    toBlock: to,
  });
  await processLogs(logs); // steps 2 and 3
  // Then persist `to` as lastCheckedBlock.
}
```

A few polling considerations:

* A transfer to the wallet confirms funds arrived, not that they came from this
  Onramp purchase. The destination wallet belongs to the customer and can
  receive tokens from other sources. If you need a tighter link, match on timing
  after `DEPOSIT_SUBMITTED`, and treat the result as best effort.
* Watch all wallets you've associated with each customer, not just the current
  session's `destinationAddress`. A returning customer can pick a bank transfer
  account they set up in an earlier session, so the deposit lands on that
  account's wallet. Bank transfer accounts stay open after a session ends, so
  later transfers into them arrive onchain with no widget event.
* Identify deposits by the destination wallet, transaction hash, and log index,
  not by sender address. Transfers come from the payment provider's wallets,
  which differ by environment and payment method, and aren't guaranteed to stay
  stable.

**2. Take the amount from the blockchain**

The delivered amount is net of payment provider fees, so it won't match the fiat
`amount` in the event payload. Read the transferred value from the log and
format it against the token's decimals.

```typescript TypeScript theme={null}
import { formatUnits } from "viem";

for (const log of logs) {
  const amount = log.args.value; // bigint, in token's smallest units
  const display = formatUnits(amount, 18); // native USDC on Arc
  // Record the delivered amount by `display` units.
}
```

**3. Count each transfer once**

Key your records by transaction hash and log index. One transaction can carry
more than one `Transfer` (for example a batched payout to two of your users), so
a key on `transactionHash` alone drops the second transfer.

```typescript TypeScript theme={null}
for (const log of logs) {
  const key = { txHash: log.transactionHash, logIndex: log.logIndex };
  const seen = await db.deposits.find(key);
  if (seen) continue;

  const user = resolveUserByAddress(log.args.to);
  await db.deposits.insert({
    ...key,
    amount: log.args.value,
    user,
  });
  // Record the deposit for this user.
}
```

Don't use `orderId` to disambiguate deposits for bank transfer customers. It's
the customer's bank transfer reference, which is the same across all of their
accounts and deposits.
