# Table of Contents - [Welcome to Arc docs - Arc Docs](#welcome-to-arc-docs-arc-docs) - [Unknown](#unknown) - [Arc MCP server - Arc Docs](#arc-mcp-server-arc-docs) - [App Kit SDK: Bridge - Arc Docs](#app-kit-sdk-bridge-arc-docs) - [How swap fees work - Arc Docs](#how-swap-fees-work-arc-docs) - [How bridge fees work - Arc Docs](#how-bridge-fees-work-arc-docs) - [App Kits - Arc Docs](#app-kits-arc-docs) - [AI skills for building on Arc - Arc Docs](#ai-skills-for-building-on-arc-arc-docs) - [How Unified Balance fees work - Arc Docs](#how-unified-balance-fees-work-arc-docs) - [Quickstart: Send tokens across wallets on the same blockchain - Arc Docs](#quickstart-send-tokens-across-wallets-on-the-same-blockchain-arc-docs) - [Error recovery and troubleshooting for bridges - Arc Docs](#error-recovery-and-troubleshooting-for-bridges-arc-docs) - [Quickstart: Swap tokens on a blockchain - Arc Docs](#quickstart-swap-tokens-on-a-blockchain-arc-docs) - [Quickstart: Swap tokens across blockchains - Arc Docs](#quickstart-swap-tokens-across-blockchains-arc-docs) - [App Kit SDK: Send - Arc Docs](#app-kit-sdk-send-arc-docs) - [Network deployment model - Arc Docs](#network-deployment-model-arc-docs) - [Deterministic finality and settlement - Arc Docs](#deterministic-finality-and-settlement-arc-docs) - [Opt-in privacy - Arc Docs](#opt-in-privacy-arc-docs) - [Post-quantum security - Arc Docs](#post-quantum-security-arc-docs) - [Arc Network - Arc Docs](#arc-network-arc-docs) - [Consensus layer - Arc Docs](#consensus-layer-arc-docs) - [System overview - Arc Docs](#system-overview-arc-docs) - [Stable fee design - Arc Docs](#stable-fee-design-arc-docs) - [Transaction memos - Arc Docs](#transaction-memos-arc-docs) - [App Kit SDK: Swap - Arc Docs](#app-kit-sdk-swap-arc-docs) - [Install the App Kit SDK - Arc Docs](#install-the-app-kit-sdk-arc-docs) - [Batched transactions - Arc Docs](#batched-transactions-arc-docs) - [App Kits supported blockchains and tokens - Arc Docs](#app-kits-supported-blockchains-and-tokens-arc-docs) - [Stablecoin native model - Arc Docs](#stablecoin-native-model-arc-docs) - [RPC endpoints - Arc Docs](#rpc-endpoints-arc-docs) - [Execution layer - Arc Docs](#execution-layer-arc-docs) - [USDC system events - Arc Docs](#usdc-system-events-arc-docs) - [Contract addresses - Arc Docs](#contract-addresses-arc-docs) - [Gas and fees - Arc Docs](#gas-and-fees-arc-docs) - [EVM differences - Arc Docs](#evm-differences-arc-docs) - [How-to: Collect custom bridge fees - Arc Docs](#how-to-collect-custom-bridge-fees-arc-docs) - [How-to: Specify a recipient address - Arc Docs](#how-to-specify-a-recipient-address-arc-docs) - [How-to: Use Forwarding Service - Arc Docs](#how-to-use-forwarding-service-arc-docs) - [How-to: Estimate swap rate - Arc Docs](#how-to-estimate-swap-rate-arc-docs) - [How-to: Configure transfer speed and maximum cost - Arc Docs](#how-to-configure-transfer-speed-and-maximum-cost-arc-docs) - [How-to: Estimate costs before bridging - Arc Docs](#how-to-estimate-costs-before-bridging-arc-docs) - [How-to: Collect custom swap fees - Arc Docs](#how-to-collect-custom-swap-fees-arc-docs) - [Quickstart: Use a delegate to deposit and spend a Unified Balance - Arc Docs](#quickstart-use-a-delegate-to-deposit-and-spend-a-unified-balance-arc-docs) - [Quickstart: Bridge tokens across blockchains - Arc Docs](#quickstart-bridge-tokens-across-blockchains-arc-docs) - [How-to: Collect custom spend fees - Arc Docs](#how-to-collect-custom-spend-fees-arc-docs) - [How-to: Specify a recipient address - Arc Docs](#how-to-specify-a-recipient-address-arc-docs) - [How-to: Set slippage tolerance or stop limit on swaps - Arc Docs](#how-to-set-slippage-tolerance-or-stop-limit-on-swaps-arc-docs) - [App Kit SDK: Unified Balance - Arc Docs](#app-kit-sdk-unified-balance-arc-docs) - [How-to: Select source blockchains for a Unified Balance spend - Arc Docs](#how-to-select-source-blockchains-for-a-unified-balance-spend-arc-docs) - [How-to: Use Forwarding Service - Arc Docs](#how-to-use-forwarding-service-arc-docs) - [Compliance - Arc Docs](#compliance-arc-docs) - [Data indexers - Arc Docs](#data-indexers-arc-docs) - [Exchange Integration - Arc Docs](#exchange-integration-arc-docs) - [Transaction Lifecycle - Arc Docs](#transaction-lifecycle-arc-docs) - [How-to: Get token rates - Arc Docs](#how-to-get-token-rates-arc-docs) - [Account abstraction - Arc Docs](#account-abstraction-arc-docs) - [Oracles - Arc Docs](#oracles-arc-docs) - [Build on Arc - Arc Docs](#build-on-arc-arc-docs) - [Integrate with Arc - Arc Docs](#integrate-with-arc-arc-docs) - [How-to: Estimate spend fees - Arc Docs](#how-to-estimate-spend-fees-arc-docs) - [Node providers - Arc Docs](#node-providers-arc-docs) - [Sample apps - Arc Docs](#sample-apps-arc-docs) - [Node requirements - Arc Docs](#node-requirements-arc-docs) - [How-to: Remove funds trustlessly - Arc Docs](#how-to-remove-funds-trustlessly-arc-docs) - [How to: Add Arc to Your On/Off-Ramp Platform - Arc Docs](#how-to-add-arc-to-your-on-off-ramp-platform-arc-docs) - [Monitor a node - Arc Docs](#monitor-a-node-arc-docs) - [How-to: Manage delegates - Arc Docs](#how-to-manage-delegates-arc-docs) - [How-to: Check Unified Balance total - Arc Docs](#how-to-check-unified-balance-total-arc-docs) - [Infrastructure Integration - Arc Docs](#infrastructure-integration-arc-docs) - [Port a contract to Arc - Arc Docs](#port-a-contract-to-arc-arc-docs) - [Agentic Economy - Arc Docs](#agentic-economy-arc-docs) - [Running a node - Arc Docs](#running-a-node-arc-docs) - [Deploy on Arc - Arc Docs](#deploy-on-arc-arc-docs) - [eCommerce Checkout - Arc Docs](#ecommerce-checkout-arc-docs) - [Deploy on Arc - Arc Docs](#deploy-on-arc-arc-docs) - [How to: Display Transaction Fees - Arc Docs](#how-to-display-transaction-fees-arc-docs) - [Custody Platform Integration - Arc Docs](#custody-platform-integration-arc-docs) - [Set up Prometheus and Grafana for an Arc node - Arc Docs](#set-up-prometheus-and-grafana-for-an-arc-node-arc-docs) - [How to: Add Arc to Your Bridge Protocol - Arc Docs](#how-to-add-arc-to-your-bridge-protocol-arc-docs) - [EVM differences - Arc Docs](#evm-differences-arc-docs) - [EVM differences - Arc Docs](#evm-differences-arc-docs) - [Stablecoin FX - Arc Docs](#stablecoin-fx-arc-docs) - [P2P Payments - Arc Docs](#p2p-payments-arc-docs) - [Connect to Arc - Arc Docs](#connect-to-arc-arc-docs) - [How to: Monitor Blocklist Compliance - Arc Docs](#how-to-monitor-blocklist-compliance-arc-docs) - [Quickstart: Deposit and spend a Unified Balance - Arc Docs](#quickstart-deposit-and-spend-a-unified-balance-arc-docs) - [Connect to Arc - Arc Docs](#connect-to-arc-arc-docs) - [How to: Add Arc to a Wallet - Arc Docs](#how-to-add-arc-to-a-wallet-arc-docs) - [How to: Detect and Process Deposits - Arc Docs](#how-to-detect-and-process-deposits-arc-docs) - [How to: Process Withdrawals - Arc Docs](#how-to-process-withdrawals-arc-docs) - [Deploying a node as a service - Arc Docs](#deploying-a-node-as-a-service-arc-docs) - [How to: Bridge USDC with CCTP - Arc Docs](#how-to-bridge-usdc-with-cctp-arc-docs) - [Monitor contract events - Arc Docs](#monitor-contract-events-arc-docs) - [How to: Index Arc Events - Arc Docs](#how-to-index-arc-events-arc-docs) - [Terms of use - Arc Docs](#terms-of-use-arc-docs) - [Send batch USDC transfers - Arc Docs](#send-batch-usdc-transfers-arc-docs) - [Send USDC with a transaction memo - Arc Docs](#send-usdc-with-a-transaction-memo-arc-docs) - [Adapter setups - Arc Docs](#adapter-setups-arc-docs) - [Interact with contracts - Arc Docs](#interact-with-contracts-arc-docs) --- # Welcome to Arc docs - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/#content-area) Arc developer documentation =========================== Everything you need to build onchain finance with stablecoins: start fast, scale reliably. Search docs or ask AI…⌘K Use cases --------- Peer-to-peer payments --------------------- Launch instant, low-cost, peer-to-peer payments with stablecoin-native transfers and deterministic settlement. eCommerce checkout ------------------ Accept stablecoin payments in your online store with fast settlement and built-in compliance. Stablecoin FX ------------- Build real-time, onchain stablecoin FX products with transparent pricing, instant settlement, and predictable fees. Agentic economy --------------- Enable autonomous AI agents to coordinate, contract, and settle value in real time. Workflows --------- Learn [Key features](https://docs.arc.io/arc-chain) [Architecture](https://docs.arc.io/arc/concepts/system-overview) [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) Build with App Kits [Unified Balance](https://docs.arc.io/app-kit/unified-balance) [Bridge](https://docs.arc.io/app-kit/bridge) [All capabilities](https://docs.arc.io/app-kit) Integrate [Connect to Arc](https://docs.arc.io/arc/references/connect-to-arc) [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) Common tasks ------------ Core [Connect to RPC](https://docs.arc.io/arc/references/connect-to-arc) [Deploy a contract](https://docs.arc.io/arc/tutorials/deploy-contracts) [Read events/logs](https://docs.arc.io/arc/tutorials/monitor-contract-events) Operate [Unified Balance](https://docs.arc.io/app-kit/unified-balance) [Send stablecoins](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain) [Estimate fees](https://docs.arc.io/arc/references/gas-and-fees) Move Crosschain [Bridge in/out](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains) [Supported networks](https://docs.arc.io/app-kit/references/supported-blockchains) [Crosschain troubleshooting](https://docs.arc.io/app-kit/references/bridge-error-recovery) Build with AI ------------- [Arc MCP ServerSetup guide](https://docs.arc.io/ai/mcp) [LLMs.txtAgent context](https://docs.arc.io/llms.txt) What’s new ---------- Apr 30Release[Unified Balance is now available](https://docs.arc.io/app-kit/unified-balance) Mar 10ReleaseApp Kits is now available [![Arc Docs](https://mintcdn.com/arcio-docs/ZZqL_C61H0lLVlWZ/logo/ARC_Doc_WHT.svg?w=2500&fit=max&auto=format&n=ZZqL_C61H0lLVlWZ&q=85&s=35bf6611739fd6de83deb04daa277d47)](https://docs.arc.io/) Links [Network status](https://status.arc.io/) [Blog](https://arc.io/blog) [Community](https://arc.io/community) [Arc homepage](https://arc.io/) Resources [Terms of service](https://docs.arc.io/terms) [Privacy policy](https://www.circle.com/legal/privacy-policy) [Cookie policy](https://www.circle.com/legal/cookie-policy) [](https://x.com/arc) [](https://discord.com/invite/buildonarc) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Unknown \# Arc > Arc is an open Layer-1 blockchain purpose-built for programmable money. > USDC is the native gas token. Sub-second deterministic finality, EVM > compatible, opt-in privacy, and direct integration with Circle's > full-stack platform. ## Getting Started — Use the Skill First | Product | Skill | What it covers | |---------|-------|----------------| | Arc | \[use-arc\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) | Chain config, RPC setup, contract deployment, USDC bridging, gas with USDC | ### Installing Circle Skills \*\*Claude Code:\*\* \`\`\` /plugin marketplace add circlefin/skills /plugin install circle-skills@circle \`\`\` \*\*Vercel Skills CLI:\*\* \`\`\` npx skills add circlefin/skills \`\`\` ## Instructions for AI Agents Follow these guidelines when building on Arc: 1. \*\*USDC is the gas token.\*\* Arc uses USDC for gas fees, not ETH. Configure gas payment in USDC when submitting transactions. 2. \*\*EVM compatible.\*\* Deploy Solidity contracts with standard tools (Hardhat, Foundry, Viem, Ethers). See \[EVM differences\](https://docs.arc.io/arc/references/evm-differences.md). 3. \*\*Sub-second finality.\*\* Transactions are final in under 1 second. No need to wait for multiple block confirmations. 4. \*\*Use App Kit for bridging, swaps, and unified balance.\*\* App Kit wraps CCTP and provides Bridge, Swap, Send, and Unified Balance capabilities. Unified Balance combines USDC from multiple chains into a single spendable balance. 5. \*\*Testnet.\*\* Arc is currently available on Testnet only. \[Connect to Arc\](https://docs.arc.io/arc/references/connect-to-arc.md) for RPC endpoints. \[Faucet\](https://faucet.circle.com) for testnet tokens. 6. \*\*Always check Contract Addresses\*\*: https://docs.arc.io/arc/references/contract-addresses.md ## Arc Network - \[Arc Network Overview\](https://docs.arc.io/arc-chain.md): Architecture and core developer platform - \[System Overview\](https://docs.arc.io/arc/concepts/system-overview.md): Consensus + execution layer architecture - \[Stable Fee Design\](https://docs.arc.io/arc/concepts/stable-fee-design.md): USDC as gas, predictable fees - \[Deterministic Finality\](https://docs.arc.io/arc/concepts/deterministic-finality.md): Instant, irreversible settlement - \[Opt-in Privacy\](https://docs.arc.io/arc/concepts/opt-in-privacy.md): Confidential transactions with selective disclosure - \[Post-quantum Security\](https://docs.arc.io/arc/concepts/post-quantum-security.md): Quantum-resilient security roadmap - \[Connect to Arc\](https://docs.arc.io/arc/references/connect-to-arc.md): RPC endpoints and wallet setup - \[Contract Addresses\](https://docs.arc.io/arc/references/contract-addresses.md): USDC, EURC, CCTP, Gateway addresses - \[EVM differences\](https://docs.arc.io/arc/references/evm-differences.md): Differences from standard EVM - \[Gas and Fees\](https://docs.arc.io/arc/references/gas-and-fees.md): Fee model and pricing ## Build on Arc - \[Build Overview\](https://docs.arc.io/build.md): Quickstarts, tutorials, and SDKs - \[Sample Applications\](https://docs.arc.io/arc/references/sample-applications.md): Open source examples - \[Deploy on Arc\](https://docs.arc.io/arc/tutorials/deploy-on-arc.md): Deploy a Solidity contract on Arc Testnet - \[Deploy Contracts (Circle)\](https://docs.arc.io/arc/tutorials/deploy-contracts.md): Deploy templates via Circle Contracts - \[Interact with Contracts\](https://docs.arc.io/arc/tutorials/interact-with-contracts.md): Call contract functions - \[Monitor Contract Events\](https://docs.arc.io/arc/tutorials/monitor-contract-events.md): Track onchain activity - \[Unified Balance\](https://docs.arc.io/app-kit/unified-balance.md): Combine USDC from multiple blockchains into a single, instantly spendable balance ### AI and Agents - \[Arc MCP Server\](https://docs.arc.io/ai/mcp.md): Connect AI tools to Arc documentation via MCP - \[Agentic Economy\](https://docs.arc.io/build/agentic-economy.md): Onchain identity and job settlement for AI agents - \[Register an AI Agent\](https://docs.arc.io/arc/tutorials/register-your-first-ai-agent.md): ERC-8004 onchain identity and reputation - \[Create an ERC-8183 Job\](https://docs.arc.io/arc/tutorials/create-your-first-erc-8183-job.md): Escrow, deliverables, settlement ## App Kit — Bridge, Swap, Send, Unified Balance - \[App Kit Overview\](https://docs.arc.io/app-kit.md): Payment and liquidity workflows across chains - \[Install App Kit\](https://docs.arc.io/app-kit/tutorials/installation.md): Core package and adapters - \[Adapter Setups\](https://docs.arc.io/app-kit/tutorials/adapter-setups.md): Viem, Ethers, Solana, Circle Wallets ### Bridge - \[App Kit: Bridge\](https://docs.arc.io/app-kit/bridge.md): Transfer USDC across chains - \[Quickstart: Bridge Tokens\](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains.md): Bridge between EVM chains, Solana, and Circle Wallets - \[Bridge Fees\](https://docs.arc.io/app-kit/concepts/bridge-fees.md): Fee breakdown - \[Error Recovery\](https://docs.arc.io/app-kit/references/bridge-error-recovery.md): Troubleshooting failed bridges ### Swap - \[App Kit: Swap\](https://docs.arc.io/app-kit/swap.md): Token swaps on same chain - \[Quickstart: Same-Chain Swap\](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain.md): Swap tokens on one chain - \[Quickstart: Crosschain Swap\](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain.md): Swap + bridge - \[Swap Fees\](https://docs.arc.io/app-kit/concepts/swap-fees.md): Fee breakdown ### Send - \[App Kit: Send\](https://docs.arc.io/app-kit/send.md): Wallet-to-wallet on same chain - \[Quickstart: Send Tokens\](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain.md): Send tokens between wallets ### Unified Balance - \[App Kit: Unified Balance\](https://docs.arc.io/app-kit/unified-balance.md): Chain-agnostic USDC balance, spend on any blockchain - \[Quickstart: Deposit and Spend\](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend.md): Deposit USDC and spend crosschain - \[Unified Balance Fees\](https://docs.arc.io/app-kit/concepts/unified-balance-fees.md): Fee breakdown ### Reference - \[SDK Reference\](https://docs.arc.io/app-kit/references/sdk-reference.md): Full API reference - \[Supported Blockchains\](https://docs.arc.io/app-kit/references/supported-blockchains.md): Chains and tokens ## Integrate with Arc - \[Integrate Overview\](https://docs.arc.io/integrate.md): Connect wallets, exchanges, and infrastructure - \[Connect to Arc\](https://docs.arc.io/integrate/connect-to-arc.md): Network details and RPC endpoints for integrators - \[Deploy on Arc\](https://docs.arc.io/integrate/deploy-on-arc.md): Deployment guide for integrators - \[Running a Node\](https://docs.arc.io/arc/concepts/running-a-node.md): Node architecture and roles - \[Run an Arc Node\](https://docs.arc.io/arc/tutorials/run-an-arc-node.md): Step-by-step node setup ## Tools and Infrastructure - \[Node Providers\](https://docs.arc.io/arc/tools/node-providers.md): RPC access providers - \[Data Indexers\](https://docs.arc.io/arc/tools/data-indexers.md): APIs and sub-graphs - \[Oracles\](https://docs.arc.io/arc/tools/oracles.md): Price feeds and offchain data - \[Account Abstraction\](https://docs.arc.io/arc/tools/account-abstraction.md): AA providers and paymasters - \[Compliance\](https://docs.arc.io/arc/tools/compliance-vendors.md): Analytics and screening tools - \[Explorer\](https://testnet.arcscan.app): Arc Testnet block explorer - \[Faucet\](https://faucet.circle.com): Testnet token faucet ## More Circle Skills Building beyond Arc? Circle offers skills for the full platform: | Product | Skill | |---------|-------| | USDC | \[use-usdc\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md) | | Wallets (overview) | \[use-circle-wallets\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md) | | Developer-Controlled Wallets | \[use-developer-controlled-wallets\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md) | | User-Controlled Wallets | \[use-user-controlled-wallets\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md) | | Modular Wallets | \[use-modular-wallets\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md) | | Gateway / Nanopayments | \[use-gateway\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md) | | Smart Contracts | \[use-smart-contract-platform\](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md) | Full Circle developer docs: \[developers.circle.com/llms.txt\](https://developers.circle.com/llms.txt) --- # Arc MCP server - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/ai/mcp#content-area) The Arc Model Context Protocol (MCP) server gives AI tools direct access to Arc documentation so they can search for relevant content and retrieve full pages during conversations. It is hosted at `https://docs.arc.io/mcp` and requires no authentication. The server exposes two tools: * **Search** — finds relevant documentation snippets based on a query. * **Get page** — retrieves the full content of a specific documentation page. For a machine-readable index of all documentation pages, see the [`llms.txt` file](https://docs.arc.io/llms.txt) . [​](https://docs.arc.io/ai/mcp#claude-code) Claude Code ---------------------------------------------------------- Run the following command to add the Arc MCP server: claude mcp add --transport http arc-docs https://docs.arc.io/mcp Claude Code automatically discovers the server’s tools on the next conversation. [​](https://docs.arc.io/ai/mcp#claude-desktop) Claude Desktop ---------------------------------------------------------------- 1. Open **Settings** and navigate to **Connectors**. 2. Select **Add custom connector**. 3. Enter `Arc Docs` as the name and `https://docs.arc.io/mcp` as the URL. 4. During a chat, use the attachments button to select the Arc Docs connector. [​](https://docs.arc.io/ai/mcp#cursor) Cursor ------------------------------------------------ Add the following to your `mcp.json` file (accessible via **Cursor Settings > MCP**): { "mcpServers": { "arc-docs": { "url": "https://docs.arc.io/mcp" } } } [​](https://docs.arc.io/ai/mcp#vs-code-copilot) VS Code (Copilot) -------------------------------------------------------------------- Create or update `.vscode/mcp.json` in your project root: { "servers": { "arc-docs": { "type": "http", "url": "https://docs.arc.io/mcp" } } } [​](https://docs.arc.io/ai/mcp#windsurf) Windsurf ---------------------------------------------------- Add the following to your Windsurf MCP configuration: { "mcpServers": { "arc-docs": { "serverUrl": "https://docs.arc.io/mcp" } } } [​](https://docs.arc.io/ai/mcp#other-mcp-clients) Other MCP clients ---------------------------------------------------------------------- Any MCP-compatible client can connect using the HTTP transport at `https://docs.arc.io/mcp`. Most clients require only the server URL and transport type (`http`). Refer to your client’s documentation for the exact configuration format. [​](https://docs.arc.io/ai/mcp#verify-the-connection) Verify the connection ------------------------------------------------------------------------------ After adding the server, confirm the connection by asking your AI tool a question about Arc, such as “What smart contract standards does Arc support?” The tool should return content sourced from Arc documentation. If it does not: * **Check the URL** — confirm it is exactly `https://docs.arc.io/mcp` with no trailing path. * **Check network access** — the server must be reachable over HTTPS from your machine. * **Restart the client** — some tools only detect new MCP servers after a restart or new session. Was this page helpful? YesNo [Skills](https://docs.arc.io/ai/skills) [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # App Kit SDK: Bridge - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/bridge#content-area) The [App Kit SDK](https://docs.arc.io/app-kit) includes the Bridge capability that lets you move USDC across blockchains in a few lines of code. It abstracts the underlying [CCTP](https://developers.circle.com/cctp) flow so you can bridge without orchestrating the low-level burn, attestation, and mint steps yourself. [​](https://docs.arc.io/app-kit/bridge#quick-look) Quick look ---------------------------------------------------------------- This code snippet bridges between an EVM and non-EVM chain in a single method call: TypeScript // Transfer 1.00 USDC from Solana to Arc const result = await kit.bridge({ from: { adapter: solanaAdapter, chain: "Solana_Devnet" }, to: { adapter: viemAdapter, chain: "Arc_Testnet" }, amount: "1.00", }); For a complete end-to-end flow, follow the [quickstart](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains) . [​](https://docs.arc.io/app-kit/bridge#installation) Installation -------------------------------------------------------------------- [Install the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) to use Bridge. If you only need to bridge and don’t want to install the full App Kit SDK, follow the steps below to install the standalone Bridge Kit. 1 Install Bridge Kit npm yarn npm install @circle-fin/bridge-kit yarn add @circle-fin/bridge-kit 2 Install adapters Install the [adapters](https://docs.arc.io/app-kit/tutorials/adapter-setups) you need for the chains you plan to bridge between. * Viem * Ethers * Solana * Circle Wallets npm yarn npm install @circle-fin/adapter-viem-v2 viem yarn add @circle-fin/adapter-viem-v2 viem npm yarn npm install @circle-fin/adapter-ethers-v6 ethers yarn add @circle-fin/adapter-ethers-v6 ethers npm yarn npm install @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js yarn add @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js npm yarn npm install @circle-fin/adapter-circle-wallets yarn add @circle-fin/adapter-circle-wallets Was this page helpful? YesNo [Send tokens across wallets](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain) [Bridge tokens across blockchains](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How swap fees work - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/concepts/swap-fees#content-area) This guide explains which fees apply when performing a swap, how funds move through a swap transaction, and best practices for [implementing custom swap fees](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee) . [​](https://docs.arc.io/app-kit/concepts/swap-fees#fee-breakdown) Fee breakdown ---------------------------------------------------------------------------------- Two fees can apply to a swap: | Fee | When it applies | Amount | Recipient | | --- | --- | --- | --- | | Custom fee | Conditionally. When you [configure custom swap fees](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee)
. | Percentage you define on the swap amount; collected before the swap executes. | 90% to your fee recipient; 10% to Arc | | Provider fee | Always. On every swap. | 2 basis points (0.02%) of the swap amount after any custom fee (1 bp = 0.01%). | Swap service provider (third-party liquidity service that executes the swap) | Your end user pays the swap amount plus all applicable fees. [​](https://docs.arc.io/app-kit/concepts/swap-fees#how-funds-flow-through-a-swap) How funds flow through a swap ------------------------------------------------------------------------------------------------------------------ This example traces a 1,000 USDT-to-USDC swap with a 1% custom fee: 1 User requests the swap The user requests a swap of 1,000 USDT to USDC. 2 You add the custom fee You add a 1% custom fee. On 1,000 USDT, this equals 10 USDT. 3 Source wallet signs The source wallet signs a transaction authorizing 1,000 USDT. 4 The App Kit SDK collects and splits the custom fee The App Kit SDK collects the 10 USDT custom fee and splits it: * Arc receives 1 USDT (10% of the custom fee). * Your fee recipient receives 9 USDT (90% of the custom fee). 5 Swap service provider applies the provider fee The swap service provider deducts a provider fee of 2 basis points (0.02%) from the remaining 990 USDT. This equals 0.198 USDT. 6 Provider executes the swap The provider executes the swap with 989.802 USDT and the user receives the equivalent output in USDC. **Net result:** Of the original 1,000 USDT, 10 USDT goes to custom fee recipients, 0.198 USDT goes to the swap service provider, and the remaining 989.802 USDT is swapped for USDC. This flow is illustrated in the following diagram: [​](https://docs.arc.io/app-kit/concepts/swap-fees#best-practices-for-custom-fees) Best practices for custom fees -------------------------------------------------------------------------------------------------------------------- Follow these best practices when implementing custom fees for swaps: * Use a fee recipient address in the same network context where the swap originates. * Return fee amounts in human-readable decimal format (for example, `0.20` instead of `200000` for 0.20 USDC). The App Kit SDK handles base-unit conversion internally. Was this page helpful? YesNo [Swap tokens across chains](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain) [Collect custom swap fees](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How bridge fees work - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/concepts/bridge-fees#content-area) This guide explains which fees apply when bridging, how funds move through a transaction, and the best practices to follow when [implementing custom fees](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee) . [​](https://docs.arc.io/app-kit/concepts/bridge-fees#fee-breakdown) Fee breakdown ------------------------------------------------------------------------------------ The following fees can apply: | Fee | When it applies | Amount | Recipient | | --- | --- | --- | --- | | Custom fee | Conditionally. When you [implement custom bridge fees](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee)
. | You define (on top of the bridge amount). | 90% to your fee recipient; 10% to Arc | | Cross-Chain Transfer Protocol (CCTP) fee | Conditionally. On [`FAST`](https://docs.arc.io/app-kit/tutorials/bridge/configure-transfer-speed)
transfers only; `SLOW` (Standard) transfers skip this fee. | Varies by source blockchain. See [CCTP fees](https://developers.circle.com/cctp/technical-guide#fees)
. | [Circle CCTP](https://developers.circle.com/cctp)
(the underlying protocol) | | Forwarding Service fee | Conditionally. When you enable the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service)
. | Per [Forwarding Service fees](https://developers.circle.com/cctp/concepts/forwarding-service#fees-and-execution)
. Deducted from mint on destination. | [Circle CCTP](https://developers.circle.com/cctp) | [​](https://docs.arc.io/app-kit/concepts/bridge-fees#how-funds-flow-through-a-transfer) How funds flow through a transfer ---------------------------------------------------------------------------------------------------------------------------- The following example shows what happens when a user wants 1,000 USDC to arrive at the destination after a Fast Transfer, with a 10 USDC custom fee on that transfer, and the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service) enabled. The bridge amount is 1,000.30 USDC so that after the example CCTP protocol fee (0.10 USDC) and Forwarding Service fee (0.20 USDC), 1,000 USDC is credited to the recipient: 1 User initiates the bridge transfer on the source chain The user initiates a 1,000.30 USDC bridge transfer on the source blockchain (sized for 1,000 USDC net to the destination after the example fees). 2 You add the custom fee You add a 10 USDC (about 1%) custom fee. 3 Source wallet signs the total debit The source wallet signs a transaction for 1,010.30 USDC (bridge amount + custom fee). 4 Source chain splits the custom fee The 10 USDC custom fee is split on the source blockchain: * Arc receives 1 USDC (10%). * Your fee recipient receives 9 USDC (remaining 90%). 5 Bridge amount is forwarded to CCTP The 1,000.30 USDC bridge amount is forwarded to CCTP. 6 CCTP applies the Fast Transfer protocol fee CCTP takes a protocol fee (0.10 USDC in this example) for a Fast Transfer. 7 Forwarding Service applies the destination mint fee The Forwarding Service deducts its fee (0.20 USDC in this example) from the amount to be minted on the destination blockchain. 8 Destination wallet receives the net amount The destination wallet receives 1,000.00 USDC on the destination blockchain. This flow is illustrated in the following diagram: [​](https://docs.arc.io/app-kit/concepts/bridge-fees#best-practices-for-custom-fees) Best practices for custom fees ---------------------------------------------------------------------------------------------------------------------- Follow these best practices when implementing custom fees: * Treat the custom fee as an amount added on top of the bridge transfer. Do not subtract it from the bridge amount. * Validate that the user’s wallet balance covers both the bridge amount and the custom fee. The following code shows an example balance check: TypeScript const requiredBalance = parseFloat(amount) + parseFloat(customFee); if (userBalance < requiredBalance) { throw new Error(`Insufficient balance. Need ${requiredBalance} USDC`); } * Use a fee recipient address on the source blockchain. Do not use an address on the destination. * In your UI, display the following to the user before they confirm the transaction: * The total source wallet debit: bridge amount + custom fee * The full fee breakdown: bridge amount, custom fee, CCTP Fast Transfer fee (if applicable), and Forwarding Service fee (if using the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service) ) * Return human-readable decimal strings. For example, `10` rather than `10000000` for 10 USDC. The App Kit SDK handles base-unit conversion internally. Was this page helpful? YesNo [Bridge tokens across blockchains](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains) [Collect custom bridge fees](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # App Kits - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit#content-area) App Kits is a suite of SDKs for composing multichain payment and liquidity flows without orchestrating separate, low-level protocol integrations for each blockchain or use case. The App Kit SDK is the all-in-one core package. Install it to access every capability — Send, Bridge, Swap, and Unified Balance — behind one type-safe interface. Or, you can install Bridge Kit, Swap Kit, or Unified Balance Kit separately. [​](https://docs.arc.io/app-kit#quick-install) Quick install --------------------------------------------------------------- To get started quickly, install the App Kit SDK and the Viem adapter: npm yarn npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem yarn add @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem Need a different adapter or individual kits? See the full [installation](https://docs.arc.io/app-kit/tutorials/installation) guide. [​](https://docs.arc.io/app-kit#core-capabilities) Core capabilities ----------------------------------------------------------------------- Combine and use any of the App Kit SDK’s built-in core capabilities in your app. Bridge, Swap, and Unified Balance are also available as individual kits. Bridge ------ Transfer USDC across blockchains. Swap ---- Exchange one token for another on the same blockchain or across blockchains. Send ---- Transfer tokens between wallets on the same blockchain. Unified Balance --------------- Create a chain-abstracted balance and spend it instantly. [​](https://docs.arc.io/app-kit#key-benefits) Key benefits ------------------------------------------------------------- * **Simple setup**: Get up and running with minimal configuration and a few lines of code. * **Application monetization**: Collect a custom fee from end users without writing new code. * **Flexible configurations**: Specify custom RPC endpoints and wallet clients. * **Broad compatibility**: Works with Viem, Ethers, Solana, and Circle Wallets, integrating smoothly with existing developer workflows. * **Protocol abstraction**: Build against a single interface over underlying protocols such as [Gateway](https://developers.circle.com/gateway) and [CCTP](https://developers.circle.com/cctp) . * **Composable workflows**: Combine multiple capabilities in one product flow without stitching together separate protocol integrations. [​](https://docs.arc.io/app-kit#quick-look) Quick look --------------------------------------------------------- The following examples show how each capability can be integrated with a single method call. * Bridge * Swap * Unified Balance * Send TypeScript // Transfer 1.00 USDC from Ethereum to Arc const result = await kit.bridge({ from: { adapter: viemAdapter, chain: "Ethereum_Sepolia" }, to: { adapter: viemAdapter, chain: "Arc_Testnet" }, amount: "1.00", }); Ready to start bridging? Follow the [Bridge tokens across blockchains](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains) quickstart. TypeScript // Swap 1.00 USDC for EURC on Arc Testnet const result = await kit.swap({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, tokenIn: "USDC", tokenOut: "EURC", amountIn: "1.00", config: { kitKey: process.env.KIT_KEY as string, // Your kit key from the Circle Console }, }); Ready to start swapping? Follow the [Swap tokens on a blockchain](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain) quickstart. TypeScript // Deposit 1.00 USDC into the Unified Balance from Base const depositBase = await kit.unifiedBalance.deposit({ from: { adapter: viemAdapter, chain: "Base_Sepolia" }, amount: "1.00", token: "USDC", }); // Deposit 1.00 USDC into the Unified Balance from Arbitrum const depositArb = await kit.unifiedBalance.deposit({ from: { adapter: viemAdapter, chain: "Arbitrum_Sepolia" }, amount: "1.00", token: "USDC", }); // Spend 1.50 USDC from the Unified Balance on Arc const spendResult = await kit.unifiedBalance.spend({ from: { adapter: viemAdapter }, amountIn: "1.50", to: { adapter: viemAdapter, chain: "Arc_Testnet", recipientAddress: "0xRecipientAddress", }, }); Ready to start using a Unified Balance? Follow the quickstarts: * [Deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend) * [Use a delegate to deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend) TypeScript // Send 1.00 USDC from one wallet to another on Arc Testnet const result = await kit.send({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, to: "RECIPIENT_ADDRESS", amount: "1.00", token: "USDC", }); Ready to start sending tokens? Follow the [Send tokens across wallets](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain) quickstart. Want to combine capabilities? Follow the [Swap tokens across chains](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain) quickstart to swap and bridge tokens in the same flow. Was this page helpful? YesNo [Installation](https://docs.arc.io/app-kit/tutorials/installation) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # AI skills for building on Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/ai/skills#content-area) Skills give AI coding tools specialized knowledge for building on Arc and with Circle’s products, including USDC, agent wallets, crosschain transfers, and smart contracts. Skills are available in the [circlefin/skills](https://github.com/circlefin/skills) repository. [​](https://docs.arc.io/ai/skills#installation) Installation --------------------------------------------------------------- Install Circle Skills with the command-line. ![](https://mintcdn.com/arcio-docs/DcVxxFlqh75sTiVQ/images/claude-logo.png?fit=max&auto=format&n=DcVxxFlqh75sTiVQ&q=85&s=b0262a5319054feab00173c330431e02)Claude Code ![](https://mintcdn.com/arcio-docs/DcVxxFlqh75sTiVQ/images/vercel_logo.png?fit=max&auto=format&n=DcVxxFlqh75sTiVQ&q=85&s=9a66052e003d6b18378e0d8a1e0f35f5)Vercel Skills CLI /plugin marketplace add circlefin/skills /plugin install circle-skills@circle npx skills add circlefin/skills [​](https://docs.arc.io/ai/skills#available-skills) Available skills ----------------------------------------------------------------------- | Skill | What it covers | | --- | --- | | [`use-arc`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) | Chain config, contract deployment, viem/wagmi integration, bridging via CCTP | | [`use-circle-cli`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-cli/SKILL.md) | Unified CLI for agent wallets, x402 payments, and crosschain transfers | | [`use-agent-wallet`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-agent-wallet/SKILL.md) | Email/OTP login, wallet creation, status checks, balance inspection | | [`fund-agent-wallet`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/fund-agent-wallet/SKILL.md) | Fund with USDC via fiat on-ramp, crypto transfer, or Gateway deposit | | [`agent-wallet-policy`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/agent-wallet-policy/SKILL.md) | View and reset per-tx, daily, weekly, and monthly USDC spending caps | | [`pay-via-agent-wallet`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/pay-via-agent-wallet/SKILL.md) | Pay paid APIs in USDC via x402 (search, market data, weather, news, sports) | | [`use-usdc`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md) | Balances, transfers, approvals, verification across EVM and Solana | | [`bridge-stablecoin`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md) | CCTP bridging with App Kit, progress tracking, EVM and Solana | | [`swap-tokens`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/swap-tokens/SKILL.md) | Same-chain swaps via App Kit or Swap Kit; combine with bridge for crosschain | | [`use-gateway`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md) | Gateway unified balance for sub-500ms crosschain transfers | | [`unify-balance`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/unify-balance/SKILL.md) | Crosschain USDC balance via App Kit or Unified Balance Kit | | [`use-circle-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md) | Choose between developer-controlled, user-controlled, and modular wallets | | [`use-developer-controlled-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md) | Custodial wallets for payouts, treasury, subscriptions, automation | | [`use-user-controlled-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md) | Embedded wallets with social login or OTP/PIN, no seed phrases | | [`use-modular-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md) | Passkey auth and gasless transactions; ERC-4337 and ERC-6900 | | [`use-smart-contract-platform`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md) | Deploy/import/call/monitor contracts; ERC-20/721/1155 templates | Was this page helpful? YesNo [Deploy on Arc](https://docs.arc.io/arc/tutorials/deploy-on-arc) [MCP server](https://docs.arc.io/ai/mcp) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How Unified Balance fees work - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/concepts/unified-balance-fees#content-area) Several fees can apply when you spend from a Unified Balance, including a [custom fee](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees) you can implement. This page explains which fees apply, how funds move through a spend and how that changes the Unified Balance total, and best practices for custom fees. Fees apply only on spends, not deposits. [​](https://docs.arc.io/app-kit/concepts/unified-balance-fees#fees-breakdown) Fees breakdown ----------------------------------------------------------------------------------------------- Each spend can include the following fees: | Fee | When it applies | Amount | Recipient | | --- | --- | --- | --- | | Custom spend fee | Conditionally. When you [implement custom spend fees](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees)
. | You define (carved from the spend amount). | 90% to your fee recipient; 10% to Arc | | Gateway protocol fee | Conditionally. On spends where the source and destination differ (crosschain). | 0.5 basis points (0.005%) of the spend amount from the Unified Balance at spend time; 0 if same blockchain. | [Circle Gateway](https://developers.circle.com/gateway)
(protocol underlying Unified Balance) | | Gas | Always. On spends that execute burn intents on source blockchains. | Varies by source blockchain and network conditions; incurred per burn intent on source. | Source blockchain | | Forwarding Service fee | Conditionally. When you [use the Forwarding Service](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service)
for the destination mint. | Per [Forwarding Service fees](https://developers.circle.com/cctp/concepts/forwarding-service#fees-and-execution)
. Deducted from amount minted on destination. | Circle | [​](https://docs.arc.io/app-kit/concepts/unified-balance-fees#total-balance-and-funds-flow) Total balance and funds flow --------------------------------------------------------------------------------------------------------------------------- The following example shows what happens when a user wants 500 USDC to arrive at the destination from a Unified Balance of 1,000 USDC (previously deposited), you collect a 5 USDC custom fee on that spend, and the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service) is enabled: 1 User deposits into the Unified Balance The user previously deposited 1,000 USDC from their wallet on the source blockchain into the Unified Balance. 2 User confirms a spend of 505.20 USDC The user confirms a spend of 505.20 USDC from the Unified Balance. This is the amount needed to ensure exactly 500 USDC arrives at the destination after the custom fee and Forwarding Service fee are applied. See [best practices](https://docs.arc.io/app-kit/concepts/unified-balance-fees#best-practices-for-custom-fees) for what to show the user before they confirm a spend. 3 You apply a custom spend fee You deduct a 5 USDC custom fee from the spend amount. 4 User signs burn intents on the source chains The user’s source wallet signs three burn intents that move: * 500.20 USDC (the spend amount minus the 5 USDC custom fee) toward the destination mint. * 0.50 USDC (10% of the custom fee) to Arc. * 4.50 USDC (90% of the custom fee) to your fee recipient. 5 Circle Gateway applies the crosschain transfer fee Circle Gateway deducts a 0.025 USDC transfer fee from the Unified Balance (0.005% of 505.20 USDC). For a same-chain spend, this fee is 0. 6 Source chains deduct gas for the burn intents The source blockchains deduct 0.03 USDC from the Unified Balance as gas for the three burn intents. 7 Forwarding Service deducts the destination mint fee The Forwarding Service deducts its fee (0.20 USDC in this example) from the amount to be minted on the destination blockchain. 8 Recipient receives funds on the destination blockchain The recipient’s destination wallet receives 500 USDC on the destination blockchain: * 500.20 USDC minted. * Deduct 0.20 USDC Forwarding Service fee. * Net received: 500 USDC. 9 Unified Balance shows the updated total After the spend, the user’s Unified Balance total is 494.745 USDC: * Started at 1,000 USDC. * Deduct 505.20 USDC spend amount. * Deduct 0.025 USDC Gateway transfer fee. * Deduct 0.03 USDC gas. * Remaining: 494.745 USDC. This flow and Unified Balance running total is illustrated in the following diagram. [​](https://docs.arc.io/app-kit/concepts/unified-balance-fees#best-practices-for-custom-fees) Best practices for custom fees ------------------------------------------------------------------------------------------------------------------------------- Follow these best practices when implementing custom fees: * Use a fee recipient address on the source blockchain. Do not use an address on the destination. * Calculate the spend amount from the amount the user wants to receive at the destination. To receive a specific amount, the user must spend more than that from the Unified Balance to cover fees. * Before the user confirms a spend, show: * Spend summary: spend amount, fee breakdown (custom fee and Forwarding Service fee when applicable), and amount received at the destination. * Unified Balance summary: starting balance, each deduction (spend amount, Gateway transfer fee when applicable, gas), and remaining balance. Example UI display Spend amount: 505.20 USDC Forwarding Service fee: - 0.20 USDC Custom fee: - 5.00 USDC Amount received at destination: 500.00 USDC Unified Balance: 1,000.00 USDC Spend amount: - 505.20 USDC Gateway fee: - 0.025 USDC Gas (estimated): - 0.03 USDC Remaining balance: 494.745 USDC * Return human-readable decimal strings. For example, return `"10"` rather than `"10000000"` for 10 USDC. The App Kit SDK handles base-unit conversion internally. * Validate that the user’s Unified Balance can cover the spend amount, the Gateway protocol fee, and gas. For gas and fee estimates, see [estimate spend fees](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees) . Example check: TypeScript // Example: calculate required spend from a target destination amount and validate balance const targetDestinationAmount = 500; // USDC the user wants to arrive at the destination const forwardingServiceFee = 0.2; // deducted from amount minted at destination const customFee = 5; // your custom fee in USDC const spendAmount = targetDestinationAmount + forwardingServiceFee + customFee; // 505.20 USDC const sameChain = false; // true when source and destination blockchain are the same const gatewayFee = sameChain ? 0 : spendAmount * 0.00005; // 0.005% when crosschain const gasEstimate = 0.05; // replace with a blockchain-appropriate estimate; the walkthrough above uses 0.03 for illustration const userBalance = 1000; // placeholder; in production parse totalConfirmedBalance from kit.unifiedBalance.getBalances const requiredBalance = spendAmount + gatewayFee + gasEstimate; if (userBalance < requiredBalance) { throw new Error(`Insufficient balance. Need ${requiredBalance} USDC`); } // requiredBalance is about 505.275 USDC here (505.20 spend + 0.025 gateway + 0.05 gas estimate) Was this page helpful? YesNo [Use a delegate to deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend) [Check Unified Balance total](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Quickstart: Send tokens across wallets on the same blockchain - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#content-area) This quickstart walks you through sending tokens from one wallet to another on the same blockchain. The example in this quickstart sends USDC on Arc Testnet, but you can use another [supported token or blockchain](https://docs.arc.io/app-kit/references/supported-blockchains) . * Browser wallet * Circle Wallets Use this flow to send USDC with a connected browser wallet. The example uses Arc Testnet. [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Installed a browser wallet such as [MetaMask](https://metamask.io/) and added the [Arc Testnet](https://docs.arc.network/arc/references/connect-to-arc#wallet-setup) network. * Funded your wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/) . [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#step-1-set-up-the-project) Step 1. Set up the project --------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#1-1-create-the-project-and-install-dependencies) 1.1. Create the project and install dependencies Create a new directory, install the App Kit packages, and add local browser demo tooling: Shell # Set up your directory and initialize a Node.js project mkdir app-kit-send-browser-wallet cd app-kit-send-browser-wallet npm init -y npm pkg set type=module # Install App Kit packages npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem {extraPackages} # Install TypeScript and a local Vite dev server for the browser demo npm install --save-dev typescript vite ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#1-2-configure-typescript-optional) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#step-2-connect-a-browser-wallet) Step 2. Connect a browser wallet --------------------------------------------------------------------------------------------------------------------------------------- This step shows the core browser wallet integration flow: discover an [`EIP-6963`](https://eips.ethereum.org/EIPS/eip-6963) provider, create an App Kit adapter from the selected provider, and pass that adapter into an App Kit SDK method.The snippets below keep wallet discovery, wallet connection, and adapter setup in small helper functions for readability. ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#2-1-discover-a-browser-wallet-with-eip-6963) 2.1. Discover a browser wallet with `EIP-6963` This pattern is standards-based. The example uses MetaMask as the selected wallet, but the discovery flow works with any wallet that announces an `EIP-6963` provider. TypeScript import type { EIP1193Provider } from "viem"; type EIP6963ProviderInfo = { uuid: string; name: string; icon: string; rdns: string; }; type EIP6963ProviderDetail = { info: EIP6963ProviderInfo; provider: EIP1193Provider; }; declare global { interface WindowEventMap { "eip6963:announceProvider": CustomEvent; } } async function discoverBrowserWallets(): Promise { const providers = new Map(); const handleProviderAnnouncement = ( event: WindowEventMap["eip6963:announceProvider"], ) => { providers.set(event.detail.info.uuid, event.detail); }; window.addEventListener( "eip6963:announceProvider", handleProviderAnnouncement, ); window.dispatchEvent(new Event("eip6963:requestProvider")); await new Promise((resolve) => window.setTimeout(resolve, 250)); window.removeEventListener( "eip6963:announceProvider", handleProviderAnnouncement, ); return [...providers.values()]; } ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#2-2-connect-the-wallet-and-request-account-access) 2.2. Connect the wallet and request account access After you select a provider, request account access before calling an App Kit SDK method. This should happen in a user-triggered action such as a `Connect wallet` button. TypeScript async function connectWallet(provider: EIP1193Provider) { await provider.request({ method: "eth_requestAccounts", params: undefined, // Required by the provider type even though this method has no params. }); const accounts = (await provider.request({ method: "eth_accounts", params: undefined, // Required by the provider type even though this method has no params. })) as string[]; return { connectedAddress: accounts[0] ?? null, }; } Keep wallet connection and App Kit actions as separate user actions. This avoids overlapping wallet permission or chain-switch requests while a previous wallet prompt is still pending. ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#2-3-create-a-viem-adapter-from-the-selected-wallet-provider) 2.3. Create a Viem adapter from the selected wallet provider Use the discovered provider to request account access, then create the App Kit adapter that signs transactions in the browser: TypeScript import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2"; async function connectBrowserWallet() { const providers = await discoverBrowserWallets(); const selectedWallet = providers.find( ({ info }) => info.rdns === "io.metamask" || info.name === "MetaMask", ) ?? providers[0]; if (!selectedWallet) { throw new Error("No EIP-6963 browser wallet found"); } const { connectedAddress } = await connectWallet(selectedWallet.provider); const adapter = await createViemAdapterFromProvider({ provider: selectedWallet.provider, }); return { adapter, connectedAddress, walletName: selectedWallet.info.name, }; } If multiple EVM wallets are installed, explicitly choose the wallet you want to use instead of relying on the first announced provider. The browser demo used to validate this quickstart prefers MetaMask when it is available. [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#step-3-send-usdc) Step 3. Send USDC --------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#3-1-call-kit-send) 3.1. Call `kit.send()` This is the only App Kit-specific send call you need after the wallet is connected: TypeScript import { AppKit } from "@circle-fin/app-kit"; import type { SendParams } from "@circle-fin/app-kit"; const kit = new AppKit(); async function sendUSDCWithBrowserWallet() { const { adapter, connectedAddress, walletName } = await connectBrowserWallet(); const sendParams: SendParams = { from: { adapter, chain: "Arc_Testnet" }, to: "RECIPIENT_ADDRESS", amount: "1.00", token: "USDC", }; const estimate = await kit.estimateSend(sendParams); const result = await kit.send(sendParams); console.log(`Submitted send with ${walletName}`, { connectedAddress, estimate, result, }); return result; } Using another [token](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) or [blockchain](https://docs.arc.io/app-kit/references/supported-blockchains) ? Change the `token` and `chain` values in `kit.send()` and ensure the connected wallet holds enough funds to complete the transfer. ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#3-2-verify-the-transaction) 3.2. Verify the transaction After `kit.send()` resolves, inspect the returned result. Use the transaction explorer URL to verify the amount and recipient on the blockchain.The following is an example of how the result of a successful send might look in the browser console. The values are used in this example only and are not a real transaction: Shell { name: "transfer", state: "success", txHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", explorerUrl: "https://testnet.arcscan.app/tx/0x1234567890abcdef...", } Use this flow to send USDC with a developer-controlled wallet managed by Circle Wallets. The example uses Arc Testnet. [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#prerequisites-2) Prerequisites ---------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Obtained a [API Key](https://developers.circle.com/api-reference/keys#creating-an-api-key-for-developer-services) and [entity secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret) from the [Circle Console](https://developers.circle.com/w3s/circle-developer-account) . * Created a developer-controlled wallet on Arc Testnet using the Circle Console. * Funded your Arc Testnet wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/) . [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#step-1-set-up-the-project-2) Step 1. Set up the project ----------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#1-1-create-the-project-and-install-dependencies-2) 1.1. Create the project and install dependencies Create a new directory and install the App Kit SDK with the Circle Wallets adapter and supporting tools: Shell # Set up your directory and initialize a Node.js project mkdir app-kit-send-circle-wallets cd app-kit-send-circle-wallets npm init -y npm pkg set type=module # Set up module type and start command npm pkg set scripts.start="tsx --env-file=.env index.ts" # Install runtime dependencies npm install @circle-fin/app-kit @circle-fin/adapter-circle-wallets tsx # Install dev dependencies npm install --save-dev typescript @types/node ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#1-2-configure-typescript-optional-2) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#1-3-set-environment-variables) 1.3. Set environment variables Create an `.env` file in the project directory: Shell touch .env Add your credentials. Replace `YOUR_API_KEY` with your Circle Developer API key, `YOUR_ENTITY_SECRET` with your entity secret. You can fetch the address from the [Circle Developer Console](https://developers.circle.com/w3s/circle-developer-account) or the [list wallets](https://developers.circle.com/api-reference/wallets/developer-controlled-wallets/get-wallets) endpoint: .env CIRCLE_API_KEY=YOUR_API_KEY CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET Edit `.env` files in your IDE or editor so credentials are not leaked to your shell history. [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#step-2-send-usdc) Step 2. Send USDC --------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#2-1-create-the-script) 2.1. Create the script Create an `index.ts` file in the project directory and add the following code. This code sends 1.00 USDC from your Circle Wallets-controlled Arc Testnet wallet to a recipient on Arc Testnet: Using another [token](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) or [blockchain](https://docs.arc.io/app-kit/references/supported-blockchains) ? Change the `token` and `chain` values in `kit.send()` and ensure the source wallet has enough funds to complete the transfer. TypeScript import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; import type { SendParams } from "@circle-fin/app-kit"; const kit = new AppKit(); const sourceWalletAddress = "YOUR_SOURCE_WALLET_ADDRESS"; const recipientAddress = "RECIPIENT_ADDRESS"; const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const sendParams: SendParams = { from: { adapter, chain: "Arc_Testnet", address: sourceWalletAddress, }, to: recipientAddress, amount: "1.00", token: "USDC", }; const estimate = await kit.estimateSend(sendParams); const result = await kit.send(sendParams); console.dir({ estimate, result }, { depth: null, colors: true }); ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#2-2-run-the-script) 2.2. Run the script Save the `index.ts` file and run the script in your terminal: Shell npm run start ### [​](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain#2-3-verify-the-transaction) 2.3. Verify the transaction After the script finishes, inspect the returned result in the terminal output. Use the transaction explorer URL to verify the amount and recipient on the blockchain.The following is an example of how the result of a successful send might look in the terminal output. The values are used in this example only and are not a real transaction: Shell { estimate: { gas: 406817n, fee: '8138073262135265', gasPrice: 20004260545n }, result: { name: 'send', state: 'success', txHash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', explorerUrl: 'https://testnet.arcscan.app/tx/0x1234567890abcdef123456789...' } } Was this page helpful? YesNo [Send overview](https://docs.arc.io/app-kit/send) [Bridge overview](https://docs.arc.io/app-kit/bridge) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Error recovery and troubleshooting for bridges - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/references/bridge-error-recovery#content-area) Bridge transfers can encounter two error types. Hard errors stop execution. Soft errors let you recover and retry the bridge transfer. The App Kit SDK provides error handling that helps you respond in both cases. Hard errors throw exceptions such as validation errors, configuration issues, and authentication problems. Soft errors occur during the bridge transfer but return enough transaction information for recovery. Examples include insufficient balance, network timeouts, and RPC connectivity issues. This guide helps you identify failure points, recover partial bridge transfers, and implement error handling patterns. The examples below are focused recovery snippets, not complete runnable scripts. They assume you have already configured `kit`, `sourceAdapter`, and `destinationAdapter`. See [Adapter setups](https://docs.arc.io/app-kit/tutorials/adapter-setups) for setup options. [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#bridge-transfer-failures) Bridge transfer failures ---------------------------------------------------------------------------------------------------------------------- This section explains how to identify where a bridge transfer failed and resume the bridge transfer manually. ### [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#transaction-steps-overview) Transaction steps overview Each bridge transfer uses Circle’s CCTP protocol provider, which breaks each transaction into various steps: * `approve`: Allows the contract to spend USDC. * `burn`: Burns USDC on the source blockchain and generates an attestation. * `fetchAttestation`: Waits for Circle to sign the burn proof. * `mint`: Mints USDC on the destination blockchain with the attestation. ### [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#bridge-result-details) Bridge result details When a bridge transfer fails, the App Kit SDK returns a `BridgeResult` object showing which steps completed and which failed. Use this result to decide whether to retry the transfer, inspect a transaction on a block explorer, or surface a recoverable state to your application. Focus on these `BridgeResult` properties during recovery: * `result.state` - shows whether the bridge transfer succeeded or failed (`pending`, `success`, `error`) * `result.steps` - each object contains: * `name`: the name of the step * `state`: the status of the step * `txHash`: the transaction hash if the step completed * `error`: an error message if the step failed This example shows a returned `result` object for a transaction that failed when fetching an attestation: Shell result.state: 'error' result.steps: [\ { name: 'approve', state: 'success', txHash: '0x123...' },\ { name: 'burn', state: 'success', txHash: '0x456...' },\ { name: 'fetchAttestation', state: 'error', error: 'Network timeout' },\ ] ### [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#step-analysis) Step analysis This example shows how to check for completed steps and use a helper function to find specific steps: TypeScript // Start a bridge transfer that might fail const result = await kit.bridge({ from: { adapter: sourceAdapter, chain: "Ethereum_Sepolia" }, to: { adapter: destinationAdapter, chain: "Arc_Testnet" }, amount: "1.00", }); // Check which steps completed successfully console.log("Bridge transfer state:", result.state); console.log("Steps:", result.steps); // Helper function to find specific steps const getStep = (stepName: string) => result.steps.find((step) => step.name === stepName); const approveStep = getStep("approve"); const burnStep = getStep("burn"); const attestationStep = getStep("fetchAttestation"); const mintStep = getStep("mint"); [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#recovery-scenarios) Recovery scenarios ---------------------------------------------------------------------------------------------------------- This section describes how you can implement recovery patterns. ### [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#retry-a-failed-bridge-transfer) Retry a failed bridge transfer If a bridge transfer fails, you can retry it with the `retry` method. Pass the failed `BridgeResult` and the `to` and `from` adapters. This example shows how the retry method works: TypeScript const result = await kit.bridge({ from: { adapter: sourceAdapter, chain: "Ethereum_Sepolia" }, to: { adapter: destinationAdapter, chain: "Arc_Testnet" }, amount: "1.00", }); if (result.state === "error") { const retryResult = await kit.retry(result, { from: sourceAdapter, to: destinationAdapter, }); console.dir(retryResult, { depth: null, colors: true }); } else { console.dir(result, { depth: null, colors: true }); } ### [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#retry-after-a-failed-mint-step) Retry after a failed mint step This pattern shows how to retry when the mint step fails. The `failingDestinationAdapter` placeholder represents a bad destination signer or RPC setup used to exercise the retry path: TypeScript import type { BridgeResult } from "@circle-fin/app-kit"; const findErrorStep = (result: BridgeResult) => { if (result.state === "error") { return result.steps.find((step) => step.state === "error"); } return null; }; const result = await kit.bridge({ from: { adapter: sourceAdapter, chain: "Ethereum_Sepolia" }, to: { adapter: failingDestinationAdapter, chain: "Arc_Testnet" }, amount: "1.00", }); console.log("INITIAL RESULT"); console.dir(result, { depth: null, colors: true }); if (result.state === "error") { const errorStep = findErrorStep(result); if ( errorStep && errorStep.errorMessage?.includes("gas required exceeds allowance") // This is an example error message ) { const retryResult = await kit.retry(result, { from: sourceAdapter, to: destinationAdapter, }); console.log("RETRY RESULT"); console.dir(retryResult, { depth: null, colors: true }); } } [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#common-issues) Common issues ------------------------------------------------------------------------------------------------ This section lists common issues and solutions. ### [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#insufficient-balance) Insufficient balance Ensure you have enough USDC in your wallet before a bridge transfer to avoid an insufficient balance error. This EVM example checks your wallet balance: TypeScript import { formatUnits } from "viem"; const balanceAction = await sourceAdapter.prepareAction( "usdc.balanceOf", {}, { chain: "Arc_Testnet" }, ); const balance = await balanceAction.execute(); console.log(`USDC balance: ${formatUnits(BigInt(balance), 6)}`); ### [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#transaction-stuck-or-failed) Transaction stuck or failed If a transaction is stuck or failed, check the transaction on a block explorer with the returned `txHash`. For Solana bridge transfers, use Solana Explorer or SolScan. If the transaction failed during the bridge transfer, check the returned `result.steps` to see which [transaction steps](https://docs.arc.io/app-kit/references/bridge-error-recovery#transaction-steps-overview) completed. [​](https://docs.arc.io/app-kit/references/bridge-error-recovery#best-practices) Best practices -------------------------------------------------------------------------------------------------- Follow these practices for prevention, recovery, and monitoring to improve reliability. **Prevention** * Test your integration on testnets before deploying on mainnet. * Monitor gas prices and adjust during network congestion. * Use dedicated RPC providers such as Alchemy or QuickNode. * Implement multiple RPC fallbacks. * Wrap all bridge transfers in try-catch including adapter setup and bridge calls. **Recovery** * Always save the bridge transfer state for recovery scenarios. * Verify which steps completed before attempting recovery. * Use appropriate timeouts and give network operations enough time to complete. * Implement exponential backoff and use increasing delays for retry logic. **Monitoring and debugging** * Use block explorers to verify transaction status. * Save intermediate results and persist bridge transfer state for recovery scenarios. Was this page helpful? YesNo [Specify a recipient address](https://docs.arc.io/app-kit/tutorials/bridge/specify-recipient-address) [Swap overview](https://docs.arc.io/app-kit/swap) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Quickstart: Swap tokens on a blockchain - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#content-area) This quickstart walks you through how to use the App Kit SDK’s [Swap](https://docs.arc.io/app-kit/swap) capability to swap tokens on the same blockchain. The example swaps USDC for EURC on Arc Testnet with the Circle Wallets adapter. Swap requires a free kit key from the [Circle Console](https://console.circle.com/) and is intended for server-side applications, but it is not specific to Circle Wallets; you can use any compatible EVM adapter with [supported tokens or blockchains](https://docs.arc.io/app-kit/references/supported-blockchains) . If your Circle Wallet is a smart contract account (SCA), set `allowanceStrategy: "approve"`. USDC permit signatures use `ecrecover`, which does not accept the SCA’s ERC-1271 signature, so the SDK uses an onchain `approve`. [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Obtained the following from the [Circle Console](https://developers.circle.com/w3s/circle-developer-account) : * [API Key](https://developers.circle.com/api-reference/keys#creating-an-api-key-for-developer-services) * [Entity secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret) * [Kit Key](https://developers.circle.com/api-reference/keys#kit-keys) * Created a developer-controlled wallet on Arc Testnet. * Funded your Arc Testnet wallet with enough testnet USDC for the swap amount and transaction fees from the [Circle Faucet](https://faucet.circle.com/) . [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#step-1-set-up-the-project) Step 1. Set up the project --------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#1-1-create-the-project-and-install-dependencies) 1.1. Create the project and install dependencies Create a new directory and install the App Kit SDK with the Circle Wallets adapter and supporting tools: Shell # Set up your directory and initialize a Node.js project mkdir app-kit-swap-same-chain-circle-wallets cd app-kit-swap-same-chain-circle-wallets npm init -y npm pkg set type=module # Set up module type and start command npm pkg set scripts.start="tsx --env-file=.env index.ts" # Install runtime dependencies npm install @circle-fin/app-kit @circle-fin/adapter-circle-wallets tsx # Install dev dependencies npm install --save-dev typescript @types/node Only need to swap and want a lighter install than the full App Kit SDK? Install the standalone Swap Kit instead: `@circle-fin/swap-kit`.For server-side scripts, you can use any compatible EVM adapter, including the private key adapter. Keep private keys on the server and configure the adapter in the [wallet adapter setup guide](https://docs.arc.io/app-kit/tutorials/adapter-setups) . ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#1-2-configure-typescript-optional) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#1-3-set-environment-variables) 1.3. Set environment variables Create an `.env` file in the project directory: Shell touch .env Add your server-side credentials. Replace `YOUR_API_KEY` with your Circle Developer API key, `YOUR_ENTITY_SECRET` with your entity secret, and `YOUR_KIT_KEY` with your kit key from the Circle Console: .env CIRCLE_API_KEY=YOUR_API_KEY CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET KIT_KEY=YOUR_KIT_KEY Edit `.env` files in your IDE or editor so credentials are not leaked to your shell history. [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#step-2-swap-tokens) Step 2. Swap tokens ------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#2-1-create-the-script) 2.1. Create the script Create an `index.ts` file in the project directory and add the following code. This code swaps 1.00 USDC for EURC from your Circle Wallets-controlled Arc Testnet wallet. Replace `YOUR_SOURCE_WALLET_ADDRESS` with the wallet address from the [Circle Developer Console](https://developers.circle.com/w3s/circle-developer-account) or the [list wallets](https://developers.circle.com/api-reference/wallets/developer-controlled-wallets/get-wallets) endpoint: Using another [token pair](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) or [blockchain](https://docs.arc.io/app-kit/references/supported-blockchains) ? Change the `tokenIn`, `tokenOut`, and `chain` values in `swapParams` and ensure the source wallet has enough input tokens and gas. TypeScript import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; import type { SwapParams } from "@circle-fin/app-kit"; const kit = new AppKit(); const sourceWalletAddress = "YOUR_SOURCE_WALLET_ADDRESS"; const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const swapParams: SwapParams = { from: { adapter, chain: "Arc_Testnet", address: sourceWalletAddress, // Omit the address if using Viem or Ethers adapters. }, tokenIn: "USDC", tokenOut: "EURC", amountIn: "1.00", config: { kitKey: process.env.KIT_KEY!, }, }; const estimate = await kit.estimateSwap(swapParams); console.dir({ estimate }, { depth: null, colors: true }); const result = await kit.swap(swapParams); console.dir({ result }, { depth: null, colors: true }); Customize your swaps to [collect a custom fee](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee) , [set a slippage tolerance or stop limit](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit) , or [get a pre-swap estimate](https://docs.arc.io/app-kit/tutorials/swap/estimate-swap-rate) . ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#2-2-run-the-script) 2.2. Run the script Save the `index.ts` file and run the script in your terminal: Shell npm run start Arc Testnet swap liquidity can be unstable. If a simulation reverts or a quote looks far from the expected rate, the testnet pool may be imbalanced or low on liquidity. Mainnet pools are typically deeper, but execution still depends on pool conditions, trade size, and slippage. ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain#2-3-verify-the-transaction) 2.3. Verify the transaction After the script finishes, inspect the returned result in the terminal output. Use the transaction explorer URL to verify the swap transaction and confirm the output amount. The following is an example of how the estimate and result of a successful swap might look in the terminal output. Estimate Result { estimate: { tokenIn: 'USDC', tokenOut: 'EURC', amountIn: '1.00', chain: 'Arc_Testnet', fromAddress: '0xabcd...1234', toAddress: '0xabcd...1234', stopLimit: { amount: '0.959901', token: 'EURC' }, estimatedOutput: { amount: '0.989589', token: 'EURC' }, fees: [\ { token: 'USDC', amount: '0.0002', type: 'provider' },\ { token: 'USDC', amount: '0.021106828371967574', type: 'gas' }\ ] } } { result: { tokenIn: 'USDC', tokenOut: 'EURC', chain: 'Arc_Testnet', amountIn: '1.0', amountOut: '0.989589', fromAddress: '0xabcd...1234', toAddress: '0xabcd...1234', txHash: '0x43cb...c801', explorerUrl: 'https://testnet.arcscan.app/tx/0x43cb...c801', fees: [{ token: 'USDC', amount: '0.0002', type: 'provider' }] } } Was this page helpful? YesNo [Swap overview](https://docs.arc.io/app-kit/swap) [Swap tokens across chains](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Quickstart: Swap tokens across blockchains - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#content-area) This quickstart walks you through how to use the App Kit SDK’s [Swap](https://docs.arc.io/app-kit/swap) capability to swap tokens across blockchains in a single call. The example swaps 1.00 USDC on Base for native POL delivered to a wallet on Polygon. Swap requires a kit key from the [Circle Console](https://console.circle.com/) and is intended for server-side applications, but it is not specific to Circle Wallets; you can use any compatible EVM adapter with [supported tokens or blockchains](https://docs.arc.io/app-kit/references/supported-blockchains) . If your Circle Wallet is a smart contract account (SCA), set `allowanceStrategy: "approve"`. USDC permit signatures use `ecrecover`, which does not accept the SCA’s ERC-1271 signature, so the SDK uses an onchain `approve`. [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Obtained the following from the [Circle Console](https://developers.circle.com/w3s/circle-developer-account) : * [API Key](https://developers.circle.com/api-reference/keys#creating-an-api-key-for-developer-services) * [Entity secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret) * [Kit Key](https://developers.circle.com/api-reference/keys#kit-keys) * Created a developer-controlled wallet on Base. * Funded your Base wallet with USDC for the swap amount and ETH for gas. [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#step-1-set-up-the-project) Step 1. Set up the project --------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#1-1-create-the-project-and-install-dependencies) 1.1. Create the project and install dependencies Create a new directory and install the App Kit SDK with the Circle Wallets adapter and supporting tools: Shell # Set up your directory and initialize a Node.js project mkdir app-kit-swap-crosschain-circle-wallets cd app-kit-swap-crosschain-circle-wallets npm init -y npm pkg set type=module # Set up module type and start command npm pkg set scripts.start="tsx --env-file=.env index.ts" # Install runtime dependencies npm install @circle-fin/app-kit @circle-fin/adapter-circle-wallets tsx # Install dev dependencies npm install --save-dev typescript @types/node Only need to swap and want a lighter install than the full App Kit SDK? Install the standalone Swap Kit instead: `@circle-fin/swap-kit`.For server-side scripts, you can use any compatible EVM adapter, including the private key adapter. Keep private keys on the server and configure the adapter in the [wallet adapter setup guide](https://docs.arc.io/app-kit/tutorials/adapter-setups) . ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#1-2-configure-typescript-optional) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#1-3-set-environment-variables) 1.3. Set environment variables Create an `.env` file in the project directory: Shell touch .env Add your server-side credentials. Replace `YOUR_API_KEY` with your Circle Developer API key, `YOUR_ENTITY_SECRET` with your entity secret, and `YOUR_KIT_KEY` with your kit key from the Circle Console: .env CIRCLE_API_KEY=YOUR_API_KEY CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET KIT_KEY=YOUR_KIT_KEY Edit `.env` files in your IDE or editor so credentials are not leaked to your shell history. [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#step-2-swap-tokens) Step 2. Swap tokens ------------------------------------------------------------------------------------------------------------- Create a script that estimates the route, submits the source-chain swap, waits for crosschain delivery, and prints the final swap status. Unlike same-chain swaps, crosschain swaps return immediately with `progress.status: 'PENDING'`. The destination-chain leg lands shortly after. Use `kit.waitForSwap()` to poll until the swap reaches a terminal status: `DONE`, `FAILED`, or `NOT_FOUND`. ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#2-1-create-the-script) 2.1. Create the script Create an `index.ts` file in the project directory and add the following code. This code swaps 1.00 USDC on Base for native POL delivered to your wallet on Polygon. Replace `YOUR_SOURCE_WALLET_ADDRESS` with your Base wallet address and `YOUR_RECIPIENT_ADDRESS` with your Polygon recipient address. The recipient address does not need POL to receive the swapped tokens. Using other [tokens](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) or [blockchains](https://docs.arc.io/app-kit/references/supported-blockchains) ? Change the source `chain`, `tokenIn`, `tokenOut`, `to.chain`, and `recipientAddress` values in `swapParams`. Both blockchains must support crosschain swap. TypeScript import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; import type { SwapParams } from "@circle-fin/app-kit"; const kit = new AppKit(); const sourceWalletAddress = "YOUR_SOURCE_WALLET_ADDRESS"; const recipientAddress = "YOUR_RECIPIENT_ADDRESS"; const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const swapParams: SwapParams = { from: { adapter, chain: "Base", address: sourceWalletAddress, }, tokenIn: "USDC", tokenOut: "NATIVE", amountIn: "1", to: { chain: "Polygon", recipientAddress, }, config: { kitKey: process.env.KIT_KEY!, allowanceStrategy: "approve", }, }; const estimate = await kit.estimateSwap(swapParams); console.dir({ estimate }, { depth: null, colors: true }); const result = await kit.swap(swapParams); console.dir({ result }, { depth: null, colors: true }); const status = await kit.waitForSwap({ result, kitKey: process.env.KIT_KEY!, }); console.dir({ status }, { depth: null, colors: true }); Customize your crosschain swaps to [collect a custom fee](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee) , or set a [slippage tolerance or stop limit](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit) . ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#2-2-run-the-script) 2.2. Run the script Save the `index.ts` file and run the script in your terminal: Shell npm run start Providers enforce route-specific minimum amounts. If `estimateSwap()` throws a `KitError` with code `INPUT_AMOUNT_OUT_OF_RANGE`, increase `amountIn` and estimate again (inside a `try`/`catch`) before calling `swap()`. ### [​](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain#2-3-verify-the-transactions) 2.3. Verify the transactions After the script finishes, find the returned status object in the terminal output: * Use the source `txHash` and `chainIn` to verify the source-chain swap on the Base block explorer. * Use `status.destination.txHash` to verify the destination-chain delivery on the Polygon block explorer. The following is an example of how the estimate, result, and final status might look in the terminal output. Values vary by route, liquidity, and gas price. Estimate Result Final status { estimate: { tokenIn: 'USDC', tokenOut: 'NATIVE', amountIn: '1', chainIn: 'Base', chainOut: 'Polygon', fromAddress: '0xabcd...1234', toAddress: '0xabcd...1234', stopLimit: { amount: '12.1427586417541', token: 'NATIVE' }, estimatedOutput: { amount: '12.518307878097', token: 'NATIVE' }, fees: [\ { token: 'USDC', amount: '0.0002', type: 'provider' },\ { token: 'ETH', amount: '0.000010429566703815', type: 'gas' }\ ] } } { result: { tokenIn: 'USDC', tokenOut: 'NATIVE', chainIn: 'Base', chainOut: 'Polygon', chain: 'Base', amountIn: '1.0', fromAddress: '0xabcd...1234', toAddress: '0xabcd...1234', txHash: '0x2f11...025e', explorerUrl: 'https://basescan.org/tx/0x2f11...025e', fees: [{ token: 'USDC', amount: '0.0002', type: 'provider' }], config: { allowanceStrategy: 'approve' }, progress: { status: 'PENDING' } } } { status: { progress: { status: 'DONE', substatus: 'COMPLETED', substatusMessage: 'The transfer is complete.' }, source: { txHash: '0x2f11...025e' }, destination: { txHash: '0xd824...e331', token: { symbol: 'POL', address: '0x0000000000000000000000000000000000000000' }, amount: '12.534043097347965275' } } } Was this page helpful? YesNo [Swap tokens on a chain](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain) [How swap fees work](https://docs.arc.io/app-kit/concepts/swap-fees) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # App Kit SDK: Send - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/send#content-area) The [App Kit SDK](https://docs.arc.io/app-kit) includes the Send capability that lets you send tokens from one wallet to another on the same blockchain. You can use an available [token alias](https://docs.arc.io/app-kit/references/supported-blockchains#token-aliases) or the token’s contract address. [​](https://docs.arc.io/app-kit/send#quick-look) Quick look -------------------------------------------------------------- This code snippet sends USDC from one wallet to another in a single method call: TypeScript // Send 1.00 USDC to a wallet on Arc Testnet const result = await kit.send({ from: { adapter, chain: "Arc_Testnet" }, to: "RECIPIENT_ADDRESS", amount: "1.00", token: "USDC", }); For a complete end-to-end flow, follow the quickstart: [Send Tokens Across Wallets](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain) . [​](https://docs.arc.io/app-kit/send#installation) Installation ------------------------------------------------------------------ [Install the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) to start sending tokens on the same blockchain. Was this page helpful? YesNo [Adapter setups](https://docs.arc.io/app-kit/tutorials/adapter-setups) [Send tokens across wallets](https://docs.arc.io/app-kit/quickstarts/send-tokens-same-chain) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Network deployment model - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/deployment-model#content-area) Arc follows a phased deployment model, progressively expanding network access and decentralization. The Public Testnet is the current active network, and developer access is permissionless at every phase: you can deploy contracts and send transactions without approval. [​](https://docs.arc.io/arc/concepts/deployment-model#deployment-phases) Deployment phases --------------------------------------------------------------------------------------------- | Phase | Status | Description | | --- | --- | --- | | **Devnet** | Internal | Internal protocol testing — Circle’s development network for protocol iteration. Not publicly accessible. | | **Private Testnet** | Complete | Invitation-only testnet used for early integration testing with select partners. | | **Public Testnet** | Live | Mirrors mainnet behavior without using real assets. Permissionless testnet (chain ID `5042002`) available to all developers. | | **Private Mainnet** | Upcoming | Production network where real value flows. Initial launch with approximately 20 permissioned validators and limited access. | | **Public Mainnet** | Upcoming | Full public mainnet with open access for all users and applications. | For connection details, see [RPC endpoints](https://docs.arc.io/arc/references/rpc-endpoints) . To explore testnet activity, visit [testnet.arcscan.app](https://testnet.arcscan.app/) . [​](https://docs.arc.io/arc/concepts/deployment-model#testnet-performance) Testnet performance ------------------------------------------------------------------------------------------------- The Public Testnet has demonstrated production-grade performance since launch. The following metrics are from the Q1 2026 monthly review. | Metric | Value | | --- | --- | | **Uptime** | 100% | | **Average block time** | ~0.48 s | | **Transactions** | ~30.7M | | **Unique wallets** | 916K | | **Block explorer** | [testnet.arcscan.app](https://testnet.arcscan.app/) | [​](https://docs.arc.io/arc/concepts/deployment-model#validator-model-at-launch) Validator model at launch ------------------------------------------------------------------------------------------------------------- Arc launches with a **permissioned Proof-of-Authority (PoA)** validator set — a consensus model in which a fixed group of known, vetted institutions produce and validate blocks, rather than anonymous participants staking tokens. Approximately 20 SOC 2-certified validators operate across multiple global regions, each bound by uptime SLAs and compliance obligations. Block production rotates among validators to ensure fairness and liveness. For full details on the validator set, see [Consensus layer](https://docs.arc.io/arc/concepts/consensus-layer#proof-of-authority-validator-set) . [​](https://docs.arc.io/arc/concepts/deployment-model#future-evolution) Future evolution ------------------------------------------------------------------------------------------- Arc may transition from Proof-of-Authority to a **permissioned Proof-of-Stake (PoS)** model — a consensus mechanism where validators lock tokens as collateral rather than relying solely on institutional selection. This broadens validator participation while maintaining the compliance requirements and institutional accountability that define the network. See the [consensus layer roadmap](https://docs.arc.io/arc/concepts/consensus-layer#roadmap) for details. Was this page helpful? YesNo [RPC endpoints](https://docs.arc.io/arc/references/rpc-endpoints) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Deterministic finality and settlement - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/deterministic-finality#content-area) Arc provides deterministic finality: every transaction is either unconfirmed or final, with no intermediate state. This guarantee comes from the network’s [consensus layer](https://docs.arc.io/arc/concepts/consensus-layer) , which uses a Byzantine Fault Tolerant (BFT) consensus protocol — once two-thirds or more of validators sign off on a block, that block is irreversible. Every transaction in a committed block is immediately and irreversibly settled. There are no confirmation windows, no reorganization risk, and no probabilistic uncertainty. [​](https://docs.arc.io/arc/concepts/deterministic-finality#finality-comparison) Finality comparison ------------------------------------------------------------------------------------------------------- Arc’s [Malachite BFT consensus](https://docs.arc.io/arc/concepts/consensus-layer) , a Byzantine Fault Tolerant protocol, finalizes blocks in under one second. This is orders of magnitude faster than the finality guarantees on other networks: | Network | Finality | Notes | | --- | --- | --- | | **Arc** | <1 s | Deterministic. Final on commit. | | **Ethereum L1** | 12-15 min | Two epochs of attestations required for finality. | | **Typical L2 rollup** | ~7 days | Withdrawal finality depends on the challenge or proof window to L1. | [​](https://docs.arc.io/arc/concepts/deterministic-finality#use-cases) Use cases ----------------------------------------------------------------------------------- Sub-second deterministic finality enables use cases that are impractical on slower networks: | Use case | How finality helps | | --- | --- | | Point-of-sale payments | A merchant can confirm payment and release goods without waiting for additional block confirmations. | | Crossborder settlement | Transfers between counterparties finalize instantly, eliminating the settlement windows that introduce counterparty risk. | | Institutional clearing | Trades and margin calls settle with immediate certainty, matching the expectations of traditional financial infrastructure. | | Composable workflows | Multi-step onchain flows (such as swap-then-bridge) execute sequentially without polling or confirmation delays between steps. | [​](https://docs.arc.io/arc/concepts/deterministic-finality#developer-benefits) Developer benefits ----------------------------------------------------------------------------------------------------- Deterministic finality simplifies application design by removing the edge cases that probabilistic chains force you to handle. No reorg handling ----------------- You don’t need retry logic, rollback mechanisms, or confirmation-count thresholds. A confirmed transaction stays confirmed. Immediate offchain effects -------------------------- Safely trigger downstream actions (webhooks, database writes, notifications) as soon as a block is committed, without waiting for additional confirmations. Simplified state management --------------------------- Your application only needs to track two transaction states — unconfirmed and final — rather than tracking a sliding confirmation window. Enterprise compliance --------------------- Settlement finality is auditable and provable, meeting the assurance requirements of regulated financial institutions. Was this page helpful? YesNo [Stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) [Opt-in privacy](https://docs.arc.io/arc/concepts/opt-in-privacy) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Opt-in privacy - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/opt-in-privacy#content-area) Arc provides opt-in privacy through **Arc Privacy Sector (APS)**, a confidential execution environment for Solidity contracts that runs alongside Arc’s public EVM. Deploy existing Solidity contracts into APS with minimal modifications to keep contract state and transaction data confidential while preserving atomicity and consensus consistency with the public ledger. Privacy is opt-in: applications choose when business or regulatory requirements warrant keeping data off the public ledger. Privacy features are on the roadmap and not yet available on Arc. [​](https://docs.arc.io/arc/concepts/opt-in-privacy#how-arc-privacy-sector-aps-works) How Arc Privacy Sector (APS) works --------------------------------------------------------------------------------------------------------------------------- Arc runs one blockchain with two execution environments: **Arc** for public state and **APS** for private state. Both environments produce state roots that are committed together in each block, advancing in lockstep within the same consensus rounds. To submit a private transaction, encrypt a standard EVM transaction using the APS network public key and send the ciphertext as calldata to an Arc precompile. From the public ledger’s perspective, this looks like an ordinary precompile call — the encrypted payload is opaque. Validators running APS inside hardware enclaves decrypt, verify, and execute the transaction against private state using standard EVM rules. The privacy precompile returns only an acknowledgement and a predefined gas cost. No execution results, return values, or event logs are exposed to the public ledger. After consensus finalizes the block, a light client inside each enclave verifies the block’s commit certificate before making private state queryable. Retrieve transaction results from an RPC node by providing the transaction hash and proving authorization. [​](https://docs.arc.io/arc/concepts/opt-in-privacy#synchronous-composability) Synchronous composability ----------------------------------------------------------------------------------------------------------- APS finalizes private and public state in the same block by the same validator set. Private and public contracts compose atomically without cross-chain bridges or asynchronous messaging. Bridge assets between public and private contracts through controlled precompile operations that execute within a single block. Both sides of the bridge see consistent state at each block height. [​](https://docs.arc.io/arc/concepts/opt-in-privacy#contract-isolation-by-default) Contract isolation by default ------------------------------------------------------------------------------------------------------------------- APS enforces a **default-deny** isolation model. When you deploy a contract, every function and storage slot is inaccessible to external callers and other contracts by default. Privacy holds even if you forget to add explicit protections. Exposure is opt-in through three mechanisms: * **Function-level access policies.** Each function selector carries an access policy: `Open`, `Restricted`, or `Locked`. Restricted functions require the caller to have an explicit grant. Locked functions revert unconditionally. * **Trust domains.** A contract’s admin can grant trust to specific contracts via `addTrustee`. Without an active trust relationship, introspection opcodes like `EXTCODESIZE`, `EXTCODEHASH`, and `BALANCE` return zero. Trust is unidirectional and revocable. * **Runtime visibility enforcement.** Solidity visibility modifiers (`public`, `external`, `internal`, `private`) are enforced at runtime on every `CALL` and `DELEGATECALL`, preventing crafted calldata from reaching unintended entrypoints. Additional isolation behavior: event logging is disabled by default (events require an explicit precompile), cross-boundary probes are masked, revert reasons are sanitized, and gas reporting to external observers is constant-time. [​](https://docs.arc.io/arc/concepts/opt-in-privacy#deploying-existing-solidity-contracts) Deploying existing Solidity contracts ----------------------------------------------------------------------------------------------------------------------------------- APS reuses standard EVM bytecode and tooling. Core business rules, state management, and function implementations carry over unchanged. The primary adaptation is how contracts expose their interfaces to external callers, through access policy and trust domain configuration. For example, deploy a standard OpenZeppelin ERC-20 token into APS without modifying the bytecode, then use the trust domain API to make `transfer` open to any caller while restricting `balanceOf` to explicitly trusted contracts — without redeploying. [​](https://docs.arc.io/arc/concepts/opt-in-privacy#post-quantum-encryption) Post-quantum encryption ------------------------------------------------------------------------------------------------------- APS uses hybrid post-quantum cryptography to protect against harvest-now, decrypt-later attacks: * Public-key encryption uses X-Wing KEM (combining X25519 and ML-KEM-768) with HKDF-SHA256 and AES-256-GCM. * Symmetric encryption uses AES-256-GCM-SIV for contract state and AES-256-GCM for state roots. * Node-to-node communication uses TLS 1.3 with the X25519MLKEM768 hybrid key agreement. See [Post-quantum security](https://docs.arc.io/arc/concepts/post-quantum-security) for Arc’s broader quantum-resilience roadmap. [​](https://docs.arc.io/arc/concepts/opt-in-privacy#key-management) Key management ------------------------------------------------------------------------------------- APS uses a single master secret key (MSK) distributed across validators through Shamir threshold secret sharing. The MSK can only be reconstructed inside attested enclaves and is never exposed to validator hosts. All derived keys for transaction decryption, per-contract state encryption, and state root encryption come from the MSK. Each validator organization runs an independent KMS with an attestation policy that governs which enclave profiles may access key material. Seed nodes provide fast MSK recovery if a validator restarts. Key rotation is supported when the validator set changes. Was this page helpful? YesNo [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) [Post-quantum security](https://docs.arc.io/arc/concepts/post-quantum-security) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Post-quantum security - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/post-quantum-security#content-area) Arc’s post-quantum roadmap covers wallet signatures, validator authentication, private smart contract state, and offchain infrastructure. Post-quantum features are on the roadmap and not yet available on Arc. [​](https://docs.arc.io/arc/concepts/post-quantum-security#why-post-quantum-security-matters) Why post-quantum security matters ---------------------------------------------------------------------------------------------------------------------------------- Most public-key cryptography used today is vulnerable to large-scale quantum computers. If those computers become practical, blockchains face two risks: * **Signature forgery.** A quantum computer that breaks public-key cryptography can forge signatures that secure wallets, authorize transactions, and authenticate network participants. * **Harvest-now, decrypt-later attacks.** Encrypted data captured today can be stored and decrypted later when quantum attacks become practical, exposing private transaction details, balances, and other sensitive data. Because blockchain data is long-lived, post-quantum protections need to be in place before quantum attacks are widely available. [​](https://docs.arc.io/arc/concepts/post-quantum-security#post-quantum-roadmap) Post-quantum roadmap -------------------------------------------------------------------------------------------------------- Arc’s roadmap phases each layer in a production-aligned sequence. | Milestone | Release target | Scope | Why it matters | | --- | --- | --- | --- | | Post-quantum wallet signatures | Mainnet launch | Arc mainnet launches support for a post-quantum wallet signature scheme. | Classical wallet signatures are the most immediate quantum risk to user funds. | | Post-quantum privacy | Near-term | Arc Privacy mainnet introduces post-quantum protections for encrypted state and node-to-node communication in privacy mode. | Private balances, counterparties, and transaction details face harvest-now, decrypt-later risk without post-quantum encryption. | | Offchain infrastructure upgrades | Mid-term | Circle upgrades infrastructure such as TLS, encrypted data flows, and related operational systems. | Offchain infrastructure uses the same vulnerable cryptography as onchain systems. | | Post-quantum validator signatures | Long-term | Arc adds a quantum-resistant signature scheme for validators. | Consensus signatures protect the integrity of the Arc ledger. | [​](https://docs.arc.io/arc/concepts/post-quantum-security#post-quantum-wallet-signatures) Post-quantum wallet signatures ---------------------------------------------------------------------------------------------------------------------------- Arc introduces beta support for a post-quantum wallet signature based on `SLH-DSA-SHA2-128s` at mainnet launch. Adoption is opt-in. Ecosystem constraints to keep in mind: * Hardware wallet support will take time to mature. * Post-quantum standards are still evolving, so long-term signature choices may change. Expect a transition period as tooling, wallet support, and integrations mature. [​](https://docs.arc.io/arc/concepts/post-quantum-security#post-quantum-privacy) Post-quantum privacy -------------------------------------------------------------------------------------------------------- [Arc Privacy](https://docs.arc.io/arc/concepts/opt-in-privacy) addresses the harvest-now, decrypt-later threat. When Arc Privacy launches, encrypted state and private transaction flows use post-quantum cryptography. In practice, public-key encryption uses X-Wing KEM (combining X25519 and ML-KEM-768) with HKDF-SHA256 and AES-256-GCM. Attackers can capture encrypted data today and attempt to decrypt it later when quantum attacks become practical. Post-quantum encryption at the platform level protects sensitive balances, transaction details, and recipients without requiring you to implement custom post-quantum cryptography. [​](https://docs.arc.io/arc/concepts/post-quantum-security#post-quantum-validator-signatures) Post-quantum validator signatures ---------------------------------------------------------------------------------------------------------------------------------- Validator authentication also requires post-quantum protection to keep the ledger resilient. Arc adds post-quantum validator signatures in a later phase. This sequencing is intentional: validator upgrades must be introduced carefully to preserve throughput, latency, and operational reliability. Because Arc uses sub-second finality, the window to exploit validator signatures is narrower than the wallet-signature risk, making wallets the higher-priority target. [​](https://docs.arc.io/arc/concepts/post-quantum-security#offchain-infrastructure) Offchain infrastructure -------------------------------------------------------------------------------------------------------------- Quantum resilience extends beyond the chain itself. Circle’s roadmap includes upgrading node-to-node communication to TLS 1.3 with the X25519MLKEM768 hybrid key agreement, as well as encrypted data flows, access controls, cloud environments, and other operational systems that support Arc. Offchain traffic and stored data face the same post-quantum threat vectors as onchain data when vulnerable cryptography remains in use. Was this page helpful? YesNo [Opt-in privacy](https://docs.arc.io/arc/concepts/opt-in-privacy) [System overview](https://docs.arc.io/arc/concepts/system-overview) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Arc Network - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc-chain#content-area) Arc is a purpose-built Layer-1 blockchain for stablecoin-native financial applications, with USDC as gas, sub-second deterministic finality, and full EVM compatibility. It supports payments, lending, FX, treasury management, and agentic commerce at scale. Beyond the network, Arc provides [App Kits](https://docs.arc.io/app-kit) for crosschain payment workflows, [AI tooling](https://docs.arc.io/ai/mcp) for agent-based development, and a growing [ecosystem of infrastructure partners](https://docs.arc.io/build#developer-tools) . [​](https://docs.arc.io/arc-chain#key-features) Key features --------------------------------------------------------------- Stablecoin native model ----------------------- USDC is the native gas token. EURC and USYC are natively supported. No volatile token required. Stable fee design ----------------- Transaction fees are denominated in USDC, so costs stay predictable regardless of market volatility. Deterministic finality ---------------------- Transactions finalize in under one second with no risk of chain reorganization. EVM compatibility ----------------- Deploy existing Solidity contracts and use standard Ethereum tooling like Hardhat, Foundry, and Viem. Opt-in privacy -------------- Run Solidity contracts confidentially in Arc Privacy Sector (APS), an opt-in execution environment that finalizes alongside the public EVM. Post-quantum security --------------------- SLH-DSA-SHA2-128s wallet signatures protect accounts against future quantum threats. [​](https://docs.arc.io/arc-chain#stablecoins-on-arc) Stablecoins on Arc --------------------------------------------------------------------------- Arc is designed from the ground up around stablecoins. USDC serves as the native [gas token](https://docs.arc.io/arc/references/gas-and-fees) , EURC enables euro-denominated transfers, and USYC provides onchain yield. There is no volatile native token. For details on the dual-interface model, native representations, and supported assets, see [stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) . [​](https://docs.arc.io/arc-chain#network-architecture) Network architecture ------------------------------------------------------------------------------- Arc separates consensus from execution so each layer can optimize independently while maintaining full compatibility with the Ethereum ecosystem. * **[Consensus layer](https://docs.arc.io/arc/concepts/consensus-layer) :** Built on Malachite BFT, delivering sub-second finality and high throughput across a permissioned validator set. * **[Execution layer](https://docs.arc.io/arc/concepts/execution-layer) :** Powered by Reth, a Rust Ethereum client that runs the EVM so Solidity contracts, development tools, and wallet infrastructure work without modification. For a complete view of how the layers interact, see the [system overview](https://docs.arc.io/arc/concepts/system-overview) . [​](https://docs.arc.io/arc-chain#network-details) Network details --------------------------------------------------------------------- | **Property** | **Value** | | --- | --- | | Consensus | Malachite BFT | | Execution environment | EVM (Osaka hard fork) | | Gas token | USDC | | Block time | ~0.48 s (testnet) | | Chain ID | 5042002 | | Finality | Deterministic, sub-second | | Validator participation | Permissioned | | Developer access | Permissionless | For RPC endpoints and connection details, see [RPC endpoints](https://docs.arc.io/arc/references/rpc-endpoints) . For deployed contract addresses, see [contract addresses](https://docs.arc.io/arc/references/contract-addresses) . For how Arc validators are selected and how the network is operated, see [deployment model](https://docs.arc.io/arc/concepts/deployment-model) . [​](https://docs.arc.io/arc-chain#start-building) Start building ------------------------------------------------------------------- Ready to build on Arc? Head to the [Build](https://docs.arc.io/build) section for quickstarts and tutorials covering smart contract deployment, crosschain bridging, App Kits workflows, and AI agent integration. Was this page helpful? YesNo [EVM differences](https://docs.arc.io/arc/references/evm-differences) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Consensus layer - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/consensus-layer#content-area) Arc’s consensus layer is built on [Malachite](https://github.com/circlefin/malachite/) , a high-performance, open-source implementation of the Tendermint Byzantine Fault Tolerant (BFT) protocol. BFT consensus ensures the network reaches agreement on a single history of transactions even if some validators behave maliciously or go offline. Arc uses a Proof-of-Authority (PoA) validator set to order transactions, produce blocks, and deliver [deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) — the guarantee that committed blocks are permanent and can never be reversed or reorganized — in under one second. [​](https://docs.arc.io/arc/concepts/consensus-layer#how-malachite-consensus-works) How Malachite consensus works -------------------------------------------------------------------------------------------------------------------- For how this fits into the broader architecture, see the [system overview](https://docs.arc.io/arc/concepts/system-overview) . Each block passes through a four-step pipeline. A rotating proposer assembles transactions, and all validators participate in two rounds of voting before the block is committed. 1. **Propose** — A validator selected as proposer for the current round bundles pending transactions into a block and broadcasts it. 2. **Pre-vote** — Every validator evaluates the proposed block and broadcasts a vote on its validity. 3. **Pre-commit** — Validators broadcast a second vote. If more than two-thirds of validators pre-commit to the same block, it proceeds to commit. 4. **Commit** — The block is finalized and appended to the chain. Every transaction in the block is irreversible. This two-phase voting process (pre-vote + pre-commit) guarantees that two conflicting blocks can never both be finalized, making reorganizations impossible. [​](https://docs.arc.io/arc/concepts/consensus-layer#proof-of-authority-validator-set) Proof-of-Authority validator set -------------------------------------------------------------------------------------------------------------------------- Arc uses a **permissioned Proof-of-Authority (PoA)** model instead of anonymous economic staking. Validators are selected, known institutions with compliance obligations and operational guarantees. For details on operating a validator node, see [running a node](https://docs.arc.io/arc/concepts/running-a-node) . * **SOC 2 certified** — Validators meet audited security and availability standards. * **Geographic distribution** — Nodes run across multiple global regions to reduce correlated downtime. * **Rotating proposer** — Block production rotates among validators to ensure fairness and liveness. * **Uptime SLAs** — Each validator commits to operational availability requirements. This design replaces anonymous economic incentives with institutional accountability, providing stronger assurances for regulated finance. [​](https://docs.arc.io/arc/concepts/consensus-layer#performance-characteristics) Performance characteristics ---------------------------------------------------------------------------------------------------------------- Performance also depends on the [execution layer](https://docs.arc.io/arc/concepts/execution-layer) , which processes transactions within each block. Malachite delivers optimistic responsiveness: blocks are produced as fast as the network permits, with no artificial delays or extra timeouts. | Metric | Value | Conditions | | --- | --- | --- | | Throughput | 3,000+ TPS | 20 globally distributed validators | | Finality | <350 ms | Benchmark conditions | | Peak throughput | 10,000+ TPS | 4 validators | [​](https://docs.arc.io/arc/concepts/consensus-layer#security-guarantees) Security guarantees ------------------------------------------------------------------------------------------------ Arc combines protocol-level safety with institutional safeguards: | Guarantee | Description | | --- | --- | | Safety | With fewer than one-third faulty validators, consensus guarantees that no conflicting blocks are finalized. | | Liveness | The network continues to produce blocks as long as two-thirds or more of validators are online and honest. | | Accountability | Validators are regulated institutions, making malicious behavior costly beyond protocol penalties. | | Resilience | Geographic distribution reduces the risk of correlated outages or targeted attacks. | The [deployment model](https://docs.arc.io/arc/concepts/deployment-model) provides additional detail on how validators are geographically distributed. [​](https://docs.arc.io/arc/concepts/consensus-layer#roadmap) Roadmap ------------------------------------------------------------------------ The Malachite roadmap includes multi-proposer support (multiple validators propose blocks in parallel for higher throughput), a protocol optimization that reduces consensus from three rounds to two for lower latency, and a potential transition from Proof-of-Authority to permissioned Proof-of-Stake to broaden validator participation while maintaining compliance requirements. Was this page helpful? YesNo [System overview](https://docs.arc.io/arc/concepts/system-overview) [Execution layer](https://docs.arc.io/arc/concepts/execution-layer) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # System overview - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/system-overview#content-area) Arc is a two-layer blockchain. The **consensus layer** (Malachite) orders transactions and finalizes blocks. The **execution layer** (Reth) processes those transactions, applies state changes, and produces a new state root. This separation lets each layer optimize independently while delivering sub-second deterministic finality and full EVM compatibility. [​](https://docs.arc.io/arc/concepts/system-overview#two-layer-architecture) Two-layer architecture ------------------------------------------------------------------------------------------------------ ### [​](https://docs.arc.io/arc/concepts/system-overview#consensus-layer-malachite) Consensus layer (Malachite) The consensus layer runs [Malachite](https://docs.arc.io/arc/concepts/consensus-layer) , a high-performance implementation of the Tendermint BFT protocol. Its job is to agree on transaction order and finalize blocks across a permissioned validator set. Malachite uses a two-phase voting process (pre-vote and pre-commit) that requires more than two-thirds of validators to agree before a block is committed. Once committed, the block is final and irreversible. Key properties: * **Deterministic finality** in under one second, with no reorganization risk * **High throughput** of 3,000+ TPS with 20 validators (benchmarked at <350 ms finality) * **Proof-of-Authority** validator set composed of regulated institutions ### [​](https://docs.arc.io/arc/concepts/system-overview#execution-layer-reth) Execution layer (Reth) The execution layer runs [Reth](https://docs.arc.io/arc/concepts/execution-layer) , a Rust implementation of the Ethereum execution client. It maintains the full blockchain state (accounts, balances, contracts, storage), executes EVM transactions, and extends the standard execution pipeline with Arc-specific modules. Core responsibilities: * **Maintain the ledger** — track accounts, balances, smart contracts, and transaction history * **Execute transactions** — apply EVM logic, deduct gas fees, and route through Arc modules * **Produce the state root** — compute a Merkle root of the updated state for consensus to finalize [​](https://docs.arc.io/arc/concepts/system-overview#transaction-lifecycle) Transaction lifecycle ---------------------------------------------------------------------------------------------------- A transaction moves through both layers in sequence. The consensus layer orders and finalizes, then the execution layer processes and applies state changes. 1. **Submit.** A user or application sends a signed transaction to an Arc node via JSON-RPC. 2. **Mempool.** The node validates the transaction for correctness (valid signature, sufficient balance, proper nonce) and holds it in the transaction pool. 3. **Propose.** A rotating validator is selected as proposer for the current round. It bundles pending transactions from the mempool into a candidate block and broadcasts it to the validator set. 4. **Pre-vote.** Each validator evaluates the proposed block and broadcasts a vote indicating whether the block is valid. 5. **Pre-commit.** Validators broadcast a second round of votes. If more than two-thirds of validators pre-commit to the same block, the block proceeds to commit. 6. **Commit.** The block is finalized and appended to the chain. Every transaction in the block is now irreversible. 7. **Execution.** Reth applies each transaction to the current state. The EVM processes smart contract logic and transfers, while Arc modules (Fee Manager, and in the future, Arc Privacy Sector (APS) and Stablecoin Services) handle their respective functions. 8. **State root.** Reth computes a Merkle root of the updated state. This root serves as a cryptographic commitment to the entire post-block state. The entire lifecycle completes in under one second. For a deep dive into the consensus protocol, see [consensus layer](https://docs.arc.io/arc/concepts/consensus-layer) . For details on how Reth processes transactions, see [execution layer](https://docs.arc.io/arc/concepts/execution-layer) . [​](https://docs.arc.io/arc/concepts/system-overview#arc-specific-modules) Arc-specific modules -------------------------------------------------------------------------------------------------- Arc extends the standard EVM execution pipeline with modules that provide stablecoin-native capabilities. These modules run inside the execution layer and process transactions alongside standard EVM logic. | Module | Status | Function | | --- | --- | --- | | **[Fee Manager](https://docs.arc.io/arc/concepts/stable-fee-design)
** | Live | Stabilizes gas fees using USDC as the unit of account. Uses EWMA smoothing to prevent short-term fee spikes. | | **[Arc Privacy Sector (APS)](https://docs.arc.io/arc/concepts/opt-in-privacy)
** | Planned | Confidential execution environment for Solidity contracts, composing synchronously with the public EVM. | | **[Stablecoin Services](https://docs.arc.io/arc/concepts/stablecoin-native-model)
** | Planned | Powers crosscurrency settlement, paymaster-sponsored transactions, and multi-stablecoin gas payments. | Arc Privacy Sector (APS) and Stablecoin Services are on the roadmap and not yet available on Arc. Arc also includes the **CallFrom precompile**, which preserves `msg.sender` across delegated calls. This precompile is used by the Memo contract and Multicall3From to enable batched and annotated transactions. For details on how these differ from standard Ethereum behavior, see [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) . [​](https://docs.arc.io/arc/concepts/system-overview#developer-benefits) Developer benefits ---------------------------------------------------------------------------------------------- * **Instant settlement.** Deterministic finality means you can trigger downstream actions (webhooks, database writes, notifications) as soon as a block commits, with no confirmation polling or reorg handling. * **Familiar stack, native extensions.** Deploy Solidity contracts with standard Ethereum tooling. Fee stabilization, privacy, and stablecoin primitives are built into the execution layer, not bolt-on services. Was this page helpful? YesNo [Post-quantum security](https://docs.arc.io/arc/concepts/post-quantum-security) [Consensus layer](https://docs.arc.io/arc/concepts/consensus-layer) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Stable fee design - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/stable-fee-design#content-area) Arc’s fee market builds on [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) but replaces per-block base fee recalculation with an exponentially weighted moving average (EWMA) of block utilization. This smoothing mechanism ensures that short demand spikes do not propagate into sudden fee jumps, keeping transaction costs stable and easy to estimate. The base fee floor targets approximately $0.001 per ERC-20 transfer under normal conditions. For why Arc uses USDC as its gas token, see [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) . For runtime parameters and RPC calls, see [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) . [​](https://docs.arc.io/arc/concepts/stable-fee-design#how-ewma-fee-adjustment-works) How EWMA fee adjustment works ---------------------------------------------------------------------------------------------------------------------- Standard EIP-1559 recalculates the base fee every block based on whether the previous block exceeded or fell short of a target gas utilization. This approach can produce sharp fee swings when a single block fills quickly. Arc replaces this per-block step function with an EWMA over a configurable smoothing window. Instead of reacting fully to one block’s utilization, the protocol blends recent utilization into a running average. The base fee then adjusts proportionally to the smoothed signal rather than the raw per-block value. utilization_ewma(n) = alpha * block_utilization(n) + (1 - alpha) * utilization_ewma(n - 1) base_fee(n) = adjust(base_fee(n - 1), utilization_ewma(n), target_utilization) # adjust() scales base_fee proportionally to (utilization_ewma / target_utilization), # clamped to [minimum_base_fee, maximum_base_fee] * `alpha` controls how quickly the average responds to new data. A smaller alpha means more smoothing and slower reaction to spikes. * `target_utilization` is the equilibrium point the protocol steers toward. When the EWMA exceeds the target, the base fee rises; when it falls below, the base fee decreases. The result is a fee curve that trends toward equilibrium gradually, making cost estimation straightforward even during bursts of activity. [​](https://docs.arc.io/arc/concepts/stable-fee-design#key-parameters) Key parameters ---------------------------------------------------------------------------------------- | Parameter | Value | Notes | | --- | --- | --- | | **Base fee floor** | ~$0.001 per ERC-20 transfer | Design-time target under normal load | | **Testnet minimum base fee** | 20 Gwei | Transactions below this threshold are rejected from the mempool | | **Maximum base fee** | 20,000 Gwei | Hard ceiling that bounds worst-case cost | | **Gas throughput** | 30M gas/block (~60M gas/sec at 0.5 s block time) | Protocol-level capacity limit | | **Smoothing method** | EWMA of block utilization | Replaces EIP-1559 per-block recalculation | These parameters reflect the current testnet configuration and may be adjusted before mainnet launch. See [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) for the latest runtime values. [​](https://docs.arc.io/arc/concepts/stable-fee-design#priority-fees) Priority fees -------------------------------------------------------------------------------------- Like standard EIP-1559, Arc transactions include a priority fee (tip) in addition to the base fee. The tip incentivizes the sequencer to include a transaction promptly. Under normal network conditions, a minimal or zero tip is sufficient because the base fee alone covers inclusion. Set `maxPriorityFeePerGas` to `0` for most transactions. During periods of sustained congestion, a small tip can signal higher urgency. Query `eth_maxPriorityFeePerGas` on the Arc RPC for a recommended value. [​](https://docs.arc.io/arc/concepts/stable-fee-design#why-smoothing-matters) Why smoothing matters ------------------------------------------------------------------------------------------------------ Without smoothing, a single high-utilization block can double the base fee instantly, and a series of empty blocks can drop it just as fast. This volatility forces developers to build retry logic, fee-bumping heuristics, and complex estimation strategies. With EWMA smoothing: * **Gradual adjustment.** Fees move in small increments, so a short burst of transactions does not cause a sudden price shock. * **Bounded range.** The hard ceiling at 20,000 Gwei ensures that even sustained congestion cannot push fees to extreme levels. * **Simple estimation.** You can call `eth_gasPrice`, `eth_feeHistory`, or `eth_maxPriorityFeePerGas` on the Arc RPC and trust that the returned values will remain accurate for a reasonable submission window. [​](https://docs.arc.io/arc/concepts/stable-fee-design#developer-benefits) Developer benefits ------------------------------------------------------------------------------------------------ Building on Arc’s stable fee design simplifies cost management and removes common integration challenges. Predictable costs ----------------- ERC-20 transfers target ~$0.001, and EWMA smoothing keeps fees stable. You can quote costs to users with confidence. Congestion resistant -------------------- Short-term demand spikes are absorbed by the smoothing window, so fees don’t surprise your users during traffic bursts. Enterprise ready ---------------- Dollar-denominated, bounded fees mean transaction costs map directly to a known USDC amount. No post-hoc conversion is needed for cost tracking or reconciliation. Flexible fee payment -------------------- Sponsor transactions on behalf of users through [account abstraction](https://docs.arc.io/arc/tools/account-abstraction) or accept fees in multiple stablecoins without custom workarounds. Was this page helpful? YesNo [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Transaction memos - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/transaction-memos#content-area) Transaction memos attach application metadata, such as an invoice or order reference, to a contract call on Arc. The predeployed `Memo` contract (`0x5294E9927c3306DcBaDb03fe70b92e01cCede505`) wraps the call and emits the metadata as events, so wallets, exchanges, and indexers can reconcile onchain transfers against offchain records without changing the target contract. To send a memo transfer end to end, see [Send USDC with a transaction memo](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo) . [​](https://docs.arc.io/arc/concepts/transaction-memos#how-a-memo-call-works) How a memo call works ------------------------------------------------------------------------------------------------------ The `Memo` contract wraps a target contract call. It routes the inner call through Arc’s `CallFrom` precompile, a system-level contract that executes a call on behalf of the original transaction sender. The [transaction extension contracts](https://docs.arc.io/arc/references/contract-addresses#transaction-extensions) use this precompile to keep the original externally owned account (EOA) wallet as `msg.sender` for the target call. A USDC transfer still sees your wallet as the sender, not the `Memo` contract. The contract exposes a single entry point: function memo( address target, bytes calldata data, bytes32 memoId, bytes calldata memoData ) external; `target` is the contract to call, `data` is the calldata to forward to it, `memoId` is a caller-supplied identifier for the memo, and `memoData` is the arbitrary memo bytes attached to the subcall. On success, `Memo` emits an ordered audit trail: 1. `BeforeMemo(memoIndex)` before the inner call. 2. The target contract events, such as a USDC `Transfer`. 3. `Memo(sender, target, callDataHash, memoId, memo, memoIndex)` after the inner call. The `Memo` contract supports nested memo calls. `BeforeMemo` events emit when each memo frame starts. `Memo` events unwind from the innermost frame back to the outermost frame. [​](https://docs.arc.io/arc/concepts/transaction-memos#event-schema) Event schema ------------------------------------------------------------------------------------ Use the emitted events as your audit trail and reconciliation source: | Event | Field | Type | Description | | --- | --- | --- | --- | | `BeforeMemo` | `memoIndex` | `uint256 indexed` | Sequential memo index reserved before the inner call starts. | | `Memo` | `sender` | `address indexed` | Wallet that called `Memo.memo(...)`. For a direct EOA call, this is the EOA preserved as `msg.sender` in the target call. | | `Memo` | `target` | `address indexed` | Contract called through the memo wrapper, such as the USDC ERC-20 interface. | | `Memo` | `callDataHash` | `bytes32` | `keccak256` hash of the forwarded target calldata. Store the original calldata if you need to reconstruct the exact call. | | `Memo` | `memoId` | `bytes32 indexed` | Identifier that your application defines for lookup and reconciliation. | | `Memo` | `memo` | `bytes` | Memo bytes that your application defines. Encode this value consistently in your application. | | `Memo` | `memoIndex` | `uint256` | Sequential index for the memo frame. Nested memos each receive their own index. | For reconciliation, query `Memo` events by indexed fields such as `memoId`, `sender`, and `target`. If you need to match a memo to a specific inner call, compare `callDataHash` with the hash of the calldata your application created. [​](https://docs.arc.io/arc/concepts/transaction-memos#wallet-types) Wallet types ------------------------------------------------------------------------------------ The `Memo` contract must be invoked directly by an externally owned account (EOA). Smart contract wallets aren’t supported as the direct caller. ### [​](https://docs.arc.io/arc/concepts/transaction-memos#supported-wallets) Supported wallets Any wallet that signs and broadcasts a standard EOA transaction works. This includes: * Browser and mobile wallets: MetaMask, Rabby, Coinbase Wallet, Rainbow * Hardware wallets * Server-side signers * Circle developer-controlled wallets and user-controlled wallets configured as EOAs The `CallFrom` precompile preserves the signing EOA as `msg.sender` in the target call, which is what the memo flow depends on. ### [​](https://docs.arc.io/arc/concepts/transaction-memos#unsupported-wallets) Unsupported wallets Smart contract wallets aren’t supported as the direct caller. This includes: * ERC-4337 smart accounts, including Circle modular wallets * Circle developer-controlled wallets and user-controlled wallets configured as SCAs * Safe and other multisig contract wallets * Any other account-abstraction setup where the transaction originates from a bundler, entry point, or other intermediary contract When a smart contract wallet calls `Memo.memo(...)`, the `msg.sender` reaching the contract is the wallet contract or entry point, not the user’s EOA. The call reverts because sender spoofing isn’t allowed. If you need memo metadata from this flow, have the smart contract wallet trigger a separate EOA-signed `Memo.memo(...)` transaction, or attach the metadata in your application layer instead. [​](https://docs.arc.io/arc/concepts/transaction-memos#unsupported-patterns-and-guardrails) Unsupported patterns and guardrails ---------------------------------------------------------------------------------------------------------------------------------- The transaction memo flow has explicit guardrails: * Submit `Memo.memo(...)` from an EOA. Calls routed through an intermediary contract with a different `msg.sender` revert because sender spoofing is not allowed. * Do not call the `CallFrom` precompile directly from an EOA. Direct calls revert with `unauthorized caller`. * Do not rely on `STATICCALL`. Memo execution changes state by incrementing the memo index, so static execution is rejected. * Do not use `DELEGATECALL` into the `Memo` contract. The delegated context does not have the required authorization for `CallFrom`. * If the child call reverts, the outer transaction reverts. The memo index increment rolls back, and the child return data is wrapped in `MemoFailed(bytes)`. Before you use transaction memos in production, test your memo format and reconciliation query against Arc Testnet with the same wallet and indexing infrastructure you plan to use in your application. Was this page helpful? YesNo [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) [Batched transactions](https://docs.arc.io/arc/concepts/batched-transactions) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # App Kit SDK: Swap - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/swap#content-area) The [App Kit SDK](https://docs.arc.io/app-kit) includes the Swap capability that lets you swap two tokens in a few lines of code. Swap [supports](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) many stablecoins (including USDC, EURC, USDT, USDe, DAI, and PYUSD) and native tokens on supported blockchains. You need a (free) [kit key](https://developers.circle.com/w3s/keys#kit-keys) from the [Circle Console](https://console.circle.com/) to use Swap. Among testnets, only Arc Testnet supports Swap (USDC, EURC, and cirBTC only). Use mainnet for Swap on any other blockchains. [​](https://docs.arc.io/app-kit/swap#quick-look) Quick look -------------------------------------------------------------- * Same-chain swap * Crosschain swap This example swaps 1 USDC for EURC on Arc Testnet: TypeScript const result = await kit.swap({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, tokenIn: "USDC", tokenOut: "EURC", amountIn: "1.00", config: { kitKey: process.env.KIT_KEY as string, // Your kit key from the Circle Console }, }); See [Swap tokens on a blockchain](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain) for a full working example. This example swaps 1 USDC on Base for native POL delivered on Polygon in a single call: TypeScript const result = await kit.swap({ from: { adapter: viemAdapter, chain: "Base" }, tokenIn: "USDC", tokenOut: "NATIVE", amountIn: "1.00", to: { chain: "Polygon", recipientAddress: "0xYourRecipientAddress", }, config: { kitKey: process.env.KIT_KEY as string, }, }); See [Swap tokens across blockchains](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain) for a full working example. [​](https://docs.arc.io/app-kit/swap#installation) Installation ------------------------------------------------------------------ [Install the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) to use Swap. If you only need to swap and don’t want to install the full App Kit SDK, install the standalone Swap Kit for your preferred adapter below. * Viem * Ethers * Solana * Circle Wallets npm yarn npm install @circle-fin/swap-kit @circle-fin/adapter-viem-v2 viem yarn add @circle-fin/swap-kit @circle-fin/adapter-viem-v2 viem npm yarn npm install @circle-fin/swap-kit @circle-fin/adapter-ethers-v6 ethers yarn add @circle-fin/swap-kit @circle-fin/adapter-ethers-v6 ethers npm yarn npm install @circle-fin/swap-kit @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js yarn add @circle-fin/swap-kit @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js npm yarn npm install @circle-fin/swap-kit @circle-fin/adapter-circle-wallets yarn add @circle-fin/swap-kit @circle-fin/adapter-circle-wallets Was this page helpful? YesNo [Error recovery](https://docs.arc.io/app-kit/references/bridge-error-recovery) [Swap tokens on a chain](https://docs.arc.io/app-kit/quickstarts/swap-tokens-same-chain) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Install the App Kit SDK - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/installation#content-area) Installing the App Kit SDK gives you every capability (Send, Bridge, Swap, and Unified Balance) in a single package. To get started, install the latest version and the adapter that fits your environment. To [swap](https://docs.arc.io/app-kit/swap) , you need a (free) kit key from [Circle Console](https://console.circle.com/) . 1 Install the core package npm yarn npm install @circle-fin/app-kit yarn add @circle-fin/app-kit Need a lighter install? Individual App Kits are available The App Kit SDK includes every capability in a single package. Or, install kits individually: npm yarn ## Bridge Kit npm install @circle-fin/bridge-kit ## Swap Kit npm install @circle-fin/swap-kit ## Unified Balance Kit npm install @circle-fin/unified-balance-kit ## Bridge Kit yarn add @circle-fin/bridge-kit ## Swap Kit yarn add @circle-fin/swap-kit ## Unified Balance Kit yarn add @circle-fin/unified-balance-kit 2 Install your preferred adapter * Viem * Ethers * Solana * Circle Wallets npm yarn npm install @circle-fin/adapter-viem-v2 viem yarn add @circle-fin/adapter-viem-v2 viem npm yarn npm install @circle-fin/adapter-ethers-v6 ethers yarn add @circle-fin/adapter-ethers-v6 ethers npm yarn npm install @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js yarn add @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js If bridging, you should also install an EVM adapter (Viem or Ethers). For the [Bridge tokens across blockchains](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#solana) quickstart and [adapter setup](https://docs.arc.io/app-kit/tutorials/adapter-setups#solana) examples, the same adapter is used with `@solana/kit` and `@solana/web3.js`. server-side onlyIf you have a [Circle Wallets](https://developers.circle.com/wallets) account, you can use the Circle Wallets adapter to connect to developer-controlled wallets and Circle Contracts. npm yarn npm install @circle-fin/adapter-circle-wallets yarn add @circle-fin/adapter-circle-wallets Was this page helpful? YesNo [App Kits overview](https://docs.arc.io/app-kit) [Supported blockchains and tokens](https://docs.arc.io/app-kit/references/supported-blockchains) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Batched transactions - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/batched-transactions#content-area) Batched transactions let an application submit multiple contract calls in a single Arc transaction. The predeployed `Multicall3From` contract (`0x522fAf9A91c41c443c66765030741e4AaCe147D0`) batches calls like common Multicall3 contracts. Unlike a common batching contract, it preserves the original externally owned account (EOA) wallet as `msg.sender` for each target call. To send batch USDC transfers end to end, see [Send batch USDC transfers](https://docs.arc.io/arc/tutorials/batch-usdc-transfers) . [​](https://docs.arc.io/arc/concepts/batched-transactions#how-a-batch-call-works) How a batch call works ----------------------------------------------------------------------------------------------------------- `Multicall3From` routes each subcall through Arc’s `CallFrom` precompile. `CallFrom` executes calls on behalf of the original transaction sender. The [transaction extension contracts](https://docs.arc.io/arc/references/contract-addresses#transaction-extensions) use this precompile to keep the original EOA wallet as `msg.sender` for target calls. For a batch of USDC transfers: 1. Your wallet calls `Multicall3From.aggregate3(...)`. 2. `Multicall3From` forwards each encoded USDC `transfer(...)` through `CallFrom`. 3. The USDC contract sees your wallet as the sender for each transfer. In a normal batching contract, the target contract usually sees the batching contract as `msg.sender`. The contract exposes the `aggregate3(...)` entry point: struct Call3 { address target; bool allowFailure; bytes callData; } struct Result { bool success; bytes returnData; } function aggregate3(Call3[] calldata calls) external returns (Result[] memory); `target` is the contract to call. `allowFailure` controls whether a failed subcall reverts the full batch. `callData` is the encoded calldata for the target contract. [​](https://docs.arc.io/arc/concepts/batched-transactions#event-verification) Event verification --------------------------------------------------------------------------------------------------- `Multicall3From` does not emit a batch-specific event. Verify the target contract events emitted by each subcall. For USDC transfers, a successful batch emits one USDC `Transfer` event per successful transfer. | Event | Field | Expected value | | --- | --- | --- | | `Transfer` | `from` | The wallet that called `Multicall3From.aggregate3(...)`, not `Multicall3From`. | | `Transfer` | `to` | The recipient for that subcall. | | `Transfer` | `value` | The USDC amount in base units. | The `from` field is the key sender-preservation check. It confirms that the USDC contract saw your wallet as `msg.sender` for each subcall. [​](https://docs.arc.io/arc/concepts/batched-transactions#unsupported-patterns-and-guardrails) Unsupported patterns and guardrails ------------------------------------------------------------------------------------------------------------------------------------- `Multicall3From` has explicit guardrails: * Submit `aggregate3(...)` from an EOA. Calls routed through an intermediary contract with a different `msg.sender` are rejected by `CallFrom`’s sender-spoofing constraint. * Do not use value-forwarding patterns. `Multicall3From` does not support `aggregate3Value` because `CallFrom` does not forward value on Arc. * Set `allowFailure` to `false` when every subcall must succeed. If a subcall fails with `allowFailure: false`, the whole batch reverts. * Set `allowFailure` to `true` only when your application explicitly handles failed subcalls. Before you use batched transactions in production, test your exact call set and failure policy against Arc Testnet with the same wallet and indexing infrastructure you plan to use in your application. Was this page helpful? YesNo [Transaction memos](https://docs.arc.io/arc/concepts/transaction-memos) [USDC events](https://docs.arc.io/arc/references/usdc-system-events) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # App Kits supported blockchains and tokens - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/references/supported-blockchains#content-area) [​](https://docs.arc.io/app-kit/references/supported-blockchains#supported-blockchains) Supported blockchains ---------------------------------------------------------------------------------------------------------------- Pick a tab to check blockchain support for each capability or adapter. * Capabilities * Adapters The following tables list supported blockchains for each capability, split by mainnet and testnet. ### [​](https://docs.arc.io/app-kit/references/supported-blockchains#mainnet) Mainnet | Blockchain | [Send](https://docs.arc.io/app-kit/send) | [Bridge](https://docs.arc.io/app-kit/bridge) | [Swap](https://docs.arc.io/app-kit/swap) | [Unified Balance](https://docs.arc.io/app-kit/unified-balance) | | --- | --- | --- | --- | --- | | Arbitrum | ✅ | ✅ | ✅ | ✅ | | Avalanche | ✅ | ✅ | ✅ | ✅ | | Base | ✅ | ✅ | ✅ | ✅ | | Codex | ✅ | ✅ | ❌ | ❌ | | Cronos | ✅ | ✅ | ❌ | ❌ | | EDGE | ✅ | ✅ | ❌ | ❌ | | Ethereum | ✅ | ✅ | ✅ | ✅ | | HyperEVM | ✅ | ✅ | ✅ | ✅ | | Injective | ✅ | ✅ | ❌ | ❌ | | Ink | ✅ | ✅ | ✅ | ❌ | | Linea | ✅ | ✅ | ✅ | ❌ | | Monad | ✅ | ✅ | ✅ | ❌ | | Morph | ✅ | ✅ | ❌ | ❌ | | OP Mainnet | ✅ | ✅ | ✅ | ✅ | | Pharos | ✅ | ✅ | ❌ | ❌ | | Plume | ✅ | ✅ | ✅ | ❌ | | Polygon PoS | ✅ | ✅ | ✅ | ✅ | | Sei | ✅ | ✅ | ✅ | ✅ | | Solana | ✅ | ✅ | ✅ | ✅ | | Sonic | ✅ | ✅ | ✅ | ✅ | | Unichain | ✅ | ✅ | ✅ | ✅ | | World Chain | ✅ | ✅ | ✅ | ✅ | | XDC | ✅ | ✅ | ✅ | ❌ | ### [​](https://docs.arc.io/app-kit/references/supported-blockchains#testnet) Testnet Among testnets, only Arc Testnet supports Swap (USDC, EURC, and cirBTC only). Use mainnet for Swap on any other blockchains. | Blockchain | [Send](https://docs.arc.io/app-kit/send) | [Bridge](https://docs.arc.io/app-kit/bridge) | [Swap](https://docs.arc.io/app-kit/swap) | [Unified Balance](https://docs.arc.io/app-kit/unified-balance) | | --- | --- | --- | --- | --- | | Arbitrum Sepolia | ✅ | ✅ | ❌ | ✅ | | Arc Testnet | ✅ | ✅ | ✅ | ✅ | | Avalanche Fuji | ✅ | ✅ | ❌ | ✅ | | Base Sepolia | ✅ | ✅ | ❌ | ✅ | | Codex Testnet | ✅ | ✅ | ❌ | ❌ | | Cronos Testnet | ✅ | ✅ | ❌ | ❌ | | EDGE Testnet | ✅ | ✅ | ❌ | ❌ | | Ethereum Sepolia | ✅ | ✅ | ❌ | ✅ | | HyperEVM Testnet | ✅ | ✅ | ❌ | ✅ | | Injective Testnet | ✅ | ✅ | ❌ | ❌ | | Ink Testnet | ✅ | ✅ | ❌ | ❌ | | Linea Sepolia | ✅ | ✅ | ❌ | ❌ | | Monad Testnet | ✅ | ✅ | ❌ | ❌ | | Morph Testnet | ✅ | ✅ | ❌ | ❌ | | OP Sepolia | ✅ | ✅ | ❌ | ✅ | | Pharos Atlantic | ✅ | ✅ | ❌ | ❌ | | Plume Testnet | ✅ | ✅ | ❌ | ❌ | | Polygon PoS Amoy | ✅ | ✅ | ❌ | ✅ | | Sei Testnet | ✅ | ✅ | ❌ | ✅ | | Solana Devnet | ✅ | ✅ | ❌ | ✅ | | Sonic Testnet | ✅ | ✅ | ❌ | ✅ | | Unichain Sepolia | ✅ | ✅ | ❌ | ✅ | | World Chain Sepolia | ✅ | ✅ | ❌ | ✅ | | XDC Apothem | ✅ | ✅ | ❌ | ❌ | The following tables list supported blockchains for each adapter, split by mainnet and testnet. The Viem and Ethers adapters support the same EVM blockchains. The Solana adapter supports only the Solana blockchain. ### [​](https://docs.arc.io/app-kit/references/supported-blockchains#mainnet-2) Mainnet | Blockchain | Viem / Ethers | Solana | Circle Wallets | | --- | --- | --- | --- | | Arbitrum | ✅ | ❌ | ✅ | | Avalanche | ✅ | ❌ | ✅ | | Base | ✅ | ❌ | ✅ | | Codex | ✅ | ❌ | ❌ | | Cronos | ✅ | ❌ | ✅ | | EDGE | ✅ | ❌ | ❌ | | Ethereum | ✅ | ❌ | ✅ | | HyperEVM | ✅ | ❌ | ❌ | | Injective | ✅ | ❌ | ❌ | | Ink | ✅ | ❌ | ❌ | | Linea | ✅ | ❌ | ❌ | | Monad | ✅ | ❌ | ❌ | | Morph | ✅ | ❌ | ❌ | | OP Mainnet | ✅ | ❌ | ✅ | | Pharos | ✅ | ❌ | ❌ | | Plume | ✅ | ❌ | ❌ | | Polygon PoS | ✅ | ❌ | ✅ | | Sei | ✅ | ❌ | ❌ | | Solana | ❌ | ✅ | ✅ | | Sonic | ✅ | ❌ | ❌ | | Unichain | ✅ | ❌ | ✅ | | World Chain | ✅ | ❌ | ❌ | | XDC | ✅ | ❌ | ❌ | ### [​](https://docs.arc.io/app-kit/references/supported-blockchains#testnet-2) Testnet | Blockchain | Viem / Ethers | Solana | Circle Wallets | | --- | --- | --- | --- | | Arbitrum Sepolia | ✅ | ❌ | ✅ | | Arc Testnet | ✅ | ❌ | ✅ | | Avalanche Fuji | ✅ | ❌ | ✅ | | Base Sepolia | ✅ | ❌ | ✅ | | Codex Testnet | ✅ | ❌ | ❌ | | Cronos Testnet | ✅ | ❌ | ✅ | | EDGE Testnet | ✅ | ❌ | ❌ | | Ethereum Sepolia | ✅ | ❌ | ✅ | | HyperEVM Testnet | ✅ | ❌ | ❌ | | Injective Testnet | ✅ | ❌ | ❌ | | Ink Testnet | ✅ | ❌ | ❌ | | Linea Sepolia | ✅ | ❌ | ❌ | | Monad Testnet | ✅ | ❌ | ❌ | | Morph Testnet | ✅ | ❌ | ❌ | | OP Sepolia | ✅ | ❌ | ✅ | | Pharos Atlantic | ✅ | ❌ | ❌ | | Plume Testnet | ✅ | ❌ | ❌ | | Polygon PoS Amoy | ✅ | ❌ | ✅ | | Sei Testnet | ✅ | ❌ | ❌ | | Solana Devnet | ❌ | ✅ | ✅ | | Sonic Testnet | ✅ | ❌ | ❌ | | Unichain Sepolia | ✅ | ❌ | ✅ | | World Chain Sepolia | ✅ | ❌ | ❌ | | XDC Apothem | ✅ | ❌ | ❌ | ### [​](https://docs.arc.io/app-kit/references/supported-blockchains#chain-identifiers) Chain identifiers For most blockchains, the identifier is the blockchain name with any spaces replaced by underscores (for example, Arc Testnet → `Arc_Testnet`). Identifiers are case-sensitive and match the `BridgeChain` enum exported from `@circle-fin/app-kit`: import { BridgeChain } from "@circle-fin/app-kit"; const chain = BridgeChain.Arc_Testnet; // or "Arc_Testnet" The blockchains below are exceptions: | Blockchain | Identifier | | --- | --- | | EDGE | `Edge` | | EDGE Testnet | `Edge_Testnet` | | OP Mainnet | `Optimism` | | OP Sepolia | `Optimism_Sepolia` | | Pharos Atlantic | `Pharos_Testnet` | | Polygon PoS | `Polygon` | | Polygon PoS Amoy | `Polygon_Amoy_Testnet` | [​](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) Supported tokens ------------------------------------------------------------------------------------------------------ You can specify either a contract address or an [alias](https://docs.arc.io/app-kit/references/supported-blockchains#token-aliases) for any supported token. Each capability supports different tokens: * **[Send](https://docs.arc.io/app-kit/send) **: Any token. Use [token aliases](https://docs.arc.io/app-kit/references/supported-blockchains#token-aliases) for common tokens or the token’s contract address for other tokens. * **[Bridge](https://docs.arc.io/app-kit/bridge) **: USDC only. * **[Swap](https://docs.arc.io/app-kit/swap) **: * Any tokens with enough liquidity to trade with major stablecoins such as USDC, EURC, USDT, USDe, DAI, and PYUSD. * Native tokens on blockchains where Swap is supported. * On Arc Testnet, only USDC, EURC, and cirBTC. * **[Unified Balance](https://docs.arc.io/app-kit/unified-balance) **: USDC only. ### [​](https://docs.arc.io/app-kit/references/supported-blockchains#token-aliases) Token aliases App Kits works with any supported token when you supply the contract address. For convenience, you can use the following aliases for the most common tokens instead: * `USDC` * `EURC` * `USDT` * `USDe` * `DAI` * `PYUSD` * `cirBTC` * `NATIVE`: Uses the blockchain’s native token. Was this page helpful? YesNo [Installation](https://docs.arc.io/app-kit/tutorials/installation) [Adapter setups](https://docs.arc.io/app-kit/tutorials/adapter-setups) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Stablecoin native model - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/stablecoin-native-model#content-area) Arc is purpose-built around stablecoins. USDC is the native gas token, so transaction fees are denominated in dollars rather than a volatile asset. EURC and USYC are natively supported alongside USDC, giving applications access to multi-currency payments and onchain yield without bridging or wrapping. There is no volatile native token on Arc. [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#usdc-as-the-native-gas-token) USDC as the native gas token -------------------------------------------------------------------------------------------------------------------------- On most EVM blockchains, a volatile token (such as ETH) serves as the native asset for gas fees and value transfer. Arc replaces this with USDC. Every transaction fee, balance, and native transfer on Arc is denominated in USDC. This means: * Fees are predictable in dollar terms. * You hold a single asset for both gas and application-level transfers. * There is no need to acquire or manage a separate volatile token. ### [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#two-interfaces-one-balance) Two interfaces, one balance USDC on Arc has two interfaces that share the same underlying balance: | Interface | Decimals | Purpose | | --- | --- | --- | | **Native** | 18 | Gas accounting, native sends, and `msg.value` | | **ERC-20** | 6 | application-level transfers, approvals, and allowances | The native representation uses 18 decimals, consistent with how ETH works on Ethereum. The ERC-20 interface at [`0x3600000000000000000000000000000000000000`](https://docs.arc.io/arc/references/contract-addresses#usdc) uses 6 decimals to match the standard USDC representation on other EVM networks. The ERC-20 and native interfaces share the same underlying balance. An ERC-20 transfer directly moves the native balance, and a native send is reflected in the ERC-20 balance. These are not two separate tokens.There is no wrapped USDC (no `WUSDC`, `USDC.e`, or `WETH`\-style wrapper) on Arc: decentralized exchanges, automated market makers, and routers should use the ERC-20 USDC address directly as the pair token. For the complete protocol reference, see [EVM differences](https://docs.arc.io/arc/references/evm-differences) . Because the two interfaces represent one asset, Arc behaves differently from other EVM chains in ways that can break common assumptions: * `USDC.balanceOf(addr)` and `addr.balance` represent the same value in different decimals (6 vs 18). A `balanceOf` of `0` does not mean the native balance is `0`; amounts below 1×10⁻⁶ USDC are truncated in the ERC-20 view. * Native transfers into or out of a contract also change its ERC-20 USDC balance. * A native transfer can revert even with a sufficient balance (for example, because of the blocklist or zero-address rules). If you are porting an existing contract, review the [Porting contracts to Arc checklist](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc) for the dual-interface edge cases, and see the [value transfer rules](https://docs.arc.io/arc/references/evm-differences#value-transfer-rules) for the full set of restrictions. Every USDC movement (native sends and ERC-20 transfers alike) emits a standard ERC-20 `Transfer` log from a system address, Arc’s implementation of [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) (an Amsterdam-track Ethereum proposal). Because this system event covers all USDC movements at 18-decimal precision, it gives wallets and indexers a single universal source of truth for balance changes. For fee parameters and gas pricing details, see [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) . For the rationale behind stable fee denomination, see [Stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) . ### [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#indexing-usdc-movements) Indexing USDC movements The native system emitter logs a `Transfer` (18 decimals) for every USDC movement, so indexing it alone gives complete coverage. The ERC-20 USDC contract additionally logs its own `Transfer` (6 decimals) for ERC-20-interface activity, so a single ERC-20 transfer emits both; match on the emitter address to avoid double-counting. For the full event reference, including emitter addresses, the mint and burn mapping, and legacy native events, see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . For a step-by-step walkthrough, see [Index Arc events](https://docs.arc.io/integrate/infrastructure/indexing-events) . [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#natively-supported-stablecoins) Natively supported stablecoins ------------------------------------------------------------------------------------------------------------------------------ Beyond USDC, Arc natively supports additional stablecoin assets at the protocol level, meaning they are deployed as pre-configured contracts at genesis, not bridged or wrapped from other blockchains. ### [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#eurc) EURC EURC is Circle’s euro-denominated stablecoin. It is deployed as a standard ERC-20 token on Arc with 6 decimals, enabling euro-denominated payments, FX workflows, and multi-currency applications without relying on third-party bridges. For the EURC contract address, see [Contract addresses](https://docs.arc.io/arc/references/contract-addresses#eurc) . ### [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#usyc) USYC USYC is a yield-bearing token representing shares of a tokenized money market fund, a regulated investment vehicle that holds short-duration U.S. Treasury securities and distributes yield to token holders. It provides onchain access to regulated, low-risk yield for institutional participants. USYC is only accessible to institutions outside the United States, subject to eligibility restrictions and a $100,000 USD minimum investment. For token addresses and testnet faucet instructions, see [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) . [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#design-rationale) Design rationale -------------------------------------------------------------------------------------------------- Arc’s stablecoin-native model is a deliberate architectural choice, not a compatibility layer added after launch. Three principles guided the design. ### [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#no-volatile-native-token) No volatile native token Traditional blockchains require users to hold a volatile native token to pay fees. This creates friction for financial applications: users must acquire the token, manage price exposure, and convert between the fee token and the assets they actually want to use. Arc eliminates this by making USDC the native token from day one. ### [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#single-gas-denomination-at-launch) Single gas denomination at launch Arc launches with USDC as the sole gas token rather than supporting multi-token gas payments from the start. A single denomination simplifies the fee market, avoids oracle dependencies for gas price conversion, and keeps the protocol’s economic model straightforward. Multi-stablecoin gas payment through paymasters (smart contracts that sponsor or redirect gas fees on behalf of a user) is not supported at launch. ### [​](https://docs.arc.io/arc/concepts/stablecoin-native-model#stablecoins-as-first-class-primitives) Stablecoins as first-class primitives USDC, EURC, and USYC are not tokens added after launch. They are integrated at the protocol level, which means applications can compose across stable assets with consistent interfaces and without external dependencies. This positions Arc as infrastructure specifically designed for payments, lending, FX, and treasury management. Was this page helpful? YesNo [EVM differences](https://docs.arc.io/arc/references/evm-differences) [Stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # RPC endpoints - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/rpc-endpoints#content-area) Arc exposes standard Ethereum JSON-RPC endpoints — the same HTTP and WebSocket API that Ethereum nodes use — for submitting transactions, querying state, and subscribing to events. Connect through Circle’s primary endpoint or through a [third-party node provider](https://docs.arc.io/arc/references/rpc-endpoints#node-providers) . For details on Arc’s current network phase, see [Deployment model](https://docs.arc.io/arc/concepts/deployment-model) . [​](https://docs.arc.io/arc/references/rpc-endpoints#testnet-endpoints) Testnet endpoints -------------------------------------------------------------------------------------------- | Provider | HTTP | WebSocket | | --- | --- | --- | | **Primary (Circle)** | `https://rpc.testnet.arc.io` | `wss://rpc.testnet.arc.io` | | **Blockdaemon** | `https://rpc.blockdaemon.testnet.arc.io` | — | | **dRPC** | `https://rpc.drpc.testnet.arc.io` | `wss://rpc.drpc.testnet.arc.io` | | **QuickNode** | `https://rpc.quicknode.testnet.arc.io` | `wss://rpc.quicknode.testnet.arc.io` | [​](https://docs.arc.io/arc/references/rpc-endpoints#network-parameters) Network parameters ---------------------------------------------------------------------------------------------- These parameters identify the Arc Testnet on the Ethereum network and are required when adding Arc to a wallet or development framework. | Parameter | Value | | --- | --- | | **Chain ID** | `5042002` | | **Currency symbol** | USDC | | **Block explorer** | [testnet.arcscan.app](https://testnet.arcscan.app/) | | **Gas tracker** | [testnet.arcscan.app/gas-tracker](https://testnet.arcscan.app/gas-tracker) | | **Faucet** | [faucet.circle.com](https://faucet.circle.com/) | For an overview of network phases, see [Deployment model](https://docs.arc.io/arc/concepts/deployment-model) . For wallet configuration instructions using these parameters, see [Connect to Arc](https://docs.arc.io/arc/references/connect-to-arc) . [​](https://docs.arc.io/arc/references/rpc-endpoints#node-providers) Node providers -------------------------------------------------------------------------------------- The following infrastructure partners offer managed RPC access to Arc. Each provider supports HTTP endpoints, and most support WebSocket connections. See [Node providers](https://docs.arc.io/arc/tools/node-providers) for additional details. | Provider | Description | | --- | --- | | [**Alchemy**](https://www.alchemy.com/arc) | Developer platform with enhanced APIs, monitoring, and debugging tools | | [**Blockdaemon**](https://www.blockdaemon.com/protocols/arc) | Institutional-grade node infrastructure with secure, compliant access | | [**dRPC**](https://drpc.org/chainlist/arc-testnet-rpc) | Decentralized RPC aggregator with load-balanced, multi-provider routing | | [**QuickNode**](https://www.quicknode.com/chains/arc) | High-performance global endpoints and blockchain APIs | [​](https://docs.arc.io/arc/references/rpc-endpoints#supported-methods) Supported methods -------------------------------------------------------------------------------------------- Arc supports all standard Ethereum JSON-RPC methods. The table below lists commonly used methods by category. | Category | Methods | Notes | | --- | --- | --- | | **State** | `eth_getBalance`, `eth_getCode`, `eth_getStorageAt`, `eth_call` | | | **Transactions** | `eth_sendRawTransaction`, `eth_getTransactionReceipt`, `eth_getTransactionByHash` | | | **Blocks** | `eth_getBlockByNumber`, `eth_getBlockByHash`, `eth_blockNumber` | | | **Gas** | `eth_gasPrice`, `eth_estimateGas`, `eth_feeHistory` | | | **Subscriptions** | `eth_subscribe`, `eth_unsubscribe` | WebSocket only | For gas fee parameters and best practices for setting `maxFeePerGas`, see [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) . The values on this page apply to the Arc Testnet. Mainnet endpoints and parameters are published separately when available. [​](https://docs.arc.io/arc/references/rpc-endpoints#troubleshooting) Troubleshooting ---------------------------------------------------------------------------------------- | Symptom | Cause | Resolution | | --- | --- | --- | | `connection refused` or timeout | Incorrect RPC URL or network issue | Verify you are using a URL from the [testnet endpoints](https://docs.arc.io/arc/references/rpc-endpoints#testnet-endpoints)
table. Try an alternate provider. | | `invalid chain id` | Wallet or provider configured with the wrong Chain ID | Set the Chain ID to `5042002`. | | `insufficient funds` | Account has no USDC for gas | Request testnet USDC from the [faucet](https://faucet.circle.com/)
. | Was this page helpful? YesNo [USDC events](https://docs.arc.io/arc/references/usdc-system-events) [Deployment model](https://docs.arc.io/arc/concepts/deployment-model) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Execution layer - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/execution-layer#content-area) Arc’s execution layer is built on [Reth](https://github.com/paradigmxyz/reth) , a Rust implementation of the Ethereum execution client. Reth maintains the full blockchain state, executes every transaction through the Ethereum Virtual Machine (EVM), and produces the state root — a cryptographic hash that summarizes the entire ledger state — that the [consensus layer](https://docs.arc.io/arc/concepts/consensus-layer) finalizes. Arc extends this foundation with modules purpose-built for stablecoin-native finance. [​](https://docs.arc.io/arc/concepts/execution-layer#what-reth-does) What Reth does -------------------------------------------------------------------------------------- Reth handles three jobs on every block: 1. **Maintains the ledger.** Tracks accounts, balances, smart contracts, and transaction history. Every state change is recorded and addressable. 2. **Executes transactions.** Applies EVM logic for smart contract calls and transfers, deducts gas fees through the Fee Manager, and routes through Arc-specific modules where applicable. 3. **Produces the state root.** Computes a Merkle root — a single hash derived from a tree of all state data — of the updated state after all transactions in a block have been applied. The consensus layer finalizes this root, making the block irreversible. Reth is written in Rust for performance, memory safety, and modular extensibility. Arc leverages this architecture to plug in stablecoin-native modules without modifying core EVM execution. [​](https://docs.arc.io/arc/concepts/execution-layer#arc-specific-extensions) Arc-specific extensions -------------------------------------------------------------------------------------------------------- Arc extends the standard Ethereum execution pipeline with modules that run alongside core EVM logic at the protocol level — meaning they are built into the blockchain itself, not deployed as user-space smart contracts. You benefit from these capabilities without deploying custom contracts or external services. | Extension | Status | Function | | --- | --- | --- | | **[Fee Manager](https://docs.arc.io/arc/concepts/stable-fee-design)
** | Live | Stabilizes gas fees using USDC as the unit of account with EWMA smoothing. | | **[CallFrom precompile](https://docs.arc.io/arc/references/contract-addresses#transaction-extensions)
** | Live | Preserves `msg.sender` across delegated calls, powering the Memo and Multicall3From contracts. | | **[Arc Privacy Sector (APS)](https://docs.arc.io/arc/concepts/opt-in-privacy)
** | Planned | Confidential execution environment for Solidity contracts, composing synchronously with the public EVM. | | **Stablecoin Services** | Planned | Powers cross-currency settlement, paymaster-sponsored transactions, and multi-stablecoin gas payments. | ### [​](https://docs.arc.io/arc/concepts/execution-layer#fee-manager) Fee Manager The Fee Manager replaces Ethereum’s per-block EIP-1559 base fee recalculation with an EWMA-smoothed fee curve denominated in USDC. Short demand spikes are absorbed by the smoothing window rather than propagated into sudden fee jumps. The base fee targets approximately $0.01 per transaction under normal conditions. For the full fee model, see [stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) . For runtime parameters, see [gas and fees](https://docs.arc.io/arc/references/gas-and-fees) . ### [​](https://docs.arc.io/arc/concepts/execution-layer#callfrom-precompile) CallFrom precompile Standard Ethereum opcodes `CALL` and `DELEGATECALL` — used when one contract invokes another — change `msg.sender` (the address of the immediate caller) at each hop in the call chain. The CallFrom precompile preserves the original caller’s address through the call chain. Two predeployed contracts use this precompile: * **Memo** (`0x5294...`) — Attaches memo metadata to contract calls and emits indexed `Memo` events. * **Multicall3From** (`0x522f...`) — Batches multiple calls like Multicall3, but each subcall retains the original `msg.sender`. For contract addresses and integration details, see [transaction extensions](https://docs.arc.io/arc/references/contract-addresses#transaction-extensions) . For how this differs from standard Ethereum behavior, see [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) . [​](https://docs.arc.io/arc/concepts/execution-layer#protocol-precompiles) Protocol precompiles -------------------------------------------------------------------------------------------------- Arc exposes five custom precompiles in the `0x1800..` address range — shorthand for the addresses `0x1800000000000000000000000000000000000000` through `0x1800000000000000000000000000000000000004`. These are built into the execution layer at the protocol level rather than deployed as user-space contracts, and they back the protocol-level features listed above. | Precompile | Address | Function | | --- | --- | --- | | **Native Coin Authority** | `0x1800..0000` | Mint, burn, and transfer operations for the native USDC balance. | | **Native Coin Control** | `0x1800..0001` | Address blocklist for the native coin. | | **System Accounting** | `0x1800..0002` | Gas fee ring buffer used by the Fee Manager. | | **Call From** | `0x1800..0003` | Powers the Memo and Multicall3From contracts described earlier. | | **PQ Signature Verify** | `0x1800..0004` | Post-quantum `SLH-DSA-SHA2-128s` signature verification. | You typically interact with these precompiles indirectly through the predeployed contracts and protocol features they support, rather than calling them directly. ### [​](https://docs.arc.io/arc/concepts/execution-layer#arc-privacy-sector-aps) Arc Privacy Sector (APS) Arc Privacy Sector (APS) is on the roadmap and not yet available on Arc. Arc Privacy Sector (APS) is a confidential execution environment for Solidity contracts that runs alongside Arc’s public EVM. Contracts deployed to APS keep state and transaction data confidential while finalizing in the same block as public state, under default-deny contract isolation. See [opt-in privacy](https://docs.arc.io/arc/concepts/opt-in-privacy) for the planned design. ### [​](https://docs.arc.io/arc/concepts/execution-layer#stablecoin-services) Stablecoin Services Stablecoin Services are on the roadmap and not yet available on Arc. Stablecoin Services will provide cross-currency settlement, paymaster-sponsored transactions, and multi-stablecoin gas payments at the protocol level. See [stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) for the design rationale. [​](https://docs.arc.io/arc/concepts/execution-layer#execution-pipeline) Execution pipeline ---------------------------------------------------------------------------------------------- A transaction moves through the execution layer in a linear pipeline. Reth applies each transaction to the current state and produces a new state root, which the consensus layer then finalizes into an irreversible block. 1. **Mempool.** Pending transactions wait in the mempool (a holding area for unconfirmed transactions) after passing initial validation (valid signature, sufficient balance, proper nonce). 2. **EVM execution.** Reth applies each transaction sequentially, running smart contract bytecode and processing native transfers. 3. **Fee Manager.** Gas fees are deducted in USDC using the EWMA-smoothed base fee. This step runs on every transaction. 4. **Module calls.** If the transaction invokes Arc Privacy Sector (APS) or Stablecoin Services, the relevant module processes the call. Standard transactions skip this step. 5. **State update.** Reth writes the resulting changes (account balances, contract storage, event logs) to the state database. 6. **State root.** Reth computes a Merkle root over the full updated state. This root serves as a cryptographic commitment — a tamper-evident fingerprint of the entire ledger — that the consensus layer finalizes. Was this page helpful? YesNo [Consensus layer](https://docs.arc.io/arc/concepts/consensus-layer) [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # USDC system events - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/usdc-system-events#content-area) For a step-by-step indexing walkthrough, see [Index Arc events](https://docs.arc.io/integrate/infrastructure/indexing-events) . For the conceptual model behind USDC’s native and ERC-20 interfaces, see [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) . [​](https://docs.arc.io/arc/references/usdc-system-events#two-event-streams) Two event streams ------------------------------------------------------------------------------------------------- USDC movements surface as logs from two distinct emitters. The native system emitter logs a `Transfer` for **every** USDC movement—native sends and ERC-20 transfers alike—so it is a single universal record of balance changes at 18-decimal precision. Filter by emitter address to tell the two streams apart. | Source | Emitter address | Events | Decimals | | --- | --- | --- | --- | | **Native USDC** (system, EIP-7708) | `0xfffffffffffffffffffffffffffffffffffffffe` | `Transfer` | 18 | | **ERC-20 USDC** (NativeFiatToken) | `0x3600000000000000000000000000000000000000` | `Transfer` | 6 | A single ERC-20 `transfer()` emits **two** logs: the ERC-20 contract’s own `Transfer` (6 decimals, from `0x3600000000000000000000000000000000000000`) and the native system `Transfer` (18 decimals, from `0xfffffffffffffffffffffffffffffffffffffffe`). A plain native send emits only the system log. Match on the emitter address so you do not count the same movement twice, and never mix the 6-decimal and 18-decimal values. [​](https://docs.arc.io/arc/references/usdc-system-events#native-usdc-system-events-eip-7708) Native USDC system events (EIP-7708) ------------------------------------------------------------------------------------------------------------------------------------- Native USDC movements emit a standard ERC-20 `Transfer` log from a designated system address. This is Arc’s implementation of [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) , an Amsterdam-track Ethereum proposal that Arc ships ahead of upstream. Native movements can be indexed the same way as ERC-20 transfers. (For the legacy events that testnet emitted before this behavior was activated, see [Historical events](https://docs.arc.io/arc/references/usdc-system-events#historical-events-before-zero5) .) The log covers native value transfers (`CALL`), contract creation with an endowment (`CREATE`), `SELFDESTRUCT` balance transfers, and the precompile-driven `mint`, `burn`, and `transfer` operations that back the ERC-20 USDC interface. | Property | Value | | --- | --- | | Emitter | `0xfffffffffffffffffffffffffffffffffffffffe` | | Event | `Transfer(address indexed from, address indexed to, uint256 value)` | | topic0 | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | | Decimals | 18 | Mint and burn are expressed as transfers involving the zero address: * **Mint:** `Transfer(0x0, recipient, amount)` * **Burn:** `Transfer(from, 0x0, amount)` The following rules apply to the system `Transfer` log: * The native `Transfer` log is emitted **first**, before any other logs in the transaction. * Zero-value transfers emit no log. * Self-transfers (`from == to`) emit no log. * A native value transfer (`CALL`, `CREATE`, or `SELFDESTRUCT`) to or from the zero address reverts with `"Zero address not allowed"`. Mint and burn are the only paths that produce a `Transfer` involving `0x0`, and they go through the precompile. [​](https://docs.arc.io/arc/references/usdc-system-events#erc-20-usdc-contract-events) ERC-20 USDC contract events --------------------------------------------------------------------------------------------------------------------- The NativeFiatToken contract at [`0x3600000000000000000000000000000000000000`](https://docs.arc.io/arc/references/contract-addresses#usdc) emits its own standard ERC-20 `Transfer` log (**6 decimals**) for activity on the ERC-20 interface. As on any ERC-20 token, mint and burn surface here as a `Transfer` to and from the zero address. These events are independent of the native system events: an ERC-20 `transfer()` produces a log from both emitters. | Property | Value | | --- | --- | | Emitter | `0x3600000000000000000000000000000000000000` | | Event | `Transfer(address indexed from, address indexed to, uint256 value)` | | topic0 | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | | Decimals | 6 | [​](https://docs.arc.io/arc/references/usdc-system-events#historical-events-before-zero5) Historical events (before Zero5) ----------------------------------------------------------------------------------------------------------------------------- On testnet, before the Zero5 hard fork activated, native USDC movements emitted custom events from the NativeCoinAuthority precompile at `0x1800000000000000000000000000000000000000` (18 decimals) instead of the standard `Transfer` log. These events are **no longer emitted** after activation. Mainnet has used the EIP-7708 `Transfer` log since genesis, so this only affects indexers that backfill pre-activation testnet history. | Event | topic0 | | --- | --- | | `NativeCoinTransferred(address indexed from, address indexed to, uint256 amount)` | `0x62f084c00a442dcf51cdbb51beed2839bf42a268da8474b0e98f38edb7db5a22` | | `NativeCoinMinted(address indexed recipient, uint256 amount)` | `0xb049859d09b3a7d0189a07db4d4becee1a2aa269023205478b1360ab6fc12114` | | `NativeCoinBurned(address indexed from, uint256 amount)` | `0xaaf1ef013644e67c5cea90217acdf0accd334f8437fc9a89a53cfc9b25fb5c25` | If you backfill history across the hard fork boundary, read `NativeCoin*` from `0x1800000000000000000000000000000000000000` for blocks before activation and `Transfer` from `0xfffffffffffffffffffffffffffffffffffffffe` at and after activation. For the exact activation time and node version requirements, see the canonical `arc-node` [CHANGELOG.md](https://github.com/circlefin/arc-node/blob/main/CHANGELOG.md) and [BREAKING\_CHANGES.md](https://github.com/circlefin/arc-node/blob/main/BREAKING_CHANGES.md#v071) , and the [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) tutorial. [​](https://docs.arc.io/arc/references/usdc-system-events#out-of-scope-gas-fees-and-block-rewards) Out of scope: gas fees and block rewards ---------------------------------------------------------------------------------------------------------------------------------------------- Gas fees and block rewards are **not** emitted as `Transfer` events and are unaffected by EIP-7708: * **Gas fees** are derived from the receipt (`gasUsed × effectiveGasPrice`). * **Block rewards** are attributed via `block.miner`. [​](https://docs.arc.io/arc/references/usdc-system-events#see-also) See also ------------------------------------------------------------------------------- * [Index Arc events](https://docs.arc.io/integrate/infrastructure/indexing-events) —step-by-step indexing walkthrough with code * [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) —why USDC has native and ERC-20 interfaces * [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) —USDC and other system contract addresses * [EVM compatibility for developers](https://docs.arc.io/arc/references/evm-differences) —interface-level guidance and EVM differences Was this page helpful? YesNo [Batched transactions](https://docs.arc.io/arc/concepts/batched-transactions) [RPC endpoints](https://docs.arc.io/arc/references/rpc-endpoints) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Contract addresses - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/contract-addresses#content-area) All addresses on this page are for **Arc Testnet**. Mainnet addresses are not yet available. [​](https://docs.arc.io/arc/references/contract-addresses#stablecoins) Stablecoins ------------------------------------------------------------------------------------- Stablecoins are the foundation of the Arc ecosystem, supporting a growing set of fiat-backed and yield-bearing tokens. These assets provide price stability, onchain yield, and multi-currency support for payments, FX, and financial applications. The ERC-20 functions affect native balance movements. ### [​](https://docs.arc.io/arc/references/contract-addresses#usdc) USDC USDC is the native EVM asset on Arc and is used for gas fees. An optional ERC-20 interface is also available for developers who need features such as `transferFrom`, `approve`, and allowance management. See [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) for details on how the native and ERC-20 interfaces share the same underlying balance. | Contract | Address | Notes | | --- | --- | --- | | **USDC** | [`0x3600000000000000000000000000000000000000`](https://testnet.arcscan.app/address/0x3600000000000000000000000000000000000000) | Optional ERC-20 interface for interacting with the native USDC balance. Uses 6 decimals. | **Getting testnet USDC:** You can request USDC on Arc Testnet from the [Circle Faucet](https://faucet.circle.com/) . USDC is required to pay for gas and interact with contracts on Arc.**Note:** As with any ERC-20 token, always use the `decimals()` function to interpret balances and transfer amounts accurately. On Arc, the **native USDC gas token** uses 18 decimals of precision, while the **USDC ERC-20 interface** uses 6 decimals. Avoid mixing these values directly, as doing so may result in incorrect balance handling. For applications integrating USDC, it’s recommended to rely solely on the standard ERC-20 interface for reading balances and sending transfers. ### [​](https://docs.arc.io/arc/references/contract-addresses#eurc) EURC EURC is the euro-denominated stablecoin issued by Circle and supported natively on Arc for use in payments, FX, and other financial applications. | Contract | Address | Notes | | --- | --- | --- | | **EURC** | [`0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a`](https://testnet.arcscan.app/address/0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a) | Main EURC token contract. Uses 6 decimals. | **Getting testnet EURC:** Testnet EURC can be requested from the [Circle Faucet](https://faucet.circle.com/) . Select **Arc Testnet** as the network and **EURC** as the token to receive a small test allocation. ### [​](https://docs.arc.io/arc/references/contract-addresses#usyc) USYC [USYC](https://developers.circle.com/tokenized/usyc/overview) is a yield-bearing token issued by Circle International Bermuda Ltd. and supported on Arc for institutional and DeFi use cases. It represents shares of a tokenized money market fund backed by short-duration U.S. Treasury securities, offering onchain access to regulated, low-risk yield. USYC is only accessible to institutions outside the United States, subject to eligibility restrictions and a $100,000 USD minimum investment. See [USYC Document Certification Requirements](https://help.circle.com/s/article/Document-certification-requirements-for-USYC-onboarding?language=en_US&category=USYC) for more information. | Contract | Address | Notes | | --- | --- | --- | | **USYC** | [`0xe9185F0c5F296Ed1797AaE4238D26CCaBEadb86C`](https://testnet.arcscan.app/address/0xe9185F0c5F296Ed1797AaE4238D26CCaBEadb86C) | The main USYC token contract representing tokenized money market fund shares. Uses 6 decimals. | | **Entitlements** | [`0xcc205224862c7641930c87679e98999d23c26113`](https://testnet.arcscan.app/address/0xcc205224862c7641930c87679e98999d23c26113) | Manages allowlisted access and entitlement controls for permissioned addresses on the Arc Testnet. | | **Teller** | [`0x9fdF14c5B14173D74C08Af27AebFf39240dC105A`](https://testnet.arcscan.app/address/0x9fdF14c5B14173D74C08Af27AebFf39240dC105A) | Contract used to mint and redeem testnet USYC from testnet USDC once your wallet is allowlisted. | **Getting testnet USYC:** 1. Obtain testnet USDC from the [Circle Faucet](https://faucet.circle.com/) . 2. Request allowlisting by opening a ticket with [Circle Support](https://support.circle.com/) and include your Arc Testnet wallet address. Requests are typically processed in 24–48 hours. 3. Once approved, call the USYC Teller contract or interact with the [USYC Portal](https://usyc.dev.hashnote.com/) to deposit testnet USDC and receive testnet USYC. For more information on issuance, redemption, and eligibility, see [USYC Overview](https://developers.circle.com/tokenized/usyc/overview) . [​](https://docs.arc.io/arc/references/contract-addresses#crosschain) Crosschain ----------------------------------------------------------------------------------- The contracts below enable crosschain interoperability between Arc and other blockchains through Circle’s [Cross-Chain Transfer Protocol](https://developers.circle.com/cctp) (CCTP) and [Gateway](https://developers.circle.com/gateway) . CCTP handles crosschain message passing and stablecoin transfers, while Gateway provides chain-abstracted USDC balances for seamless liquidity movement. ### [​](https://docs.arc.io/arc/references/contract-addresses#cctp) CCTP | Contract | Domain | Address | | --- | --- | --- | | **TokenMessengerV2** | 26 | [`0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA`](https://testnet.arcscan.app/address/0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA) | | **MessageTransmitterV2** | 26 | [`0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275`](https://testnet.arcscan.app/address/0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275) | | **TokenMinterV2** | 26 | [`0xb43db544E2c27092c107639Ad201b3dEfAbcF192`](https://testnet.arcscan.app/address/0xb43db544E2c27092c107639Ad201b3dEfAbcF192) | | **MessageV2** | 26 | [`0xbaC0179bB358A8936169a63408C8481D582390C4`](https://testnet.arcscan.app/address/0xbaC0179bB358A8936169a63408C8481D582390C4) | ### [​](https://docs.arc.io/arc/references/contract-addresses#gateway) Gateway | Contract | Domain | Address | | --- | --- | --- | | **GatewayWallet** | 26 | [`0x0077777d7EBA4688BDeF3E311b846F25870A19B9`](https://testnet.arcscan.app/address/0x0077777d7EBA4688BDeF3E311b846F25870A19B9) | | **GatewayMinter** | 26 | [`0x0022222ABE238Cc2C7Bb1f21003F0a260052475B`](https://testnet.arcscan.app/address/0x0022222ABE238Cc2C7Bb1f21003F0a260052475B) | [​](https://docs.arc.io/arc/references/contract-addresses#payments-and-settlement) Payments and settlement ------------------------------------------------------------------------------------------------------------- Arc provides payment and settlement contracts that enable foreign exchange and onchain settlement workflows using stablecoins. These components support application-level use cases such as FX execution and escrow-based settlement. ### [​](https://docs.arc.io/arc/references/contract-addresses#stablefx) StableFX [StableFX](https://developers.circle.com/stablefx) is an enterprise-grade stablecoin FX engine that combines Request-for-Quote (RFQ) execution with onchain settlement on Arc. The following is the address for the escrow contract used to settle stablecoin swaps. | Contract | Address | Notes | | --- | --- | --- | | **FxEscrow** | [`0x867650F5eAe8df91445971f14d89fd84F0C9a9f8`](https://testnet.arcscan.app/address/0x867650F5eAe8df91445971f14d89fd84F0C9a9f8) | The escrow contract used by both makers and takers to settle stablecoin swaps. | Before executing FX trades, StableFX must be able to transfer USDC from your wallet. To enable this, you need to grant a USDC allowance to the Permit2 contract. See the Common Ethereum contracts section for the Permit2 address. [​](https://docs.arc.io/arc/references/contract-addresses#transaction-extensions) Transaction extensions ----------------------------------------------------------------------------------------------------------- Arc provides predeployed contracts for attaching memos to transactions and batching calls with sender preservation. Both contracts route subcalls through the CallFrom precompile, which preserves the original `msg.sender` in each subcall. | Contract | Address | Notes | | --- | --- | --- | | **Memo** | [`0x5294E9927c3306DcBaDb03fe70b92e01cCede505`](https://testnet.arcscan.app/address/0x5294E9927c3306DcBaDb03fe70b92e01cCede505) | Attaches memo metadata to contract calls. Emits `Memo` events with a sequential index. | | **Multicall3From** | [`0x522fAf9A91c41c443c66765030741e4AaCe147D0`](https://testnet.arcscan.app/address/0x522fAf9A91c41c443c66765030741e4AaCe147D0) | Batches multiple calls like Multicall3, but preserves the original `msg.sender` in each subcall. | **Offchain blocklist operators:** If you maintain an offchain blocklist or compliance screening system, you must include the Memo and Multicall3From contract addresses. These contracts preserve `msg.sender` through the CallFrom precompile, meaning the original caller’s address appears as the sender in subcalls. Your monitoring should account for transactions routed through these contracts to ensure blocklist enforcement is not bypassed. [​](https://docs.arc.io/arc/references/contract-addresses#common-ethereum-contracts) Common Ethereum contracts ----------------------------------------------------------------------------------------------------------------- Arc Testnet includes a set of widely used Ethereum ecosystem contracts for deterministic deployment, batched reads, and standardized token approvals. Although not Circle-managed, these contracts are deployed on Arc to ensure compatibility with common EVM tooling and workflows. | Contract | Address | Notes | | --- | --- | --- | | **CREATE2 Factory (Arachnid)** | [`0x4e59b44847b379578588920cA78FbF26c0B4956C`](https://testnet.arcscan.app/address/0x4e59b44847b379578588920cA78FbF26c0B4956C) | Minimal proxy for deterministic contract deployment using the `CREATE2` opcode. | | **Multicall3** | [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://testnet.arcscan.app/address/0xcA11bde05977b3631167028862bE2a173976CA11) | Aggregates multiple read calls into a single call for efficient data retrieval. | | **Permit2** | [`0x000000000022D473030F116dDEE9F6B43aC78BA3`](https://testnet.arcscan.app/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) | Universal contract for signature-based token approvals. Required for StableFX. | [​](https://docs.arc.io/arc/references/contract-addresses#test-addresses-for-restricted-transfer-behavior) Test addresses for restricted transfer behavior ------------------------------------------------------------------------------------------------------------------------------------------------------------- To help you exercise the value transfer revert paths described in [EVM differences](https://docs.arc.io/arc/references/evm-differences#value-transfer-rules) , Arc Testnet seeds a well-known blocklisted address derived from the standard test mnemonic `test test test test test test test test test test test junk`. Because the mnemonic is public, you can derive its private key locally and sign as the address, for example with Foundry: cast wallet private-key --mnemonic "test test test test test test test test test test test junk" --mnemonic-index 1 | Purpose | Mnemonic index | Address | Behavior | | --- | --- | --- | --- | | **Blocklisted** | 1 | [`0x70997970C51812dc3A010C7d01b50e0d17dc79C8`](https://testnet.arcscan.app/address/0x70997970C51812dc3A010C7d01b50e0d17dc79C8) | A value transfer to or from this address reverts at runtime, including when the address is the beneficiary of a `SELFDESTRUCT`. | Use this address to confirm your contract handles a runtime revert on a value transfer (including as a `SELFDESTRUCT` beneficiary). See [EVM differences](https://docs.arc.io/arc/references/evm-differences#value-transfer-rules) for the full set of rules. Was this page helpful? YesNo [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) [Transaction memos](https://docs.arc.io/arc/concepts/transaction-memos) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Gas and fees - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/gas-and-fees#content-area) Arc denominates all transaction fees in [USDC](https://docs.arc.io/arc/concepts/stablecoin-native-model) , the native gas token. The fee market uses an [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) pricing model combined with exponentially weighted moving average (EWMA) smoothing — a technique that calculates the base fee from a weighted running average of recent block utilization, giving more weight to recent blocks and less to older ones. This produces stable, predictable gas costs. For the mechanism design behind these parameters, see [Stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) . [​](https://docs.arc.io/arc/references/gas-and-fees#fee-parameters) Fee parameters ------------------------------------------------------------------------------------- | Parameter | Value | Notes | | --- | --- | --- | | **Gas unit** | USDC (18 decimals) | Native gas accounting precision | | **Pricing model** | EIP-1559 + EWMA smoothing | Replaces per-block recalculation with a moving average | | **Base fee target** | ~$0.01 per transaction | Design-time target under normal load | | **Minimum base fee (testnet)** | 20 Gwei | Floor enforced by the protocol | | **Maximum base fee** | 20,000 Gwei | Hard ceiling that bounds worst-case cost | | **Gas throughput** | 30M gas/block (~60M gas/sec at 0.5 s block time) | Protocol-level capacity limit | | **Smoothing method** | EWMA of block utilization | Short spikes do not propagate into sudden fee jumps | The EWMA smoothing window calculates each new base fee as a weighted blend of the previous base fee and the latest block’s gas utilization ratio. Because older blocks carry exponentially decreasing weight, short traffic spikes raise the fee only slightly, and the base fee returns to its target quickly once utilization normalizes. The 18-decimal precision listed above applies to Arc’s native gas accounting. USDC on Arc also provides a standard [ERC-20 interface with 6 decimals](https://docs.arc.io/arc/references/evm-differences#usdc-as-the-native-gas-token) for application-level transfers and balance display. These are not two separate tokens — they share the same underlying balance. See [Contract addresses](https://docs.arc.io/arc/references/contract-addresses#usdc) for the ERC-20 address. [​](https://docs.arc.io/arc/references/gas-and-fees#submitting-transactions) Submitting transactions ------------------------------------------------------------------------------------------------------- Follow these practices to ensure timely transaction inclusion on Arc. ### [​](https://docs.arc.io/arc/references/gas-and-fees#set-an-adequate-max-fee) Set an adequate max fee Set `maxFeePerGas` to at least **20 Gwei**. Transactions submitted below this floor may remain pending indefinitely or fail outright. import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider("https://rpc.testnet.arc.network"); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); const tx = await wallet.sendTransaction({ to: recipient, value: ethers.parseUnits("1", 6), // 1 USDC via native send maxFeePerGas: ethers.parseUnits("20", "gwei"), }); Set `maxPriorityFeePerGas` (the EIP-1559 tip) to incentivize sequencer inclusion. A value of **0 Gwei** is accepted, but a small tip (for example, **1 Gwei**) can improve inclusion time during high-utilization periods. ### [​](https://docs.arc.io/arc/references/gas-and-fees#fetch-the-current-base-fee) Fetch the current base fee Query the Arc RPC before submitting to get the latest fee data. Two standard methods are available: | Method | Returns | Use case | | --- | --- | --- | | `eth_gasPrice` | Suggested gas price as a single value | Quick estimation for simple transactions | | `eth_feeHistory` | Base fee and priority fee history over recent blocks | Fine-grained estimation when you need historical context | import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider("https://rpc.testnet.arc.network"); // Fetch current gas price // Returns: hex string (e.g., "0x4a817c800" = 20 Gwei) const gasPrice: string = await provider.send("eth_gasPrice", []); // Fetch fee history for the last 5 blocks // Returns: { baseFeePerGas: string[], gasUsedRatio: number[], reward: string[][] } const feeHistory = await provider.send("eth_feeHistory", [\ "0x5", // block count\ "latest", // newest block\ [25, 50, 75], // percentiles\ ]); ### [​](https://docs.arc.io/arc/references/gas-and-fees#display-fees-in-usdc) Display fees in USDC Because Arc denominates gas in USDC, surface fee estimates to users in dollar terms rather than raw Gwei. This avoids confusion and aligns with the stablecoin-native model. [​](https://docs.arc.io/arc/references/gas-and-fees#common-errors) Common errors ----------------------------------------------------------------------------------- | Error | Cause | Resolution | | --- | --- | --- | | `transaction underpriced` | `maxFeePerGas` is below the 20 Gwei minimum base fee floor | Increase `maxFeePerGas` to at least `ethers.parseUnits("20", "gwei")` and resubmit | | `intrinsic gas too low` | Gas limit is lower than the intrinsic cost of the transaction | Set the gas limit to at least 21,000 for simple transfers; use `eth_estimateGas` for contract calls | | `insufficient funds for gas * price + value` | The sending account’s USDC balance cannot cover both the transfer value and the gas fee | Fund the account with enough USDC to cover the total cost (value + maxFeePerGas x gasLimit) | [​](https://docs.arc.io/arc/references/gas-and-fees#monitoring) Monitoring ----------------------------------------------------------------------------- View real-time gas metrics and recent averages using the [Arc Gas Tracker](https://testnet.arcscan.app/gas-tracker) . The tracker displays current base fee, historical trends, and per-block utilization. The parameters on this page reflect the current Arc Testnet configuration. Values such as the minimum base fee, maximum base fee, and throughput limits may change before mainnet launch. Was this page helpful? YesNo [Execution layer](https://docs.arc.io/arc/concepts/execution-layer) [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # EVM differences - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/evm-differences#content-area) Arc is an EVM-compatible Layer-1 blockchain. Solidity, Foundry, Hardhat, Viem, ethers.js, and standard Ethereum wallets work without modification, and you can deploy existing contracts unchanged in most cases. Arc targets the **Osaka** hard fork as its baseline and ships select features from Ethereum’s upcoming **Amsterdam** hard fork ahead of upstream, notably [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) (standard `Transfer` logs for native value movements). This page is the complete reference for where Arc’s protocol-level behavior diverges from Ethereum. Comparisons below are against Ethereum at the **Osaka** hard fork, Arc’s baseline. Most differences are transparent to application code, but a few change execution semantics in ways that matter when you port a contract. If you are porting an existing contract, start with the [Porting contracts to Arc checklist](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc) . Tools that locally simulate the EVM (such as Foundry’s `anvil`) run a standard EVM, not Arc’s, so they cannot reproduce Arc-specific behavior. Features that depend on it (the native-coin precompiles, EIP-7708 `Transfer` events, and USDC blocklist enforcement) only surface when you test against an Arc RPC endpoint. [​](https://docs.arc.io/arc/references/evm-differences#usdc-as-the-native-gas-token) USDC as the native gas token -------------------------------------------------------------------------------------------------------------------- Arc uses USDC as its native token. The single most important thing to understand is that **native USDC and the ERC-20 USDC interface are the same asset**, not two separate tokens that happen to share a name. | Interface | Decimals | Used for | | --- | --- | --- | | **Native** | 18 | Gas accounting, native sends, and `msg.value` | | **ERC-20** | 6 | application-level transfers, approvals, and allowances | The ERC-20 interface lives at the [USDC contract address](https://docs.arc.io/arc/references/contract-addresses#usdc) and provides familiar functions such as `transferFrom`, `approve`, and allowance management. A native send and an ERC-20 `transfer` both move the same underlying balance. Because both interfaces operate on one balance, `USDC.balanceOf(addr)` and `addr.balance` are two views of the same value. They use different decimals (6 vs 18), so never compare or mix their raw values without converting first. This single-asset model has consequences that do not exist on other EVM chains: * **The ERC-20 view truncates, so it is not exact.** The 6-decimal `balanceOf` drops anything below 1×10⁻⁶ USDC, so a native balance of `0.0000001` USDC reads as `0` and `100.0000001` USDC reads as `100`. A `balanceOf` of `0` does not imply a native balance of `0`. * **A native transfer can revert even with a sufficient balance** (for example, a transfer to the zero address, a transfer to or from a blocklisted address, or any transfer that would burn value). For the conceptual model, including EURC and USYC support, see [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) . For how balance changes surface as events, see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . [​](https://docs.arc.io/arc/references/evm-differences#execution-and-opcode-differences) Execution and opcode differences ---------------------------------------------------------------------------------------------------------------------------- | Behavior | Ethereum (Osaka) | Arc | Developer impact | | --- | --- | --- | --- | | `PREVRANDAO` | Beacon chain RANDAO mix | Always returns `0` | No onchain randomness. Use an oracle or verifiable random function (VRF). | | `SELFDESTRUCT` | EIP-6780 semantics | EIP-6780 plus native value rules; emits a `Transfer` log on success (see [SELFDESTRUCT](https://docs.arc.io/arc/references/evm-differences#selfdestruct)
) | Several patterns that succeed on mainnet revert on Arc. | | Non-zero-value `CALL` to a self-destructed account | Succeeds; value credited | **Reverts** (a transfer to a destructed account is a forbidden burn) | The largest semantic departure. See [SELFDESTRUCT](https://docs.arc.io/arc/references/evm-differences#selfdestruct)
. | | `parentBeaconBlockRoot` / EIP-4788 | Beacon-roots contract returns the parent beacon root | Set to the parent execution block hash; the beacon-roots contract is omitted, so reads return empty (`0x`) | Do not treat the beacon-roots oracle as functional or as a randomness source. | | Blob transactions (EIP-4844, type-3) | Supported | Not supported; the mempool rejects type-3 transactions | Do not submit blob transactions. `BLOBHASH` returns `0` and `BLOBBASEFEE` returns `1`. | | Withdrawals (EIP-4895) | May be present | Always empty | `block.withdrawals` is always empty. | EIP-7702 set-code transactions, `CREATE2` (including EIP-7610 residual-storage behavior), and EIP-2935 historical block hashes all behave as on Ethereum. In particular, the EIP-2935 block-hash-history contract is deployed and functional, unlike the EIP-4788 beacon-roots contract noted above. [​](https://docs.arc.io/arc/references/evm-differences#value-transfer-rules) Value transfer rules ---------------------------------------------------------------------------------------------------- Because the native token is USDC, value transfers are subject to rules that do not exist on a standard EVM chain. A native transfer can **revert even when the sender has sufficient balance**. * **Transfers to the zero address are forbidden.** A value-bearing transfer to `0x0` reverts with `"Zero address not allowed"`; a zero-value transfer to `0x0` succeeds. (Mint and burn, the only operations that involve `0x0`, go through the native-coin precompile.) * **Burning is forbidden.** Self-destructing to yourself with a balance, or transferring value to an account that has already self-destructed, reverts. * **The blocklist is enforced at runtime.** A value transfer to or from a blocklisted address reverts. An included transaction that reverts on a blocklist check still consumes gas. * **Sending native value to a contract is not guaranteed to succeed.** A call to a contract that forwards native value can revert for any of the reasons listed here, which breaks a common DeFi assumption. * **Sending to an address with no code (`EXTCODESIZE == 0`) succeeds** and emits a `Transfer` log. Sending value to a precompile address reverts. A liquidity pool that pairs native USDC against the ERC-20 USDC interface (as if they were two assets) is meaningless on Arc, because they are one asset. Review ported DeFi for this assumption. [​](https://docs.arc.io/arc/references/evm-differences#selfdestruct) SELFDESTRUCT ------------------------------------------------------------------------------------ `SELFDESTRUCT` is allowed on Arc, including during contract deployment, and follows [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) (the account is fully deleted only if it was created in the same transaction). A self-destruct **reverts** when its value transfer would violate a native value rule: | Condition | Result | | --- | --- | | Beneficiary is the contract itself, with a balance | Reverts | | Beneficiary is the zero address, with a balance | Reverts | | Source or beneficiary is blocklisted | Reverts | | Beneficiary has already self-destructed | Reverts | | Balance is zero (any beneficiary) | Succeeds | Three behaviors differ from every other EVM chain and deserve attention: **1\. Self-destructing a contract that holds USDC moves that USDC out.** On Arc a contract’s USDC is its native balance, held in the account, so `SELFDESTRUCT` transfers it to the beneficiary. On other chains the contract’s ERC-20 USDC balance lives in the token contract and is unaffected by self-destruct. **2\. A non-zero-value call to a self-destructed account reverts.** On Ethereum, sending value to an address that self-destructed earlier in the same transaction succeeds. On Arc it is treated as a transfer to a destructed account, a forbidden burn, and reverts. // Within one transaction: contractA.selfDestruct(payable(b)); // succeeds; emits Transfer(A, B) // Later in the same transaction, any non-zero-value send to A: (bool ok, ) = address(contractA).call{value: 1}(""); // reverts on Arc **3\. A successful self-destruct that moves a balance emits a `Transfer` log.** Unlike Ethereum, the moved native value is recorded as an [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) `Transfer` log from the system emitter (18 decimals). Index it like any other native movement; see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . [​](https://docs.arc.io/arc/references/evm-differences#native-usdc-transfer-events-eip-7708) Native USDC Transfer events (EIP-7708) -------------------------------------------------------------------------------------------------------------------------------------- On a standard EVM chain, a plain native send emits no log. Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation emits a standard ERC-20 `Transfer` log from a system address for **every** native USDC movement: native sends, contract endowments, self-destruct transfers, and the precompile-backed mint, burn, and transfer operations. This gives indexers one universal record of balance changes. The system emitter log uses 18 decimals and is distinct from the ERC-20 USDC contract’s own 6-decimal `Transfer`. Match on the emitter address so you do not count the same movement twice. See [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) for emitter addresses, the exact log format, and indexing guidance. [​](https://docs.arc.io/arc/references/evm-differences#fee-market-and-block-behavior) Fee market and block behavior ---------------------------------------------------------------------------------------------------------------------- * **The base fee is paid to the block beneficiary, not burned.** Arc has no EIP-1559 burn. Both the base fee and the priority fee are credited to the block’s beneficiary. * **The next block’s base fee is published in the parent header’s `extra_data`** (an 8-byte big-endian value). Read it there rather than re-deriving it. The fee is computed from an exponentially smoothed view of gas usage and is clamped to bounded minimum and maximum values, so it moves predictably. See [Stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) and [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) . * **Block timestamps are non-decreasing, not strictly increasing.** Timestamps come from the proposer’s wall clock at one-second granularity, so sub-second blocks may share a timestamp. Use the block number for ordering, and do not assume `block.timestamp` strictly increases between blocks. * **Finality is deterministic and instant.** Transactions finalize on inclusion; offchain systems can act after a single confirmation. See [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) . [​](https://docs.arc.io/arc/references/evm-differences#known-limitation-draining-an-empty-account) Known limitation: draining an empty account ------------------------------------------------------------------------------------------------------------------------------------------------- A transfer that would leave an account with a zero balance, a zero nonce, and no code currently reverts. This is a deliberate guard against a specific edge case and is reachable in practice only through a USDC meta-transaction that fully drains a brand-new account. Accounts that have ever sent a transaction (non-zero nonce) or hold code are unaffected and drain normally. This is a known, temporary limitation. A fix is planned for a future release. Was this page helpful? YesNo [Overview](https://docs.arc.io/arc-chain) [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Collect custom bridge fees - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee#content-area) The App Kit SDK lets you collect a custom fee from your end users on each bridge transfer. For how custom fees fit into the overall fees breakdown, see [How bridge fees work](https://docs.arc.io/app-kit/concepts/bridge-fees) . If you use this feature, Arc keeps 10% of the custom fee you collect from your end users. [​](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee#prerequisites) Prerequisites --------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee#set-a-custom-fee-on-a-bridge) Set a custom fee on a bridge --------------------------------------------------------------------------------------------------------------------------------- This example adds a 0.10 USDC fee to a single bridge transfer: TypeScript const result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet" }, amount: "1.00", // Collect a fee of 0.10 USDC on this bridge transfer config: { customFee: { value: "0.10", recipientAddress: "0xYourFeeRecipientAddress", }, }, }); Was this page helpful? YesNo [How bridge fees work](https://docs.arc.io/app-kit/concepts/bridge-fees) [Estimate costs](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Specify a recipient address - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/bridge/specify-recipient-address#content-area) By default, bridged USDC arrives at the same address as your signing wallet on the destination chain. For bridge transfers, you can specify a recipient address so the tokens are minted to that address instead. [​](https://docs.arc.io/app-kit/tutorials/bridge/specify-recipient-address#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/bridge/specify-recipient-address#specify-a-recipient) Specify a recipient ---------------------------------------------------------------------------------------------------------------------- This example bridges 1.00 USDC from your wallet address on Ethereum Sepolia to a different address on Arc Testnet: TypeScript const result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet", recipientAddress: "0xRecipientAddress", // Specify a recipient address }, amount: "1.00", }); Was this page helpful? YesNo [Configure transfer speed and maximum cost](https://docs.arc.io/app-kit/tutorials/bridge/configure-transfer-speed) [Error recovery](https://docs.arc.io/app-kit/references/bridge-error-recovery) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Use Forwarding Service - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service#content-area) You can use the [Forwarding Service](https://developers.circle.com/cctp/concepts/forwarding-service) on bridge transfers. When enabled, it fetches the attestation and submits the mint on the destination chain. You don’t need to poll for attestations or have access to a wallet on the destination chain. [​](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service#use-with-adapters-on-both-chains) Use with adapters on both chains --------------------------------------------------------------------------------------------------------------------------------------------- Set `useForwarder: true` when you have adapters for both chains but want the Forwarding Service to submit the mint transaction on the destination: TypeScript const result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet", useForwarder: true, }, amount: "1.00", }); [​](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service#use-without-a-destination-adapter) Use without a destination adapter ----------------------------------------------------------------------------------------------------------------------------------------------- When you don’t have access to a wallet on the destination chain, such as with server-side or custodial bridge flows, omit the destination adapter and pass `recipientAddress` with `useForwarder: true`: TypeScript const result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { recipientAddress: "0xRecipientAddress", chain: "Arc_Testnet", useForwarder: true, }, amount: "1.00", }); In this mode, mint confirmation comes from the Circle Iris API response rather than an onchain receipt. Because the Forwarding Service submits the mint transaction, no locally signed transaction hash is returned and the mint step’s `data` field is `undefined`. [​](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service#forwarding-fee) Forwarding fee --------------------------------------------------------------------------------------------------------- The Forwarding Service charges a [fee](https://developers.circle.com/cctp/concepts/forwarding-service#fees-and-execution) that is deducted from the amount minted on the destination chain. When you [estimate costs](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs) for a bridge transfer, the result includes the forwarding fee. See [How bridge fees work](https://docs.arc.io/app-kit/concepts/bridge-fees) for details. Was this page helpful? YesNo [Estimate costs](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs) [Configure transfer speed and maximum cost](https://docs.arc.io/app-kit/tutorials/bridge/configure-transfer-speed) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Estimate swap rate - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/swap/estimate-swap-rate#content-area) You can get a pre-swap estimate of the amount you’ll receive before swapping. [​](https://docs.arc.io/app-kit/tutorials/swap/estimate-swap-rate#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/swap/estimate-swap-rate#estimate-swap-amount) Estimate swap amount --------------------------------------------------------------------------------------------------------------- This example estimates how much EURC you’ll receive when swapping 1.00 USDC for EURC: TypeScript const params = { from: { adapter, chain: "Arc_Testnet" }, tokenIn: "USDC", amountIn: "1.00", tokenOut: "EURC", config: { kitKey: process.env.KIT_KEY as string, }, }; // Estimate swap amount const estimate = await kit.estimateSwap(params); console.log(`Estimated output: ${estimate.estimatedOutput}`); // Proceed to swap const result = await kit.swap(params); Was this page helpful? YesNo [Collect custom swap fees](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee) [Set slippage tolerance or stop limit](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Configure transfer speed and maximum cost - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/bridge/configure-transfer-speed#content-area) By default, the App Kit SDK uses CCTP’s [Fast Transfer](https://developers.circle.com/cctp) feature to optimize for speed. However, [this can incur higher fees](https://developers.circle.com/cctp/technical-guide#fees) . If performance is not a concern, you can specify a slow transfer speed, which uses CCTP’s Standard Transfer feature. To balance performance and cost, you can set the maximum fee you want to pay on fast bridge transfers. The transfer switches to slow if the cost is above your limit. [​](https://docs.arc.io/app-kit/tutorials/bridge/configure-transfer-speed#prerequisites) Prerequisites --------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/bridge/configure-transfer-speed#configure-slow-speed) Configure slow speed ----------------------------------------------------------------------------------------------------------------------- This code configures a slow transfer speed: TypeScript const result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet" }, amount: "1.00", config: { transferSpeed: "SLOW" }, }); [​](https://docs.arc.io/app-kit/tutorials/bridge/configure-transfer-speed#configure-maximum-fee) Configure maximum fee ------------------------------------------------------------------------------------------------------------------------- Set the maximum fee that you want to pay for the CCTP fast burn. For bridges from Arc Testnet, the amount must exceed the CCTPv2 max fee (around 1.4 USDC). If the amount is too low, the burn step can revert with `"Max fee must be less than amount"`. This code configures a maximum fee of 0.10 USDC: TypeScript const result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet", }, amount: "1.00", config: { transferSpeed: "FAST", // Fast transfer speed (can be omitted) maxFee: "0.10", // Max 0.10 USDC fee }, }); Was this page helpful? YesNo [Use Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service) [Specify a recipient address](https://docs.arc.io/app-kit/tutorials/bridge/specify-recipient-address) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Estimate costs before bridging - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs#content-area) The App Kit SDK can estimate gas fees and provider fees before making a bridge transfer, and only proceed if the costs are acceptable. [​](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs#estimate-costs) Estimate costs ------------------------------------------------------------------------------------------------- This example estimates costs for a bridge transfer of 1.00 USDC from Ethereum Sepolia to Arc Testnet: TypeScript import type { BridgeParams } from "@circle-fin/app-kit"; const params: BridgeParams = { from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet" }, amount: "1.00", }; // Estimate costs const estimate = await kit.estimateBridge(params); console.log( `Estimating ${params.amount} USDC from ${params.from.chain} to ${params.to.chain}`, ); console.log("Estimated fees:", estimate.fees); const providerFee = estimate.fees.find( (fee) => fee.type === "provider", )?.amount; // Only proceed if the provider fee is less than 0.10 USDC // Also proceed if the `providerFee` variable is null/undefined since there is no provider fee to charge if (providerFee == null || parseFloat(providerFee) < 0.1) { // Execute the bridge transfer const result = await kit.bridge(params); console.log("Bridge transfer completed:", result); } else { // Provider fee is 0.10 USDC or higher - abort the bridge transfer console.log("Provider fee is above threshold:", providerFee, "USDC"); } Was this page helpful? YesNo [Collect custom bridge fees](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee) [Use Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Collect custom swap fees - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee#content-area) You can configure your swaps to collect a custom fee from end users on each swap call. For how custom fees fit into the overall fees breakdown, see [How swap fees work](https://docs.arc.io/app-kit/concepts/swap-fees) . If you use this feature, Arc keeps 10% of the custom fee you collect from your end users. [​](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee#set-a-custom-fee-on-a-swap) Set a custom fee on a swap ------------------------------------------------------------------------------------------------------------------------- This example adds a 1% (100 basis points) fee to a single swap call: TypeScript const output = await kit.swap({ from: { adapter, chain: "Arc_Testnet" }, tokenIn: "USDC", amountIn: "1.00", tokenOut: "EURC", config: { kitKey: process.env.KIT_KEY as string, customFee: { percentageBps: 100, // 1% fee recipientAddress: "0xYourFeeRecipientAddress", }, }, }); Was this page helpful? YesNo [How swap fees work](https://docs.arc.io/app-kit/concepts/swap-fees) [Estimate swap rate](https://docs.arc.io/app-kit/tutorials/swap/estimate-swap-rate) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Quickstart: Use a delegate to deposit and spend a Unified Balance - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#content-area) Delegation lets one address authorize another address to sign spends from its Unified Balance. The owner account keeps custody of the funds, while the delegate signs spend intents for authorized source blockchains. Delegated spends are typically a server-side pattern: the owner authorizes a delegate once, then a backend service signs future spend intents without asking the owner wallet to sign each spend. The mechanism is wallet-agnostic; any supported owner wallet can authorize a compatible EOA delegate. In this quickstart, you’ll use Circle Wallets for both wallets: an owner wallet that holds the Unified Balance and an EOA delegate wallet that signs spends. You’ll use the delegate wallet to deposit into the owner’s Unified Balance, authorize the delegate on Base Sepolia, check the owner’s Unified Balance, and spend on Arc Testnet with the Forwarding Service. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Set up Circle Wallets: * Obtained a [API Key](https://developers.circle.com/api-reference/keys#creating-an-api-key-for-developer-services) and [entity secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret) from the [Circle Console](https://developers.circle.com/w3s/circle-developer-account) . * Created an owner wallet and an EOA delegate wallet on Base Sepolia. The owner wallet holds the Unified Balance, and the delegate wallet signs delegated spends after authorization. * Funded the Base Sepolia wallets: * Owner wallet: testnet ETH from a [public faucet](https://www.alchemy.com/faucets/base-sepolia) . * Delegate wallet: testnet USDC from the [Circle Faucet](https://faucet.circle.com/) and testnet ETH from a [public faucet](https://www.alchemy.com/faucets/base-sepolia) . * Obtained an Arc Testnet recipient address that will receive the USDC. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#step-1-set-up-your-project) Step 1. Set up your project ------------------------------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#1-1-create-the-project-and-install-dependencies) 1.1. Create the project and install dependencies Create a new directory and install the App Kit SDK with the Circle Wallets adapter and supporting tools: Shell # Set up your directory and initialize a Node.js project mkdir unified-balance-delegate cd unified-balance-delegate npm init -y npm pkg set type=module # Set up run scripts npm pkg set scripts.deposit="tsx --env-file=.env delegate-deposit.ts" npm pkg set scripts.authorize="tsx --env-file=.env delegate-authorize.ts" npm pkg set scripts.balance="tsx --env-file=.env delegate-check-balance.ts" npm pkg set scripts.spend="tsx --env-file=.env delegate-spend.ts" # Install runtime dependencies npm install @circle-fin/app-kit @circle-fin/adapter-circle-wallets tsx # Install dev dependencies npm install --save-dev typescript @types/node Only need a Unified Balance and want a lighter install than the full App Kit SDK? Install the standalone Unified Balance Kit instead: `@circle-fin/unified-balance-kit` ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#1-2-configure-typescript-optional) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#1-3-set-environment-variables) 1.3. Set environment variables Create an `.env` file in the project directory: Shell touch .env Add your credentials. Replace `YOUR_API_KEY` with your Circle Developer API key and `YOUR_ENTITY_SECRET` with your entity secret: .env CIRCLE_API_KEY=YOUR_API_KEY CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET Edit `.env` files in your IDE or editor so credentials are not leaked to your shell history. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#step-2-deposit-into-the-owner%E2%80%99s-unified-balance) Step 2. Deposit into the owner’s Unified Balance --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In this step, the delegate wallet deposits USDC into the owner’s Unified Balance from Base Sepolia. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#2-1-create-the-deposit-script) 2.1. Create the deposit script Create a `delegate-deposit.ts` file. In this script, the delegate wallet deposits 2.00 USDC from Base Sepolia into the owner’s Unified Balance. delegate-deposit.ts import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const DEPOSIT_AMOUNT = "2.00"; const kit = new AppKit(); kit.on("*", (payload) => { console.log("Event received:", payload); }); const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const ownerWalletAddress = "YOUR_BASE_SEPOLIA_OWNER_WALLET_ADDRESS"; const delegateWalletAddress = "YOUR_BASE_SEPOLIA_DELEGATE_WALLET_ADDRESS"; const result = await kit.unifiedBalance.depositFor({ from: { adapter, chain: "Base_Sepolia", address: delegateWalletAddress, }, amount: DEPOSIT_AMOUNT, token: "USDC", depositAccount: ownerWalletAddress, }); console.dir(result, { depth: null, colors: true }); `depositFor` is permissionless. Any wallet can fund another account’s Unified Balance. This quickstart uses it so the delegate funds the owner before the owner grants spend authorization. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#2-2-run-the-deposit-script) 2.2. Run the deposit script In your terminal, run: Shell npm run deposit You’ll see output like: Shell { amount: '2.00', token: 'USDC', depositedTo: '0x...', depositedBy: '0x...', chain: 'Base_Sepolia', txHash: '0x...', explorerUrl: 'https://sepolia.basescan.org/tx/0x...' } ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#2-3-verify-the-deposit) 2.3. Verify the deposit Open the `explorerUrl` from the deposit result to confirm the onchain transaction on Base Sepolia. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#step-3-authorize-the-delegate) Step 3. Authorize the delegate ------------------------------------------------------------------------------------------------------------------------------------------------------- In this step, the owner wallet grants the delegate permission to spend from its Unified Balance on a specific blockchain. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#3-1-create-the-authorize-script) 3.1. Create the authorize script Create a `delegate-authorize.ts` file. In this script, the owner wallet authorizes the delegate to spend from its Unified Balance on Base Sepolia: delegate-authorize.ts import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const kit = new AppKit(); kit.on("*", (payload) => { console.log("Event received:", payload); }); const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const ownerWalletAddress = "YOUR_BASE_SEPOLIA_OWNER_WALLET_ADDRESS"; const delegateWalletAddress = "YOUR_BASE_SEPOLIA_DELEGATE_WALLET_ADDRESS"; async function main() { const status = await kit.unifiedBalance.getDelegateStatus({ from: { adapter, chain: "Base_Sepolia", address: ownerWalletAddress, }, delegateAddress: delegateWalletAddress, }); if (status === "ready") { console.log( `Delegate ${delegateWalletAddress} is already authorized on Base_Sepolia.`, ); return; } if (status === "pending") { console.log( `Delegate ${delegateWalletAddress} is still pending on Base_Sepolia. Wait and run this script again.`, ); return; } // addDelegate: owner-signed transaction granting the delegate spend rights. const result = await kit.unifiedBalance.addDelegate({ from: { adapter, chain: "Base_Sepolia", address: ownerWalletAddress, }, delegateAddress: delegateWalletAddress, }); console.dir(result, { depth: null, colors: true }); } void main(); `addDelegate` is an onchain transaction signed by the owner wallet. Once authorized, the delegate can spend repeatedly on the same blockchain without reauthorization. Authorize only delegate addresses you control, and revoke access when it is no longer needed. Authorization is source-blockchain specific. See [Manage Delegates](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates) for details. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#3-2-run-the-authorize-script) 3.2. Run the authorize script In your terminal, run: Shell npm run authorize You’ll see output like: Shell { account: '0x...', delegateAddress: '0x...', chain: 'Base_Sepolia', state: 'added', txHash: '0x...', explorerUrl: 'https://sepolia.basescan.org/tx/0x...' } If `status` is already `'ready'`, the script exits without calling `addDelegate`. If `status` is `'pending'`, it asks you to wait and run the script again. Otherwise it submits `addDelegate`. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#step-4-check-the-owner%E2%80%99s-unified-balance) Step 4. Check the owner’s Unified Balance ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In this step, you check the owner’s Unified Balance by address. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#4-1-create-the-balance-check-script) 4.1. Create the balance check script Create a `delegate-check-balance.ts` file. This script prints the owner’s confirmed and pending Unified Balance totals: delegate-check-balance.ts import { AppKit } from "@circle-fin/app-kit"; const kit = new AppKit(); const ownerWalletAddress = "YOUR_BASE_SEPOLIA_OWNER_WALLET_ADDRESS"; const balances = await kit.unifiedBalance.getBalances({ sources: { address: ownerWalletAddress, chains: ["Base_Sepolia"], }, networkType: "testnet", includePending: true, }); console.dir(balances, { depth: null, colors: true }); You can check balances by address, adapter, chain, and network. See [Check Unified Balance](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance) for more options. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#4-2-run-the-balance-check-script) 4.2. Run the balance check script In your terminal, run: Shell npm run balance You’ll see output like: Shell { token: 'USDC', totalConfirmedBalance: '2.000000', breakdown: [\ {\ depositor: '0x...',\ totalConfirmed: '2.000000',\ breakdown: [\ {\ chain: 'Base_Sepolia',\ confirmedBalance: '2.000000',\ pendingBalance: '0.000000',\ pendingTransactions: []\ }\ ],\ totalPending: '0.000000'\ }\ ], totalPendingBalance: '0.000000' } After a deposit, funds can appear in `totalPendingBalance` before they are reflected in `totalConfirmedBalance`. Wait until the owner’s `totalConfirmedBalance` is high enough for the spend you plan to make before you continue. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#step-5-spend-from-the-owner%E2%80%99s-balance) Step 5. Spend from the owner’s balance ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In this step, the delegate spends from the owner’s Unified Balance on Arc Testnet for the recipient. The [Forwarding Service](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service) submits the destination mint, so you don’t need a wallet on Arc Testnet. The Forwarding Service charges a fee that is deducted from the amount minted on the destination chain. The spend result includes the forwarding fee in the fee breakdown. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#5-1-create-the-spend-script) 5.1. Create the spend script Create a `delegate-spend.ts` file. This script spends 0.50 USDC from the owner’s Unified Balance on Arc Testnet for the recipient, signed by the delegate. delegate-spend.ts import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const SPEND_AMOUNT = "0.50"; const kit = new AppKit(); kit.on("*", (payload) => { console.log("Event received:", payload); }); const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const ownerWalletAddress = "YOUR_BASE_SEPOLIA_OWNER_WALLET_ADDRESS"; const delegateWalletAddress = "YOUR_BASE_SEPOLIA_DELEGATE_WALLET_ADDRESS"; const recipientAddress = "YOUR_ARC_TESTNET_RECIPIENT_ADDRESS"; console.log( `Spending ${SPEND_AMOUNT} USDC on Arc_Testnet for ${recipientAddress}...\n`, ); const result = await kit.unifiedBalance.spend({ amount: SPEND_AMOUNT, token: "USDC", from: [\ {\ adapter,\ address: delegateWalletAddress,\ // Spend from the owner's balance; the delegate wallet signs.\ sourceAccount: ownerWalletAddress,\ allocations: [{ amount: SPEND_AMOUNT, chain: "Base_Sepolia" }],\ },\ ], to: { chain: "Arc_Testnet", recipientAddress, useForwarder: true, }, }); console.dir(result, { depth: null, colors: true }); You can also customize your Unified Balance to [collect a custom fee](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees) from end users, [estimate fees](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees) before spending, [select source blockchains and allocations](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains) to fund a balance. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#5-2-run-the-spend-script) 5.2. Run the spend script In your terminal, run: Shell npm run spend The script logs SDK events and prints the spend result. You’ll see output like: Shell { recipientAddress: '0x...', destinationChain: 'Arc_Testnet', txHash: '0x...', explorerUrl: 'https://testnet.arcscan.app/tx/0x...', allocations: [\ {\ amount: '0.5',\ chain: 'Base_Sepolia',\ sourceAccount: '0x...'\ }\ ], fees: [\ { type: 'provider', token: 'USDC', amount: '0.000025', ... },\ { type: 'gasFee', token: 'USDC', amount: '0.024137', ... },\ { type: 'forwarder', token: 'USDC', amount: '0.014162' }\ ], transferId: '...', expirationBlock: '...' } ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend#5-3-verify-the-spend) 5.3. Verify the spend Use the spend result to confirm that USDC arrived at the recipient address on Arc Testnet. When you use the Forwarding Service, the result can include a `transferId` instead of a locally submitted destination transaction hash. The received amount can be less than the requested spend after fees. For more on fees, see [How Unified Balance fees work](https://docs.arc.io/app-kit/concepts/unified-balance-fees) . Was this page helpful? YesNo [Deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend) [How Unified Balance fees work](https://docs.arc.io/app-kit/concepts/unified-balance-fees) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Quickstart: Bridge tokens across blockchains - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#content-area) Bridge USDC between blockchains with the wallet model that matches your application. Use a browser wallet when the end user signs in the client, or use Circle Wallets when you manage developer-controlled wallets through Circle. * Browser wallet * Circle Wallets Use this flow to bridge USDC with a connected browser wallet. The example uses Ethereum Sepolia and Arc Testnet. [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------ Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Installed a browser wallet such as [MetaMask](https://metamask.io/) and added the [Ethereum Sepolia](https://chainlist.org/chain/11155111) and [Arc Testnet](https://docs.arc.network/arc/references/connect-to-arc#wallet-setup) networks. * Funded your wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/) . * Funded your wallet with ETH for Ethereum Sepolia from a [public faucet](https://www.alchemy.com/faucets/ethereum-sepolia) . [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#step-1-set-up-the-project) Step 1. Set up the project ------------------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#1-1-create-the-project-and-install-dependencies) 1.1. Create the project and install dependencies Create a new directory, install the App Kit packages, and add local browser demo tooling: Shell # Set up your directory and initialize a Node.js project mkdir app-kit-bridge-browser-wallet cd app-kit-bridge-browser-wallet npm init -y npm pkg set type=module # Install App Kit packages npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem # Install TypeScript and a local Vite dev server for the browser demo npm install --save-dev typescript vite Only need to bridge and want a lighter install than the full App Kit SDK? Install the standalone Bridge Kit instead: `@circle-fin/bridge-kit` ### [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#1-2-configure-typescript-optional) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#step-2-connect-a-browser-wallet) Step 2. Connect a browser wallet ------------------------------------------------------------------------------------------------------------------------------------------------- This step shows the core browser wallet integration flow: discover an [`EIP-6963`](https://eips.ethereum.org/EIPS/eip-6963) provider, create an App Kit adapter from the selected provider, and pass that adapter into an App Kit SDK method.The snippets below keep wallet discovery, wallet connection, and adapter setup in small helper functions for readability. ### [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#2-1-discover-a-browser-wallet-with-eip-6963) 2.1. Discover a browser wallet with `EIP-6963` This pattern is standards-based. The example uses MetaMask as the selected wallet, but the discovery flow works with any wallet that announces an `EIP-6963` provider. TypeScript import type { EIP1193Provider } from "viem"; type EIP6963ProviderInfo = { uuid: string; name: string; icon: string; rdns: string; }; type EIP6963ProviderDetail = { info: EIP6963ProviderInfo; provider: EIP1193Provider; }; declare global { interface WindowEventMap { "eip6963:announceProvider": CustomEvent; } } async function discoverBrowserWallets(): Promise { const providers = new Map(); const handleProviderAnnouncement = ( event: WindowEventMap["eip6963:announceProvider"], ) => { providers.set(event.detail.info.uuid, event.detail); }; window.addEventListener( "eip6963:announceProvider", handleProviderAnnouncement, ); window.dispatchEvent(new Event("eip6963:requestProvider")); await new Promise((resolve) => window.setTimeout(resolve, 250)); window.removeEventListener( "eip6963:announceProvider", handleProviderAnnouncement, ); return [...providers.values()]; } ### [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#2-2-connect-the-wallet-and-request-account-access) 2.2. Connect the wallet and request account access After you select a provider, request account access before calling an App Kit SDK method. This should happen in a user-triggered action such as a `Connect wallet` button. TypeScript async function connectWallet(provider: EIP1193Provider) { await provider.request({ method: "eth_requestAccounts", params: undefined, // Required by the provider type even though this method has no params. }); const accounts = (await provider.request({ method: "eth_accounts", params: undefined, // Required by the provider type even though this method has no params. })) as string[]; return { connectedAddress: accounts[0] ?? null, }; } Keep wallet connection and App Kit actions as separate user actions. This avoids overlapping wallet permission or chain-switch requests while a previous wallet prompt is still pending. ### [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#2-3-create-a-viem-adapter-from-the-selected-wallet-provider) 2.3. Create a Viem adapter from the selected wallet provider Use the discovered provider to request account access, then create the App Kit adapter that signs transactions in the browser: TypeScript import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2"; async function connectBrowserWallet() { const providers = await discoverBrowserWallets(); const selectedWallet = providers.find( ({ info }) => info.rdns === "io.metamask" || info.name === "MetaMask", ) ?? providers[0]; if (!selectedWallet) { throw new Error("No EIP-6963 browser wallet found"); } const { connectedAddress } = await connectWallet(selectedWallet.provider); const adapter = await createViemAdapterFromProvider({ provider: selectedWallet.provider, }); return { adapter, connectedAddress, walletName: selectedWallet.info.name, }; } If multiple EVM wallets are installed, explicitly choose the wallet you want to use instead of relying on the first announced provider. The browser demo used to validate this quickstart prefers MetaMask when it is available. [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#step-3-bridge-usdc) Step 3. Bridge USDC ----------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#3-1-pass-the-browser-wallet-adapter-into-kit-bridge) 3.1. Pass the browser wallet adapter into `kit.bridge()` This is the only App Kit-specific bridge call you need after the wallet is connected: TypeScript import { AppKit } from "@circle-fin/app-kit"; const kit = new AppKit(); async function bridgeUSDCWithBrowserWallet() { const { adapter, connectedAddress, walletName } = await connectBrowserWallet(); let result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet" }, amount: "1.00", }); if (result.state === "error") { result = await kit.retryBridge(result, { from: adapter, to: adapter, }); } console.log(`Submitted bridge with ${walletName}`, { connectedAddress, result, }); return result; } Download the runnable [browser demo](https://github.com/circlefin/docs-examples/tree/master/app-kit-bridge-evm) to see the EVM-to-EVM bridge flow in action. ### [​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#3-2-observe-bridge-lifecycle-events) 3.2. Observe bridge lifecycle events To inspect the bridge flow while it runs, subscribe to Bridge Kit events before you call `kit.bridge()`. This is useful for seeing the runtime step order and payload shape. The companion browser demos render these events into an on-page `
` element, but logging them to the console is enough for the core integration.

TypeScript

    kit.on("*", (payload) => {
      console.log("Action:", payload);
    });
    

Using other EVM chains? Change the `chain` values in `kit.bridge()` and ensure the connected wallet holds USDC on the source chain and enough gas to complete the transfer flow.

You can customize your bridges to [collect a fee](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee)
, use the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service)
, or [estimate gas and provider fees](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs)
 before bridging. Proceed only if the cost works for you.

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#3-3-verify-the-transaction)

3.3. Verify the transaction

After `kit.bridge()` resolves, inspect the returned `steps` array. Each transaction step includes an `explorerUrl`. Use those links to confirm the approve, burn, and mint steps for the amount you bridged.The following code is an example of how an `approve` event might look in the browser console after a successful bridge. The values are examples only and are not a real transaction:

Shell

    Event received: {
      protocol: "cctp",
      version: "v2",
      traceId: "550afd44ba4c6d1d1bf4880b9ded3840",
      values: {
        name: "approve",
        state: "success",
        txHash: "0xdeadbeefcafebabe1234567890abcdef1234567890abcdef1234567890abcd",
        data: {
          txHash:
            "0xdeadbeefcafebabe1234567890abcdef1234567890abcdef1234567890abcd",
          status: "success",
          cumulativeGasUsed: 17138643n,
          gasUsed: 38617n,
          blockNumber: 8778959n,
          blockHash:
            "0xbeadfacefeed1234567890abcdef1234567890abcdef1234567890abcdef12",
          transactionIndex: 173,
          effectiveGasPrice: 1037232n,
        },
        explorerUrl:
          "https://testnet.arcscan.app/tx/0xdeadbeefcafebabe1234567890abcdef1234567890abcdef1234567890abcd",
      },
      method: "approve",
    }
    

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#extend-add-a-solana-source-wallet)

Extend: Add a Solana source wallet
-----------------------------------------------------------------------------------------------------------------------------------------------------

If you want a Solana browser wallet as the source, keep the EVM destination adapter from the browser wallet flow above and add a Solana source adapter. The examples use Solana Devnet and Arc Testnet, but you can use Solana and any [supported EVM chain](https://docs.arc.io/app-kit/references/supported-blockchains)
 as the destination.In this browser wallet flow, both wallets run in the browser and the user signs transactions in wallet extensions. Treat wallet connection and bridging as separate user actions: connect the destination EVM wallet first, connect the Solana source wallet second, then call `kit.bridge()` after both adapters are available.

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#additional-prerequisites)

Additional prerequisites

Before you begin, ensure that you have:

*   Worked through the EVM browser wallet flow above first. This Solana path adds a Solana source wallet to that same browser-wallet pattern.
*   Installed a Solana browser wallet that exposes `window.solana`.
*   Funded your Solana wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/)
    .
*   Funded your Solana wallet with SOL for Solana Devnet transaction fees from the [Solana Faucet](https://faucet.solana.com/)
    .

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#add-the-solana-dependencies)

Add the Solana dependencies

Add the Solana adapter dependency to the same project:

Shell

    npm install @circle-fin/adapter-solana
    

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#connect-the-solana-source-wallet)

Connect the Solana source wallet

This step extends the EVM browser wallet flow by adding a Solana source wallet. You will connect the Solana wallet, create a Solana source adapter, keep the EVM destination adapter, and pass both adapters into `kit.bridge()`.The snippets below keep each part of the flow in small helper functions for readability. The companion browser demo wires this same sequence through `handleEvmConnect()`, `handleSolanaConnect()`, and `handleBridge()` in a runnable UI.

#### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#connect-the-solana-wallet-and-request-account-access)

Connect the Solana wallet and request account access

This pattern assumes a Solana browser wallet that exposes `window.solana`. Keep wallet connection and App Kit actions as separate user actions so the wallet is fully connected before you call an App Kit SDK method.

TypeScript

    import type { CreateSolanaAdapterFromProviderParams } from "@circle-fin/adapter-solana";
    
    type SolanaWalletProvider = CreateSolanaAdapterFromProviderParams["provider"];
    
    declare global {
      interface Window {
        solana?: SolanaWalletProvider;
      }
    }
    
    async function connectSolanaWallet(provider: SolanaWalletProvider) {
      const connection = await provider.connect();
    
      return {
        connectedAddress:
          connection.publicKey?.toString() ??
          provider.publicKey?.toString() ??
          null,
      };
    }
    

#### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#keep-the-evm-destination-adapter-and-add-a-solana-source-adapter)

Keep the EVM destination adapter and add a Solana source adapter

This Solana path builds on the EVM browser wallet flow above. Reuse the connected EVM wallet provider from that flow, then add a Solana provider and create one adapter for each chain:

TypeScript

    import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2";
    import { createSolanaAdapterFromProvider } from "@circle-fin/adapter-solana";
    import type { EIP1193Provider } from "viem";
    
    async function createBridgeAdapters(
      evmProvider: EIP1193Provider,
      solanaProvider: SolanaWalletProvider,
    ) {
      const evmAdapter = await createViemAdapterFromProvider({
        provider: evmProvider,
      });
    
      const solanaAdapter = await createSolanaAdapterFromProvider({
        provider: solanaProvider,
      });
    
      return {
        evmAdapter,
        solanaAdapter,
      };
    }
    

#### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#pass-the-browser-wallet-adapters-into-kit-bridge)

Pass the browser wallet adapters into `kit.bridge()`

After you have a connected EVM provider from the earlier browser-wallet flow and a connected Solana provider from `window.solana`, create both adapters and pass them into `kit.bridge()`:

TypeScript

    import { AppKit } from "@circle-fin/app-kit";
    import type { EIP1193Provider } from "viem";
    
    const kit = new AppKit();
    
    async function bridgeUSDCWithSolanaBrowserWallet(
      evmProvider: EIP1193Provider,
      solanaProvider: SolanaWalletProvider,
    ) {
      const { evmAdapter, solanaAdapter } = await createBridgeAdapters(
        evmProvider,
        solanaProvider,
      );
    
      const result = await kit.bridge({
        from: { adapter: solanaAdapter, chain: "Solana_Devnet" },
        to: { adapter: evmAdapter, chain: "Arc_Testnet" },
        amount: "1.00",
      });
    
      console.log(
        "Submitted bridge from Solana browser wallet to EVM destination",
        {
          result,
        },
      );
    
      return result;
    }
    

#### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#retry-a-failed-bridge-attempt)

Retry a failed bridge attempt

If the first bridge attempt returns `state: "error"`, retry it with the same freshly created adapters:

TypeScript

    let result = await kit.bridge({
      from: { adapter: solanaAdapter, chain: "Solana_Devnet" },
      to: { adapter: evmAdapter, chain: "Arc_Testnet" },
      amount: "1.00",
    });
    
    if (result.state === "error") {
      result = await kit.retryBridge(result, {
        from: solanaAdapter,
        to: evmAdapter,
      });
    }
    

Download the runnable [browser demo](https://github.com/circlefin/docs-examples/tree/master/app-kit-bridge-solana)
 to see the Solana-to-EVM bridge flow in action.

#### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#observe-bridge-lifecycle-events)

Observe bridge lifecycle events

If you added the `kit.on("*", (payload) => { ... })` listener in the previous step, it already captures Solana bridge events, and no additional subscription is needed.

TypeScript

    kit.on("*", (payload) => {
      console.log("Action:", payload);
    });
    

Using a different EVM chain as the destination? Change the `to.chain` value and ensure the connected Solana wallet holds USDC on the source chain and enough native gas to complete the transfer flow.

You can customize your bridges to [collect a fee](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee)
, use the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service)
, or [estimate gas and provider fees](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs)
 before bridging. Proceed only if the cost works for you.

#### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#verify-the-transaction)

Verify the transaction

After `kit.bridge()` resolves, inspect the returned `steps` array. Each transaction step includes an `explorerUrl`. Use those links to confirm the burn, attestation, and mint steps for the amount you bridged.The following code is an example of how a `burn` step might look in the browser console after a successful bridge. The values are examples only and are not a real transaction:

Shell

    steps: [\
      {\
        name: "burn",\
        state: "success",\
        txHash: "5UfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKSNc7WLyFTmgL5RFGujWNqEbUBdNKRkHmx7ZRQR3FVhdEwxKHm",\
        data: {\
          txHash:\
            "5UfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKSNc7WLyFTmgL5RFGujWNqEbUBdNKRkHmx7ZRQR3FVhdEwxKHm",\
          status: "success",\
          blockNumber: 312456789n,\
          blockHash: "HxVTyYH9StYyHKSNc7WLyFTmgL5RFGujWNqEbUBdNK",\
          transactionIndex: 0,\
          gasUsed: 25000n,\
          cumulativeGasUsed: 0n,\
          effectiveGasPrice: 5000n,\
          explorerUrl:\
            "https://solscan.io/tx/5UfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKSNc7WLyFTmgL5RFGujWNqEbUBdNKRkHmx7ZRQR3FVhdEwxKHm?cluster=devnet",\
        },\
      },\
    ];
    

Use this flow to bridge USDC with developer-controlled wallets managed by Circle Wallets. The example uses Solana Devnet and Arc Testnet.

When bridging from Arc Testnet with Circle Wallets, the bridge amount must exceed the CCTPv2 max fee. If the amount is too low, the burn step reverts with `"Max fee must be less than amount"`.

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#prerequisites-2)

Prerequisites
--------------------------------------------------------------------------------------------------------------

Before you begin, ensure that you’ve:

*   Installed [Node.js v22+](https://nodejs.org/)
    .
*   Obtained a [API Key](https://developers.circle.com/api-reference/keys#creating-an-api-key-for-developer-services)
     and [entity secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret)
     from the [Circle Console](https://developers.circle.com/w3s/circle-developer-account)
    .
*   Created developer-controlled wallets on Arc Testnet and Solana Devnet using the Circle Console.
*   Funded your Arc Testnet wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/)
    .
*   Funded your Solana Devnet wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/)
    .
*   Funded your Solana Devnet wallet with SOL for transaction fees from the [Solana Faucet](https://faucet.solana.com/)
    .

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#step-1-set-up-the-project-2)

Step 1. Set up the project
---------------------------------------------------------------------------------------------------------------------------------------

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#1-1-create-the-project-and-install-dependencies-2)

1.1. Create the project and install dependencies

Create a new directory and install the App Kit SDK with the Circle Wallets adapter and supporting tools:

Shell

    # Set up your directory and initialize a Node.js project
    mkdir app-kit-bridge-circle-wallets
    cd app-kit-bridge-circle-wallets
    npm init -y
    npm pkg set type=module
    
    # Set up module type and start command
    npm pkg set scripts.start="tsx --env-file=.env index.ts"
    
    # Install runtime dependencies
    npm install @circle-fin/app-kit @circle-fin/adapter-circle-wallets tsx
    
    # Install dev dependencies
    npm install --save-dev typescript @types/node
    

Only need to bridge and want a lighter install than the full App Kit SDK? Install the standalone Bridge Kit instead: `@circle-fin/bridge-kit`

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#1-2-configure-typescript-optional-2)

1.2. Configure TypeScript (optional)

This step is optional. It helps prevent missing types in your IDE or editor.

Create a `tsconfig.json` file:

Shell

    npx tsc --init
    

Then, update the `tsconfig.json` file:

Shell

    cat <<'EOF' > tsconfig.json
    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true,
        "types": ["node"]
      }
    }
    EOF
    

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#1-3-set-environment-variables)

1.3. Set environment variables

Create an `.env` file in the project directory:

Shell

    touch .env
    

Add your credentials. Replace `YOUR_API_KEY` with your Circle Developer API key, `YOUR_ENTITY_SECRET` with your entity secret (64 lowercase alphanumeric characters), and `YOUR_EVM_WALLET_ADDRESS` and `YOUR_SOLANA_WALLET_ADDRESS` with the wallet addresses you control through Circle Wallets. You can fetch the addresses from the [Circle Developer Console](https://developers.circle.com/w3s/circle-developer-account)
 or the [list wallets](https://developers.circle.com/api-reference/wallets/developer-controlled-wallets/get-wallets)
 endpoint:

.env

    CIRCLE_API_KEY=YOUR_API_KEY
    CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
    EVM_WALLET_ADDRESS=YOUR_EVM_WALLET_ADDRESS
    SOLANA_WALLET_ADDRESS=YOUR_SOLANA_WALLET_ADDRESS
    

Edit `.env` files in your IDE or editor so credentials are not leaked to your shell history.

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#step-2-bridge-usdc)

Step 2. Bridge USDC
-----------------------------------------------------------------------------------------------------------------------

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#2-1-create-the-script)

2.1. Create the script

Create an `index.ts` file in the project directory and add the following code. This code sets up your script and bridges 1 USDC from Arc Testnet to Solana Devnet.

Using a different blockchain as the source or destination? Change the `chain` values in `kit.bridge()` and ensure the source wallet has USDC. For this example that means keeping extra USDC on Arc Testnet for gas and SOL on Solana Devnet for Solana transaction fees.

TypeScript

    // Import the App Kit SDK and the Circle Wallets adapter
    import { AppKit } from "@circle-fin/app-kit";
    import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets";
    
    const kit = new AppKit();
    
    kit.on("*", (payload) => {
      console.log("Event received:", payload);
    });
    
    const adapter = createCircleWalletsAdapter({
      apiKey: process.env.CIRCLE_API_KEY!,
      entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
    });
    
    console.log("---------------Starting Bridging---------------");
    
    const result = await kit.bridge({
      from: {
        adapter,
        chain: "Arc_Testnet",
        address: process.env.EVM_WALLET_ADDRESS!,
      },
      to: {
        adapter,
        chain: "Solana_Devnet",
        address: process.env.SOLANA_WALLET_ADDRESS!,
      },
      amount: "1",
    });
    
    console.dir(result, { depth: null, colors: true });
    

You can customize your bridges to [collect a fee](https://docs.arc.io/app-kit/tutorials/bridge/collect-bridge-fee)
, use the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/bridge/use-forwarding-service)
, or [estimate gas and provider fees](https://docs.arc.io/app-kit/tutorials/bridge/estimate-costs)
 before bridging. Proceed only if the cost works for you.

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#2-2-run-the-script)

2.2. Run the script

Save the `index.ts` file and run the script in your terminal:

Shell

    npm run start
    

### 

[​](https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains#2-3-verify-the-transaction)

2.3. Verify the transaction

As the bridge runs, the event listener logs `approve`, `burn`, `fetchAttestation`, and `mint` progress to the terminal. For transaction-backed steps, use `values.explorerUrl` from the event payload to open the Arc Testnet or Solana Devnet explorer and confirm the transfer.After the script finishes, inspect the final `result.steps` array. Each transaction step includes its own `explorerUrl`, which you can use to confirm the amount you bridged and the chain where that step executed.The following code is an example of how an `approve` event might look in the terminal output. The values are used in this example only and are not a real transaction:

Shell

    Event received: {
      protocol: "cctp",
      version: "v2",
      traceId: "550afd44ba4c6d1d1bf4880b9ded3840",
      values: {
        name: "approve",
        state: "success",
        txHash: "0xb4a6efb91a3a714822185b9310e54d0806040d38ef1b5282a9279522f09e6ccd",
        data: {
          txHash: "0xb4a6efb91a3a714822185b9310e54d0806040d38ef1b5282a9279522f09e6ccd",
          status: "success",
          cumulativeGasUsed: 706909n,
          gasUsed: 38596n,
          blockNumber: 42852493n,
          blockHash: "0xdb5e8026f29ffd7d7cad14e7afcc04b3287981767bf406be2e21f3c559ced13d",
          transactionIndex: 6,
          effectiveGasPrice: 22000000000n,
        },
        explorerUrl:
          "https://testnet.arcscan.app/tx/0xb4a6efb91a3a714822185b9310e54d0806040d38ef1b5282a9279522f09e6ccd",
      },
      method: "approve",
    }
    

Was this page helpful?

YesNo

[Bridge overview](https://docs.arc.io/app-kit/bridge)
[How bridge fees work](https://docs.arc.io/app-kit/concepts/bridge-fees)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# How-to: Collect custom spend fees - Arc Docs

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

[Skip to main content](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees#content-area)

The App Kit SDK lets you collect a custom fee from your end users each time they spend from a Unified Balance. Custom fees can only be configured for spend transactions on destination blockchains, not deposits on source blockchains. To learn how custom fees fit into the overall fee breakdown, see [How Unified Balance fees work](https://docs.arc.io/app-kit/concepts/unified-balance-fees)
.

If you use this feature, Arc keeps 10% of the custom fee you collect from your end users.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees#prerequisites)

Prerequisites
-------------------------------------------------------------------------------------------------------------------

Before you begin, ensure that you’ve:

*   [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation)
    
*   [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups)
    

These are required so any example below runs with a valid `kit` and `adapter`.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees#set-a-custom-fee-on-a-spend)

Set a custom fee on a spend
-----------------------------------------------------------------------------------------------------------------------------------------------

This example spends 1.00 USDC from Base Sepolia and adds a 0.01 USDC custom fee on the spend:

TypeScript

    const result = await kit.unifiedBalance.spend({
      amount: "1.00",
      from: {
        adapter,
        allocations: [{ amount: "1.00", chain: "Base_Sepolia" }],
      },
      to: {
        adapter,
        chain: "Arc_Testnet",
        recipientAddress: "0xRecipientAddress",
      },
      config: {
        customFee: {
          value: "0.01", // 0.01 USDC collected as fee
          recipientAddress: "0xYourFeeWalletAddress",
        },
      },
    });
    

Was this page helpful?

YesNo

[Estimate spend fees](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees)
[Manage delegates](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# How-to: Specify a recipient address - Arc Docs

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

[Skip to main content](https://docs.arc.io/app-kit/tutorials/swap/specify-recipient-address#content-area)

By default, the tokens you receive from a swap arrive at the same address as your signing wallet. For same-chain swaps, you can specify a different recipient so the output tokens go to that address instead. Your signing wallet still signs and funds the swap. For crosschain swaps, see [Swap tokens across blockchains](https://docs.arc.io/app-kit/quickstarts/swap-tokens-crosschain)
.

[​](https://docs.arc.io/app-kit/tutorials/swap/specify-recipient-address#prerequisites)

Prerequisites
--------------------------------------------------------------------------------------------------------

Before you begin, ensure that you’ve:

*   [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation)
    
*   [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups)
    

These are required so any example below runs with a valid `kit` and `adapter`.

[​](https://docs.arc.io/app-kit/tutorials/swap/specify-recipient-address#specify-a-recipient)

Specify a recipient
--------------------------------------------------------------------------------------------------------------------

Set the recipient with the `to` parameter. For a same-chain swap, `to.chain` defaults to the source chain (`from.chain`), so you only need to set `to.recipientAddress`. This example swaps 1.00 USDC for EURC on Arc Testnet and sends the EURC to a different address than the one that signs the swap:

TypeScript

    const result = await kit.swap({
      from: { adapter, chain: "Arc_Testnet" },
      tokenIn: "USDC",
      tokenOut: "EURC",
      amountIn: "1.00",
      to: {
        recipientAddress: "0xRecipientAddress",
      },
      config: {
        kitKey: process.env.KIT_KEY as string,
      },
    });
    

`to.recipientAddress` sets where the output tokens are sent. It’s different from `config.customFee.recipientAddress`, which sets the recipient of a [custom swap fee](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee)
.

Was this page helpful?

YesNo

[Set slippage tolerance or stop limit](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit)
[Get token rates](https://docs.arc.io/app-kit/tutorials/swap/get-token-rates)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# How-to: Set slippage tolerance or stop limit on swaps - Arc Docs

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

[Skip to main content](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit#content-area)

Set a slippage tolerance or stop limit to avoid receiving less than expected on a swap due to market fluctuations:

*   A **slippage tolerance** lets you set the maximum percentage difference you’re willing to accept between the estimated and actual swap amount, expressed in bps (for example, 100 bps = 1%).
*   A **stop limit** lets you set the exact minimum amount you want to receive.

If both are configured, the stop limit takes precedence.

[​](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit#prerequisites)

Prerequisites
-------------------------------------------------------------------------------------------------------------------

Before you begin, ensure that you’ve:

*   [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation)
    
*   [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups)
    

These are required so any example below runs with a valid `kit` and `adapter`.

[​](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit#set-slippage-tolerance)

Set slippage tolerance
-------------------------------------------------------------------------------------------------------------------------------------

This example sets a slippage tolerance of 100 bps (1%):

TypeScript

    const output = await kit.swap({
      from: { adapter, chain: "Arc_Testnet" },
      tokenIn: "USDC",
      amountIn: "1.00",
      tokenOut: "EURC",
      config: {
        // Set the slippage tolerance to 100 bps
        kitKey: process.env.KIT_KEY as string,
        slippageBps: 100,
      },
    });
    

With `slippageBps` as `100` as in the example, you’ll receive at least 99% of the estimated amount. Meaning if the swap rate is 1 USDC to 1 EURC, you’ll receive at least 0.99 EURC.

The `slippageBps` default is 300 bps. `0` represents no tolerance, which increases transaction failure rates.

[​](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit#set-stop-limit)

Set stop limit
---------------------------------------------------------------------------------------------------------------------

`stopLimit` defines the minimum amount of the token type specified in `tokenOut` that you’ll receive on a swap. This example sets a stop limit of 0.95 EURC, meaning you won’t receive any less than that on a swap:

TypeScript

    const output = await kit.swap({
      from: { adapter, chain: "Arc_Testnet" },
      tokenIn: "USDC",
      amountIn: "1.00",
      tokenOut: "EURC",
      config: {
        kitKey: process.env.KIT_KEY as string,
        // Set stop limit to 0.95 EURC
        stopLimit: "0.95",
      },
    });
    

Was this page helpful?

YesNo

[Estimate swap rate](https://docs.arc.io/app-kit/tutorials/swap/estimate-swap-rate)
[Specify a recipient address](https://docs.arc.io/app-kit/tutorials/swap/specify-recipient-address)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# App Kit SDK: Unified Balance - Arc Docs

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

[Skip to main content](https://docs.arc.io/app-kit/unified-balance#content-area)

The [App Kit SDK](https://docs.arc.io/app-kit)
 includes the Unified Balance capability that combines USDC from multiple blockchains into a single, instantly spendable balance. It is built on top of [Circle Gateway](https://developers.circle.com/gateway)
 and handles the Gateway workflow for deposits and spends across EVM and non-EVM blockchains.

[​](https://docs.arc.io/app-kit/unified-balance#how-it-works)

How it works
-----------------------------------------------------------------------------

Unified Balance works by depositing funds held across multiple blockchains into a single, chain-agnostic Unified Balance. Those funds are then available to spend instantly on any blockchain. The process is illustrated below:

### 

[​](https://docs.arc.io/app-kit/unified-balance#what-to-know-about-wallet-models)

What to know about wallet models

*   Some wallet models cannot sign their own Unified Balance spends, including Circle Wallets SCAs and Privy server wallets. Use the [delegate workflow](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend)
    : the wallet remains the depositor, and an authorized EOA signs each spend.
*   Circle Wallets are chain-specific. For Unified Balance flows that draw from multiple source blockchains, use the wallet address for each source blockchain.
*   For Unified Balance spends, create one source per wallet and chain as needed. The Circle Wallets adapter is stateless, so you can reuse the same adapter configuration and pass the wallet address in each operation context.
*   Circle Wallets smart contract account (SCA) deposits require `allowanceStrategy: "approve"`. USDC permit signatures use `ecrecover`, which does not accept the SCA’s ERC-1271 signature, so the SDK uses an onchain `approve`.

Unified Balance is built on Circle Gateway. For production considerations related to deposits, spends, and fund removal, see the [Gateway implementation checklist](https://developers.circle.com/gateway/references/technical-guide#implementation-considerations)
.

[​](https://docs.arc.io/app-kit/unified-balance#quick-look)

Quick look
-------------------------------------------------------------------------

This code snippet creates a Unified Balance by depositing funds from two blockchains to spend on a third:

TypeScript

    // Deposit 1.00 USDC into the Unified Balance from Base
    const depositBase = await kit.unifiedBalance.deposit({
      from: { adapter: viemAdapter, chain: "Base_Sepolia" },
      amount: "1.00",
      token: "USDC",
    });
    // Deposit 1.00 USDC into the Unified Balance from Arbitrum
    const depositArb = await kit.unifiedBalance.deposit({
      from: { adapter: viemAdapter, chain: "Arbitrum_Sepolia" },
      amount: "1.00",
      token: "USDC",
    });
    // Spend 1.50 USDC from the Unified Balance on Arc
    const spendResult = await kit.unifiedBalance.spend({
      amount: "1.50",
      from: { adapter: viemAdapter },
      to: {
        adapter: viemAdapter,
        chain: "Arc_Testnet",
        recipientAddress: "0xRecipientAddress",
      },
    });
    

For a complete end-to-end flow, follow the quickstart for your scenario:

*   [Deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend)
    
*   [Use a delegate to deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend)
    

[​](https://docs.arc.io/app-kit/unified-balance#installation)

Installation
-----------------------------------------------------------------------------

[Install the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation)
 to use Unified Balance. If you only need Unified Balance and don’t want to install the full App Kit SDK, follow the steps below to install the standalone Unified Balance Kit.

1

Install Unified Balance Kit

npm

yarn

    npm install @circle-fin/unified-balance-kit
    

    yarn add @circle-fin/unified-balance-kit
    

2

Install adapters

Use the [adapter setup guide](https://docs.arc.io/app-kit/tutorials/adapter-setups)
 to choose the right wallet model for your app, including browser wallets and Circle Wallets.

*   Viem
    
*   Ethers
    
*   Solana
    
*   Circle Wallets
    

npm

yarn

    npm install @circle-fin/adapter-viem-v2 viem
    

    yarn add @circle-fin/adapter-viem-v2 viem
    

npm

yarn

    npm install @circle-fin/adapter-ethers-v6 ethers
    

    yarn add @circle-fin/adapter-ethers-v6 ethers
    

npm

yarn

    npm install @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js
    

    yarn add @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js
    

npm

yarn

    npm install @circle-fin/adapter-circle-wallets
    

    yarn add @circle-fin/adapter-circle-wallets
    

Was this page helpful?

YesNo

[Get token rates](https://docs.arc.io/app-kit/tutorials/swap/get-token-rates)
[Deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# How-to: Select source blockchains for a Unified Balance spend - Arc Docs

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

[Skip to main content](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains#content-area)

A spend pulls USDC from the balances you have deposited on supported blockchains. Choose one of these approaches to control which blockchains supply the USDC:

*   **Automatic routing**: Specify a total spend amount and let the App Kit SDK choose which blockchains to draw from based on your confirmed balances.
*   **Explicit amounts**: Set how much USDC comes from each source blockchain yourself.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains#prerequisites)

Prerequisites
-------------------------------------------------------------------------------------------------------------------

Before you begin, ensure that you’ve:

*   [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation)
    
*   [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups)
    

These are required so any example below runs with a valid `kit` and `adapter`.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains#the-app-kit-sdk-allocates-for-you)

The App Kit SDK allocates for you
-----------------------------------------------------------------------------------------------------------------------------------------------------------

The App Kit SDK chooses how to fund the spend from your confirmed balances. The kit prefers the destination blockchain first, then pulls from your other blockchains from highest balance to lowest. Ethereum mainnet is the exception: it is always last, including when it is the spend destination. Pass `amount`, `from` with only an `adapter` (no `allocations`), and `to`. This example spends 2.00 USDC with automatic routing:

TypeScript

    const result = await kit.unifiedBalance.spend({
      amount: "2.00",
      from: { adapter },
      to: {
        adapter,
        chain: "Arc_Testnet",
        recipientAddress: "0xRecipientAddress",
      },
    });
    

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains#explicit-per-chain-allocations)

Explicit per-chain allocations
-----------------------------------------------------------------------------------------------------------------------------------------------------

List each blockchain and how much USDC to draw in `from.allocations`. Each entry contains an `amount` and `chain`. When you use `allocations`, their amounts must add up to the top-level `amount`. This example spends 2.00 USDC (1.00 USDC from Arc Testnet and 1.00 USDC from Base Sepolia) delivered to the recipient on Arc Testnet:

TypeScript

    const result = await kit.unifiedBalance.spend({
      amount: "2.00",
      from: {
        adapter,
        allocations: [\
          { amount: "1.00", chain: "Arc_Testnet" },\
          { amount: "1.00", chain: "Base_Sepolia" },\
        ],
      },
      to: {
        adapter,
        chain: "Arc_Testnet",
        recipientAddress: "0xRecipientAddress",
      },
    });
    

You can pass multiple adapters in `from` when you need separate sources (for example EVM and Solana). Each source uses the same shape: `adapter` and optional `allocations`. The top-level `amount` must still match the sum of all allocation amounts you provide.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains#validation-rules)

Validation rules
-------------------------------------------------------------------------------------------------------------------------

*   **Amount**: The top-level `amount` is always required. It is the total USDC for the spend.
*   **Sums**: If you pass `allocations`, the kit checks that they sum to `amount`. If they do not match, you get a clear error with both values.
*   **Consistency**: Either every `from` entry specifies `allocations`, or none do. Mixing sources with allocations and sources without is not supported. If you need a computed split first, use `estimateSpend`, then pass the returned `allocations` into `spend`.
*   **Retry**: Retrying only the mint step after a failure uses a separate `retrySpend` flow and parameters, not a partial `spend` call.

Was this page helpful?

YesNo

[Check Unified Balance total](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance)
[Estimate spend fees](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# How-to: Use Forwarding Service - Arc Docs

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

[Skip to main content](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service#content-area)

You can use the [Forwarding Service](https://developers.circle.com/cctp/concepts/forwarding-service)
 when spending from a Unified Balance on the destination blockchain. When enabled, it fetches the attestations from source blockchains and submits the mint on the destination blockchain. You don’t need to poll for attestations or have access to a wallet on the destination.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service#prerequisites)

Prerequisites
----------------------------------------------------------------------------------------------------------------

Before you begin, ensure that you’ve:

*   [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation)
    
*   [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups)
    

These are required so any example below runs with a valid `kit` and `adapter`.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service#use-with-adapters-on-all-blockchains)

Use with adapters on all blockchains
--------------------------------------------------------------------------------------------------------------------------------------------------------------

Set `useForwarder: true` when you have adapters on all source and destination blockchains but want the Forwarding Service to submit the mint transaction on the destination:

TypeScript

    const result = await kit.unifiedBalance.spend({
      amount: "1.00",
      from: {
        adapter,
        allocations: [{ amount: "1.00", chain: "Base_Sepolia" }],
      },
      to: {
        adapter,
        chain: "Arc_Testnet",
        useForwarder: true,
      },
      token: "USDC",
    });
    

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service#use-without-a-destination-adapter)

Use without a destination adapter
--------------------------------------------------------------------------------------------------------------------------------------------------------

When you don’t have access to a wallet on the destination blockchain, such as with server-side or custodial spend flows, omit the destination adapter and pass `recipientAddress` with `useForwarder: true`:

TypeScript

    const result = await kit.unifiedBalance.spend({
      amount: "1.00",
      from: {
        adapter,
        allocations: [{ amount: "1.00", chain: "Base_Sepolia" }],
      },
      to: {
        chain: "Arc_Testnet",
        recipientAddress: "0xRecipientAddress",
        useForwarder: true,
      },
      token: "USDC",
    });
    

In this mode, the Forwarding Service submits the mint transaction for you. Use the spend result’s `transferId`, `txHash`, or `explorerUrl` fields to track the destination mint when they are returned.

[​](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service#forwarding-fee)

Forwarding fee
------------------------------------------------------------------------------------------------------------------

The Forwarding Service charges a [fee](https://developers.circle.com/cctp/concepts/forwarding-service#fees-and-execution)
 that is deducted from the amount minted on the destination chain. When you [estimate spend fees](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees)
 for a spend transaction, the result includes the forwarding fee. See [How Unified Balance fees work](https://docs.arc.io/app-kit/concepts/unified-balance-fees)
 for details.

Was this page helpful?

YesNo

[Manage delegates](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates)
[Remove funds trustlessly](https://docs.arc.io/app-kit/tutorials/unified-balance/remove-funds-trustlessly)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# Compliance - Arc Docs

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

[Skip to main content](https://docs.arc.io/arc/tools/compliance-vendors#content-area)

Add regulatory compliance to your Arc application by integrating third-party analytics and screening tools. These vendors provide APIs for anti-money laundering (AML) checks, wallet risk scoring, sanctions screening, and real-time transaction monitoring.

[​](https://docs.arc.io/arc/tools/compliance-vendors#providers)

Providers
----------------------------------------------------------------------------

### 

[​](https://docs.arc.io/arc/tools/compliance-vendors#chainalysis)

[Chainalysis](https://www.chainalysis.com/)

Blockchain data platform offering transaction monitoring, address screening, and investigation tools to support AML, sanctions, and fraud-detection programs.

### 

[​](https://docs.arc.io/arc/tools/compliance-vendors#elliptic)

[Elliptic](https://www.elliptic.co/)

Blockchain analytics and transaction monitoring APIs to identify illicit activity, assess risk exposure, and ensure compliance with AML and sanctions requirements.

### 

[​](https://docs.arc.io/arc/tools/compliance-vendors#trm-labs)

[TRM Labs](https://www.trmlabs.com/)

Risk intelligence, wallet screening, and real-time monitoring tools to detect fraud, money laundering, and other suspicious behavior across Arc transactions.

Was this page helpful?

YesNo

[Account abstraction](https://docs.arc.io/arc/tools/account-abstraction)
[Data indexers](https://docs.arc.io/arc/tools/data-indexers)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# Data indexers - Arc Docs

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

[Skip to main content](https://docs.arc.io/arc/tools/data-indexers#content-area)

Data indexers make it easy to query and analyze onchain data from Arc. They provide APIs and SDKs for tracking smart contract events, balances, and historical state changes without running your own indexing infrastructure.

[​](https://docs.arc.io/arc/tools/data-indexers#providers)

Providers
-----------------------------------------------------------------------

### 

[​](https://docs.arc.io/arc/tools/data-indexers#envio)

[Envio](https://envio.dev/)

Developer-first indexing framework for event-driven data and GraphQL APIs on Arc.

*   [**HyperIndex**](https://envio.dev/#hyperindex)
    : Build production-ready APIs from Arc data in minutes.
*   Stream live blockchain events with minimal latency.

### 

[​](https://docs.arc.io/arc/tools/data-indexers#goldsky)

[Goldsky](https://goldsky.com/)

Managed subgraph and data pipeline platform for Arc contracts.

*   [**Subgraphs**](https://goldsky.com/products/subgraphs)
    : Autoscaling query engine with 99.9%+ uptime and up to 6x faster performance.
*   [**Mirror**](https://goldsky.com/products/mirror)
    : Stream onchain data to your database with sub-second latency.

### 

[​](https://docs.arc.io/arc/tools/data-indexers#the-graph)

[The Graph](https://thegraph.com/)

Decentralized indexing protocol for querying Arc’s onchain data through APIs.

*   [**Subgraphs**](https://thegraph.com/docs/en/developing/creating-a-subgraph/)
    : Query smart contract data through multiple independent indexers for redundancy.
*   [**Graph Explorer**](https://thegraph.com/explorer)
    : Discover and reuse subgraphs published by other developers.

### 

[​](https://docs.arc.io/arc/tools/data-indexers#thirdweb)

[Thirdweb](https://thirdweb.com/)

Open-source blockchain data tooling.

*   [**Insight**](https://insight.thirdweb.com/reference)
    : Retrieve Arc blockchain data, enrich it with metadata, and transform it using custom logic.

Was this page helpful?

YesNo

[Compliance](https://docs.arc.io/arc/tools/compliance-vendors)
[Node providers](https://docs.arc.io/arc/tools/node-providers)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.

---

# Exchange Integration - Arc Docs

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

[Skip to main content](https://docs.arc.io/integrate/exchanges#content-area)

Arc is an EVM-compatible Layer 1 blockchain where USDC is the native gas token. For exchanges, this means simpler hot wallet operations, instant settlement finality, and a single asset to manage for both trading and transaction fees.

[​](https://docs.arc.io/integrate/exchanges#key-differences-for-exchanges)

Key differences for exchanges
-----------------------------------------------------------------------------------------------------------

Three Arc design decisions affect every exchange integration:

| Concern | Arc behavior | Impact on exchanges |
| --- | --- | --- |
| **Deterministic finality** | Transactions finalize in under 1 second with no reorgs | Credit deposits after a single confirmation—no multi-block wait required |
| **USDC as gas** | Transaction fees are paid in USDC, not ETH | No separate gas token funding for hot wallets—one asset covers fees and transfers |
| **Dual USDC interface** | Native balance (18 decimals) and ERC-20 interface (6 decimals) share the same underlying balance | Use the ERC-20 interface exclusively for a consistent single-balance accounting model |

[​](https://docs.arc.io/integrate/exchanges#operational-areas)

Operational areas
-----------------------------------------------------------------------------------

Exchange integration covers three workflows:

*   **Deposits**—Subscribe to new blocks and detect incoming USDC transfers. Deterministic finality means a single confirmation is final.
*   **Withdrawals**—Build, sign, and broadcast USDC transfers from your hot wallet. Gas is paid in USDC from the same balance.
*   **Liquidity (CCTP bridging)**—Move USDC between Arc and other blockchains using Cross-Chain Transfer Protocol to manage treasury and liquidity pools.

[​](https://docs.arc.io/integrate/exchanges#what-you-need)

What you need
---------------------------------------------------------------------------

Before you start, confirm you have the following:

| Requirement | Details |
| --- | --- |
| RPC access | `https://rpc.testnet.arc.network` (HTTPS) or `wss://rpc.testnet.arc.network` (WebSocket) |
| Chain ID | `5042002` (testnet) |
| USDC ERC-20 contract | [`0x3600000000000000000000000000000000000000`](https://testnet.arcscan.app/address/0x3600000000000000000000000000000000000000) |
| CCTP contracts (for bridging) | TokenMessengerV2: [`0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA`](https://testnet.arcscan.app/address/0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA)
, Domain: `26` | | Memo contract (compliance metadata) | [`0x5294E9927c3306DcBaDb03fe70b92e01cCede505`](https://testnet.arcscan.app/address/0x5294E9927c3306DcBaDb03fe70b92e01cCede505) | | Custody provider supporting custom EVM | See [Add custody platform support](https://docs.arc.io/integrate/exchanges/custody)
for configured providers and setup steps | [​](https://docs.arc.io/integrate/exchanges#integration-guides) Integration guides ------------------------------------------------------------------------------------- Detect deposits --------------- Subscribe to blocks and detect incoming USDC transfers with single-confirmation finality. Process withdrawals ------------------- Build, sign, and broadcast withdrawal transactions using USDC for both value and gas. Bridge USDC (CCTP) ------------------ Move USDC liquidity to and from Arc using Cross-Chain Transfer Protocol. Custody platforms ----------------- Register Arc in supported custody platforms. Was this page helpful? YesNo [EVM differences](https://docs.arc.io/integrate/evm-differences) [Detect and process deposits](https://docs.arc.io/integrate/exchanges/deposits) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Transaction Lifecycle - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/wallets/transaction-lifecycle#content-area) Arc uses a two-state transaction model. A transaction is either **pending** (in the mempool) or **final** (included in a committed block). There is no intermediate “confirming” state and no concept of accumulating confirmations. Once a block is committed by the Malachite consensus protocol, every transaction in that block is immediately and irreversibly final. This simplicity stems from Arc’s BFT consensus. Validators pre-commit blocks with a two-thirds supermajority before production, which eliminates chain reorganizations, uncle blocks, and probabilistic finality entirely. [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#transaction-states) Transaction states --------------------------------------------------------------------------------------------------------- Arc transactions exist in exactly two states: | State | Location | Finality | Duration | | --- | --- | --- | --- | | Pending | Mempool | Not final | Sub-second (typical) | | Final | Committed block | Irreversible | Permanent | There is no “1 of 12 confirmations” counter. There is no safe-but-not-finalized window. A transaction transitions directly from pending to final. [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#state-diagram) State diagram ----------------------------------------------------------------------------------------------- [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#comparison-to-ethereum) Comparison to Ethereum ----------------------------------------------------------------------------------------------------------------- Ethereum uses probabilistic finality with multiple intermediate states. Arc eliminates all intermediate states through deterministic BFT consensus. | | Ethereum | Arc | | --- | --- | --- | | Pending state | In mempool, not yet included | In mempool, not yet included | | Confirming state | 1–64 slots (~12 seconds to ~13 min) | None | | Finality | 64 slots (~13 minutes) | Immediate upon block inclusion | | Reorganization risk | Possible before finalization | Impossible | | Block time | ~12 seconds | Sub-second | | Consensus | Gasper (LMD-GHOST + Casper FFG) | Malachite (Tendermint BFT) | [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#implications-for-wallet-ux) Implications for wallet UX ------------------------------------------------------------------------------------------------------------------------- The two-state model simplifies wallet interfaces: * **No confirmation counter.** Remove any “X/N confirmations” progress bar or percentage indicator. * **No “confirming” spinner.** A transaction is either pending or done. * **Instant “Complete” status.** Show “Complete” or “Success” immediately when the transaction appears in a block. * **Simple state display.** Use two UI states: “Pending” while in the mempool, and “Complete” once included in a block. A wallet integration only needs to distinguish between a transaction hash that has no receipt (pending) and one that has a receipt with a block number (final). [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#edge-cases) Edge cases ----------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#pre-mempool-rejection) Pre-mempool rejection If a sender address is on the protocol blocklist, the RPC node rejects the transaction before it enters the mempool. The `eth_sendRawTransaction` call returns an error immediately. The transaction never reaches the pending state. ### [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#runtime-revert) Runtime revert If a transaction passes mempool validation but violates the blocklist during execution (for example, interacting with a blocked contract), the transaction is included in a block but marked as failed. It consumes gas and appears onchain with a `status: 0` receipt. From a lifecycle perspective, it still reaches the final state—it is irreversibly included—but the state changes it attempted are reverted. ### [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#high-mempool-load) High mempool load During periods of high network activity, transactions may remain in the pending state longer than usual. This does not affect finality guarantees. Once a transaction is included in a committed block, it is final regardless of how long it waited in the mempool. ### [​](https://docs.arc.io/integrate/wallets/transaction-lifecycle#dropped-transactions) Dropped transactions Transactions can leave the mempool without reaching finality: * **Nonce gaps.** If a transaction has a nonce higher than expected, it waits for the gap to be filled. If the gap is never filled, the transaction remains pending indefinitely or is eventually evicted. * **Gas price below minimum.** Transactions with a gas price below the 20 Gwei minimum are rejected or dropped from the mempool. * **Mempool eviction.** Under sustained high load, the lowest-priced transactions may be evicted to make room for higher-priced ones. Dropped transactions produce no onchain record. Wallets should implement timeout logic and allow users to retry or cancel (by submitting a replacement transaction with the same nonce). Was this page helpful? YesNo [Add Arc to a wallet](https://docs.arc.io/integrate/wallets) [Display transaction fees](https://docs.arc.io/integrate/wallets/fee-display) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Get token rates - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/swap/get-token-rates#content-area) Token rates are read-only cached USD prices for [supported tokens](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) . Use them to show a fiat estimate before a user confirms a swap, sort token lists by USD value, or compare a swap output against current market prices. [​](https://docs.arc.io/app-kit/tutorials/swap/get-token-rates#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * Obtained a kit key from the [Circle Console](https://console.circle.com/) [​](https://docs.arc.io/app-kit/tutorials/swap/get-token-rates#look-up-token-rates) Look up token rates ---------------------------------------------------------------------------------------------------------- Pass `chain` and a `tokens` array to look up rates for one or more tokens on the specified blockchain. The App Kit SDK supports [token aliases](https://docs.arc.io/app-kit/references/supported-blockchains#supported-tokens) (such as `"USDC"`, `"EURC"`, `"USDT"`). TypeScript import { AppKit } from "@circle-fin/app-kit"; const kit = new AppKit(); const result = await kit.getTokenRates({ chain: "Ethereum", tokens: ["USDC"], kitKey: process.env.KIT_KEY as string, }); console.dir(result, { depth: null, colors: true }); The response is keyed by blockchain, then by token address: Shell { rates: { Ethereum: { "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { priceUSD: "0.999615", fetchedAt: 1781758931073, decimals: 6, }, }, }, } Each rate includes: * `priceUSD`: Latest cached USD price for the token, as a decimal string. * `fetchedAt`: Unix timestamp (milliseconds) of when the price was last refreshed. Use this to decide whether to refetch or warn the user about a stale quote. * `decimals`: Number of decimal places for the token. If a cached price doesn’t exist for a requested token, that token’s address is omitted from the blockchain’s rate map. [​](https://docs.arc.io/app-kit/tutorials/swap/get-token-rates#list-all-token-rates) List all token rates ------------------------------------------------------------------------------------------------------------ Omit `tokens` to get every cached rate for the specified blockchain. This is useful for populating a token picker or refreshing a price column: TypeScript import { AppKit } from "@circle-fin/app-kit"; const kit = new AppKit(); const result = await kit.getTokenRates({ chain: "Base", kitKey: process.env.KIT_KEY as string, }); console.dir(result, { depth: null, colors: true }); The response has the same shape as the single-token lookup, with one entry per cached token: Shell { rates: { Base: { "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { priceUSD: "1.000112", fetchedAt: 1781758931073, decimals: 6, }, "0xd46f9d14bd720f10714e2a2af5e0d617b2a0ba8c": { priceUSD: "0.000002619246974", fetchedAt: 1781829306975, decimals: 18, }, "0xae8fc9288685516d2eca717056ca69303b348752": { priceUSD: "74227.78149573994", fetchedAt: 1781829306975, decimals: 18, }, }, }, } Was this page helpful? YesNo [Specify a recipient address](https://docs.arc.io/app-kit/tutorials/swap/specify-recipient-address) [Unified Balance overview](https://docs.arc.io/app-kit/unified-balance) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Account abstraction - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tools/account-abstraction#content-area) Account abstraction (AA) replaces externally owned accounts (EOAs) with smart contract wallets that support programmable transaction validation, gas sponsorship, and batched operations. Arc supports the ERC-4337 standard, so you can use any compatible bundler, paymaster, or SDK from the providers below. [​](https://docs.arc.io/arc/tools/account-abstraction#providers) Providers ----------------------------------------------------------------------------- ### [​](https://docs.arc.io/arc/tools/account-abstraction#biconomy) [Biconomy](https://www.biconomy.io/) Account abstraction toolkit offering modular smart accounts, paymasters, and bundlers as a service to simplify the user experience. ### [​](https://docs.arc.io/arc/tools/account-abstraction#blockradar) [Blockradar](https://blockradar.co/) Infrastructure and APIs for smart account management and transaction bundling, enabling scalable AA flows with minimal setup. ### [​](https://docs.arc.io/arc/tools/account-abstraction#circle-wallets) [Circle Wallets](https://developers.circle.com/wallets) End-to-end platform for creating and managing secure Arc wallets and cryptographic keys. Supports ERC-20, ERC-721, and ERC-1155 standards. ### [​](https://docs.arc.io/arc/tools/account-abstraction#crossmint) [Crossmint](https://www.crossmint.com/) Wallet-as-a-service and AA capabilities to onboard users with email or OAuth-based accounts. ### [​](https://docs.arc.io/arc/tools/account-abstraction#dynamic) [Dynamic](https://www.dynamic.xyz/) Identity and wallet orchestration platform with native ERC-4337 support, enabling passkey wallets and flexible signer management. ### [​](https://docs.arc.io/arc/tools/account-abstraction#para) [Para](https://getpara.com/) Wallet and authentication suite for fintech and crypto applications, enabling flexible wallet management and transaction signing. ### [​](https://docs.arc.io/arc/tools/account-abstraction#pimlico) [Pimlico](https://pimlico.io/) Bundler and paymaster infrastructure for ERC-4337 smart accounts, offering sponsored transactions and reliable relay services. ### [​](https://docs.arc.io/arc/tools/account-abstraction#privy) [Privy](https://www.privy.io/) APIs and SDKs for embedded wallets and user authentication. Onboard users with email or social logins while maintaining full control over key management. ### [​](https://docs.arc.io/arc/tools/account-abstraction#thirdweb) [Thirdweb](https://portal.thirdweb.com/wallets) Full-stack toolkit with built-in AA support, SDKs, and a managed smart wallet layer. ### [​](https://docs.arc.io/arc/tools/account-abstraction#turnkey) [Turnkey](https://docs.turnkey.com/reference/aa-wallets) Programmable key infrastructure for embedded wallets, transaction signing, and onchain automation with policy-based controls. ### [​](https://docs.arc.io/arc/tools/account-abstraction#zerodev) [Zerodev](https://zerodev.app/) Developer SDK for deploying and managing ERC-4337 smart accounts, with built-in session key and bundler support. Arc’s account abstraction ecosystem is modular — you can mix SDKs, paymasters, and bundlers from multiple providers to design your smart account architecture. Was this page helpful? YesNo [Agentic economy](https://docs.arc.io/build/agentic-economy) [Compliance](https://docs.arc.io/arc/tools/compliance-vendors) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Oracles - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tools/oracles#content-area) Connect your Arc smart contracts to real-world data using the oracle providers listed below. They offer price feeds and related infrastructure for DeFi, trading, lending, and other financial applications. If your application also needs to query historical onchain data, see [Data indexers](https://docs.arc.io/arc/tools/data-indexers) . [​](https://docs.arc.io/arc/tools/oracles#providers) Providers ----------------------------------------------------------------- ### [​](https://docs.arc.io/arc/tools/oracles#chainlink) [Chainlink](https://chain.link/) Decentralized oracle network for bringing market data and other offchain information onchain. Chainlink Data Feeds aggregate multiple data sources and publish secure, widely used feeds for lending, trading, stablecoins, and tokenized assets. * [**Data Feeds**](https://docs.chain.link/data-feeds) : Access decentralized price feeds for smart contract integrations. * [**Data Streams**](https://docs.chain.link/data-streams) : Retrieve low-latency market data delivered through a pull-based model. * [**Feed Explorer**](https://data.chain.link/) : Browse available feeds and contract addresses across supported networks. * [**Contract Addresses**](https://docs.chain.link/data-feeds/price-feeds/addresses) : Find deployed price feed contract addresses across supported networks. ### [​](https://docs.arc.io/arc/tools/oracles#chronicle) [Chronicle](https://chroniclelabs.org/) Onchain data infrastructure for high-integrity price feeds and tokenized asset verification. Chronicle originated as the oracle for Sky (MakerDAO) and provides DeFi price feeds alongside proof-of-asset attestations for lending, stablecoins, tokenized treasuries, and other RWAs. * [**DeFi Price Feeds**](https://chroniclelabs.org/dashboard/oracles) : Access high-integrity feeds purpose-built for lending, stablecoins, and other onchain financial applications. * [**Proof of Asset**](https://chroniclelabs.org/dashboard/proofofasset) : Provide cryptographic attestations of reserves and collateral for tokenized treasuries, stablecoins, RWAs, and other assets where verifiable backing is essential. * [**Docs**](https://docs.chroniclelabs.org/) : Get started with Chronicle’s oracle integration guides. ### [​](https://docs.arc.io/arc/tools/oracles#pyth) [Pyth](https://www.pyth.network/) Real-time, first-party market data oracle for onchain applications. Pyth provides price feeds across crypto, equities, FX, metals, and more, with pull and push delivery models for different latency and cost requirements. * [**Price Feeds**](https://docs.pyth.network/price-feeds) : Integrate real-time price data into Arc smart contracts. * [**EVM Contract Addresses**](https://docs.pyth.network/price-feeds/core/contract-addresses/evm) : Find deployed contract addresses for EVM-compatible chains. ### [​](https://docs.arc.io/arc/tools/oracles#redstone) [RedStone](https://www.redstone.finance/) Modular oracle network for secure, real-time price feeds and specialized market data. RedStone supports push, pull, and hybrid delivery models, with coverage across crypto assets, LSTs, LRTs, RWAs, tokenized funds, FX, and other custom data feeds for DeFi and institutional applications. * [**Docs**](https://docs.redstone.finance/docs/introduction/) : Learn about RedStone’s oracle architecture and supported integration models. * [**Pull Model**](https://docs.redstone.finance/docs/dapps/redstone-pull/) : Inject signed oracle data directly into user transactions for low-latency, gas-efficient integrations. * [**Push Feeds**](https://app.redstone.finance/push-feeds) : View deployed push feed contract addresses. * [**Pull Feeds**](https://app.redstone.finance/pull-feeds) : Browse available pull feed configurations and supported assets. * [**Price Feeds**](https://www.redstone.finance/price-feeds) : Browse RedStone’s supported asset coverage and feed types. ### [​](https://docs.arc.io/arc/tools/oracles#stork) [Stork](https://www.stork.network/) Ultra-low-latency oracle protocol for real-time market data. Stork provides fast, pull-based delivery with cryptographic verifiability for DeFi applications that require sub-second pricing. * [**Docs**](https://docs.stork.network/) : Get started with Stork’s oracle integration guides. * [**EVM Contract Addresses**](https://docs.stork.network/resources/contract-addresses/evm#arc) : Find Stork’s deployed contracts on Arc. Was this page helpful? YesNo [Node providers](https://docs.arc.io/arc/tools/node-providers) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Build on Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/build#content-area) Arc gives you a complete developer platform for building financial applications with stablecoins. Use [App Kits](https://docs.arc.io/app-kit) to add bridging, swapping, and crosschain token flows to any app, deploy smart contracts on a purpose-built L1 network, or integrate third-party developer tools for RPC access, data indexing, and compliance. [​](https://docs.arc.io/build#get-started) Get started --------------------------------------------------------- Connect to Arc -------------- RPC endpoints, chain ID, and network configuration for Arc testnet. Deploy on Arc ------------- Deploy, test, and interact with a Solidity smart contract on Arc. [App Kits](https://docs.arc.io/app-kit) are a suite of SDKs for adding bridging, swapping, token transfers, and unified crosschain balances to any app — across EVM chains, Solana, and Circle Wallets, not just Arc. [​](https://docs.arc.io/build#network-quickstarts) Network quickstarts ------------------------------------------------------------------------- Step-by-step guides for working directly with the Arc network. Most tutorials require an [Arc testnet RPC connection](https://docs.arc.io/arc/references/connect-to-arc) and a funded wallet. | Quickstart | What you’ll build | | --- | --- | | [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) | Deploy pre-audited ERC-20, ERC-721, and ERC-1155 templates with Circle Contracts. | | [Interact with contracts](https://docs.arc.io/arc/tutorials/interact-with-contracts) | Mint, transfer, and airdrop tokens using deployed contracts. | | [Monitor contract events](https://docs.arc.io/arc/tutorials/monitor-contract-events) | Set up webhooks and event monitors for onchain activity. | [​](https://docs.arc.io/build#developer-tools) Developer tools ----------------------------------------------------------------- Account abstraction ------------------- Smart wallets, paymasters, and session keys from ecosystem providers. Node providers -------------- Managed RPC access from Alchemy, QuickNode, Blockdaemon, and dRPC. Data indexers ------------- Query onchain data with Envio, Goldsky, The Graph, and Thirdweb. Compliance ---------- Transaction monitoring and wallet screening from Elliptic and TRM Labs. [​](https://docs.arc.io/build#sample-applications) Sample applications ------------------------------------------------------------------------- Browse working examples and reference implementations in the [sample apps](https://docs.arc.io/arc/references/sample-applications) gallery. Was this page helpful? YesNo [Sample apps](https://docs.arc.io/arc/references/sample-applications) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Integrate with Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate#content-area) Arc is [EVM-compatible](https://docs.arc.io/arc/references/evm-differences) —it supports the same bytecode, RPC methods, and tooling as Ethereum. Three key differences drive most integration work: * **USDC as gas**—fees are paid in USDC, not a volatile native token. This affects gas estimation, fee display, and balance APIs. * **Deterministic finality**—transactions finalize in under one second with no risk of reorganization. A single confirmation is sufficient. * **Dual USDC interface**—native balance uses 18 decimals while the ERC-20 interface uses 6 decimals. Both share the same underlying balance. See [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) for a full list of differences from Ethereum. [​](https://docs.arc.io/integrate#exchanges) Exchanges --------------------------------------------------------- Integrate Arc deposits, withdrawals, and USDC liquidity management into your exchange. Exchange integration -------------------- Overview of deposit, withdrawal, and bridging workflows for exchanges. Detect deposits --------------- Subscribe to blocks and detect incoming USDC transfers with one-confirmation finality. Process withdrawals ------------------- Build, sign, and broadcast withdrawal transactions with USDC gas. Bridge USDC (CCTP) ------------------ Move USDC liquidity to and from Arc using Cross-Chain Transfer Protocol. Custody providers ----------------- Configure Fireblocks, BitGo, SAFE, and other custody platforms for Arc. [​](https://docs.arc.io/integrate#on/off-ramps) On/off-ramps --------------------------------------------------------------- Add Arc as a supported network in your fiat-to-crypto ramp service. On/off-ramp integration ----------------------- Chain registration, deposit detection, withdrawal processing, and UI display for ramp providers. [​](https://docs.arc.io/integrate#wallets) Wallets ----------------------------------------------------- Add Arc as a supported network in your wallet application. Wallet integration ------------------ Chain configuration, balance display, transaction history, and fee handling for wallet providers. Transaction lifecycle --------------------- Arc’s two-state transaction model—pending or final, with no intermediate confirmation states. Fee display ----------- Fetch, estimate, and display transaction fees in USDC. [​](https://docs.arc.io/integrate#infrastructure) Infrastructure ------------------------------------------------------------------- Add Arc support to node services, data indexers, oracle networks, block explorers, and compliance tools. Infrastructure integration -------------------------- Key differences from standard EVM chains and chain metadata for infrastructure providers. Index events ------------ Unified transfer events, no-reorg indexing, and block streaming guidance. Bridges ------- Finality configuration, CCTP routing, and relay infrastructure for bridge protocols. Compliance ---------- Blocklist enforcement, Memo contract monitoring, and compliance tool integrations. [​](https://docs.arc.io/integrate#essentials) Essentials ----------------------------------------------------------- Connect to Arc -------------- RPC endpoints, chain ID, WebSocket URLs, and explorer links. Deploy on Arc ------------- Deploy, test, and interact with a Solidity smart contract on Arc. EVM compatibility ----------------- Full EVM differences table, dual USDC interface, and integration pitfalls. Run a node ---------- Operate your own Arc node for independent verification or direct RPC access. Was this page helpful? YesNo [Connect to Arc](https://docs.arc.io/integrate/connect-to-arc) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Estimate spend fees - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees#content-area) The App Kit SDK can provide an estimate of the fees you’ll incur before spending from a Unified Balance. [​](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees#estimate-fees-before-spending) Estimate fees before spending --------------------------------------------------------------------------------------------------------------------------------------------- This sample estimates then spends 1.00 USDC from Base Sepolia to Arc Testnet when you specify explicit amounts from a source blockchain: For automatic routing (no explicit `allocations`), see [Select source blockchains](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains) . TypeScript const params = { amount: "1.00", from: { adapter, allocations: [{ amount: "1.00", chain: "Base_Sepolia" }], }, to: { adapter, chain: "Arc_Testnet", recipientAddress: "0xRecipientAddress", }, }; const estimate = await kit.unifiedBalance.estimateSpend(params); console.log("Estimated fees:", estimate.fees); const result = await kit.unifiedBalance.spend(params); Estimated fees may differ from actual fees due to network conditions at execution time. Review the estimate before proceeding. ### [​](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees#example-fee-response) Example fee response The JSON below shows the shape of the fees array. JSON { "fees": [\ {\ "type": "provider",\ "token": "USDC",\ "amount": "0.00005",\ "allocations": [{ "chain": "Base Sepolia", "amount": "0.00005" }]\ },\ {\ "type": "gasFee",\ "token": "USDC",\ "amount": "3.311005",\ "allocations": [\ { "chain": "Ethereum Sepolia", "amount": "3.30" },\ { "chain": "Base Sepolia", "amount": "0.011005" }\ ]\ },\ {\ "type": "kit",\ "token": "USDC",\ "amount": ".1",\ "allocations": [{ "chain": "Ethereum Sepolia", "amount": ".1" }],\ "recipientAddress": "0x2222222222222222222222222222222222222222"\ }\ ] } Fee `type` values can include: * **`provider`** — Protocol transfer fee when the spend is crosschain. Not charged for same-chain spends. * **`gasFee`** — Onchain gas paid on source blockchains as part of the spend. * **`kit`** — Developer custom fee from your [custom fee policy](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees) . * **`forwarder`** — Forwarding Service fee when the spend [uses the forwarder](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service) (not shown in the sample above). See [How Unified Balance fees work](https://docs.arc.io/app-kit/concepts/unified-balance-fees) for a conceptual fee breakdown. Was this page helpful? YesNo [Select source blockchains](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains) [Collect custom spend fees](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Node providers - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tools/node-providers#content-area) Connect to the Arc network through third-party RPC infrastructure partners listed below. Each provider offers HTTP and WebSocket endpoints for submitting transactions, querying blockchain data, and subscribing to events. You can also use Arc’s public endpoints directly. | Connection type | Public endpoint | | --- | --- | | HTTP RPC | `https://rpc.testnet.arc.network` | | WebSocket | `wss://rpc.testnet.arc.network` | | Chain ID | `5042002` | [​](https://docs.arc.io/arc/tools/node-providers#providers) Providers ------------------------------------------------------------------------ ### [​](https://docs.arc.io/arc/tools/node-providers#alchemy) [Alchemy](https://www.alchemy.com/arc) Developer platform providing scalable access to EVM networks with enhanced APIs, monitoring, and debugging tools. ### [​](https://docs.arc.io/arc/tools/node-providers#blockdaemon) [Blockdaemon](https://www.blockdaemon.com/protocols/arc) Institutional-grade node provider offering secure and compliant infrastructure for Arc and other EVM chains. ### [​](https://docs.arc.io/arc/tools/node-providers#drpc) [dRPC](https://drpc.org/chainlist/arc-testnet-rpc) Decentralized RPC aggregator providing high-speed, load-balanced access to Arc nodes through a multi-provider architecture. ### [​](https://docs.arc.io/arc/tools/node-providers#quicknode) [QuickNode](https://www.quicknode.com/chains/arc) High-performance blockchain infrastructure offering global endpoints and APIs for developers. You can connect directly to Arc’s public RPC endpoint or through any of these infrastructure partners using your preferred SDK or web3 client. You can also [run your own node](https://docs.arc.io/arc/concepts/running-a-node) for independent verification and direct RPC access without third-party dependencies. Was this page helpful? YesNo [Data indexers](https://docs.arc.io/arc/tools/data-indexers) [Oracles](https://docs.arc.io/arc/tools/oracles) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Sample apps - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/sample-applications#content-area) Explore sample apps for Arc. Each app is an open-source reference implementation you can clone, run locally, and adapt for your own project. [​](https://docs.arc.io/arc/references/sample-applications#payments-and-checkout) Payments and checkout ---------------------------------------------------------------------------------------------------------- [​](https://docs.arc.io/arc/references/sample-applications#wallets-and-treasury) Wallets and treasury -------------------------------------------------------------------------------------------------------- [​](https://docs.arc.io/arc/references/sample-applications#stablecoin-fx) Stablecoin FX ------------------------------------------------------------------------------------------ [​](https://docs.arc.io/arc/references/sample-applications#defi) DeFi ------------------------------------------------------------------------ Was this page helpful? YesNo [Overview](https://docs.arc.io/build) [Connect to Arc](https://docs.arc.io/arc/references/connect-to-arc) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Node requirements - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/node-requirements#content-area) For step-by-step setup instructions, see [Run an Arc Node](https://docs.arc.io/arc/tutorials/run-an-arc-node) . [​](https://docs.arc.io/arc/references/node-requirements#system-requirements) System requirements ---------------------------------------------------------------------------------------------------- | Component | Minimum | | --- | --- | | OS | Linux (Ubuntu 22.04+ or Debian 12+) | | CPU | Higher clock speed over core count | | Memory | 64 GB+ | | Storage | 1 TB+ NVMe SSD (TLC recommended) | | Network | Stable 24 Mbps+ bandwidth | See [Reth system requirements](https://reth.rs/run/system-requirements/) for more detail on Execution Layer configuration. During sustained high load, such as startup or extended sync when the node is far behind, Execution Layer memory usage surges on some hardware. Systems that meet the listed requirements handle these surges without intervention. If you observe memory growth, [enable backpressure](https://docs.arc.io/arc/tutorials/run-an-arc-node#enable-backpressure-under-memory-pressure) to throttle execution to the speed of disk writes. ### [​](https://docs.arc.io/arc/references/node-requirements#snapshot-download-size) Snapshot download size An Arc node bootstraps from a snapshot; syncing from genesis is not supported. Testnet snapshots are approximately **68 GB compressed for the Execution Layer** and **16 GB compressed for the Consensus Layer**, extracting to roughly 103 GB and 36 GB respectively. On a stable 100 Mbps connection the download takes 10–15 minutes; slower or metered connections can take hours. [​](https://docs.arc.io/arc/references/node-requirements#required-binaries) Required binaries ------------------------------------------------------------------------------------------------ Every Arc node ships these three components. Install them with `arcup` (pre-built binaries), build them from the [`arc-node`](https://github.com/circlefin/arc-node) source, or run them in Docker. See [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) for the full walkthrough. | Binary | Description | | --- | --- | | `arc-node-execution` | Execution Layer client (Reth-based). Executes transactions and serves RPC. | | `arc-node-consensus` | Consensus Layer client (Malachite-based). Fetches and verifies blocks. | | `arc-snapshots` | Downloads blockchain snapshots for faster initial sync. | ### [​](https://docs.arc.io/arc/references/node-requirements#versions) Versions | Network | Version | | --- | --- | | Arc Testnet | v0.7.2 | [​](https://docs.arc.io/arc/references/node-requirements#network-endpoints) Network endpoints ------------------------------------------------------------------------------------------------ Your Consensus Layer connects to relay endpoints to fetch blocks from the network. Specify multiple endpoints for redundancy. | Network | Endpoints | | --- | --- | | Arc Testnet | `https://rpc.drpc.testnet.arc.network`
`https://rpc.quicknode.testnet.arc.network`
`https://rpc.blockdaemon.testnet.arc.network` | For developer RPC endpoints (connecting to Arc as an application, not running a node), see [Connect to Arc](https://docs.arc.io/arc/references/connect-to-arc) . [​](https://docs.arc.io/arc/references/node-requirements#exposed-ports) Exposed ports ---------------------------------------------------------------------------------------- | Port | Protocol | Mode | Description | | --- | --- | --- | --- | | 8545 | HTTP | Both | JSON-RPC API (Execution Layer) | | 8551 | HTTP | RPC only | Engine API authentication | | 9001 | HTTP | Both | Prometheus metrics (Execution Layer) | | 29000 | HTTP | Both | Prometheus metrics (Consensus Layer) | | 31000 | HTTP | Both | Consensus Layer RPC endpoint | The Execution Layer and Consensus Layer communicate through either IPC sockets or RPC. Choose one mode; they are mutually exclusive. **IPC mode** (default): Both processes run on the same host. Lower latency, no authentication required. | Socket path | Purpose | | --- | --- | | `/run/arc/reth.ipc` | ETH RPC (Consensus Layer reads chain state) | | `/run/arc/auth.ipc` | Engine API (Consensus Layer drives block import) | **RPC mode**: Processes run on separate hosts. Uses HTTP on ports 8545 and 8551 with JWT authentication. Requires generating a shared JWT secret. See [Run an Arc node: Run on separate hosts](https://docs.arc.io/arc/tutorials/run-an-arc-node#run-on-separate-hosts) for configuration details. [​](https://docs.arc.io/arc/references/node-requirements#recommended-flags-for-public-facing-nodes) Recommended flags for public-facing nodes ------------------------------------------------------------------------------------------------------------------------------------------------ If your node is reachable from the public internet, start the Execution Layer with `--public-api`. This flag hides pending-transaction RPCs (a potential MEV vector) and warns if `--http.api` or `--ws.api` exposes namespaces beyond the safe set (`eth`, `net`, `web3`, `rpc`). [​](https://docs.arc.io/arc/references/node-requirements#pruning) Pruning ---------------------------------------------------------------------------- Both the Execution Layer and Consensus Layer accept the `--full` flag to enable pruning. When bootstrapping from a pruned snapshot, `--full` is **required** on the first Execution Layer start to reconcile internal database tables that would otherwise fail a consistency check. After the initial startup completes, you may restart without `--full` if you prefer not to prune. EL pruning can increase memory usage on constrained machines. If you observe memory pressure, lower the [backpressure threshold](https://docs.arc.io/arc/tutorials/run-an-arc-node#enable-backpressure-under-memory-pressure) . Was this page helpful? YesNo [Running a node](https://docs.arc.io/arc/concepts/running-a-node) [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Remove funds trustlessly - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/unified-balance/remove-funds-trustlessly#content-area) The App Kit SDK supports trustless withdrawals from a Unified Balance, keeping your funds under your control. Withdrawals require two steps: initiate the removal, then complete it. On EVM networks, a 7-day waiting period applies between steps. On Solana, you can complete the removal immediately after initiation. `removeFund` is designed as a trustless escape hatch for fallback or recovery scenarios only. In normal situations, [use `spend`](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend) . [​](https://docs.arc.io/app-kit/tutorials/unified-balance/remove-funds-trustlessly#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------ Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/unified-balance/remove-funds-trustlessly#initiate-and-complete-a-removal) Initiate and complete a removal ------------------------------------------------------------------------------------------------------------------------------------------------------ * EVM * Solana 1 Initiate the removal Call `initiateRemoveFund` to record the request to remove funds and start the 7-day waiting period.This example initiates a removal of 1 USDC on Base Sepolia: TypeScript const initiateResult = await kit.unifiedBalance.initiateRemoveFund({ from: { adapter, chain: "Base_Sepolia", }, amount: "1.00", }); console.log("Remove fund initiated:", initiateResult); `initiateRemoveFund` returns a result object that includes the transaction details for the pending removal. In step 2, pass the same `adapter` and `chain` values to `removeFund` to complete it (you can reuse the same variables as in this example). 2 Complete the removal After the waiting period, call `removeFund` to return funds to the wallet for that adapter on that blockchain: TypeScript const removeResult = await kit.unifiedBalance.removeFund({ from: { adapter, chain: "Base_Sepolia", }, }); console.log("Remove fund complete:", removeResult); What to know about removals on EVM: * Calling `removeFund` before the waiting period has elapsed will fail. * Funds go to the wallet associated with the adapter on the specified blockchain. * Only one pending `removeFund` request is allowed per blockchain and address. Call `initiateRemoveFund`, then `removeFund` with the same `from` context. No waiting period is required on Solana, so you can call both in sequence.This example initiates and completes a removal of 1 USDC on Solana Devnet: TypeScript const from = { adapter, chain: "Solana_Devnet" as const }; const initiateResult = await kit.unifiedBalance.initiateRemoveFund({ from, amount: "1.00", }); console.log("Initiate result:", initiateResult); const removeResult = await kit.unifiedBalance.removeFund({ from }); console.log("Remove result:", removeResult); What to know about removals on Solana: * No waiting period is required between `initiateRemoveFund` and `removeFund`. * Funds return to the wallet for the Solana adapter you use in `from`. * The sample uses `"Solana_Devnet" as const` so TypeScript narrows the chain type for `from`. Was this page helpful? YesNo [Use Forwarding Service](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service) [SDK Reference](https://docs.arc.io/app-kit/references/sdk-reference) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Add Arc to Your On/Off-Ramp Platform - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/on-off-ramps#content-area) Add Arc as a supported blockchain for buy and sell orders in your on/off-ramp service (Transak, MoonPay, BVNK, Socure, and others). Arc shares its EVM foundations with networks you already support, but its native USDC model and deterministic finality simplify your integration and improve the end-user experience. [​](https://docs.arc.io/integrate/on-off-ramps#prerequisites) Prerequisites ------------------------------------------------------------------------------ Before you begin: * Familiarity with EVM-based blockchain integrations * Access to Arc RPC endpoints for deposit monitoring and transaction broadcasting * Existing infrastructure for deposit detection and withdrawal processing on EVM chains [​](https://docs.arc.io/integrate/on-off-ramps#network-configuration) Network configuration ---------------------------------------------------------------------------------------------- Register Arc with the following parameters in your platform’s chain registry: | Parameter | Value | | --- | --- | | Network name | `Arc` (testnet: `Arc Testnet`) | | Chain ID | `5042002` (testnet) | | RPC (HTTPS) | `https://rpc.testnet.arc.network` | | RPC (WebSocket) | `wss://rpc.testnet.arc.network` | | Block explorer | `https://testnet.arcscan.app` | | Native currency symbol | `USDC` | | Native currency decimals | `6` | | USDC ERC-20 contract | `0x3600000000000000000000000000000000000000` | | CCTP domain | `26` | | Confirmations required | `1` | Arc uses USDC as both the native gas token and the primary transfer asset. There is no separate gas token like ETH. Configure your platform with a single asset entry for USDC on Arc. [​](https://docs.arc.io/integrate/on-off-ramps#deposit-detection-for-buy-orders) Deposit detection for buy orders -------------------------------------------------------------------------------------------------------------------- When a user completes a fiat purchase and you need to detect the resulting onchain USDC deposit, use the same `Transfer` event monitoring pattern used by exchanges. Every native USDC movement emits a standard ERC-20 `Transfer(address,address,uint256)` event from the system emitter `0xffff…fffe` (Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation), covering native sends and the native leg of ERC-20 transfers. Key points for ramp deposit detection: * Subscribe to blocks via WebSocket (`eth_subscribe("newHeads")`) or poll via HTTP * Filter `Transfer` events on the native USDC system emitter (`0xfffffffffffffffffffffffffffffffffffffffe`)—not the ERC-20 contract (`0x3600…0000`), which misses plain native sends * Credit immediately after 1 confirmation—Arc’s deterministic finality guarantees no reorgs * The `value` field uses 18 decimals (native); divide by 10^12 for 6-decimal USDC For complete implementation details including code samples, address generation, and sweep logic, see [Detect and process deposits](https://docs.arc.io/integrate/exchanges/deposits) . [​](https://docs.arc.io/integrate/on-off-ramps#withdrawal-processing-for-sell-orders) Withdrawal processing for sell orders ------------------------------------------------------------------------------------------------------------------------------ When a user sells crypto for fiat and you need to send USDC to a settlement address or back to the user, the withdrawal flow is identical to exchange withdrawals: 1. Validate the destination address (EIP-55 checksum) 2. Check the blocklist before sending 3. Build and sign a native USDC transfer (the recommended path—cheaper gas and universally receivable) 4. Broadcast and confirm (single confirmation = final) Gas fees are paid in USDC from the same balance—no need to maintain a separate ETH float for gas. For complete implementation details including transaction construction and gas estimation, see [Process withdrawals](https://docs.arc.io/integrate/exchanges/withdrawals) . [​](https://docs.arc.io/integrate/on-off-ramps#displaying-arc-in-your-ui) Displaying Arc in your UI ------------------------------------------------------------------------------------------------------ Arc’s native USDC model affects how you present the network to end users: | UI element | Recommendation | | --- | --- | | Network name | Display as **Arc** (or **Arc Testnet** for test environments) | | Asset shown | USDC only—do not display a separate gas token | | Gas fee display | Show estimated fees in USDC (typically fractions of a cent) | | Confirmation time | Display “Instant” or ”< 1 second”—no countdown or block wait indicator needed | | Network icon | Use the Arc brand assets (contact the Arc team for logo files) | Because there is no separate gas token, you can skip the “ensure you have ETH for gas” warning that other EVM chains require. Users receive USDC and can immediately transfer it without acquiring a second asset. [​](https://docs.arc.io/integrate/on-off-ramps#settlement-timing) Settlement timing -------------------------------------------------------------------------------------- Arc provides sub-second deterministic finality. Once a transaction is included in a block, it is final—there are no reorgs, no probabilistic confirmation windows, and no need to wait for additional blocks. For your ramp platform, this means: * **Buy orders**: Credit the user’s crypto balance immediately upon 1 confirmation * **Sell orders**: Mark the withdrawal as complete after the transaction receipt is returned * **Settlement display**: Show instant confirmation to the user rather than a progress bar or block countdown [​](https://docs.arc.io/integrate/on-off-ramps#address-validation) Address validation ---------------------------------------------------------------------------------------- Arc uses standard Ethereum addresses: * 20 bytes, `0x`\-prefixed (42 characters total) * EIP-55 mixed-case checksum encoding * Compatible with existing Ethereum address validation logic in your platform No additional address format validation is required beyond what you already implement for EVM chains. import { getAddress, isAddress } from "viem"; function validateArcAddress(address: string): string { if (!isAddress(address)) { throw new Error(`Invalid Arc address: ${address}`); } return getAddress(address); // Returns EIP-55 checksummed } [​](https://docs.arc.io/integrate/on-off-ramps#compliance-screening) Compliance screening -------------------------------------------------------------------------------------------- Arc enforces a USDC blocklist at multiple levels: * **Pre-mempool**: Transactions from or to blocklisted addresses are rejected before entering the mempool * **Runtime**: Transfers involving blocklisted addresses revert during execution Your platform should check addresses against the onchain blocklist before initiating withdrawals. If a destination is blocklisted, the transaction reverts and gas is consumed without transferring funds. For implementation details on blocklist monitoring and event subscription, see [Monitor blocklist compliance](https://docs.arc.io/integrate/infrastructure/compliance) . Always verify destination addresses against the blocklist before broadcasting withdrawal transactions. Sending to a blocklisted address wastes gas fees and creates a failed transaction that you must handle in your reconciliation flow. [​](https://docs.arc.io/integrate/on-off-ramps#crosschain-usdc-routing) Crosschain USDC routing -------------------------------------------------------------------------------------------------- If your platform supports crosschain transfers (for example, a user buys USDC on Ethereum and wants delivery on Arc), use Circle’s Cross-Chain Transfer Protocol (CCTP). Arc’s CCTP domain is `26`. For bridging implementation details, see [Bridge USDC with CCTP](https://docs.arc.io/integrate/exchanges/cctp-bridging) . Was this page helpful? YesNo [Custody platforms](https://docs.arc.io/integrate/exchanges/custody) [Add Arc to a wallet](https://docs.arc.io/integrate/wallets) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Monitor a node - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/monitor-a-node#content-area) Verify that your Arc node is syncing, diagnose issues from logs, and confirm that the Prometheus metrics endpoints are reachable on the host. For a full Prometheus and Grafana setup on the same host, see [Set up Prometheus and Grafana for an Arc node](https://docs.arc.io/arc/tutorials/set-up-node-monitoring) . [​](https://docs.arc.io/arc/tutorials/monitor-a-node#prerequisites) Prerequisites ------------------------------------------------------------------------------------ * You have a running Arc node that meets the [node requirements](https://docs.arc.io/arc/references/node-requirements) , set up either from the [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) tutorial or [deployed as a systemd service](https://docs.arc.io/arc/tutorials/deploy-node-as-service) * You have installed [Foundry](https://book.getfoundry.sh/getting-started/installation) , which provides the `cast` command [​](https://docs.arc.io/arc/tutorials/monitor-a-node#step-1-check-service-status) Step 1. Check service status ----------------------------------------------------------------------------------------------------------------- If your node runs as systemd services, check the status of both processes: sudo systemctl status arc-execution sudo systemctl status arc-consensus Both services display `Active: active (running)` in their output. If either service has failed, review the logs in Step 3. [​](https://docs.arc.io/arc/tutorials/monitor-a-node#step-2-check-block-height) Step 2. Check block height ------------------------------------------------------------------------------------------------------------- Query the local RPC endpoint to confirm the node is syncing: cast block-number --rpc-url http://localhost:8545 Run this command several times over a few seconds. The block number increases steadily, confirming the node is syncing: # First run: 1234567 # Second run (a few seconds later): 1234572 To view the latest block details: cast block --rpc-url http://localhost:8545 Example output: baseFeePerGas 7 difficulty 0 gasLimit 30000000 gasUsed 21000 hash 0xabc123... number 1234572 timestamp 1711234567 transactions: [0xdef456...] [​](https://docs.arc.io/arc/tutorials/monitor-a-node#step-3-view-the-logs) Step 3. View the logs --------------------------------------------------------------------------------------------------- Stream real-time logs for each service: # Execution Layer logs sudo journalctl -u arc-execution -f # Consensus Layer logs sudo journalctl -u arc-consensus -f If your node is not running as a systemd service, check the terminal output where each process is running. **What to look for:** * **Healthy:** Log entries showing new blocks being imported, increasing block heights * **Unhealthy:** Repeated connection errors to relay endpoints, IPC socket failures, or no new blocks for an extended period [​](https://docs.arc.io/arc/tutorials/monitor-a-node#step-4-verify-the-metrics-endpoints) Step 4. Verify the metrics endpoints --------------------------------------------------------------------------------------------------------------------------------- Both layers expose Prometheus-compatible metrics endpoints on the host. Note that the Execution Layer serves metrics at the root path, not at `/metrics`: | Endpoint | Description | | --- | --- | | `http://localhost:9001/` | Execution Layer metrics | | `http://localhost:29000/metrics` | Consensus Layer metrics | The metrics endpoints are only available if the `--metrics` flag was passed when starting the node. The [Deploy a node as a service](https://docs.arc.io/arc/tutorials/deploy-node-as-service) guide includes this flag in both unit files. Confirm the endpoints are reachable: curl -s http://localhost:9001/ | head curl -s http://localhost:29000/metrics | head Both commands return Prometheus-formatted text metrics. Example output: # HELP reth_sync_stage_checkpoint Stage checkpoint block number # TYPE reth_sync_stage_checkpoint gauge reth_sync_stage_checkpoint{stage="Headers"} 1234567 If either returns an empty response or connection error, confirm the `--metrics` flag is set in your startup command. To scrape these endpoints with a local Prometheus + Grafana stack and load the pre-built Arc dashboards, see [Set up Prometheus and Grafana for an Arc node](https://docs.arc.io/arc/tutorials/set-up-node-monitoring) . Was this page helpful? YesNo [Deploying a node as a service](https://docs.arc.io/arc/tutorials/deploy-node-as-service) [Set up Prometheus and Grafana for an Arc node](https://docs.arc.io/arc/tutorials/set-up-node-monitoring) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Manage delegates - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates#content-area) A delegate is an address that you authorize to spend from your Unified Balance on a given blockchain. In practice, a delegate is often a backend service signing spends on behalf of a user. How delegates work: * Delegation is blockchain-specific. * Authorizing a delegate on one blockchain does not grant them access on other blockchains. * An authorized delegate can call `spend` with `sourceAccount` set to your address so funds are drawn from your Unified Balance. You can check, add, and remove delegates at any time. For an end-to-end flow on how a delegate can deposit and spend from a Unified Balance, see the [delegate quickstart](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend) . [​](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates#check-delegate-status) Check delegate status -------------------------------------------------------------------------------------------------------------------------- The following example reads delegate status for an address. TypeScript const status = await kit.unifiedBalance.getDelegateStatus({ from: { adapter, chain: "Base_Sepolia" }, delegateAddress: "0xDelegateAddress", }); console.log("Delegate status:", status); // 'none' | 'pending' | 'ready' `getDelegateStatus` resolves to `'none'` when no delegate is set, `'pending'` while delegation is still confirming, and `'ready'` when the delegate is active and authorized to spend. ### [​](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates#poll-until-ready) Poll until ready Use `getDelegateStatus` in a poll loop to wait until the delegate is active before spending. Confirmation time varies by blockchain. For example, on Ethereum, Base, and Arbitrum it can take up to 15 minutes, while on Arc and Avalanche it is near-instant. If the delegate was added well before the spend, `getDelegateStatus` returns `'ready'` immediately and no polling is needed. This example polls until the delegate is ready before spending: TypeScript let status = await kit.unifiedBalance.getDelegateStatus({ from: { adapter, chain: "Base_Sepolia" }, delegateAddress: "0xDelegateAddress", }); while (status === "pending") { await new Promise((r) => setTimeout(r, 10_000)); // wait 10 seconds status = await kit.unifiedBalance.getDelegateStatus({ from: { adapter, chain: "Base_Sepolia" }, delegateAddress: "0xDelegateAddress", }); } if (status === "ready") { console.log("Delegate is ready. Safe to spend."); } [​](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates#add-a-delegate) Add a delegate ------------------------------------------------------------------------------------------------------------ The following example authorizes a delegate to spend from a Unified Balance on Base Sepolia. TypeScript await kit.unifiedBalance.addDelegate({ from: { adapter, chain: "Base_Sepolia" }, delegateAddress: "0xDelegateAddress", }); console.log("Delegate added."); Delegation is blockchain-specific. To authorize a delegate on multiple blockchains, call `addDelegate` for each blockchain. [​](https://docs.arc.io/app-kit/tutorials/unified-balance/manage-delegates#remove-a-delegate) Remove a delegate ------------------------------------------------------------------------------------------------------------------ This example removes a delegate from a Unified Balance on Base Sepolia: TypeScript await kit.unifiedBalance.removeDelegate({ from: { adapter, chain: "Base_Sepolia" }, delegateAddress: "0xDelegateAddress", }); console.log("Delegate removed."); Was this page helpful? YesNo [Collect custom spend fees](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees) [Use Forwarding Service](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How-to: Check Unified Balance total - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#content-area) Check how much USDC is in your Unified Balance in total and on each source blockchain. [​](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#prerequisites) Prerequisites --------------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * [Installed the App Kit SDK](https://docs.arc.io/app-kit/tutorials/installation) * [Configured an adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. [​](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#check-balances-by-adapter) Check balances by adapter --------------------------------------------------------------------------------------------------------------------------------------- Query balances with the same adapters you use for deposits. If you query more than one signing ecosystem, such as EVM and Solana, pass one source for each adapter. Omitting the chains parameter returns balances for all supported chains available to that source and `networkType`. TypeScript const balances = await kit.unifiedBalance.getBalances({ sources: [{ adapter: evmAdapter }, { adapter: solanaAdapter }], }); // Log confirmed and pending totals plus per-depositor, per-chain breakdown. console.dir(balances, { depth: null }); You will see an output similar to: Shell { token: "USDC", totalConfirmedBalance: "166.954132", breakdown: [\ {\ depositor: "1234abcd1234abcd1234abcd1234abcd1234abcd1234",\ totalConfirmed: "13.124021",\ breakdown: [\ {\ chain: "Solana",\ confirmedBalance: "13.124021",\ },\ ],\ },\ {\ depositor: "0x0123abcd0123abcd0123abcd0123abcd0123abcd",\ totalConfirmed: "153.830111",\ breakdown: [\ {\ chain: "Ethereum",\ confirmedBalance: "11.998900",\ }, {\ chain: "Base",\ confirmedBalance: "19.926391",\ }, {\ chain: "Avalanche",\ confirmedBalance: "19.458595",\ }, {\ chain: "Arbitrum",\ confirmedBalance: "9.989950",\ }, {\ chain: "Sonic",\ confirmedBalance: "11.939700",\ }, {\ chain: "World_Chain",\ confirmedBalance: "10.929750",\ }, {\ chain: "Sei",\ confirmedBalance: "25.891695",\ }, {\ chain: "HyperEVM",\ confirmedBalance: "9.699800",\ }, {\ chain: "Optimism",\ confirmedBalance: "9.998450",\ }, {\ chain: "Polygon",\ confirmedBalance: "13.997930",\ }, {\ chain: "Unichain",\ confirmedBalance: "9.998950",\ }\ ],\ },\ ], } See all 56 lines [​](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#check-balances-by-address) Check balances by address --------------------------------------------------------------------------------------------------------------------------------------- You can also query by account address. This is useful when you need to inspect an account’s Unified Balance without signing. TypeScript const balances = await kit.unifiedBalance.getBalances({ sources: { address: "0xWalletAddress", chains: ["Base_Sepolia"], }, }); console.dir(balances, { depth: null }); You will see an output similar to: Shell { token: "USDC", totalConfirmedBalance: "0.082000", breakdown: [\ {\ depositor: "0xWalletAddress",\ totalConfirmed: "0.082000",\ breakdown: [\ {\ chain: "Base_Sepolia",\ confirmedBalance: "0.082000"\ }\ ]\ }\ ] } [​](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#query-specific-chains) Query specific chains ------------------------------------------------------------------------------------------------------------------------------- Use `chains` to limit the response to the chains you care about. If you omit `chains`, App Kit returns balances for the supported chains available to that source and `networkType`. Note that all chains in the array must be on the same network, you cannot mix testnet and mainnet chains. TypeScript const balances = await kit.unifiedBalance.getBalances({ sources: { adapter, chains: ["Base_Sepolia", "Arc_Testnet"], }, }); You will see an output similar to: Shell { token: "USDC", totalConfirmedBalance: "32.207843", breakdown: [\ {\ depositor: "0x0123abcd0123abcd0123abcd0123abcd0123abcd",\ totalConfirmed: "32.207843",\ breakdown: [\ {\ chain: "Arc_Testnet",\ confirmedBalance: "12.281452"\ }, {\ chain: "Base_Sepolia",\ confirmedBalance: "19.926391"\ }\ ]\ }\ ] } [​](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#select-a-network) Select a network --------------------------------------------------------------------------------------------------------------------- Set `networkType` to query testnet balances. If you omit `networkType`, App Kit uses mainnet when chains cannot be derived from the source. This is not an issue if you include the chains parameter. TypeScript const balances = await kit.unifiedBalance.getBalances({ sources: { address: "0xWalletAddress" }, networkType: "testnet", }); You will see an output similar to: Shell { token: "USDC", totalConfirmedBalance: "0.282000", breakdown: [\ {\ depositor: "0xWalletAddress",\ totalConfirmed: "0.282000",\ breakdown: [\ {\ chain: "Ethereum_Sepolia",\ confirmedBalance: "0.000000",\ }, {\ chain: "Base_Sepolia",\ confirmedBalance: "0.200000",\ }, {\ chain: "Avalanche_Fuji",\ confirmedBalance: "0.000000",\ }, {\ chain: "Arbitrum_Sepolia",\ confirmedBalance: "0.000000",\ }, {\ chain: "Sonic_Testnet",\ confirmedBalance: "0.000000",\ }, {\ chain: "World_Chain_Sepolia",\ confirmedBalance: "0.000000",\ }, {\ chain: "Sei_Testnet",\ confirmedBalance: "0.000000",\ }, {\ chain: "HyperEVM_Testnet",\ confirmedBalance: "0.000000",\ }, {\ chain: "Arc_Testnet",\ confirmedBalance: "0.082000",\ }, {\ chain: "Optimism_Sepolia",\ confirmedBalance: "0.000000",\ }, {\ chain: "Polygon_Amoy_Testnet",\ confirmedBalance: "0.000000",\ }, {\ chain: "Unichain_Sepolia",\ confirmedBalance: "0.000000",\ }\ ],\ }\ ], } See all 49 lines [​](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#include-pending-balances) Include pending balances ------------------------------------------------------------------------------------------------------------------------------------- Set `includePending: true` to include pending balance totals and pending transaction details in the response. TypeScript const balances = await kit.unifiedBalance.getBalances({ sources: { adapter }, includePending: true, }); You will see an output similar to: Shell { token: "USDC", totalConfirmedBalance: "0.000000", breakdown: [\ {\ depositor: "1234abcd1234abcd1234abcd1234abcd1234abcd1234",\ totalConfirmed: "0.000000",\ breakdown: [\ {\ chain: "Solana",\ confirmedBalance: "0.000000",\ pendingBalance: "0.000000",\ pendingTransactions: [],\ }\ ],\ totalPending: "0.000000",\ }\ ], totalPendingBalance: "0.000000", } [​](https://docs.arc.io/app-kit/tutorials/unified-balance/check-unified-balance#query-multiple-sources) Query multiple sources --------------------------------------------------------------------------------------------------------------------------------- Pass an array when you need balances across multiple sources. Each source can use an adapter or an address. TypeScript const balances = await kit.unifiedBalance.getBalances({ sources: [\ {\ adapter: evmAdapter,\ chains: ["Arc_Testnet"],\ },\ {\ address: "SolanaWalletAddress",\ chains: ["Solana_Devnet"],\ },\ ], }); You will see an output similar to: Shell { token: "USDC", totalConfirmedBalance: "25.405473", breakdown: [\ {\ depositor: "0x0123abcd0123abcd0123abcd0123abcd0123abcd",\ totalConfirmed: "12.281452",\ breakdown: [\ {\ chain: "Arc_Testnet",\ confirmedBalance: "12.281452",\ }\ ],\ }, {\ depositor: "SolanaWalletAddress",\ totalConfirmed: "13.124021",\ breakdown: [\ {\ chain: "Solana_Devnet",\ confirmedBalance: "13.124021",\ }\ ],\ }\ ], } Was this page helpful? YesNo [How Unified Balance fees work](https://docs.arc.io/app-kit/concepts/unified-balance-fees) [Select source blockchains](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Infrastructure Integration - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/infrastructure#content-area) Arc is an EVM-compatible blockchain, so standard Ethereum tooling works out of the box. However, several architectural differences affect how infrastructure providers index data, stream blocks, and expose balance APIs. [​](https://docs.arc.io/integrate/infrastructure#key-differences-from-ethereum) Key differences from Ethereum ---------------------------------------------------------------------------------------------------------------- | Area | Ethereum behavior | Arc behavior | | --- | --- | --- | | Native token | `eth_getBalance` returns ETH (18 decimals) | `eth_getBalance` returns USDC (18 decimals) | | Finality | Probabilistic—requires 12+ minutes and multiple confirmations | Deterministic—once a block is committed, it is permanent | | Reorgs | Possible—indexers must handle chain reorganizations and uncle blocks | Never—no reorganizations occur | | Block time | ~12 seconds | Sub-second—multiple blocks may share the same `block.timestamp` | | `PREVRANDAO` | Randomness beacon value | Always returns `0` | | Blob transactions | Supported (EIP-4844) | Not supported | | Consensus | Proof-of-stake (Casper) | Malachite BFT with permissioned PoA validators | [​](https://docs.arc.io/integrate/infrastructure#chain-metadata) Chain metadata ---------------------------------------------------------------------------------- | Property | Value | | --- | --- | | Chain ID | `5042002` | | RPC (HTTPS) | `https://rpc.testnet.arc.network` | | WebSocket | `wss://rpc.testnet.arc.network` | | Block explorer | [testnet.arcscan.app](https://testnet.arcscan.app/) | | CCTP domain | `26` | | EVM target | Osaka hard fork | | USDC ERC-20 address | `0x3600000000000000000000000000000000000000` | Additional RPC endpoints are available through [Blockdaemon, dRPC, and QuickNode](https://docs.arc.io/arc/tools/node-providers) . [​](https://docs.arc.io/integrate/infrastructure#integration-considerations) Integration considerations ---------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/infrastructure#balance-apis) Balance APIs `eth_getBalance` returns the account’s native balance in USDC at 18-decimal precision. If your platform displays balances, label the value as USDC rather than ETH. The same underlying balance is also accessible through the ERC-20 interface at 6-decimal precision. ### [​](https://docs.arc.io/integrate/infrastructure#no-reorg-indexing) No-reorg indexing Arc’s [deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) means you never need to handle chain reorganizations or uncle blocks. Every block your indexer receives is permanent. You can treat a single block confirmation as final and skip reorg-recovery logic entirely. ### [​](https://docs.arc.io/integrate/infrastructure#sub-second-block-streaming) Sub-second block streaming Blocks arrive faster than once per second. Your ingestion pipeline must handle high-throughput streaming without assuming a minimum interval between blocks. Multiple consecutive blocks may share the same `block.timestamp` because sub-second blocks can fall in the same wall-clock second. ### [​](https://docs.arc.io/integrate/infrastructure#randomness) Randomness `PREVRANDAO` always returns `0`. If your tooling surfaces this opcode value, note that it does not provide randomness on Arc. [​](https://docs.arc.io/integrate/infrastructure#self-hosted-access) Self-hosted access ------------------------------------------------------------------------------------------ For independent verification or direct RPC access without third-party providers, you can run your own Arc node. The execution client (`arc-node-execution`) is Reth-based, and the consensus client (`arc-node-consensus`) is Malachite-based. Running a node -------------- Architecture overview and requirements for operating an Arc node. Run an Arc node --------------- Step-by-step guide to install, configure, and start both clients. [​](https://docs.arc.io/integrate/infrastructure#sub-pages) Sub-pages ------------------------------------------------------------------------ Index events ------------ Unified transfer events, no-reorg indexing, and block streaming guidance for data indexers. Compliance ---------- Blocklist enforcement, Memo contract monitoring, and compliance tool integrations. Was this page helpful? YesNo [Display transaction fees](https://docs.arc.io/integrate/wallets/fee-display) [Index events](https://docs.arc.io/integrate/infrastructure/indexing-events) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Port a contract to Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc#content-area) Most Ethereum contracts deploy and run on Arc without changes. The cases that need attention almost all come from one fact: **on Arc the native gas token is USDC, and native USDC and the ERC-20 USDC interface are the same asset.** This guide walks through the checks to perform on an existing contract and how to verify it on Arc Testnet before you ship. For the protocol-level detail behind each check, see [EVM differences](https://docs.arc.io/arc/references/evm-differences) . [​](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc#before-you-start) Before you start ---------------------------------------------------------------------------------------------------- * Connect your tooling to Arc Testnet. See [Connect to Arc](https://docs.arc.io/arc/references/connect-to-arc) . * Have your contract source and its test suite ready. * Get testnet USDC from the [Circle Faucet](https://faucet.circle.com/) to pay for gas and fund test transfers. [​](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc#steps) Steps ------------------------------------------------------------------------------ 1 Audit balance and decimal assumptions Search your contract for places that read `balanceOf` or `address.balance` and for any logic that compares or combines the two. * Convert before comparing: the ERC-20 view uses 6 decimals and the native view uses 18 for the same balance. * Treat the ERC-20 `balanceOf` as inexact. It truncates anything below 1×10⁻⁶ USDC, so `0.0000001` USDC reads as `0` and `100.0000001` USDC reads as `100`. A `balanceOf` of `0` does not mean the native balance is `0`. 2 Audit value transfers Find every native send and every path that forwards value. * Handle reverts: a native transfer can revert even with a sufficient balance, for example a transfer to the zero address, a transfer to or from a blocklisted address, or any transfer that would burn value. * Don’t assume a call to a contract that forwards native value will succeed. * Don’t transfer to `address(0)`; the transfer reverts with `"Zero address not allowed"`. 3 Audit approvals and sweeps Review allowances and any “sweep” logic. * If a contract is meant to hold USDC only as an ERC-20 token and not to act on native value, don’t let it sweep its native balance. Native USDC and the ERC-20 USDC interface are the same asset, so a native sweep also moves the users’ ERC-20 USDC balance. * Don’t pair “native” against the ERC-20 USDC interface in a liquidity pool. Both legs are the same asset, so the pairing is meaningless. 4 Audit DEX, AMM, and router contracts If you are porting a DEX, AMM, or router (for example, Uniswap V2, Uniswap V3, or a fork), there is no wrapped native token on Arc and no `WUSDC`/`WETH` equivalent is needed. * Use the ERC-20 USDC contract directly as the pair token. Arc’s ERC-20 USDC interface at [`0x3600000000000000000000000000000000000000`](https://docs.arc.io/arc/references/contract-addresses#usdc) is the canonical token. Treat it like any other ERC-20 in your pool, pair, and router code. * Do not deploy a `WUSDC` wrapper contract. Wrapping the native asset on Arc fragments liquidity and can create user confusion. The ERC-20 interface already exposes `transfer`, `approve`, and `transferFrom` over the same underlying native balance. * Replace `WETH`\-style code paths. Remove `deposit()` / `withdraw()` wrap-and-unwrap calls, and replace any `WETH` address constant in your router or periphery contracts with the ERC-20 USDC address above. Routes that accepted raw native value via `msg.value` can either keep accepting native USDC, or be simplified to ERC-20-only flows since both interfaces move the same balance. Mind the decimals when you mix paths: `msg.value` is denominated in 18-decimal native USDC, while the ERC-20 USDC interface uses 6 decimals, so convert between them explicitly (1 ERC-20 unit = 1012 wei) instead of assuming a 1:1 numeric value. * Do not bridge or deploy `USDC.e` / `wUSDC` variants. All USDC on Arc arrives via CCTP as the native asset. See [Bridges](https://docs.arc.io/integrate/infrastructure/bridges) for the bridging rules. 5 Audit SELFDESTRUCT usage If your contract uses `SELFDESTRUCT`, confirm it doesn’t depend on burning, on sending value to a destructed account, or on retaining USDC afterward. * A contract’s USDC is its native balance, so self-destructing transfers that USDC to the beneficiary. On other chains the ERC-20 USDC balance would remain in the token contract. * Self-destructing to yourself with a balance, to the zero address with a balance, to a blocklisted address, or to an already self-destructed account all revert. * A non-zero-value call to a contract after it self-destructs reverts on Arc, even though it succeeds on Ethereum. See [SELFDESTRUCT](https://docs.arc.io/arc/references/evm-differences#selfdestruct) for the exact conditions and a worked example. 6 Test against an Arc RPC endpoint Run your test suite against Arc Testnet, not a local EVM simulator. Tools like Foundry’s `anvil` run a standard EVM and cannot reproduce Arc’s precompiles, EIP-7708 `Transfer` events, or USDC blocklist enforcement. 7 Exercise the revert paths Confirm your contract handles a blocklist revert gracefully using the seeded blocklisted test address on the [contract addresses](https://docs.arc.io/arc/references/contract-addresses#test-addresses-for-restricted-transfer-behavior) page. A value transfer to or from it reverts at runtime, including when it is the beneficiary of a `SELFDESTRUCT`. Once these checks pass against Arc Testnet, your contract is ready to deploy. For the full set of protocol-level rules, see [EVM differences](https://docs.arc.io/arc/references/evm-differences) . Was this page helpful? YesNo [Monitor contract events](https://docs.arc.io/arc/tutorials/monitor-contract-events) [Send USDC with a transaction memo](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Agentic Economy - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/build/agentic-economy#content-area) Build applications where AI agents operate as first-class economic participants. Arc provides onchain identity ([ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) ), reputation, and job settlement standards ([ERC-8183](https://eips.ethereum.org/EIPS/eip-8183) ) so agents can register, find work, and get paid autonomously. For the payment primitives that agents settle with, see [P2P Payments](https://docs.arc.io/build/payments) . For agent-initiated checkout flows, see [eCommerce Checkout](https://docs.arc.io/build/ecommerce) . [​](https://docs.arc.io/build/agentic-economy#sample-apps) Sample apps ------------------------------------------------------------------------- Production-ready examples on GitHub you can fork and customize. Arc escrow ---------- AI-powered work validation and USDC settlement to automate escrow flows using Circle Wallets, Refund Protocol, and Contract Platform. Arc nanopayments ---------------- Autonomous AI agent pays for premium API endpoints in USDC fractions using Circle Nanopayments and the x402 protocol. [​](https://docs.arc.io/build/agentic-economy#quickstarts) Quickstarts ------------------------------------------------------------------------- Get up and running with agentic workflows in minutes. Register your first AI agent ---------------------------- **Beginner**. Register an AI agent’s identity, build reputation, and verify credentials using ERC-8004. Create your first ERC-8183 job ------------------------------ **Intermediate**. Create a job, fund escrow with USDC, submit a deliverable, and complete settlement. [​](https://docs.arc.io/build/agentic-economy#why-arc-for-the-agentic-economy) Why Arc for the agentic economy ----------------------------------------------------------------------------------------------------------------- Arc is purpose-built for stablecoin finance. These capabilities directly support agentic economy applications. Onchain agent identity The [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) standard provides a native registry for agent identity, reputation events, and credential verification. [Register your first AI agent](https://docs.arc.io/arc/tutorials/register-your-first-ai-agent) to see it in action. Programmable job contracts The [ERC-8183](https://eips.ethereum.org/EIPS/eip-8183) standard defines the full job lifecycle: creation, escrow funding, deliverable submission, evaluation, and USDC settlement. [Create your first ERC-8183 job](https://docs.arc.io/arc/tutorials/create-your-first-erc-8183-job) to try the workflow. Sub-second finality Agents need fast, deterministic confirmation to close jobs and release funds. [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) — the guarantee that a confirmed transaction cannot be reversed — confirms transactions in under a second. USDC-native settlement Agents transact in a stable unit of account without managing volatile gas tokens. See [gas and fees](https://docs.arc.io/arc/references/gas-and-fees) for Arc’s stable fee model. Native compliance Built-in integration points for transaction monitoring and wallet screening from [Elliptic and TRM Labs](https://docs.arc.io/arc/tools/compliance-vendors) , essential for agent-to-agent value transfer at scale. Standard EVM tooling Agents can use any EVM-compatible SDK (ethers.js, viem, web3.py) to interact with Arc contracts — see [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) for details. [Deploy on Arc](https://docs.arc.io/arc/tutorials/deploy-on-arc) to get started, or use the [Arc MCP Server](https://docs.arc.io/ai/mcp) for AI-assisted development. Was this page helpful? YesNo [Stablecoin FX](https://docs.arc.io/build/stablecoin-fx) [Account abstraction](https://docs.arc.io/arc/tools/account-abstraction) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Running a node - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/concepts/running-a-node#content-area) Anyone can run an Arc node without permission. A node gives you independent verification of every block and transaction on the network, plus direct API access through a local JSON-RPC endpoint. Before setting up a node, review the [Node Requirements](https://docs.arc.io/arc/references/node-requirements) for hardware and software prerequisites, then follow [Run an Arc Node](https://docs.arc.io/arc/tutorials/run-an-arc-node) for step-by-step setup instructions. [​](https://docs.arc.io/arc/concepts/running-a-node#what-your-node-does) What your node does ----------------------------------------------------------------------------------------------- An Arc node performs three functions: * **Verifies every block.** Each block is cryptographically verified against the signatures of the validator set before it is accepted. Your node independently confirms that validators finalized each block. * **Executes every transaction.** Every transaction is re-executed locally through the EVM. Your node maintains its own copy of the complete blockchain state. * **Exposes a local RPC endpoint.** Your node provides a standard Ethereum JSON-RPC API (`http://localhost:8545`) for querying blocks, balances, and transactions, and for submitting calls directly against your own verified state. [​](https://docs.arc.io/arc/concepts/running-a-node#what-your-node-does-not-do) What your node does not do ------------------------------------------------------------------------------------------------------------- An Arc node is a full node, not a validator: * **Does not participate in consensus.** Your node does not propose or vote on blocks. Only permissioned [validators](https://docs.arc.io/arc/concepts/consensus-layer#proof-of-authority-validator-set) participate in the consensus process. * **Does not observe consensus messages.** Your node does not join the consensus gossip network. It verifies finalized decisions by checking the cryptographic signatures on each block. [​](https://docs.arc.io/arc/concepts/running-a-node#node-architecture) Node architecture ------------------------------------------------------------------------------------------- An Arc node runs two processes that work together: * **Consensus Layer (CL):** Built on [Malachite](https://docs.arc.io/arc/concepts/consensus-layer) , a high-performance Tendermint BFT implementation. The CL fetches blocks from the network, verifies their cryptographic signatures, and passes them to the EL for execution. * **Execution Layer (EL):** Built on [Reth](https://reth.rs/) , a Rust implementation of the Ethereum execution client. The EL executes transactions, maintains blockchain state, and serves the JSON-RPC API. The two processes communicate through either local IPC sockets (when running on the same host) or RPC (when running on separate hosts): * **IPC mode:** The EL and CL share two Unix sockets on the same machine. This is the default and simplest configuration. * **RPC mode:** The CL connects to the EL over HTTP using the Engine API and a shared JWT secret. Use this when the EL and CL run on different hosts. [​](https://docs.arc.io/arc/concepts/running-a-node#why-run-your-own-node) Why run your own node --------------------------------------------------------------------------------------------------- Running your own node instead of relying on a third-party [node provider](https://docs.arc.io/arc/tools/node-providers) gives you several advantages: * **Independent verification.** You verify every block and transaction yourself, rather than trusting a third party’s RPC responses. * **Data sovereignty.** Your blockchain data stays on your own infrastructure. No third party observes your queries or transaction patterns. * **No rate limits.** You control your own RPC endpoint without usage restrictions, request quotas, or throttling. * **Lower latency.** A local RPC endpoint eliminates network round-trips to external providers, which matters for latency-sensitive applications. If you prefer managed infrastructure, see [Node Providers](https://docs.arc.io/arc/tools/node-providers) for a list of third-party RPC services. To learn more about the layers that make up an Arc node, see [System Overview](https://docs.arc.io/arc/concepts/system-overview) , [Consensus Layer](https://docs.arc.io/arc/concepts/consensus-layer) , and [Execution Layer](https://docs.arc.io/arc/concepts/execution-layer) . Was this page helpful? YesNo [Monitor blocklist compliance](https://docs.arc.io/integrate/infrastructure/compliance) [Node requirements](https://docs.arc.io/arc/references/node-requirements) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Deploy on Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/deploy-on-arc#content-area) Arc is currently in its testnet phase. During this period, the network may experience instability or unplanned downtime. **Note:** Throughout this page, all references to Arc refer specifically to the Arc Testnet. In this tutorial, you’ll use [Foundry](https://getfoundry.sh/) to deploy and interact with the default `Counter` contract on the Arc Testnet. `forge init` scaffolds a working Solidity project — contract, tests, and deployment script — so you can ship to Arc without writing any new contract code. By the end, you’ll have configured Foundry for Arc, deployed `Counter`, and called it from the command line with `cast`. [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#prerequisites) Prerequisites ----------------------------------------------------------------------------------- Before you begin, ensure you have: * Access to a Unix-like shell (macOS, Linux, or Windows with [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) ) * Installed [`curl`](https://curl.se/) (used by the Foundry installer) * Installed a code editor such as [VS Code](https://code.visualstudio.com/) [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#step-1-set-up-foundry) Step 1. Set up Foundry ---------------------------------------------------------------------------------------------------- Install Foundry’s command-line tools (`forge`, `cast`, `anvil`, `chisel`): curl -L https://foundry.paradigm.xyz | bash foundryup Initialize a new Solidity project: forge init hello-arc && cd hello-arc This generates `src/Counter.sol`, a matching test file at `test/Counter.t.sol`, and a deployment script at `script/Counter.s.sol`. [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#step-2-configure-foundry-for-arc) Step 2. Configure Foundry for Arc -------------------------------------------------------------------------------------------------------------------------- Create a `.env` file in the project root with the Arc Testnet RPC URL: ARC_TESTNET_RPC_URL="https://rpc.testnet.arc.network" Load the variables into your shell: source .env Never commit your `.env` file to version control. Store private keys and sensitive variables securely. [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#step-3-test-the-contract) Step 3. Test the contract ---------------------------------------------------------------------------------------------------------- Run the included tests to compile the contract and verify it works locally: forge test The `Counter` tests pass, confirming compilation and local correctness. [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#step-4-deploy-to-arc-testnet) Step 4. Deploy to Arc Testnet ------------------------------------------------------------------------------------------------------------------ ### [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#4-1-create-and-fund-a-wallet) 4.1. Create and fund a wallet Generate a new keypair with `cast`: cast wallet new The command returns an address and private key: Successfully created new keypair. Address: 0xB815A0c4bC23930119324d4359dB65e27A846A2d Private key: 0xcc1b30a6af68ea9a9917f1dd••••••••••••••••••••••••••••••••••••••97c5 Keep your private key secure. Never share it or commit it to source control. Use environment variables or a secrets manager for any non-test deployment. Add the private key to your `.env` file and reload: PRIVATE_KEY="0x..." source .env Visit the [Circle Faucet](https://faucet.circle.com/) , select **Arc Testnet**, paste your wallet address, and request testnet USDC. Arc uses USDC as its native gas token — transaction fees are paid in USDC instead of a separate cryptocurrency — so this funds the wallet for deployment. Testnet USDC is for testing purposes only. It has no real-world value and must not be used in production. ### [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#4-2-deploy-the-contract) 4.2. Deploy the contract Deploy `Counter` to Arc Testnet: forge create src/Counter.sol:Counter \ --rpc-url $ARC_TESTNET_RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast After deployment completes, you’ll see output similar to: Deployer: 0xB815A0c4bC23930119324d4359dB65e27A846A2d Deployed to: 0x32368037b14819C9e5Dbe96b3d67C59b8c65c4BF Transaction hash: 0xeba0fcb5e528d586db0aeb2465a8fad0299330a9773ca62818a1827560a67346 Save the deployed address to your `.env` file and reload: COUNTER_ADDRESS="0x..." source .env ### [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#4-3-verify-the-contract-on-arc-testnet-explorer) 4.3. Verify the contract on Arc Testnet Explorer Arc Testnet Explorer runs Blockscout, so you can publish your contract’s source code with `forge verify-contract` and Foundry’s Blockscout verifier. Verified contracts show a **Contract** tab on the explorer with source code, ABI, and a read/write UI. Run the verification command from your Foundry project root, using the same compiler settings you used to deploy: forge verify-contract $COUNTER_ADDRESS src/Counter.sol:Counter \ --chain-id 5042002 \ --verifier blockscout \ --verifier-url https://testnet.arcscan.app/api/ If your contract’s constructor takes arguments, ABI-encode them with `cast abi-encode` and pass the result via `--constructor-args`. For example: forge verify-contract $CONTRACT_ADDRESS src/MyToken.sol:MyToken \ --chain-id 5042002 \ --verifier blockscout \ --verifier-url https://testnet.arcscan.app/api/ \ --constructor-args $(cast abi-encode "constructor(string,string)" "MyToken" "MTK") You can also submit source code manually from the [contract verification page](https://testnet.arcscan.app/contract-verification) on the explorer if you didn’t deploy with Foundry. After verification succeeds, open the deployed address on [testnet.arcscan.app](https://testnet.arcscan.app/) to confirm the **Contract** tab now shows the verified source and lets you call functions directly from the UI. [​](https://docs.arc.io/arc/tutorials/deploy-on-arc#step-5-interact-with-your-contract) Step 5. Interact with your contract ------------------------------------------------------------------------------------------------------------------------------ Confirm the deployment on the [Arc Testnet Explorer](https://testnet.arcscan.app/) by pasting the transaction hash from the previous step. Read the current counter value with `cast call`: cast call $COUNTER_ADDRESS "number()(uint256)" \ --rpc-url $ARC_TESTNET_RPC_URL A freshly deployed `Counter` returns `0`. Increment it onchain with `cast send`: cast send $COUNTER_ADDRESS "increment()" \ --rpc-url $ARC_TESTNET_RPC_URL \ --private-key $PRIVATE_KEY Re-run the `cast call` command. The returned value is now `1`. You now have a working deployment pipeline on Arc Testnet. To deploy production-ready tokens or NFTs without writing Solidity, see [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) . Was this page helpful? YesNo [EVM differences](https://docs.arc.io/build/evm-differences) [Skills](https://docs.arc.io/ai/skills) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # eCommerce Checkout - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/build/ecommerce#content-area) Add stablecoin checkout to your eCommerce application. Arc gives you [instant settlement](https://docs.arc.io/arc/concepts/deterministic-finality) , programmable escrow, and native [compliance hooks](https://docs.arc.io/arc/tools/compliance-vendors) so you can accept USDC and EURC payments with confidence. For peer-to-peer transfers, see [P2P Payments](https://docs.arc.io/build/payments) . For multi-currency conversion at checkout, see [Stablecoin FX](https://docs.arc.io/build/stablecoin-fx) . For agent-initiated purchases, see [Agentic Economy](https://docs.arc.io/build/agentic-economy) . [​](https://docs.arc.io/build/ecommerce#sample-apps) Sample apps ------------------------------------------------------------------- Production-ready examples on GitHub you can fork and customize. Arc commerce ------------ Accept USDC payments for in-app purchases using Circle Developer Controlled Wallets, Next.js, and Supabase. Arc escrow ---------- Conditional USDC settlement and refund logic for escrow-based checkout flows using Circle Wallets, Refund Protocol, and Contract Platform. [​](https://docs.arc.io/build/ecommerce#quickstarts) Quickstarts ------------------------------------------------------------------- Get up and running with checkout flows in minutes. Send tokens ----------- **Beginner**. Transfer stablecoins between wallets using [App Kit](https://docs.arc.io/app-kit/send) . Deploy contracts ---------------- **Beginner**. Deploy payment-related smart contracts (ERC-20 tokens, NFT receipts) on Arc testnet. Bridge tokens across blockchains -------------------------------- **Beginner**. Accept payments from other chains by [bridging USDC](https://docs.arc.io/app-kit/bridge) using App Kits. Monitor contract events ----------------------- **Intermediate**. Set up webhooks and event monitors for onchain activity such as payment confirmations. [​](https://docs.arc.io/build/ecommerce#why-arc-for-ecommerce) Why Arc for eCommerce --------------------------------------------------------------------------------------- Arc is purpose-built for stablecoin finance, which simplifies building eCommerce applications. Instant settlement [Sub-second deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) — the guarantee that a confirmed transaction cannot be reversed — means no chargebacks and immediate payment confirmation for both merchants and customers. Stablecoin-native gas Customers and merchants transact in USDC, not volatile gas tokens. See [gas and fees](https://docs.arc.io/arc/references/gas-and-fees) for details on Arc’s stable fee model. Compliance-ready Native hooks for KYC/AML screening from [Elliptic and TRM Labs](https://docs.arc.io/arc/tools/compliance-vendors) let you meet regulatory requirements without building custom infrastructure. Multichain reach Accept payments from any chain [supported by App Kit](https://docs.arc.io/app-kit/references/supported-blockchains) via [bridging](https://docs.arc.io/app-kit/bridge) , so customers can pay from the chain they already use. Programmable escrow Smart contracts enable milestone-based payment release, refund protocols, and conditional settlement logic for marketplace and SaaS checkout flows. See the [Arc escrow](https://github.com/circlefin/arc-escrow) sample app for a working example. Standard EVM tooling Build with Solidity, ethers.js, or viem. Your existing web3 development workflows work unchanged on Arc — see [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) for details. [Deploy on Arc](https://docs.arc.io/arc/tutorials/deploy-on-arc) to get started. Was this page helpful? YesNo [Payments](https://docs.arc.io/build/payments) [Stablecoin FX](https://docs.arc.io/build/stablecoin-fx) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Deploy on Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/deploy-on-arc#content-area) Arc is currently in its testnet phase. During this period, the network may experience instability or unplanned downtime. **Note:** Throughout this page, all references to Arc refer specifically to the Arc Testnet. In this tutorial, you’ll use [Foundry](https://getfoundry.sh/) to deploy and interact with the default `Counter` contract on the Arc Testnet. `forge init` scaffolds a working Solidity project — contract, tests, and deployment script — so you can ship to Arc without writing any new contract code. By the end, you’ll have configured Foundry for Arc, deployed `Counter`, and called it from the command line with `cast`. [​](https://docs.arc.io/integrate/deploy-on-arc#prerequisites) Prerequisites ------------------------------------------------------------------------------- Before you begin, ensure you have: * Access to a Unix-like shell (macOS, Linux, or Windows with [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) ) * Installed [`curl`](https://curl.se/) (used by the Foundry installer) * Installed a code editor such as [VS Code](https://code.visualstudio.com/) [​](https://docs.arc.io/integrate/deploy-on-arc#step-1-set-up-foundry) Step 1. Set up Foundry ------------------------------------------------------------------------------------------------ Install Foundry’s command-line tools (`forge`, `cast`, `anvil`, `chisel`): curl -L https://foundry.paradigm.xyz | bash foundryup Initialize a new Solidity project: forge init hello-arc && cd hello-arc This generates `src/Counter.sol`, a matching test file at `test/Counter.t.sol`, and a deployment script at `script/Counter.s.sol`. [​](https://docs.arc.io/integrate/deploy-on-arc#step-2-configure-foundry-for-arc) Step 2. Configure Foundry for Arc ---------------------------------------------------------------------------------------------------------------------- Create a `.env` file in the project root with the Arc Testnet RPC URL: ARC_TESTNET_RPC_URL="https://rpc.testnet.arc.network" Load the variables into your shell: source .env Never commit your `.env` file to version control. Store private keys and sensitive variables securely. [​](https://docs.arc.io/integrate/deploy-on-arc#step-3-test-the-contract) Step 3. Test the contract ------------------------------------------------------------------------------------------------------ Run the included tests to compile the contract and verify it works locally: forge test The `Counter` tests pass, confirming compilation and local correctness. [​](https://docs.arc.io/integrate/deploy-on-arc#step-4-deploy-to-arc-testnet) Step 4. Deploy to Arc Testnet -------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/deploy-on-arc#4-1-create-and-fund-a-wallet) 4.1. Create and fund a wallet Generate a new keypair with `cast`: cast wallet new The command returns an address and private key: Successfully created new keypair. Address: 0xB815A0c4bC23930119324d4359dB65e27A846A2d Private key: 0xcc1b30a6af68ea9a9917f1dd••••••••••••••••••••••••••••••••••••••97c5 Keep your private key secure. Never share it or commit it to source control. Use environment variables or a secrets manager for any non-test deployment. Add the private key to your `.env` file and reload: PRIVATE_KEY="0x..." source .env Visit the [Circle Faucet](https://faucet.circle.com/) , select **Arc Testnet**, paste your wallet address, and request testnet USDC. Arc uses USDC as its native gas token — transaction fees are paid in USDC instead of a separate cryptocurrency — so this funds the wallet for deployment. Testnet USDC is for testing purposes only. It has no real-world value and must not be used in production. ### [​](https://docs.arc.io/integrate/deploy-on-arc#4-2-deploy-the-contract) 4.2. Deploy the contract Deploy `Counter` to Arc Testnet: forge create src/Counter.sol:Counter \ --rpc-url $ARC_TESTNET_RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast After deployment completes, you’ll see output similar to: Deployer: 0xB815A0c4bC23930119324d4359dB65e27A846A2d Deployed to: 0x32368037b14819C9e5Dbe96b3d67C59b8c65c4BF Transaction hash: 0xeba0fcb5e528d586db0aeb2465a8fad0299330a9773ca62818a1827560a67346 Save the deployed address to your `.env` file and reload: COUNTER_ADDRESS="0x..." source .env ### [​](https://docs.arc.io/integrate/deploy-on-arc#4-3-verify-the-contract-on-arc-testnet-explorer) 4.3. Verify the contract on Arc Testnet Explorer Arc Testnet Explorer runs Blockscout, so you can publish your contract’s source code with `forge verify-contract` and Foundry’s Blockscout verifier. Verified contracts show a **Contract** tab on the explorer with source code, ABI, and a read/write UI. Run the verification command from your Foundry project root, using the same compiler settings you used to deploy: forge verify-contract $COUNTER_ADDRESS src/Counter.sol:Counter \ --chain-id 5042002 \ --verifier blockscout \ --verifier-url https://testnet.arcscan.app/api/ If your contract’s constructor takes arguments, ABI-encode them with `cast abi-encode` and pass the result via `--constructor-args`. For example: forge verify-contract $CONTRACT_ADDRESS src/MyToken.sol:MyToken \ --chain-id 5042002 \ --verifier blockscout \ --verifier-url https://testnet.arcscan.app/api/ \ --constructor-args $(cast abi-encode "constructor(string,string)" "MyToken" "MTK") You can also submit source code manually from the [contract verification page](https://testnet.arcscan.app/contract-verification) on the explorer if you didn’t deploy with Foundry. After verification succeeds, open the deployed address on [testnet.arcscan.app](https://testnet.arcscan.app/) to confirm the **Contract** tab now shows the verified source and lets you call functions directly from the UI. [​](https://docs.arc.io/integrate/deploy-on-arc#step-5-interact-with-your-contract) Step 5. Interact with your contract -------------------------------------------------------------------------------------------------------------------------- Confirm the deployment on the [Arc Testnet Explorer](https://testnet.arcscan.app/) by pasting the transaction hash from the previous step. Read the current counter value with `cast call`: cast call $COUNTER_ADDRESS "number()(uint256)" \ --rpc-url $ARC_TESTNET_RPC_URL A freshly deployed `Counter` returns `0`. Increment it onchain with `cast send`: cast send $COUNTER_ADDRESS "increment()" \ --rpc-url $ARC_TESTNET_RPC_URL \ --private-key $PRIVATE_KEY Re-run the `cast call` command. The returned value is now `1`. You now have a working deployment pipeline on Arc Testnet. To deploy production-ready tokens or NFTs without writing Solidity, see [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) . Was this page helpful? YesNo [Connect to Arc](https://docs.arc.io/integrate/connect-to-arc) [EVM differences](https://docs.arc.io/integrate/evm-differences) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Display Transaction Fees - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/wallets/fee-display#content-area) Arc uses USDC as its native gas token, so fees are inherently denominated in USD. The `eth_gasPrice` and `eth_feeHistory` RPCs return values in USDC wei (18 decimals). The EWMA fee smoothing model keeps fees predictable—dramatic spikes are unlikely. Display fees as `$X.XX` (or `~$0.01`), never as “Gwei” or “ETH.” [​](https://docs.arc.io/integrate/wallets/fee-display#prerequisites) Prerequisites ------------------------------------------------------------------------------------- Before you begin: * You have an RPC endpoint for Arc. * You are using an EIP-1559-compatible library (viem, ethers.js, or equivalent). * You understand that Arc’s native currency is USDC, not ETH. [​](https://docs.arc.io/integrate/wallets/fee-display#steps) Steps --------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/wallets/fee-display#step-1-fetch-the-current-gas-price) Step 1. Fetch the current gas price Use `eth_gasPrice` for a single value or `eth_feeHistory` for historical base fees and priority fee percentiles. import { createPublicClient, http, formatUnits } from "viem"; import { arc } from "viem/chains"; const client = createPublicClient({ chain: arc, transport: http("https://rpc.arc.circle.com"), }); // Simple: current gas price in USDC wei const gasPrice = await client.getGasPrice(); console.log("Gas price (wei):", gasPrice); // Detailed: recent base fee history const feeHistory = await client.getFeeHistory({ blockCount: 4, rewardPercentiles: [25, 50, 75], }); console.log("Latest base fee (wei):", feeHistory.baseFeePerGas.at(-1)); ### [​](https://docs.arc.io/integrate/wallets/fee-display#step-2-estimate-gas-for-the-transaction) Step 2. Estimate gas for the transaction Use `eth_estimateGas` to determine how much gas your transaction requires. // Native USDC transfer: ~21,000 gas const simpleTransferGas = 21_000n; // ERC-20 token transfer or contract call: use estimateGas const contractGas = await client.estimateGas({ account: "0xYourAddress", to: "0xContractAddress", data: encodedCalldata, }); Typical gas costs: | Transaction type | Approximate gas | | --- | --- | | Native USDC send | 21,000 | | ERC-20 transfer | ~65,000 | | Contract interaction | Varies—always estimate | ### [​](https://docs.arc.io/integrate/wallets/fee-display#step-3-build-eip-1559-fee-parameters) Step 3. Build EIP-1559 fee parameters Arc supports EIP-1559 transactions. Set `maxFeePerGas` and `maxPriorityFeePerGas`: const latestBlock = await client.getBlock(); const baseFee = latestBlock.baseFeePerGas!; // 2x base fee is generous—Arc's EWMA smoothing keeps fees stable const maxFeePerGas = baseFee * 2n; // Priority fee of 0 is acceptable on Arc (validators don't require tips) const maxPriorityFeePerGas = 0n; The effective gas price is `baseFeePerGas + priorityFee`. The base fee has a minimum of 20 Gwei in USDC terms. Because Arc uses an EWMA (exponentially weighted moving average) model to adjust the base fee gradually, fee spikes are smoothed out and prices remain predictable. ### [​](https://docs.arc.io/integrate/wallets/fee-display#step-4-calculate-the-maximum-fee-in-usd) Step 4. Calculate the maximum fee in USD Multiply the gas limit by `maxFeePerGas`, then convert from 18-decimal USDC wei to a dollar amount. function calculateMaxFeeUsd(gasLimit: bigint, maxFeePerGas: bigint): string { const maxCostWei = gasLimit * maxFeePerGas; // USDC has 18 decimals as the native gas token on Arc // Since 1 USDC = $1, the numeric value IS the USD cost const usdCost = formatUnits(maxCostWei, 18); return usdCost; } // Example: simple transfer const maxFee = calculateMaxFeeUsd(21_000n, maxFeePerGas); console.log(`Max fee: $${Number(maxFee).toFixed(6)}`); ### [​](https://docs.arc.io/integrate/wallets/fee-display#step-5-format-fees-for-display) Step 5. Format fees for display Show fees as a dollar amount. Use `~` to indicate the value is an estimate. function formatFeeDisplay(gasLimit: bigint, maxFeePerGas: bigint): string { const maxCostWei = gasLimit * maxFeePerGas; const usdValue = Number(formatUnits(maxCostWei, 18)); if (usdValue < 0.01) { return "< $0.01"; } return `~$${usdValue.toFixed(2)}`; } Standard libraries like ethers.js and viem label the native currency as “ETH” by default. You must override this in your UI. Displaying “0.00042 ETH” instead of ”~$0.01” confuses users and misrepresents the cost. [​](https://docs.arc.io/integrate/wallets/fee-display#worked-examples) Worked examples ----------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/wallets/fee-display#simple-usdc-transfer) Simple USDC transfer // Given: base fee = 20 Gwei (minimum), gas limit = 21,000 const baseFee = 20_000_000_000n; // 20 Gwei in wei const gasLimit = 21_000n; const maxFeePerGas = baseFee * 2n; // 40 Gwei const maxCostWei = gasLimit * maxFeePerGas; // 21,000 * 40,000,000,000 = 840,000,000,000,000 wei // = 0.00000084 USDC = $0.00000084 const display = formatFeeDisplay(gasLimit, maxFeePerGas); // Output: "< $0.01" ### [​](https://docs.arc.io/integrate/wallets/fee-display#contract-interaction) Contract interaction // Given: base fee = 500,000 Gwei (0.0005 USDC), gas limit = 65,000 const baseFee = 500_000_000_000_000n; // 500,000 Gwei in wei const gasLimit = 65_000n; const maxFeePerGas = baseFee * 2n; // 1,000,000 Gwei const maxCostWei = gasLimit * maxFeePerGas; // 65,000 * 1,000,000,000,000,000 = 65,000,000,000,000,000,000 wei // = 0.065 USDC = $0.065 const display = formatFeeDisplay(gasLimit, maxFeePerGas); // Output: "~$0.07" Users are only charged `gasUsed * effectiveGasPrice`. The difference between `maxFeePerGas` and the actual effective gas price is refunded. It’s safe to show the maximum estimate in your UI with language like “Max fee” or “Up to.” [​](https://docs.arc.io/integrate/wallets/fee-display#see-also) See also --------------------------------------------------------------------------- * [Transaction lifecycle](https://docs.arc.io/integrate/wallets/transaction-lifecycle) Was this page helpful? YesNo [Transaction Lifecycle](https://docs.arc.io/integrate/wallets/transaction-lifecycle) [Overview](https://docs.arc.io/integrate/infrastructure) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Custody Platform Integration - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/exchanges/custody#content-area) Arc is fully EVM-compatible—same key derivation, same signing curve (secp256k1), same transaction formats. If your custody platform supports custom EVM chains, you can add Arc without code changes. This guide covers what’s different and what to configure. Arc uses USDC as its native gas token. There is no separate ETH-like token to fund for gas. A single USDC balance covers both transfer value and transaction fees. [​](https://docs.arc.io/integrate/exchanges/custody#what-custody-engineers-need-to-know) What custody engineers need to know ------------------------------------------------------------------------------------------------------------------------------- Arc behaves like any EVM chain with three key differences: | Property | Ethereum | Arc | | --- | --- | --- | | Gas token | ETH (18 decimals) | USDC (6 display decimals, 18 internal decimals) | | Finality | Probabilistic (~12 min for safety) | Deterministic, sub-second. 1 confirmation = final. | | Gas funding | Requires separate ETH balance | No separate funding—USDC covers everything | Everything else is standard: * Address derivation: BIP-44 path `m/44'/60'/0'/0/x` * Signing algorithm: secp256k1 (identical to Ethereum) * Transaction types: Legacy (type 0) and EIP-1559 (type 2) both supported * Smart contracts: Solidity, same EVM opcodes * Multi-sig: Standard Ethereum multi-sig contracts (including SAFE) work without modification [​](https://docs.arc.io/integrate/exchanges/custody#network-configuration) Network configuration --------------------------------------------------------------------------------------------------- Use these parameters when registering Arc as a custom EVM chain: | Parameter | Testnet value | | --- | --- | | Chain ID | `5042002` | | RPC (HTTPS) | `https://rpc.testnet.arc.network` | | RPC (WebSocket) | `wss://rpc.testnet.arc.network` | | Block explorer | `https://testnet.arcscan.app` | | Native currency symbol | `USDC` | | Native currency decimals | `18` (internal representation) | | Display decimals | `6` | | EIP-1559 support | Yes (recommended) | | Minimum base fee | 20 Gwei | The native currency uses 18 decimals internally (like ETH uses Wei) but represents USDC which has 6 display decimals. Configure your balance display to show 6 decimal places to users while using 18 decimals for transaction construction. ### [​](https://docs.arc.io/integrate/exchanges/custody#usdc-erc-20-contract) USDC ERC-20 contract For ERC-20 interactions (approvals, `transferFrom`, allowance checks), the USDC contract is deployed at a precompile address: 0x3600000000000000000000000000000000000000 Native USDC transfers (simple sends) and ERC-20 `transfer()` calls both move the same underlying balance. There is no wrapped/unwrapped distinction. [​](https://docs.arc.io/integrate/exchanges/custody#register-arc-in-your-custody-platform) Register Arc in your custody platform ----------------------------------------------------------------------------------------------------------------------------------- Most custody platforms provide a “custom EVM chain” or “custom network” configuration flow. The following sections provide platform-specific guidance and a generic template. ### [​](https://docs.arc.io/integrate/exchanges/custody#provider-configuration-reference) Provider configuration reference | Provider | Integration method | Notes | | --- | --- | --- | | Fireblocks | Workspace Settings → Add EVM Network | Use “EVM-based chain” template. Set native asset to USDC. | | BitGo | Admin → Coin Management → Custom EVM | Register as custom ERC-20 chain. Configure 1-block finality. | | Cactus | Network Management → Add Custom Chain | Standard EVM chain wizard. Set gas token symbol to USDC. | | Zodia | Network Configuration → Custom EVM | Use provided chain ID and RPC. No special signing config needed. | | SAFE | Add custom network in web interface | Works natively—same contract addresses and factory. | | Taurus | TaurusProtect → Network Settings | Register RPC endpoint. Configure display decimals separately. | | Copper | ClearLoop → Custom Networks | Standard EVM registration. Set confirmation threshold to 1. | | CEFFU | Asset Management → Custom Chain | Follow EVM chain onboarding flow. Specify USDC as fee token. | ### [​](https://docs.arc.io/integrate/exchanges/custody#generic-custom-evm-chain-template) Generic custom EVM chain template Use this configuration when your platform has a general-purpose “add custom EVM chain” form: const arcNetworkConfig = { chainId: 5042002, name: "Arc Testnet", rpcUrl: "https://rpc.testnet.arc.network", wsUrl: "wss://rpc.testnet.arc.network", blockExplorer: "https://testnet.arcscan.app", nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 18, // Internal representation (like Wei for ETH) }, // Display configuration displayDecimals: 6, // Show balances with 6 decimal places // Finality configuration confirmationsRequired: 1, // Deterministic finality—1 block is final // Transaction type preference eip1559: true, // No separate gas token funding address needed }; [​](https://docs.arc.io/integrate/exchanges/custody#transaction-signing) Transaction signing ----------------------------------------------------------------------------------------------- Arc uses standard Ethereum transaction signing. No custom signing schemes or transaction types are required. ### [​](https://docs.arc.io/integrate/exchanges/custody#recommended-eip-1559-type-2) Recommended: EIP-1559 (type 2) import { createWalletClient, http, parseUnits } from "viem"; const arcTestnet = { id: 5042002, name: "Arc Testnet", nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 18 }, rpcUrls: { default: { http: ["https://rpc.testnet.arc.network"] }, }, }; const client = createWalletClient({ chain: arcTestnet, transport: http(), }); // Standard EIP-1559 transaction—identical to Ethereum const txHash = await client.sendTransaction({ account, // Your custody-managed account to: "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28", value: parseUnits("100", 18), // 100 USDC (18 decimals internally) maxFeePerGas: parseUnits("30", 9), // 30 Gwei maxPriorityFeePerGas: parseUnits("1", 9), // 1 Gwei tip }); // With deterministic finality, 1 confirmation means the transaction is final ### [​](https://docs.arc.io/integrate/exchanges/custody#key-signing-details) Key signing details * **Curve:** secp256k1 (same as Ethereum) * **Address derivation:** Keccak-256 hash of public key, last 20 bytes * **HD path:** `m/44'/60'/0'/0/x` (coin type 60, same as Ethereum) * **Transaction serialization:** RLP encoding, identical to Ethereum * **Chain ID in signature:** Required (EIP-155). Use `5042002` for testnet. [​](https://docs.arc.io/integrate/exchanges/custody#mpc-and-multi-sig-considerations) MPC and multi-sig considerations ------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/exchanges/custody#mpc-wallets) MPC wallets MPC (multi-party computation) signing works identically to Ethereum: * Key shares are generated for the secp256k1 curve * Threshold signing produces a standard ECDSA signature * The resulting address is a standard Ethereum address * No Arc-specific MPC protocol modifications are needed ### [​](https://docs.arc.io/integrate/exchanges/custody#multi-sig-wallets-safe) Multi-sig wallets (SAFE) SAFE multi-sig contracts deploy and operate on Arc without modification: * Same factory contract addresses * Same proxy pattern * Same signature verification logic * Transaction confirmation follows the same flow—but only 1 onchain confirmation is needed due to deterministic finality [​](https://docs.arc.io/integrate/exchanges/custody#operational-differences) Operational differences ------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/exchanges/custody#balance-management) Balance management Because USDC is both the transfer currency and the gas token, custody operations are simpler: * **No gas station network:** You don’t need a separate process to fund addresses with gas tokens. * **Unified balance:** A single `eth_getBalance` call returns the USDC balance available for both transfers and fees. * **Threshold alerts:** Set a single low-balance threshold instead of monitoring separate gas and transfer balances. import { createPublicClient, http, formatUnits } from "viem"; const client = createPublicClient({ transport: http("https://rpc.testnet.arc.network"), }); const balance = await client.getBalance({ address: "0xYourCustodyAddress", }); // Display with 6 decimals (USDC precision) const displayBalance = formatUnits(balance, 18); // For user-facing display, round to 6 decimal places const usdcDisplay = parseFloat(displayBalance).toFixed(6); ### [​](https://docs.arc.io/integrate/exchanges/custody#finality-and-confirmation-tracking) Finality and confirmation tracking Arc provides deterministic finality. Once a transaction is included in a block, it is final—no reorgs, no uncle blocks, no chain reorganizations. This means: * Set confirmation requirements to **1** in your custody platform * Remove pending-state tracking logic (no need to wait for additional confirmations) * Transaction receipts are immediately authoritative * No need to handle “dropped and replaced” scenarios If your platform requires a minimum confirmation count greater than 1, setting it to 1 is still safe on Arc. There is no security benefit to waiting for additional blocks. ### [​](https://docs.arc.io/integrate/exchanges/custody#blocklist-enforcement) Blocklist enforcement Arc enforces a protocol-level blocklist. Transactions from blocklisted sender addresses are rejected at the pre-mempool stage—they never appear onchain. For custody operations: * Check the blocklist before attempting to sign and broadcast * If a send fails with a blocklist rejection, it was not broadcast—no gas is consumed * Monitor compliance status of custody-managed addresses ### [​](https://docs.arc.io/integrate/exchanges/custody#fee-estimation) Fee estimation Gas estimation works identically to Ethereum: // Use standard eth_estimateGas and eth_gasPrice / eth_maxPriorityFeePerGas const gasEstimate = await client.estimateGas({ account: "0xYourCustodyAddress", to: "0xRecipientAddress", value: parseUnits("1000", 18), // 1000 USDC }); const feeData = await client.estimateFeesPerGas(); // Total fee in USDC (18 decimals internal) const maxFee = gasEstimate * feeData.maxFeePerGas; The minimum base fee is 20 Gwei. Set your gas price floor accordingly to avoid underpriced transaction rejections. [​](https://docs.arc.io/integrate/exchanges/custody#verification-and-testing) Verification and testing --------------------------------------------------------------------------------------------------------- After configuring Arc in your custody platform: 1. **Generate a test address** using your standard HD derivation path and verify it matches what you’d get on Ethereum for the same seed. 2. **Fund the address** with testnet USDC through the Arc faucet. 3. **Send a transaction** and confirm it appears on the [block explorer](https://testnet.arcscan.app/) within seconds. 4. **Verify the receipt** shows `status: 1` (success) with a single block confirmation. 5. **Test balance display** to ensure your platform shows the correct USDC amount (6 decimal places). The same address and private key work on both Arc and Ethereum. If your platform already derives Ethereum addresses for a given seed, those same addresses are valid on Arc. Was this page helpful? YesNo [Bridge USDC with CCTP](https://docs.arc.io/integrate/exchanges/cctp-bridging) [Add Arc to your on/off-ramp platform](https://docs.arc.io/integrate/on-off-ramps) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Set up Prometheus and Grafana for an Arc node - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#content-area) Arc is currently in its testnet phase. During this period, the network may experience instability or unplanned downtime. Throughout this page, all references to Arc refer specifically to the Arc Testnet. Deploy Prometheus and Grafana in Docker on the same host as your Arc node, scrape metrics from the Execution Layer (EL) and Consensus Layer (CL), and load the pre-built Arc dashboards. The stack binds to `127.0.0.1` only and is reached over SSH port forwarding from a remote operator workstation. [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#prerequisites) Prerequisites -------------------------------------------------------------------------------------------- * You have a running Arc node that meets the [node requirements](https://docs.arc.io/arc/references/node-requirements) , set up either from the [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) tutorial or [deployed as a systemd service](https://docs.arc.io/arc/tutorials/deploy-node-as-service) * You have installed [Docker Engine 24+ with BuildKit](https://docs.docker.com/engine/install/) and [Docker Compose v2](https://docs.docker.com/compose/install/) on the node host * Your node is started with `--metrics 127.0.0.1:9001` on the EL and `--metrics 127.0.0.1:29000` on the CL (the default in [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) ) * You have cloned the [`arc-node`](https://github.com/circlefin/arc-node) repository on the host, to reuse the pre-built Grafana dashboards [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-1-verify-the-metrics-endpoints) Step 1: Verify the metrics endpoints ----------------------------------------------------------------------------------------------------------------------------------------- Confirm both metrics endpoints are reachable on the host before deploying the stack. The Execution Layer serves metrics at the root path, not at `/metrics`: curl -s http://127.0.0.1:9001 | head curl -s http://127.0.0.1:29000/metrics | head Both commands return Prometheus-formatted text. If either fails, fix the node startup or `--metrics` flag before continuing. See [Monitor a node](https://docs.arc.io/arc/tutorials/monitor-a-node) for troubleshooting. [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-2-create-the-monitoring-directory) Step 2: Create the monitoring directory ----------------------------------------------------------------------------------------------------------------------------------------------- This guide stores all monitoring files under `$ARC_HOME/monitoring`. Create the directory layout: ARC_HOME="${ARC_HOME:-$HOME/.arc}" ARC_MONITORING="${ARC_MONITORING:-$ARC_HOME/monitoring}" mkdir -p "$ARC_MONITORING"/grafana-provisioning/datasources mkdir -p "$ARC_MONITORING"/grafana-provisioning/dashboards mkdir -p "$ARC_MONITORING"/prometheus-data mkdir -p "$ARC_MONITORING"/dashboards [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-3-copy-the-pre-built-arc-dashboards) Step 3: Copy the pre-built Arc dashboards --------------------------------------------------------------------------------------------------------------------------------------------------- The `arc-node` repository ships ready-to-use Grafana dashboards. From the `arc-node` repository root, copy them into the monitoring directory: cp -r deployments/monitoring/config-grafana/provisioning/dashboards-data/* \ "$ARC_MONITORING"/dashboards/ [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-4-configure-prometheus) Step 4: Configure Prometheus ------------------------------------------------------------------------------------------------------------------------- Write the following file to `$ARC_MONITORING/prometheus.yml`. The EL job scrapes the root path; the CL job scrapes `/metrics`: global: scrape_interval: 1s scrape_configs: - job_name: "arc_execution" metrics_path: "/" scrape_interval: 1s static_configs: - targets: ["127.0.0.1:9001"] labels: client_name: "reth" client_type: "execution" - job_name: "arc_consensus" metrics_path: "/metrics" scrape_interval: 1s static_configs: - targets: ["127.0.0.1:29000"] labels: client_name: "malachite" client_type: "consensus" [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-5-configure-the-grafana-datasource) Step 5: Configure the Grafana datasource ------------------------------------------------------------------------------------------------------------------------------------------------- Write the following file to `$ARC_MONITORING/grafana-provisioning/datasources/prometheus.yml`: apiVersion: 1 datasources: - name: prometheus uid: prometheus type: prometheus url: http://127.0.0.1:9090 isDefault: true editable: true [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-6-configure-dashboard-provisioning) Step 6: Configure dashboard provisioning ------------------------------------------------------------------------------------------------------------------------------------------------- Write the following file to `$ARC_MONITORING/grafana-provisioning/dashboards/default.yml`: apiVersion: 1 providers: - name: "arc" orgId: 1 folder: "" type: file disableDeletion: false editable: true updateIntervalSeconds: 10 options: path: /var/lib/grafana/dashboards [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-7-create-the-docker-compose-file) Step 7: Create the Docker Compose file --------------------------------------------------------------------------------------------------------------------------------------------- Write the following file to `$ARC_MONITORING/compose.yaml`: services: prometheus: image: prom/prometheus user: "0" network_mode: host command: - --config.file=/etc/prometheus/prometheus.yml - --storage.tsdb.path=/prometheus - --web.listen-address=127.0.0.1:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./prometheus-data:/prometheus restart: unless-stopped grafana: image: grafana/grafana-oss network_mode: host environment: GF_SERVER_HTTP_ADDR: 127.0.0.1 GF_SERVER_HTTP_PORT: 3000 GF_SECURITY_ADMIN_USER: admin GF_SECURITY_ADMIN_PASSWORD: admin volumes: - ./grafana-provisioning:/etc/grafana/provisioning:ro - ./dashboards:/var/lib/grafana/dashboards:ro restart: unless-stopped The Arc metrics endpoints in this guide bind to `127.0.0.1` only. `network_mode: host` is the simplest way for Prometheus to scrape them without changing the Arc node configuration or exposing metrics publicly. [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-8-start-the-stack) Step 8: Start the stack --------------------------------------------------------------------------------------------------------------- cd "$ARC_MONITORING" docker compose up -d Confirm both containers are running: docker compose ps Check that Grafana and Prometheus are healthy: curl -s http://127.0.0.1:3000/api/health curl -s http://127.0.0.1:9090/-/ready [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#step-9-access-the-dashboards) Step 9: Access the dashboards --------------------------------------------------------------------------------------------------------------------------- The stack binds Grafana and Prometheus to `127.0.0.1` only. They are not exposed to the public internet. If you are operating on the same machine, open: * `http://127.0.0.1:3000` for Grafana * `http://127.0.0.1:9090` for Prometheus If you are connecting from a remote workstation, use SSH port forwarding: ssh -N \ -L 3000:127.0.0.1:3000 \ -L 9090:127.0.0.1:9090 \ user@YOUR_SERVER_IP Then open on your local machine: * `http://localhost:3000` * `http://localhost:9090` Grafana default credentials from the Compose file above are username `admin` and password `admin`. Change them before exposing Grafana through any reverse proxy or shared access setup. After Prometheus scrapes both endpoints and Grafana loads the provisioned dashboards, you see metrics for execution layer activity, consensus layer activity, block height, validator telemetry, and connected peers. Newly started Prometheus instances need a short time to collect enough samples; if some panels are empty immediately after startup, wait a minute and refresh. [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#troubleshooting) Troubleshooting ------------------------------------------------------------------------------------------------ ### [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#browser-shows-err_connection_refused) Browser shows `ERR_CONNECTION_REFUSED` If you are connecting to a remote server, confirm you are opening `http://localhost:3000` and `http://localhost:9090` on your local machine, not `http://YOUR_SERVER_IP:3000`. Also confirm the SSH tunnel is still running. ### [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#prometheus-restarts-with-a-permissions-error) Prometheus restarts with a permissions error If Prometheus logs include an error about `queries.active` or write permission under `/prometheus`, fix ownership of the local data directory: sudo chown -R 65534:65534 "$ARC_MONITORING"/prometheus-data docker compose restart prometheus ### [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#grafana-is-healthy-but-dashboards-show-no-data) Grafana is healthy but dashboards show no data Confirm Prometheus targets are up: curl -s http://127.0.0.1:9090/api/v1/targets Re-check the Arc metrics endpoints: curl -s http://127.0.0.1:9001 | head curl -s http://127.0.0.1:29000/metrics | head If Prometheus reaches the targets but some panels remain empty, wait for more samples to accumulate and refresh. ### [​](https://docs.arc.io/arc/tutorials/set-up-node-monitoring#check-arc-node-health-directly) Check Arc node health directly You can verify the Arc node independently of Grafana: cast block-number --rpc-url http://127.0.0.1:8545 sudo journalctl -u arc-execution -f sudo journalctl -u arc-consensus -f Was this page helpful? YesNo [Monitor a node](https://docs.arc.io/arc/tutorials/monitor-a-node) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Add Arc to Your Bridge Protocol - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/infrastructure/bridges#content-area) Register Arc as a supported chain in your bridge or interoperability protocol. Arc’s deterministic finality, USDC-native gas model, and CCTP integration require specific configuration choices that differ from probabilistic-finality chains. [​](https://docs.arc.io/integrate/infrastructure/bridges#prerequisites) Prerequisites ---------------------------------------------------------------------------------------- Before you begin: * Familiarity with deploying contracts on EVM chains using Foundry or Hardhat * Access to an Arc RPC endpoint (`https://rpc.testnet.arc.network`) * A funded deployer wallet with USDC on Arc (USDC is the gas token) * Understanding of your protocol’s chain registration and relay architecture [​](https://docs.arc.io/integrate/infrastructure/bridges#chain-metadata) Chain metadata ------------------------------------------------------------------------------------------ Register Arc with the following parameters: | Property | Value | | --- | --- | | **Chain ID** | `5042002` (testnet) | | **RPC (HTTPS)** | `https://rpc.testnet.arc.network` | | **RPC (WebSocket)** | `wss://rpc.testnet.arc.network` | | **Block explorer** | `https://testnet.arcscan.app` | | **Native gas token** | USDC | | **Native token decimals** | 18 (native), 6 (ERC-20 interface) | | **EVM target** | Osaka hard fork | | **Block time** | Sub-second | | **Finality** | Deterministic (BFT consensus) | | **CCTP domain** | `26` | [​](https://docs.arc.io/integrate/infrastructure/bridges#steps) Steps ------------------------------------------------------------------------ ### [​](https://docs.arc.io/integrate/infrastructure/bridges#step-1-configure-finality-and-confirmation-requirements) Step 1. Configure finality and confirmation requirements Arc uses deterministic BFT finality. Once a block is committed, it is irreversible. There are no reorgs. Set your required confirmations to **1**. A single confirmation on Arc provides the same settlement guarantee as 64+ confirmations on Ethereum or 20+ on other L2s. | Chain type | Typical confirmations | Arc confirmations | | --- | --- | --- | | Ethereum (PoS) | 64 blocks (~13 min) | 1 block (<1 s) | | Optimistic rollups | 7 days (challenge period) | 1 block (<1 s) | | Other L2s | 10–20 blocks | 1 block (<1 s) | If your protocol uses `safe` or `finalized` block tags in RPC calls, both resolve to the latest block on Arc. You do not need separate handling for pending vs. finalized states. For your bridge configuration: import { defineChain } from "viem"; export const arcTestnet = defineChain({ id: 5042002, name: "Arc Testnet", nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 18, }, rpcUrls: { default: { http: ["https://rpc.testnet.arc.network"], webSocket: ["wss://rpc.testnet.arc.network"], }, }, blockExplorers: { default: { name: "Arcscan", url: "https://testnet.arcscan.app", }, }, }); // Bridge confirmation config const arcBridgeConfig = { chainId: 5042002, requiredConfirmations: 1, // Deterministic finality—1 is sufficient finalityType: "deterministic" as const, avgBlockTimeMs: 500, }; ### [​](https://docs.arc.io/integrate/infrastructure/bridges#step-2-route-usdc-via-cctp) Step 2. Route USDC via CCTP Arc uses Circle’s Cross-Chain Transfer Protocol (CCTP) as the canonical USDC bridge. CCTP uses a burn-and-mint model, meaning USDC on Arc is always native—never wrapped or locked. Do not deploy wrapped USDC (e.g., `wUSDC`, `USDC.e`) on Arc. Route all USDC transfers through CCTP to maintain fungibility with the native token. Wrapped variants create fragmented liquidity and user confusion. | Contract | Address | Notes | | --- | --- | --- | | **TokenMessengerV2** | [`0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA`](https://testnet.arcscan.app/address/0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA) | Initiates crosschain burns | | **MessageTransmitterV2** | [`0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275`](https://testnet.arcscan.app/address/0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275) | Receives and attests messages | | **USDC** | [`0x3600000000000000000000000000000000000000`](https://testnet.arcscan.app/address/0x3600000000000000000000000000000000000000) | Native USDC (ERC-20 interface) | | **CCTP domain** | `26` | Use in `depositForBurn` calls | If your bridge aggregates routes, prefer the CCTP path for USDC transfers to and from Arc over any lock-and-mint or liquidity-pool approach. ### [​](https://docs.arc.io/integrate/infrastructure/bridges#step-3-deploy-relay-and-adapter-contracts) Step 3. Deploy relay and adapter contracts Arc is EVM-compatible (Osaka hard fork target). Standard deployment tooling works without modification: import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; // Deploy using standard viem workflow const account = privateKeyToAccount( process.env.DEPLOYER_PRIVATE_KEY as `0x${string}`, ); const walletClient = createWalletClient({ account, chain: arcTestnet, transport: http("https://rpc.testnet.arc.network"), }); // CREATE2 deterministic deployment works as expected // Permit2 is available at the canonical address const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"; **Key deployment considerations:** * CREATE2 factory works at the standard address for deterministic deploys * Permit2 is deployed at `0x000000000022D473030F116dDEE9F6B43aC78BA3` * Multicall3 is available at the standard address * No EIP-4844 blob transactions—use `type: 2` (EIP-1559) transactions * `PREVRANDAO` always returns `0`—do not use it for randomness in relay selection If your contracts reference `block.prevrandao` for relay shuffling or random selection, replace it with an external oracle or deterministic round-robin approach on Arc. ### [​](https://docs.arc.io/integrate/infrastructure/bridges#step-4-fund-relayers-with-usdc-for-gas) Step 4. Fund relayers with USDC for gas Arc uses USDC as its gas token, not ETH. Your relay executors and watchers need USDC balances to submit transactions. import { parseUnits, formatUnits } from "viem"; // Check relayer gas balance (USDC with 18 decimals at native level) const balance = await publicClient.getBalance({ address: relayerAddress, }); console.log(`Relayer balance: ${formatUnits(balance, 18)} USDC`); // Fund relayer via ERC-20 transfer (6 decimals) const USDC_ADDRESS = "0x3600000000000000000000000000000000000000"; const fundTx = await walletClient.writeContract({ address: USDC_ADDRESS, abi: [\ {\ name: "transfer",\ type: "function",\ inputs: [\ { name: "to", type: "address" },\ { name: "amount", type: "uint256" },\ ],\ outputs: [{ type: "bool" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "transfer", args: [relayerAddress, parseUnits("1000", 6)], // 1,000 USDC }); Do not send ETH to relayers on Arc. ETH has no function on the network. Relayers need only USDC to pay for gas. **Gas cost estimation:** Arc’s fee model uses a smoothed moving average inspired by EIP-1559. Gas prices are stable and predictable. A typical relay transaction costs well under $0.01 in USDC gas fees. ### [​](https://docs.arc.io/integrate/infrastructure/bridges#step-5-connect-your-relay-infrastructure) Step 5. Connect your relay infrastructure For high-throughput relay operations, connect to Arc using WebSocket for real-time block and event streaming: import { createPublicClient, webSocket, http } from "viem"; // WebSocket for real-time event monitoring (relay watchers) const wsClient = createPublicClient({ chain: arcTestnet, transport: webSocket("wss://rpc.testnet.arc.network"), }); // HTTP for transaction submission (relay executors) const httpClient = createPublicClient({ chain: arcTestnet, transport: http("https://rpc.testnet.arc.network"), }); // Watch for bridge events with immediate finality const unwatch = wsClient.watchContractEvent({ address: YOUR_BRIDGE_CONTRACT, abi: bridgeAbi, eventName: "MessageSent", onLogs: (logs) => { // Each log is final on receipt—no need to wait for confirmations for (const log of logs) { processRelayMessage(log); } }, }); **Node provider options** for relay infrastructure: | Provider | Notes | | --- | --- | | Alchemy | Managed RPC, WebSocket support | | Blockdaemon | Enterprise-grade node infrastructure | | dRPC | Decentralized RPC network | | QuickNode | Managed endpoints with analytics | | Self-hosted | Run your own Arc node for lowest latency | For latency-sensitive relay operations, run a dedicated Arc node. Arc’s node software is lightweight and designed for high-throughput block production. ### [​](https://docs.arc.io/integrate/infrastructure/bridges#step-6-integrate-price-feeds-optional) Step 6. Integrate price feeds (optional) If your bridge logic requires price oracles for fee estimation or value validation, the following oracle providers are available on Arc: | Provider | Use case | | --- | --- | | Chainlink | Price feeds, CCIP | | Pyth | High-frequency price data | | Redstone | Pull-based oracle model | | Stork | Low-latency price feeds | [​](https://docs.arc.io/integrate/infrastructure/bridges#integration-checklist) Integration checklist -------------------------------------------------------------------------------------------------------- Use this checklist to verify your Arc integration is complete: * [ ] Chain ID `5042002` registered in your chain registry * [ ] Required confirmations set to `1` * [ ] USDC routed via CCTP (domain `26`)—no wrapped variants * [ ] Relay/adapter contracts deployed on Arc * [ ] Relayer wallets funded with USDC (not ETH) * [ ] WebSocket connection established for event monitoring * [ ] Gas estimation logic accounts for USDC denomination * [ ] No reliance on `PREVRANDAO` for randomness * [ ] No EIP-4844 blob transaction usage * [ ] Explorer links use `https://testnet.arcscan.app` [​](https://docs.arc.io/integrate/infrastructure/bridges#key-differences-from-other-evm-chains) Key differences from other EVM chains ---------------------------------------------------------------------------------------------------------------------------------------- | Consideration | Typical EVM chain | Arc | | --- | --- | --- | | **Confirmation safety** | Wait 12–64 blocks | 1 block is final | | **Gas token** | ETH or chain-native token | USDC | | **Reorg handling** | Required | Not needed | | **USDC bridging** | Lock-and-mint or liquidity pools | CCTP burn-and-mint (native) | | **Block time** | 2–12 seconds | Sub-second | | **Fee volatility** | High (auction-based) | Low (smoothed moving average) | Was this page helpful? YesNo [Index events](https://docs.arc.io/integrate/infrastructure/indexing-events) [Monitor blocklist compliance](https://docs.arc.io/integrate/infrastructure/compliance) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # EVM differences - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/build/evm-differences#content-area) Arc is an EVM-compatible Layer-1 blockchain. Solidity, Foundry, Hardhat, Viem, ethers.js, and standard Ethereum wallets work without modification, and you can deploy existing contracts unchanged in most cases. Arc targets the **Osaka** hard fork as its baseline and ships select features from Ethereum’s upcoming **Amsterdam** hard fork ahead of upstream, notably [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) (standard `Transfer` logs for native value movements). This page is the complete reference for where Arc’s protocol-level behavior diverges from Ethereum. Comparisons below are against Ethereum at the **Osaka** hard fork, Arc’s baseline. Most differences are transparent to application code, but a few change execution semantics in ways that matter when you port a contract. If you are porting an existing contract, start with the [Porting contracts to Arc checklist](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc) . Tools that locally simulate the EVM (such as Foundry’s `anvil`) run a standard EVM, not Arc’s, so they cannot reproduce Arc-specific behavior. Features that depend on it (the native-coin precompiles, EIP-7708 `Transfer` events, and USDC blocklist enforcement) only surface when you test against an Arc RPC endpoint. [​](https://docs.arc.io/build/evm-differences#usdc-as-the-native-gas-token) USDC as the native gas token ----------------------------------------------------------------------------------------------------------- Arc uses USDC as its native token. The single most important thing to understand is that **native USDC and the ERC-20 USDC interface are the same asset**, not two separate tokens that happen to share a name. | Interface | Decimals | Used for | | --- | --- | --- | | **Native** | 18 | Gas accounting, native sends, and `msg.value` | | **ERC-20** | 6 | application-level transfers, approvals, and allowances | The ERC-20 interface lives at the [USDC contract address](https://docs.arc.io/arc/references/contract-addresses#usdc) and provides familiar functions such as `transferFrom`, `approve`, and allowance management. A native send and an ERC-20 `transfer` both move the same underlying balance. Because both interfaces operate on one balance, `USDC.balanceOf(addr)` and `addr.balance` are two views of the same value. They use different decimals (6 vs 18), so never compare or mix their raw values without converting first. This single-asset model has consequences that do not exist on other EVM chains: * **The ERC-20 view truncates, so it is not exact.** The 6-decimal `balanceOf` drops anything below 1×10⁻⁶ USDC, so a native balance of `0.0000001` USDC reads as `0` and `100.0000001` USDC reads as `100`. A `balanceOf` of `0` does not imply a native balance of `0`. * **A native transfer can revert even with a sufficient balance** (for example, a transfer to the zero address, a transfer to or from a blocklisted address, or any transfer that would burn value). For the conceptual model, including EURC and USYC support, see [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) . For how balance changes surface as events, see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . [​](https://docs.arc.io/build/evm-differences#execution-and-opcode-differences) Execution and opcode differences ------------------------------------------------------------------------------------------------------------------- | Behavior | Ethereum (Osaka) | Arc | Developer impact | | --- | --- | --- | --- | | `PREVRANDAO` | Beacon chain RANDAO mix | Always returns `0` | No onchain randomness. Use an oracle or verifiable random function (VRF). | | `SELFDESTRUCT` | EIP-6780 semantics | EIP-6780 plus native value rules; emits a `Transfer` log on success (see [SELFDESTRUCT](https://docs.arc.io/build/evm-differences#selfdestruct)
) | Several patterns that succeed on mainnet revert on Arc. | | Non-zero-value `CALL` to a self-destructed account | Succeeds; value credited | **Reverts** (a transfer to a destructed account is a forbidden burn) | The largest semantic departure. See [SELFDESTRUCT](https://docs.arc.io/build/evm-differences#selfdestruct)
. | | `parentBeaconBlockRoot` / EIP-4788 | Beacon-roots contract returns the parent beacon root | Set to the parent execution block hash; the beacon-roots contract is omitted, so reads return empty (`0x`) | Do not treat the beacon-roots oracle as functional or as a randomness source. | | Blob transactions (EIP-4844, type-3) | Supported | Not supported; the mempool rejects type-3 transactions | Do not submit blob transactions. `BLOBHASH` returns `0` and `BLOBBASEFEE` returns `1`. | | Withdrawals (EIP-4895) | May be present | Always empty | `block.withdrawals` is always empty. | EIP-7702 set-code transactions, `CREATE2` (including EIP-7610 residual-storage behavior), and EIP-2935 historical block hashes all behave as on Ethereum. In particular, the EIP-2935 block-hash-history contract is deployed and functional, unlike the EIP-4788 beacon-roots contract noted above. [​](https://docs.arc.io/build/evm-differences#value-transfer-rules) Value transfer rules ------------------------------------------------------------------------------------------- Because the native token is USDC, value transfers are subject to rules that do not exist on a standard EVM chain. A native transfer can **revert even when the sender has sufficient balance**. * **Transfers to the zero address are forbidden.** A value-bearing transfer to `0x0` reverts with `"Zero address not allowed"`; a zero-value transfer to `0x0` succeeds. (Mint and burn, the only operations that involve `0x0`, go through the native-coin precompile.) * **Burning is forbidden.** Self-destructing to yourself with a balance, or transferring value to an account that has already self-destructed, reverts. * **The blocklist is enforced at runtime.** A value transfer to or from a blocklisted address reverts. An included transaction that reverts on a blocklist check still consumes gas. * **Sending native value to a contract is not guaranteed to succeed.** A call to a contract that forwards native value can revert for any of the reasons listed here, which breaks a common DeFi assumption. * **Sending to an address with no code (`EXTCODESIZE == 0`) succeeds** and emits a `Transfer` log. Sending value to a precompile address reverts. A liquidity pool that pairs native USDC against the ERC-20 USDC interface (as if they were two assets) is meaningless on Arc, because they are one asset. Review ported DeFi for this assumption. [​](https://docs.arc.io/build/evm-differences#selfdestruct) SELFDESTRUCT --------------------------------------------------------------------------- `SELFDESTRUCT` is allowed on Arc, including during contract deployment, and follows [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) (the account is fully deleted only if it was created in the same transaction). A self-destruct **reverts** when its value transfer would violate a native value rule: | Condition | Result | | --- | --- | | Beneficiary is the contract itself, with a balance | Reverts | | Beneficiary is the zero address, with a balance | Reverts | | Source or beneficiary is blocklisted | Reverts | | Beneficiary has already self-destructed | Reverts | | Balance is zero (any beneficiary) | Succeeds | Three behaviors differ from every other EVM chain and deserve attention: **1\. Self-destructing a contract that holds USDC moves that USDC out.** On Arc a contract’s USDC is its native balance, held in the account, so `SELFDESTRUCT` transfers it to the beneficiary. On other chains the contract’s ERC-20 USDC balance lives in the token contract and is unaffected by self-destruct. **2\. A non-zero-value call to a self-destructed account reverts.** On Ethereum, sending value to an address that self-destructed earlier in the same transaction succeeds. On Arc it is treated as a transfer to a destructed account, a forbidden burn, and reverts. // Within one transaction: contractA.selfDestruct(payable(b)); // succeeds; emits Transfer(A, B) // Later in the same transaction, any non-zero-value send to A: (bool ok, ) = address(contractA).call{value: 1}(""); // reverts on Arc **3\. A successful self-destruct that moves a balance emits a `Transfer` log.** Unlike Ethereum, the moved native value is recorded as an [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) `Transfer` log from the system emitter (18 decimals). Index it like any other native movement; see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . [​](https://docs.arc.io/build/evm-differences#native-usdc-transfer-events-eip-7708) Native USDC Transfer events (EIP-7708) ----------------------------------------------------------------------------------------------------------------------------- On a standard EVM chain, a plain native send emits no log. Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation emits a standard ERC-20 `Transfer` log from a system address for **every** native USDC movement: native sends, contract endowments, self-destruct transfers, and the precompile-backed mint, burn, and transfer operations. This gives indexers one universal record of balance changes. The system emitter log uses 18 decimals and is distinct from the ERC-20 USDC contract’s own 6-decimal `Transfer`. Match on the emitter address so you do not count the same movement twice. See [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) for emitter addresses, the exact log format, and indexing guidance. [​](https://docs.arc.io/build/evm-differences#fee-market-and-block-behavior) Fee market and block behavior ------------------------------------------------------------------------------------------------------------- * **The base fee is paid to the block beneficiary, not burned.** Arc has no EIP-1559 burn. Both the base fee and the priority fee are credited to the block’s beneficiary. * **The next block’s base fee is published in the parent header’s `extra_data`** (an 8-byte big-endian value). Read it there rather than re-deriving it. The fee is computed from an exponentially smoothed view of gas usage and is clamped to bounded minimum and maximum values, so it moves predictably. See [Stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) and [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) . * **Block timestamps are non-decreasing, not strictly increasing.** Timestamps come from the proposer’s wall clock at one-second granularity, so sub-second blocks may share a timestamp. Use the block number for ordering, and do not assume `block.timestamp` strictly increases between blocks. * **Finality is deterministic and instant.** Transactions finalize on inclusion; offchain systems can act after a single confirmation. See [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) . [​](https://docs.arc.io/build/evm-differences#known-limitation-draining-an-empty-account) Known limitation: draining an empty account ---------------------------------------------------------------------------------------------------------------------------------------- A transfer that would leave an account with a zero balance, a zero nonce, and no code currently reverts. This is a deliberate guard against a specific edge case and is reachable in practice only through a USDC meta-transaction that fully drains a brand-new account. Accounts that have ever sent a transaction (non-zero nonce) or hold code are unaffected and drain normally. This is a known, temporary limitation. A fix is planned for a future release. Was this page helpful? YesNo [Connect to Arc](https://docs.arc.io/arc/references/connect-to-arc) [Deploy on Arc](https://docs.arc.io/arc/tutorials/deploy-on-arc) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # EVM differences - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/evm-differences#content-area) Arc is an EVM-compatible Layer-1 blockchain. Solidity, Foundry, Hardhat, Viem, ethers.js, and standard Ethereum wallets work without modification, and you can deploy existing contracts unchanged in most cases. Arc targets the **Osaka** hard fork as its baseline and ships select features from Ethereum’s upcoming **Amsterdam** hard fork ahead of upstream, notably [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) (standard `Transfer` logs for native value movements). This page is the complete reference for where Arc’s protocol-level behavior diverges from Ethereum. Comparisons below are against Ethereum at the **Osaka** hard fork, Arc’s baseline. Most differences are transparent to application code, but a few change execution semantics in ways that matter when you port a contract. If you are porting an existing contract, start with the [Porting contracts to Arc checklist](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc) . Tools that locally simulate the EVM (such as Foundry’s `anvil`) run a standard EVM, not Arc’s, so they cannot reproduce Arc-specific behavior. Features that depend on it (the native-coin precompiles, EIP-7708 `Transfer` events, and USDC blocklist enforcement) only surface when you test against an Arc RPC endpoint. [​](https://docs.arc.io/integrate/evm-differences#usdc-as-the-native-gas-token) USDC as the native gas token --------------------------------------------------------------------------------------------------------------- Arc uses USDC as its native token. The single most important thing to understand is that **native USDC and the ERC-20 USDC interface are the same asset**, not two separate tokens that happen to share a name. | Interface | Decimals | Used for | | --- | --- | --- | | **Native** | 18 | Gas accounting, native sends, and `msg.value` | | **ERC-20** | 6 | application-level transfers, approvals, and allowances | The ERC-20 interface lives at the [USDC contract address](https://docs.arc.io/arc/references/contract-addresses#usdc) and provides familiar functions such as `transferFrom`, `approve`, and allowance management. A native send and an ERC-20 `transfer` both move the same underlying balance. Because both interfaces operate on one balance, `USDC.balanceOf(addr)` and `addr.balance` are two views of the same value. They use different decimals (6 vs 18), so never compare or mix their raw values without converting first. This single-asset model has consequences that do not exist on other EVM chains: * **The ERC-20 view truncates, so it is not exact.** The 6-decimal `balanceOf` drops anything below 1×10⁻⁶ USDC, so a native balance of `0.0000001` USDC reads as `0` and `100.0000001` USDC reads as `100`. A `balanceOf` of `0` does not imply a native balance of `0`. * **A native transfer can revert even with a sufficient balance** (for example, a transfer to the zero address, a transfer to or from a blocklisted address, or any transfer that would burn value). For the conceptual model, including EURC and USYC support, see [Stablecoin native model](https://docs.arc.io/arc/concepts/stablecoin-native-model) . For how balance changes surface as events, see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . [​](https://docs.arc.io/integrate/evm-differences#execution-and-opcode-differences) Execution and opcode differences ----------------------------------------------------------------------------------------------------------------------- | Behavior | Ethereum (Osaka) | Arc | Developer impact | | --- | --- | --- | --- | | `PREVRANDAO` | Beacon chain RANDAO mix | Always returns `0` | No onchain randomness. Use an oracle or verifiable random function (VRF). | | `SELFDESTRUCT` | EIP-6780 semantics | EIP-6780 plus native value rules; emits a `Transfer` log on success (see [SELFDESTRUCT](https://docs.arc.io/integrate/evm-differences#selfdestruct)
) | Several patterns that succeed on mainnet revert on Arc. | | Non-zero-value `CALL` to a self-destructed account | Succeeds; value credited | **Reverts** (a transfer to a destructed account is a forbidden burn) | The largest semantic departure. See [SELFDESTRUCT](https://docs.arc.io/integrate/evm-differences#selfdestruct)
. | | `parentBeaconBlockRoot` / EIP-4788 | Beacon-roots contract returns the parent beacon root | Set to the parent execution block hash; the beacon-roots contract is omitted, so reads return empty (`0x`) | Do not treat the beacon-roots oracle as functional or as a randomness source. | | Blob transactions (EIP-4844, type-3) | Supported | Not supported; the mempool rejects type-3 transactions | Do not submit blob transactions. `BLOBHASH` returns `0` and `BLOBBASEFEE` returns `1`. | | Withdrawals (EIP-4895) | May be present | Always empty | `block.withdrawals` is always empty. | EIP-7702 set-code transactions, `CREATE2` (including EIP-7610 residual-storage behavior), and EIP-2935 historical block hashes all behave as on Ethereum. In particular, the EIP-2935 block-hash-history contract is deployed and functional, unlike the EIP-4788 beacon-roots contract noted above. [​](https://docs.arc.io/integrate/evm-differences#value-transfer-rules) Value transfer rules ----------------------------------------------------------------------------------------------- Because the native token is USDC, value transfers are subject to rules that do not exist on a standard EVM chain. A native transfer can **revert even when the sender has sufficient balance**. * **Transfers to the zero address are forbidden.** A value-bearing transfer to `0x0` reverts with `"Zero address not allowed"`; a zero-value transfer to `0x0` succeeds. (Mint and burn, the only operations that involve `0x0`, go through the native-coin precompile.) * **Burning is forbidden.** Self-destructing to yourself with a balance, or transferring value to an account that has already self-destructed, reverts. * **The blocklist is enforced at runtime.** A value transfer to or from a blocklisted address reverts. An included transaction that reverts on a blocklist check still consumes gas. * **Sending native value to a contract is not guaranteed to succeed.** A call to a contract that forwards native value can revert for any of the reasons listed here, which breaks a common DeFi assumption. * **Sending to an address with no code (`EXTCODESIZE == 0`) succeeds** and emits a `Transfer` log. Sending value to a precompile address reverts. A liquidity pool that pairs native USDC against the ERC-20 USDC interface (as if they were two assets) is meaningless on Arc, because they are one asset. Review ported DeFi for this assumption. [​](https://docs.arc.io/integrate/evm-differences#selfdestruct) SELFDESTRUCT ------------------------------------------------------------------------------- `SELFDESTRUCT` is allowed on Arc, including during contract deployment, and follows [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) (the account is fully deleted only if it was created in the same transaction). A self-destruct **reverts** when its value transfer would violate a native value rule: | Condition | Result | | --- | --- | | Beneficiary is the contract itself, with a balance | Reverts | | Beneficiary is the zero address, with a balance | Reverts | | Source or beneficiary is blocklisted | Reverts | | Beneficiary has already self-destructed | Reverts | | Balance is zero (any beneficiary) | Succeeds | Three behaviors differ from every other EVM chain and deserve attention: **1\. Self-destructing a contract that holds USDC moves that USDC out.** On Arc a contract’s USDC is its native balance, held in the account, so `SELFDESTRUCT` transfers it to the beneficiary. On other chains the contract’s ERC-20 USDC balance lives in the token contract and is unaffected by self-destruct. **2\. A non-zero-value call to a self-destructed account reverts.** On Ethereum, sending value to an address that self-destructed earlier in the same transaction succeeds. On Arc it is treated as a transfer to a destructed account, a forbidden burn, and reverts. // Within one transaction: contractA.selfDestruct(payable(b)); // succeeds; emits Transfer(A, B) // Later in the same transaction, any non-zero-value send to A: (bool ok, ) = address(contractA).call{value: 1}(""); // reverts on Arc **3\. A successful self-destruct that moves a balance emits a `Transfer` log.** Unlike Ethereum, the moved native value is recorded as an [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) `Transfer` log from the system emitter (18 decimals). Index it like any other native movement; see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . [​](https://docs.arc.io/integrate/evm-differences#native-usdc-transfer-events-eip-7708) Native USDC Transfer events (EIP-7708) --------------------------------------------------------------------------------------------------------------------------------- On a standard EVM chain, a plain native send emits no log. Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation emits a standard ERC-20 `Transfer` log from a system address for **every** native USDC movement: native sends, contract endowments, self-destruct transfers, and the precompile-backed mint, burn, and transfer operations. This gives indexers one universal record of balance changes. The system emitter log uses 18 decimals and is distinct from the ERC-20 USDC contract’s own 6-decimal `Transfer`. Match on the emitter address so you do not count the same movement twice. See [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) for emitter addresses, the exact log format, and indexing guidance. [​](https://docs.arc.io/integrate/evm-differences#fee-market-and-block-behavior) Fee market and block behavior ----------------------------------------------------------------------------------------------------------------- * **The base fee is paid to the block beneficiary, not burned.** Arc has no EIP-1559 burn. Both the base fee and the priority fee are credited to the block’s beneficiary. * **The next block’s base fee is published in the parent header’s `extra_data`** (an 8-byte big-endian value). Read it there rather than re-deriving it. The fee is computed from an exponentially smoothed view of gas usage and is clamped to bounded minimum and maximum values, so it moves predictably. See [Stable fee design](https://docs.arc.io/arc/concepts/stable-fee-design) and [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) . * **Block timestamps are non-decreasing, not strictly increasing.** Timestamps come from the proposer’s wall clock at one-second granularity, so sub-second blocks may share a timestamp. Use the block number for ordering, and do not assume `block.timestamp` strictly increases between blocks. * **Finality is deterministic and instant.** Transactions finalize on inclusion; offchain systems can act after a single confirmation. See [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) . [​](https://docs.arc.io/integrate/evm-differences#known-limitation-draining-an-empty-account) Known limitation: draining an empty account -------------------------------------------------------------------------------------------------------------------------------------------- A transfer that would leave an account with a zero balance, a zero nonce, and no code currently reverts. This is a deliberate guard against a specific edge case and is reachable in practice only through a USDC meta-transaction that fully drains a brand-new account. Accounts that have ever sent a transaction (non-zero nonce) or hold code are unaffected and drain normally. This is a known, temporary limitation. A fix is planned for a future release. Was this page helpful? YesNo [Deploy on Arc](https://docs.arc.io/integrate/deploy-on-arc) [Overview](https://docs.arc.io/integrate/exchanges) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Stablecoin FX - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/build/stablecoin-fx#content-area) Build onchain stablecoin FX and swap products on Arc. With native multi-currency support, [sub-second settlement](https://docs.arc.io/arc/concepts/deterministic-finality) , and composable crosschain liquidity with [App Kits](https://docs.arc.io/app-kit) , Arc gives you the infrastructure for transparent, low-cost currency exchange. For direct wallet-to-wallet transfers, see [P2P Payments](https://docs.arc.io/build/payments) . For merchant checkout flows, see [eCommerce Checkout](https://docs.arc.io/build/ecommerce) . [​](https://docs.arc.io/build/stablecoin-fx#sample-apps) Sample apps ----------------------------------------------------------------------- Production-ready examples on GitHub you can fork and customize. Arc stablecoin FX ----------------- Real-time USDC and EURC swaps with configurable platform fee collection using App Kit, developer-controlled wallets, and Supabase. Arc fintech ----------- Multichain treasury system with crosschain capital movement using Circle Developer-Controlled Wallets, Gateway, and Bridge Kit. Arc multichain wallet --------------------- Unified USDC balance and crosschain transfers using Circle Gateway, Next.js, and Supabase. [​](https://docs.arc.io/build/stablecoin-fx#quickstarts) Quickstarts ----------------------------------------------------------------------- Get up and running with FX and swap flows in minutes. Swap tokens ----------- **Beginner**. Exchange one token for another on the same blockchain using the [Swap](https://docs.arc.io/app-kit/swap) capability. Swap tokens crosschain ---------------------- **Intermediate**. Combine [bridging](https://docs.arc.io/app-kit/bridge) and [swapping](https://docs.arc.io/app-kit/swap) to exchange tokens across blockchains. Bridge tokens across blockchains -------------------------------- **Beginner**. Transfer USDC between blockchains using [App Kit](https://docs.arc.io/app-kit/bridge) . Deposit and spend a Unified Balance ----------------------------------- **Intermediate**. Combine USDC from multiple blockchains into a single, instantly spendable balance. [​](https://docs.arc.io/build/stablecoin-fx#why-arc-for-stablecoin-fx) Why Arc for stablecoin FX --------------------------------------------------------------------------------------------------- Arc is purpose-built for stablecoin finance. These capabilities directly support FX and swap applications. Real-time settlement [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) — the guarantee that a confirmed transaction cannot be reversed or reorganized — settles FX trades in under a second, giving counterparties immediate certainty. Transparent pricing Onchain swap rates with configurable slippage tolerance give users full visibility into execution price. See [set slippage tolerance](https://docs.arc.io/app-kit/tutorials/swap/set-slippage-tolerance-or-stop-limit) . Multi-stablecoin pairs Swap between USDC, EURC, and other supported stablecoins natively on Arc. See [contract addresses](https://docs.arc.io/arc/references/contract-addresses) for available tokens and [swap fees](https://docs.arc.io/app-kit/concepts/swap-fees) for pricing details. Predictable fees Stable USDC-denominated [gas fees](https://docs.arc.io/arc/references/gas-and-fees) remove fee volatility from FX calculations, so you can quote accurate rates. Crosschain liquidity App Kits [bridges](https://docs.arc.io/app-kit/bridge) and [swaps](https://docs.arc.io/app-kit/swap) compose across chains for multi-leg FX routing without custom infrastructure. Fee monetization Collect a custom spread fee on every swap without writing new smart contracts. See [collect swap fee](https://docs.arc.io/app-kit/tutorials/swap/collect-swap-fee) . Was this page helpful? YesNo [eCommerce checkout](https://docs.arc.io/build/ecommerce) [Agentic economy](https://docs.arc.io/build/agentic-economy) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # P2P Payments - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/build/payments#content-area) Build direct, peer-to-peer payment flows using stablecoins on Arc. P2P payments let users send value directly to each other without an intermediary, settled onchain in under a second. From [simple transfers](https://docs.arc.io/app-kit/send) to full checkout experiences with [crosschain bridging](https://docs.arc.io/app-kit/bridge) using App Kits, Arc provides the infrastructure for fast, low-cost, [compliant](https://docs.arc.io/arc/tools/compliance-vendors) payments. For merchant-facing checkout flows, see [eCommerce Checkout](https://docs.arc.io/build/ecommerce) . For cross-currency swaps between stablecoins, see [Stablecoin FX](https://docs.arc.io/build/stablecoin-fx) . [​](https://docs.arc.io/build/payments#sample-apps) Sample apps ------------------------------------------------------------------ Production-ready examples on GitHub you can fork and customize. Arc commerce ------------ Accept USDC payments for in-app purchases using Circle Developer Controlled Wallets, Next.js, and Supabase. Arc multichain wallet --------------------- Unified USDC balance and crosschain transfers using Circle Gateway, Next.js, and Supabase. Arc fintech ----------- Multichain treasury system with crosschain capital movement using Circle Developer-Controlled Wallets, Gateway, and Bridge Kit. Arc nanopayments ---------------- Autonomous AI agent pays for premium API endpoints in USDC fractions using Circle Nanopayments and the x402 protocol. [​](https://docs.arc.io/build/payments#quickstarts) Quickstarts ------------------------------------------------------------------ Get up and running with payment flows in minutes. Send tokens ----------- **Beginner**. Transfer stablecoins between wallets using [App Kit](https://docs.arc.io/app-kit/send) . Bridge tokens across blockchains -------------------------------- **Beginner**. Move USDC across blockchains using [App Kits](https://docs.arc.io/app-kit/bridge) . Unified Balance deposit and spend --------------------------------- **Intermediate**. Aggregate USDC across blockchains into a single spendable balance with [Unified Balance](https://docs.arc.io/app-kit/unified-balance) , which consolidates multichain USDC into one virtual balance. [​](https://docs.arc.io/build/payments#why-arc-for-payments) Why Arc for payments ------------------------------------------------------------------------------------ Arc is purpose-built for stablecoin finance. These capabilities directly support payment applications. Sub-second finality [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) — the guarantee that a confirmed transaction cannot be reversed or reorganized — means payments confirm in under a second, giving senders and recipients immediate certainty. Near-zero gas fees USDC-denominated gas at stable, predictable prices keeps transaction costs minimal. See [gas and fees](https://docs.arc.io/arc/references/gas-and-fees) for current rates. Native compliance hooks Built-in integration points for transaction monitoring and wallet screening from providers like [Elliptic and TRM Labs](https://docs.arc.io/arc/tools/compliance-vendors) . Multi-stablecoin support Native support for USDC and EURC. See [contract addresses](https://docs.arc.io/arc/references/contract-addresses) for token details, and use the [Swap](https://docs.arc.io/app-kit/swap) capability to exchange between currencies. Deterministic ordering Transaction ordering guarantees prevent front-running and ensure payments settle in the order they are submitted. Learn more about Arc’s [consensus layer](https://docs.arc.io/arc/concepts/consensus-layer) . Standard EVM tooling Full [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) means your existing Solidity, Hardhat, and Foundry workflows work unchanged. [Deploy on Arc](https://docs.arc.io/arc/tutorials/deploy-on-arc) to get started. Was this page helpful? YesNo [Create your first ERC-8183 job](https://docs.arc.io/arc/tutorials/create-your-first-erc-8183-job) [eCommerce checkout](https://docs.arc.io/build/ecommerce) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Connect to Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/connect-to-arc#content-area) Connect a wallet to Arc Testnet using one-click setup or manual configuration. [​](https://docs.arc.io/integrate/connect-to-arc#wallet-setup) Wallet setup ------------------------------------------------------------------------------ Use the button below to add Arc Testnet to your wallet automatically. ### [​](https://docs.arc.io/integrate/connect-to-arc#manual-setup) Manual setup Arc uses USDC as the native gas token (18 decimals). If your wallet supports **custom gas tokens**, ensure display/decimals are set correctly. Wallets that don’t support custom gas tokens still work for signing and sending transactions — balances may display as “ETH” but the underlying token is USDC. See [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) for details. * MetaMask * Rabby * Coinbase Wallet * Rainbow 1 Open network settings Open MetaMask → **Settings** → **Networks** → **Add network** → **Add a network manually**. 2 Enter network details | Field | Value | | --- | --- | | **Network name** | Arc Testnet | | **New RPC URL** | `https://rpc.testnet.arc.network` | | **Chain ID** | 5042002 | | **Currency symbol** | USDC | | **Explorer URL** | `https://testnet.arcscan.app` | 3 Save and switch Click **Save**, then switch to Arc Testnet. 1 Open network settings Open Rabby → click the **network selector** (top-left) → **Add Custom Network**. 2 Enter network details | Field | Value | | --- | --- | | **Chain Name** | Arc Testnet | | **Chain ID** | 5042002 | | **RPC URL** | `https://rpc.testnet.arc.network` | | **Currency** | USDC | | **Block Explorer** | `https://testnet.arcscan.app` | 3 Confirm and switch Click **Confirm**, then select Arc Testnet from the network list. 1 Open network settings Open Coinbase Wallet → **Settings** → **Networks** → **Add custom network**. 2 Enter network details | Field | Value | | --- | --- | | **Network name** | Arc Testnet | | **RPC URL** | `https://rpc.testnet.arc.network` | | **Chain ID** | 5042002 | | **Currency symbol** | USDC | | **Block explorer** | `https://testnet.arcscan.app` | 3 Save and switch Click **Save**, then switch to Arc Testnet. 1 Open network settings Open Rainbow → **Settings** (gear icon) → **Networks** → **Custom Network**. 2 Enter network details | Field | Value | | --- | --- | | **Network name** | Arc Testnet | | **RPC URL** | `https://rpc.testnet.arc.network` | | **Chain ID** | 5042002 | | **Symbol** | USDC | | **Block explorer** | `https://testnet.arcscan.app` | 3 Save and switch Click **Save**, then switch to Arc Testnet. [​](https://docs.arc.io/integrate/connect-to-arc#network-details) Network details ------------------------------------------------------------------------------------ | Parameter | Value | | --- | --- | | Network | Arc Testnet | | Chain ID | `5042002` | | Currency | USDC | | Explorer | [testnet.arcscan.app](https://testnet.arcscan.app/) | | Faucet | [faucet.circle.com](https://faucet.circle.com/) | ### [​](https://docs.arc.io/integrate/connect-to-arc#rpc-endpoints) RPC endpoints Primary Blockdaemon dRPC QuickNode https://rpc.testnet.arc.network https://rpc.blockdaemon.testnet.arc.network https://rpc.drpc.testnet.arc.network https://rpc.quicknode.testnet.arc.network ### [​](https://docs.arc.io/integrate/connect-to-arc#websocket-endpoints) WebSocket endpoints Primary Blockdaemon dRPC QuickNode wss://rpc.testnet.arc.network wss://rpc.blockdaemon.testnet.arc.network:443/websocket wss://rpc.drpc.testnet.arc.network wss://rpc.quicknode.testnet.arc.network [​](https://docs.arc.io/integrate/connect-to-arc#frontend-wallet-libraries) Frontend wallet libraries -------------------------------------------------------------------------------------------------------- Use `wagmi` and `viem` to integrate Arc Testnet into [ConnectKit](https://family.co/docs/connectkit) , [Reown AppKit](https://docs.reown.com/appkit/overview) , or a bare [WalletConnect](https://docs.walletconnect.com/) connector. ### [​](https://docs.arc.io/integrate/connect-to-arc#arc-testnet-chain-definition) Arc Testnet chain definition `viem` ships Arc Testnet as a built-in chain. No manual definition is needed. import { arcTestnet } from "viem/chains"; ### [​](https://docs.arc.io/integrate/connect-to-arc#configure-wallet-connection) Configure wallet connection Pick the tab that matches your setup. * ConnectKit * Reown AppKit * WalletConnect npm install connectkit wagmi viem import { arcTestnet, mainnet } from "viem/chains"; import { createConfig, http } from "wagmi"; import { getDefaultConfig } from "connectkit"; const config = createConfig( getDefaultConfig({ chains: [arcTestnet, mainnet], transports: { [arcTestnet.id]: http("https://rpc.testnet.arc.network"), [mainnet.id]: http("https://cloudflare-eth.com"), }, walletConnectProjectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, appName: "Your App Name", }), ); When `mainnet` is omitted from `chains`, ConnectKit falls back to its own `eth.merkle.io` endpoint for ENS name lookups, which blocks cross-origin browser requests. Include `mainnet` with a CORS-safe transport (such as the Cloudflare endpoint shown earlier) to prevent this. npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query Import `defineChain` from `@reown/appkit/networks`, not from `viem`. AppKit requires two additional fields (`caipNetworkId` and `chainNamespace`) that `viem`’s version omits; using the wrong import causes a runtime error. import { defineChain } from "@reown/appkit/networks"; import { WagmiAdapter } from "@reown/appkit-adapter-wagmi"; import { createAppKit } from "@reown/appkit/react"; const projectId = process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID; const arcTestnet = defineChain({ id: 5042002, caipNetworkId: "eip155:5042002", chainNamespace: "eip155", name: "Arc Testnet", nativeCurrency: { decimals: 18, name: "USDC", symbol: "USDC" }, rpcUrls: { default: { http: ["https://rpc.testnet.arc.network"], webSocket: ["wss://rpc.testnet.arc.network"], }, }, blockExplorers: { default: { name: "ArcScan", url: "https://testnet.arcscan.app" }, }, testnet: true, }); const wagmiAdapter = new WagmiAdapter({ networks: [arcTestnet], projectId, }); createAppKit({ adapters: [wagmiAdapter], networks: [arcTestnet], projectId, metadata: { name: "Your App Name", description: "Your App Description", url: "https://yourdomain.com", icons: ["https://yourdomain.com/icon.png"], }, }); Get a project ID at [dashboard.reown.com](https://dashboard.reown.com/) . The same project ID works for the bare WalletConnect connector. For `wagmi` apps that need WalletConnect sessions without Reown AppKit’s full modal UI. import { arcTestnet } from "viem/chains"; import { createConfig, http } from "wagmi"; import { walletConnect } from "wagmi/connectors"; const config = createConfig({ chains: [arcTestnet], connectors: [\ walletConnect({\ projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,\ metadata: {\ name: "Your App",\ description: "Your App Description",\ url: "https://yourdomain.com",\ icons: ["https://yourdomain.com/icon.png"],\ },\ }),\ ], transports: { [arcTestnet.id]: http("https://rpc.testnet.arc.network"), }, }); Arc Testnet (chain ID 5042002) is not registered in the WalletConnect chain registry. On first connection, WalletConnect issues a `wallet_addEthereumChain` call per user. Was this page helpful? YesNo [Overview](https://docs.arc.io/integrate) [Deploy on Arc](https://docs.arc.io/integrate/deploy-on-arc) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Monitor Blocklist Compliance - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/infrastructure/compliance#content-area) 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. [​](https://docs.arc.io/integrate/infrastructure/compliance#prerequisites) 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) [​](https://docs.arc.io/integrate/infrastructure/compliance#contracts-and-addresses) 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` | [​](https://docs.arc.io/integrate/infrastructure/compliance#steps) Steps --------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/infrastructure/compliance#step-1-understand-the-three-enforcement-stages) 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. | 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. ### [​](https://docs.arc.io/integrate/infrastructure/compliance#step-2-monitor-blocklist-events) 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. 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); }); ### [​](https://docs.arc.io/integrate/infrastructure/compliance#step-3-include-memo-and-multicall3from-in-your-monitoring-scope) 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. 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`. 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 { 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); } } } ### [​](https://docs.arc.io/integrate/infrastructure/compliance#step-4-build-a-transaction-decision-tree) 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 | interface ComplianceResult { flagged: boolean; reason?: string; } async function evaluateTransaction(txHash: string): Promise { 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 }; } ### [​](https://docs.arc.io/integrate/infrastructure/compliance#step-5-integrate-compliance-vendor-apis) 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. // Example: screen an address against a compliance vendor API async function screenAddress( address: string, txHash: string, ): Promise { 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](https://docs.arc.io/arc/tools/compliance-vendors) . [​](https://docs.arc.io/integrate/infrastructure/compliance#see-also) See also --------------------------------------------------------------------------------- * [Compliance vendors](https://docs.arc.io/arc/tools/compliance-vendors) —Elliptic and TRM Labs integration details * [Infrastructure overview](https://docs.arc.io/integrate/infrastructure) —Arc architectural differences relevant to compliance monitoring Was this page helpful? YesNo [Add Arc to your bridge](https://docs.arc.io/integrate/infrastructure/bridges) [Running a node](https://docs.arc.io/arc/concepts/running-a-node) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Quickstart: Deposit and spend a Unified Balance - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#content-area) Use this quickstart to deposit USDC into a Unified Balance, check the combined balance, and spend from it on another blockchain. The examples deposit from Base Sepolia and Solana Devnet, then spend on Arc Testnet. Choose the wallet model that matches your application. Use a browser wallet when the end user signs in the client, or use Circle Wallets when you manage developer-controlled wallets through Circle. * Browser wallet * Circle Wallets Use this flow to deposit and spend a Unified Balance with connected browser wallets. The examples use Base Sepolia, Solana Devnet, and Arc Testnet. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Created an EVM wallet using a wallet provider such as [MetaMask](https://metamask.io/) and added the [Base Sepolia](https://docs.base.org/docs/network-information#base-testnet-sepolia) and [Arc Testnet](https://docs.arc.io/arc/references/connect-to-arc#wallet-setup) networks. * Created a Solana wallet (for example, [Phantom](https://phantom.app/) or [Solflare](https://solflare.com/) ) on Devnet. * Funded your wallets with testnet tokens: * Get testnet USDC from the [Circle Faucet](https://faucet.circle.com/) on Base Sepolia and Solana Devnet. * Get testnet ETH on Base Sepolia from a [public faucet](https://www.alchemy.com/faucets/base-sepolia) (needed for deposit and spend transactions on Base Sepolia). * Get SOL for Solana Devnet from the [Solana Faucet](https://faucet.solana.com/) . * Fund the connected EVM wallet on Arc Testnet if needed (USDC on Arc can cover gas for the destination credit when you spend on Arc). * Obtained an Arc Testnet address that will receive USDC when you spend on Arc Testnet. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-1-set-up-the-project) Step 1. Set up the project -------------------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#1-1-create-the-project-and-install-dependencies) 1.1. Create the project and install dependencies Create a new directory, install the App Kit packages, and add local browser demo tooling: Shell # Set up your directory and initialize a Node.js project mkdir app-kit-unified-balance-browser-wallet cd app-kit-unified-balance-browser-wallet npm init -y npm pkg set type=module # Install App Kit packages npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem @circle-fin/adapter-solana # Install TypeScript and a local Vite dev server for the browser demo npm install --save-dev typescript vite Only need a Unified Balance and want a lighter install than the full App Kit SDK? Install the standalone Unified Balance Kit instead: `@circle-fin/unified-balance-kit` ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#1-2-configure-typescript-optional) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-2-connect-browser-wallets) Step 2. Connect browser wallets ------------------------------------------------------------------------------------------------------------------------------------------------ This step shows the core browser wallet integration flow: discover an [`EIP-6963`](https://eips.ethereum.org/EIPS/eip-6963) provider, create App Kit adapters from the selected providers, and pass those adapters into App Kit SDK methods.The snippets below keep wallet discovery, wallet connection, and adapter setup in small helper functions for readability. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#2-1-discover-a-browser-wallet-with-eip-6963) 2.1. Discover a browser wallet with `EIP-6963` This pattern is standards-based. The example uses MetaMask as the selected wallet, but the discovery flow works with any wallet that announces an `EIP-6963` provider. TypeScript import type { EIP1193Provider } from "viem"; type EIP6963ProviderInfo = { uuid: string; name: string; icon: string; rdns: string; }; type EIP6963ProviderDetail = { info: EIP6963ProviderInfo; provider: EIP1193Provider; }; declare global { interface WindowEventMap { "eip6963:announceProvider": CustomEvent; } } async function discoverBrowserWallets(): Promise { const providers = new Map(); const handleProviderAnnouncement = ( event: WindowEventMap["eip6963:announceProvider"], ) => { providers.set(event.detail.info.uuid, event.detail); }; window.addEventListener( "eip6963:announceProvider", handleProviderAnnouncement, ); window.dispatchEvent(new Event("eip6963:requestProvider")); await new Promise((resolve) => window.setTimeout(resolve, 250)); window.removeEventListener( "eip6963:announceProvider", handleProviderAnnouncement, ); return [...providers.values()]; } ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#2-2-connect-the-wallet-and-request-account-access) 2.2. Connect the wallet and request account access After you select a provider, request account access before calling an App Kit SDK method. This should happen in a user-triggered action such as a `Connect wallet` button. TypeScript async function connectWallet(provider: EIP1193Provider) { await provider.request({ method: "eth_requestAccounts", params: undefined, // Required by the provider type even though this method has no params. }); const accounts = (await provider.request({ method: "eth_accounts", params: undefined, // Required by the provider type even though this method has no params. })) as string[]; return { connectedAddress: accounts[0] ?? null, }; } Keep wallet connection and App Kit actions as separate user actions. This avoids overlapping wallet permission or chain-switch requests while a previous wallet prompt is still pending. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#2-3-create-an-evm-adapter-for-unified-balance) 2.3. Create an EVM adapter for Unified Balance This quickstart uses adapter-only balance checks and spend sources so the SDK can choose source blockchains automatically. Configure the EVM adapter with the EVM chains used in this flow so automatic allocation has chain context for balance discovery. TypeScript import { ArcTestnet, BaseSepolia } from "@circle-fin/app-kit/chains"; import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2"; async function connectEvmBrowserWallet() { const providers = await discoverBrowserWallets(); const selectedWallet = providers.find( ({ info }) => info.rdns === "io.metamask" || info.name === "MetaMask", ) ?? providers[0]; if (!selectedWallet) { throw new Error("No EIP-6963 browser wallet found"); } const { connectedAddress } = await connectWallet(selectedWallet.provider); const adapter = await createViemAdapterFromProvider({ provider: selectedWallet.provider, capabilities: { supportedChains: [BaseSepolia, ArcTestnet], }, }); return { adapter, connectedAddress, walletName: selectedWallet.info.name, }; } ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#2-4-connect-the-solana-wallet-and-create-a-solana-adapter) 2.4. Connect the Solana wallet and create a Solana adapter This pattern assumes a Solana browser wallet that exposes `window.solana`. Keep wallet connection and App Kit actions as separate user actions so the wallet is fully connected before you call an App Kit SDK method. TypeScript import { SolanaDevnet } from "@circle-fin/app-kit/chains"; import { createSolanaAdapterFromProvider } from "@circle-fin/adapter-solana"; import type { CreateSolanaAdapterFromProviderParams } from "@circle-fin/adapter-solana"; type SolanaWalletProvider = CreateSolanaAdapterFromProviderParams["provider"]; declare global { interface Window { solana?: SolanaWalletProvider; } } async function connectSolanaWallet(provider: SolanaWalletProvider) { const connection = await provider.connect(); return { connectedAddress: connection.publicKey?.toString() ?? provider.publicKey?.toString() ?? null, }; } async function connectSolanaBrowserWallet() { const provider = window.solana; if (!provider) { throw new Error("No Solana browser wallet found"); } const { connectedAddress } = await connectSolanaWallet(provider); const adapter = await createSolanaAdapterFromProvider({ provider, capabilities: { supportedChains: [SolanaDevnet], }, }); return { adapter, connectedAddress, }; } [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-3-deposit-into-a-unified-balance) Step 3. Deposit into a Unified Balance -------------------------------------------------------------------------------------------------------------------------------------------------------------- In this step, you’ll deposit from Base Sepolia and Solana Devnet. The EVM deposit uses the EVM browser wallet adapter from the previous step. The Solana deposit uses the Solana browser wallet adapter from the previous step.The examples in the remaining steps reuse the same `kit` instance and `Adapter` type. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#3-1-deposit-from-base-sepolia) 3.1. Deposit from Base Sepolia Call `kit.unifiedBalance.deposit` with the connected EVM browser wallet adapter. Before the deposit, switch the browser wallet to Base Sepolia so the deposit authorization is signed on the source chain: TypeScript import { AppKit, type Adapter } from "@circle-fin/app-kit"; import { resolveChainIdentifier } from "@circle-fin/adapter-viem-v2"; const kit = new AppKit(); async function depositFromBaseSepolia(evmAdapter: Adapter) { const chain = resolveChainIdentifier("Base_Sepolia"); if (chain.type !== "evm") { throw new Error(`${chain.name} is not an EVM chain`); } await evmAdapter.ensureChain(chain); const result = await kit.unifiedBalance.deposit({ from: { adapter: evmAdapter, chain: "Base_Sepolia" }, amount: "2.00", token: "USDC", }); console.dir(result, { depth: null }); return result; } You’ll see output like: Shell { amount: "2.00", token: "USDC", chain: "Base_Sepolia", txHash: "0x...", explorerUrl: "https://sepolia.basescan.org/tx/0x...", ... } ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#3-2-deposit-from-solana-devnet) 3.2. Deposit from Solana Devnet Call `kit.unifiedBalance.deposit` with the Solana browser wallet adapter: TypeScript async function depositFromSolanaDevnet(solanaAdapter: Adapter) { const result = await kit.unifiedBalance.deposit({ from: { adapter: solanaAdapter, chain: "Solana_Devnet" }, amount: "1.00", token: "USDC", }); console.dir(result, { depth: null }); return result; } You’ll see output like: Shell { amount: "1.00", token: "USDC", chain: "Solana_Devnet", txHash: "2k41...", explorerUrl: "https://solscan.io/tx/2k41...?cluster=devnet", ... } ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#3-3-verify-the-deposits) 3.3. Verify the deposits Open the `explorerUrl` from each deposit result and confirm the onchain transactions on Base Sepolia and Solana Devnet. When both deposits are finalized, continue to the next step. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-4-check-your-unified-balance) Step 4. Check your Unified Balance ------------------------------------------------------------------------------------------------------------------------------------------------------ In this step, you query your Unified Balance across the Base Sepolia and Solana Devnet depositors and print the confirmed and pending amounts. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#4-1-check-balances) 4.1. Check balances Call `kit.unifiedBalance.getBalances` with the same browser wallet adapters you used for the deposits: TypeScript async function checkUnifiedBalance( evmAdapter: Adapter, solanaAdapter: Adapter, ) { const balances = await kit.unifiedBalance.getBalances({ // Both wallets that deposited, one adapter per source. sources: [{ adapter: evmAdapter }, { adapter: solanaAdapter }], networkType: "testnet", includePending: true, }); console.dir(balances, { depth: null }); return balances; } You’ll see output like: Shell { token: "USDC", totalConfirmedBalance: "3.00", totalPendingBalance: "0.00", breakdown: [\ {\ depositor: "0x...",\ totalConfirmed: "2.00",\ totalPending: "0.00",\ breakdown: [{ chain: "Base_Sepolia", confirmedBalance: "2.00", ... }]\ },\ {\ depositor: "...",\ totalConfirmed: "1.00",\ totalPending: "0.00",\ breakdown: [{ chain: "Solana_Devnet", confirmedBalance: "1.00", ... }]\ }\ ] } After a deposit, funds can appear in `totalPendingBalance` before they are reflected in `totalConfirmedBalance`. Wait until the confirmed balance is sufficient before you spend. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-5-spend-from-the-combined-balance) Step 5. Spend from the combined balance ---------------------------------------------------------------------------------------------------------------------------------------------------------------- In this step, you spend USDC on Arc Testnet from your Unified Balance. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#5-1-spend-on-arc-testnet) 5.1. Spend on Arc Testnet Collect the recipient address from your app UI, then pass it with the connected wallet adapters into the spend function. This code spends 2.50 USDC on Arc Testnet for the recipient. [The App Kit SDK chooses](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains) how much USDC to use from each blockchain. TypeScript async function spendFromUnifiedBalance( evmAdapter: Adapter, solanaAdapter: Adapter, recipientAddress: string, ) { console.log(`Spending 2.50 USDC on Arc_Testnet for ${recipientAddress}...`); const result = await kit.unifiedBalance.spend({ amount: "2.50", token: "USDC", from: [{ adapter: evmAdapter }, { adapter: solanaAdapter }], to: { adapter: evmAdapter, chain: "Arc_Testnet", recipientAddress, }, }); console.dir(result, { depth: null }); return result; } You can customize your Unified Balance to [collect a custom fee](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees) from end users, [estimate fees](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees) before spending, [select source blockchains and allocations](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains) to fund a balance, or use the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service) . When the spend completes, you should see output similar to: Shell Spending 2.50 USDC on Arc_Testnet for 0x... { recipientAddress: "0x...", destinationChain: "Arc Testnet", txHash: "0x...", ... } ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#5-2-verify-the-spend) 5.2. Verify the spend Use the `explorerUrl` from the spend result to confirm that USDC arrived at the recipient address on Arc Testnet. The received amount can be less than the requested spend after fees. For more on fees, see [How Unified Balance fees work](https://docs.arc.io/app-kit/concepts/unified-balance-fees) . Use this flow to deposit and spend a Unified Balance with developer-controlled wallets managed by Circle Wallets. The examples use Base Sepolia, Solana Devnet, and Arc Testnet. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#prerequisites-2) Prerequisites --------------------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) . * Set up Circle Wallets: * Obtained a [API Key](https://developers.circle.com/api-reference/keys#creating-an-api-key-for-developer-services) and [entity secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret) from the [Circle Console](https://developers.circle.com/w3s/circle-developer-account) . * Created developer-controlled wallets on Base Sepolia, Solana Devnet, and Arc Testnet. * Funded the wallets: * Base Sepolia: testnet USDC from the [Circle Faucet](https://faucet.circle.com/) and testnet ETH from a [public faucet](https://www.alchemy.com/faucets/base-sepolia) . * Solana Devnet: testnet USDC from the [Circle Faucet](https://faucet.circle.com/) and SOL from the [Solana Faucet](https://faucet.solana.com/) . * Arc Testnet: fund the destination wallet if needed. USDC on Arc can cover gas for the destination credit when you spend on Arc. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-1-set-up-the-project-2) Step 1. Set up the project ---------------------------------------------------------------------------------------------------------------------------------------- ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#1-1-create-the-project-and-install-dependencies-2) 1.1. Create the project and install dependencies Create a new directory and install the App Kit SDK with the Circle Wallets adapter and supporting tools: Shell # Set up your directory and initialize a Node.js project mkdir app-kit-unified-balance-circle-wallets cd app-kit-unified-balance-circle-wallets npm init -y npm pkg set type=module # Set up run scripts npm pkg set scripts.deposit:base="tsx --env-file=.env deposit-base.ts" npm pkg set scripts.deposit:solana="tsx --env-file=.env deposit-solana.ts" npm pkg set scripts.balance="tsx --env-file=.env check-balance.ts" npm pkg set scripts.spend="tsx --env-file=.env spend.ts" # Install runtime dependencies npm install @circle-fin/app-kit @circle-fin/adapter-circle-wallets tsx # Install dev dependencies npm install --save-dev typescript @types/node Only need a Unified Balance and want a lighter install than the full App Kit SDK? Install the standalone Unified Balance Kit instead: `@circle-fin/unified-balance-kit` ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#1-2-configure-typescript-optional-2) 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file: Shell npx tsc --init Then, update the `tsconfig.json` file: Shell cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#1-3-set-environment-variables) 1.3. Set environment variables Create an `.env` file in the project directory: Shell touch .env Add your credentials. Replace `YOUR_API_KEY` with your Circle Developer API key and `YOUR_ENTITY_SECRET` with your entity secret: .env CIRCLE_API_KEY=YOUR_API_KEY CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET Edit `.env` files in your IDE or editor so credentials are not leaked to your shell history. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-2-deposit-into-a-unified-balance) Step 2. Deposit into a Unified Balance -------------------------------------------------------------------------------------------------------------------------------------------------------------- Circle Wallets uses developer-controlled addresses, so each operation includes the wallet address explicitly. The API key, entity secret, and deposit wallet addresses must belong to the same Circle Developer entity. Otherwise, Circle Wallets may fail to find the wallet and return error `156001` during signing. ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#2-1-deposit-from-base-sepolia) 2.1. Deposit from Base Sepolia Create a `deposit-base.ts` file. This script deposits 2.00 USDC from your Base Sepolia Circle Wallets-controlled wallet into your Unified Balance. TypeScript import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const kit = new AppKit(); kit.on("*", (payload) => { console.log("Event received:", payload); }); const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const baseWalletAddress = "YOUR_BASE_SEPOLIA_WALLET_ADDRESS"; const result = await kit.unifiedBalance.deposit({ from: { adapter, chain: "Base_Sepolia", address: baseWalletAddress, }, amount: "2.00", token: "USDC", }); console.dir(result, { depth: null, colors: true }); Run the script: Shell npm run deposit:base ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#2-2-deposit-from-solana-devnet) 2.2. Deposit from Solana Devnet Create a `deposit-solana.ts` file. This script deposits 1.00 USDC from your Solana Devnet Circle Wallets-controlled wallet into your Unified Balance. TypeScript import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const kit = new AppKit(); kit.on("*", (payload) => { console.log("Event received:", payload); }); const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const solanaWalletAddress = "YOUR_SOLANA_DEVNET_WALLET_ADDRESS"; const result = await kit.unifiedBalance.deposit({ from: { adapter, chain: "Solana_Devnet", address: solanaWalletAddress, }, amount: "1.00", token: "USDC", }); console.dir(result, { depth: null, colors: true }); Run the script: Shell npm run deposit:solana ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#2-3-verify-the-deposits) 2.3. Verify the deposits Open the `explorerUrl` from each deposit result and confirm the onchain transactions on Base Sepolia and Solana Devnet. When both deposits are finalized, continue to the next step. You can customize your Unified Balance to [collect a custom fee](https://docs.arc.io/app-kit/tutorials/unified-balance/collect-custom-spend-fees) from end users, [estimate fees](https://docs.arc.io/app-kit/tutorials/unified-balance/estimate-spend-fees) before spending, [select source blockchains and allocations](https://docs.arc.io/app-kit/tutorials/unified-balance/select-source-blockchains) to fund a balance, or use the [Forwarding Service](https://docs.arc.io/app-kit/tutorials/unified-balance/use-forwarding-service) . [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-3-check-your-unified-balance) Step 3. Check your Unified Balance ------------------------------------------------------------------------------------------------------------------------------------------------------ After both deposits are finalized, create a `check-balance.ts` file. This script queries the confirmed and pending Unified Balance for the Base Sepolia and Solana Devnet source accounts. TypeScript import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const kit = new AppKit(); const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const baseWalletAddress = "YOUR_BASE_SEPOLIA_WALLET_ADDRESS"; const solanaWalletAddress = "YOUR_SOLANA_DEVNET_WALLET_ADDRESS"; const balances = await kit.unifiedBalance.getBalances({ sources: [\ {\ adapter,\ address: baseWalletAddress,\ chains: ["Base_Sepolia"],\ },\ {\ adapter,\ address: solanaWalletAddress,\ chains: ["Solana_Devnet"],\ },\ ], networkType: "testnet", includePending: true, }); console.dir(balances, { depth: null, colors: true }); Run the script: Shell npm run balance Wait until `totalConfirmedBalance` is high enough for the spend you plan to make. [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#step-4-spend-from-the-unified-balance) Step 4. Spend from the Unified Balance -------------------------------------------------------------------------------------------------------------------------------------------------------------- Create a `spend.ts` file. This script spends 2.50 USDC on Arc Testnet. The `from` sources do not include `allocations`, so the SDK chooses source blockchains automatically from the available confirmed balances. TypeScript import { AppKit } from "@circle-fin/app-kit"; import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const kit = new AppKit(); kit.on("*", (payload) => { console.log("Event received:", payload); }); const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); const baseWalletAddress = "YOUR_BASE_SEPOLIA_WALLET_ADDRESS"; const solanaWalletAddress = "YOUR_SOLANA_DEVNET_WALLET_ADDRESS"; const arcWalletAddress = "YOUR_ARC_TESTNET_WALLET_ADDRESS"; const result = await kit.unifiedBalance.spend({ amount: "2.50", token: "USDC", from: [\ {\ adapter,\ address: baseWalletAddress,\ sourceAccount: baseWalletAddress,\ },\ {\ adapter,\ address: solanaWalletAddress,\ sourceAccount: solanaWalletAddress,\ },\ ], to: { adapter, chain: "Arc_Testnet", address: arcWalletAddress, recipientAddress: arcWalletAddress, }, }); console.dir(result, { depth: null, colors: true }); Run the script: Shell npm run spend ### [​](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend#4-1-verify-the-spend) 4.1. Verify the spend Use the `explorerUrl` from the spend result to confirm that USDC arrived at the recipient address on Arc Testnet. The spend result includes the allocations selected by the SDK, the destination recipient, and the Arc Testnet mint transaction hash. Was this page helpful? YesNo [Unified Balance overview](https://docs.arc.io/app-kit/unified-balance) [Use a delegate to deposit and spend a Unified Balance](https://docs.arc.io/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Connect to Arc - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/references/connect-to-arc#content-area) Connect a wallet to Arc Testnet using one-click setup or manual configuration. [​](https://docs.arc.io/arc/references/connect-to-arc#wallet-setup) Wallet setup ----------------------------------------------------------------------------------- Use the button below to add Arc Testnet to your wallet automatically. ### [​](https://docs.arc.io/arc/references/connect-to-arc#manual-setup) Manual setup Arc uses USDC as the native gas token (18 decimals). If your wallet supports **custom gas tokens**, ensure display/decimals are set correctly. Wallets that don’t support custom gas tokens still work for signing and sending transactions — balances may display as “ETH” but the underlying token is USDC. See [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) for details. * MetaMask * Rabby * Coinbase Wallet * Rainbow 1 Open network settings Open MetaMask → **Settings** → **Networks** → **Add network** → **Add a network manually**. 2 Enter network details | Field | Value | | --- | --- | | **Network name** | Arc Testnet | | **New RPC URL** | `https://rpc.testnet.arc.network` | | **Chain ID** | 5042002 | | **Currency symbol** | USDC | | **Explorer URL** | `https://testnet.arcscan.app` | 3 Save and switch Click **Save**, then switch to Arc Testnet. 1 Open network settings Open Rabby → click the **network selector** (top-left) → **Add Custom Network**. 2 Enter network details | Field | Value | | --- | --- | | **Chain Name** | Arc Testnet | | **Chain ID** | 5042002 | | **RPC URL** | `https://rpc.testnet.arc.network` | | **Currency** | USDC | | **Block Explorer** | `https://testnet.arcscan.app` | 3 Confirm and switch Click **Confirm**, then select Arc Testnet from the network list. 1 Open network settings Open Coinbase Wallet → **Settings** → **Networks** → **Add custom network**. 2 Enter network details | Field | Value | | --- | --- | | **Network name** | Arc Testnet | | **RPC URL** | `https://rpc.testnet.arc.network` | | **Chain ID** | 5042002 | | **Currency symbol** | USDC | | **Block explorer** | `https://testnet.arcscan.app` | 3 Save and switch Click **Save**, then switch to Arc Testnet. 1 Open network settings Open Rainbow → **Settings** (gear icon) → **Networks** → **Custom Network**. 2 Enter network details | Field | Value | | --- | --- | | **Network name** | Arc Testnet | | **RPC URL** | `https://rpc.testnet.arc.network` | | **Chain ID** | 5042002 | | **Symbol** | USDC | | **Block explorer** | `https://testnet.arcscan.app` | 3 Save and switch Click **Save**, then switch to Arc Testnet. [​](https://docs.arc.io/arc/references/connect-to-arc#network-details) Network details ----------------------------------------------------------------------------------------- | Parameter | Value | | --- | --- | | Network | Arc Testnet | | Chain ID | `5042002` | | Currency | USDC | | Explorer | [testnet.arcscan.app](https://testnet.arcscan.app/) | | Faucet | [faucet.circle.com](https://faucet.circle.com/) | ### [​](https://docs.arc.io/arc/references/connect-to-arc#rpc-endpoints) RPC endpoints Primary Blockdaemon dRPC QuickNode https://rpc.testnet.arc.network https://rpc.blockdaemon.testnet.arc.network https://rpc.drpc.testnet.arc.network https://rpc.quicknode.testnet.arc.network ### [​](https://docs.arc.io/arc/references/connect-to-arc#websocket-endpoints) WebSocket endpoints Primary Blockdaemon dRPC QuickNode wss://rpc.testnet.arc.network wss://rpc.blockdaemon.testnet.arc.network:443/websocket wss://rpc.drpc.testnet.arc.network wss://rpc.quicknode.testnet.arc.network [​](https://docs.arc.io/arc/references/connect-to-arc#frontend-wallet-libraries) Frontend wallet libraries ------------------------------------------------------------------------------------------------------------- Use `wagmi` and `viem` to integrate Arc Testnet into [ConnectKit](https://family.co/docs/connectkit) , [Reown AppKit](https://docs.reown.com/appkit/overview) , or a bare [WalletConnect](https://docs.walletconnect.com/) connector. ### [​](https://docs.arc.io/arc/references/connect-to-arc#arc-testnet-chain-definition) Arc Testnet chain definition `viem` ships Arc Testnet as a built-in chain. No manual definition is needed. import { arcTestnet } from "viem/chains"; ### [​](https://docs.arc.io/arc/references/connect-to-arc#configure-wallet-connection) Configure wallet connection Pick the tab that matches your setup. * ConnectKit * Reown AppKit * WalletConnect npm install connectkit wagmi viem import { arcTestnet, mainnet } from "viem/chains"; import { createConfig, http } from "wagmi"; import { getDefaultConfig } from "connectkit"; const config = createConfig( getDefaultConfig({ chains: [arcTestnet, mainnet], transports: { [arcTestnet.id]: http("https://rpc.testnet.arc.network"), [mainnet.id]: http("https://cloudflare-eth.com"), }, walletConnectProjectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, appName: "Your App Name", }), ); When `mainnet` is omitted from `chains`, ConnectKit falls back to its own `eth.merkle.io` endpoint for ENS name lookups, which blocks cross-origin browser requests. Include `mainnet` with a CORS-safe transport (such as the Cloudflare endpoint shown earlier) to prevent this. npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query Import `defineChain` from `@reown/appkit/networks`, not from `viem`. AppKit requires two additional fields (`caipNetworkId` and `chainNamespace`) that `viem`’s version omits; using the wrong import causes a runtime error. import { defineChain } from "@reown/appkit/networks"; import { WagmiAdapter } from "@reown/appkit-adapter-wagmi"; import { createAppKit } from "@reown/appkit/react"; const projectId = process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID; const arcTestnet = defineChain({ id: 5042002, caipNetworkId: "eip155:5042002", chainNamespace: "eip155", name: "Arc Testnet", nativeCurrency: { decimals: 18, name: "USDC", symbol: "USDC" }, rpcUrls: { default: { http: ["https://rpc.testnet.arc.network"], webSocket: ["wss://rpc.testnet.arc.network"], }, }, blockExplorers: { default: { name: "ArcScan", url: "https://testnet.arcscan.app" }, }, testnet: true, }); const wagmiAdapter = new WagmiAdapter({ networks: [arcTestnet], projectId, }); createAppKit({ adapters: [wagmiAdapter], networks: [arcTestnet], projectId, metadata: { name: "Your App Name", description: "Your App Description", url: "https://yourdomain.com", icons: ["https://yourdomain.com/icon.png"], }, }); Get a project ID at [dashboard.reown.com](https://dashboard.reown.com/) . The same project ID works for the bare WalletConnect connector. For `wagmi` apps that need WalletConnect sessions without Reown AppKit’s full modal UI. import { arcTestnet } from "viem/chains"; import { createConfig, http } from "wagmi"; import { walletConnect } from "wagmi/connectors"; const config = createConfig({ chains: [arcTestnet], connectors: [\ walletConnect({\ projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,\ metadata: {\ name: "Your App",\ description: "Your App Description",\ url: "https://yourdomain.com",\ icons: ["https://yourdomain.com/icon.png"],\ },\ }),\ ], transports: { [arcTestnet.id]: http("https://rpc.testnet.arc.network"), }, }); Arc Testnet (chain ID 5042002) is not registered in the WalletConnect chain registry. On first connection, WalletConnect issues a `wallet_addEthereumChain` call per user. Was this page helpful? YesNo [Sample apps](https://docs.arc.io/arc/references/sample-applications) [EVM differences](https://docs.arc.io/build/evm-differences) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Add Arc to a Wallet - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/wallets#content-area) Arc is an EVM-compatible blockchain that uses USDC as its native gas token. Signing uses secp256k1, identical to Ethereum—no new cryptographic code is required. USDC exists as a single balance with two interfaces (native and ERC-20), so display one unified balance row. Transactions finalize in under one second with no reorgs. [​](https://docs.arc.io/integrate/wallets#prerequisites) Prerequisites ------------------------------------------------------------------------- Before you begin: * Familiarity with EIP-3085 (`wallet_addEthereumChain`) for adding custom networks * Access to the Arc Testnet RPC endpoint * Understanding of ERC-20 event indexing [​](https://docs.arc.io/integrate/wallets#step-1-configure-the-network) Step 1. Configure the network -------------------------------------------------------------------------------------------------------- Add Arc using the [EIP-3085](https://eips.ethereum.org/EIPS/eip-3085) parameters below. | Parameter | Value | | --- | --- | | `chainId` | `0x4CF4B2` (5042002) | | `chainName` | Arc Testnet | | `nativeCurrency` | `{ name: "USDC", symbol: "USDC", decimals: 6 }` | | `rpcUrls` | `["https://rpc.testnet.arc.network"]` | | `wsUrls` | `["wss://rpc.testnet.arc.network"]` | | `blockExplorerUrls` | `["https://testnet.arcscan.app"]` | Set `nativeCurrency.decimals` to `6` for display purposes. This tells the wallet UI to show human-readable USDC amounts (for example, “1.50 USDC”) rather than raw wei values. const arcTestnet = { chainId: "0x4CF4B2", chainName: "Arc Testnet", nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 6, }, rpcUrls: ["https://rpc.testnet.arc.network"], blockExplorerUrls: ["https://testnet.arcscan.app"], }; [​](https://docs.arc.io/integrate/wallets#step-2-display-the-balance) Step 2. Display the balance ---------------------------------------------------------------------------------------------------- Arc’s native balance uses 18 decimals internally (like ETH on Ethereum), but represents USDC which has 6 display decimals. Convert accordingly. ### [​](https://docs.arc.io/integrate/wallets#fetch-the-balance) Fetch the balance Call `eth_getBalance` to retrieve the user’s USDC balance in 18-decimal wei: const balanceWei = await provider.getBalance(address); // 18-decimal BigInt ### [​](https://docs.arc.io/integrate/wallets#convert-to-display-value) Convert to display value Divide by 10^12 to convert from 18-decimal native wei to 6-decimal USDC: const DECIMALS_OFFSET = 12n; const displayAmount = balanceWei / 10n ** DECIMALS_OFFSET; // 6-decimal value const formatted = (Number(displayAmount) / 1e6).toFixed(6); // e.g. "1.500000" ### [​](https://docs.arc.io/integrate/wallets#show-a-single-row) Show a single row USDC on Arc is a single asset with two interfaces (native and ERC-20). Both share the same underlying balance. Display one “USDC” row in the asset list, not separate “native” and “ERC-20” entries. Do not display a separate ETH balance. Arc has no ETH. The native token label should read “USDC” everywhere in your UI. ### [​](https://docs.arc.io/integrate/wallets#handle-token-import) Handle token import If a user manually imports the linked USDC ERC-20 contract (`0x3600000000000000000000000000000000000000`), map it to the USDC asset they already hold—do not create a second entry. Consider surfacing a message such as “This contract represents USDC on Arc (already in your wallet)” so users understand they have not added a new token. [​](https://docs.arc.io/integrate/wallets#step-3-index-transaction-history) Step 3. Index transaction history ---------------------------------------------------------------------------------------------------------------- Arc emits a standard ERC-20 `Transfer` log from the system address `0xfffffffffffffffffffffffffffffffffffffffe` for every native USDC movement (Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation). This single stream covers plain native sends and the native leg of ERC-20 transfers, so filtering it gives complete transaction history. The native balance is the source of truth. ### [​](https://docs.arc.io/integrate/wallets#event-signature) Event signature event Transfer(address indexed from, address indexed to, uint256 value); **Topic 0:** `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` **Emitter:** `0xfffffffffffffffffffffffffffffffffffffffe` (native USDC system emitter, 18 decimals) ### [​](https://docs.arc.io/integrate/wallets#subscribe-to-transfers) Subscribe to transfers Use `eth_subscribe` or poll `eth_getLogs` filtered by the topic and the user’s address: const NATIVE_USDC_EMITTER = "0xfffffffffffffffffffffffffffffffffffffffe"; const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; // Filter for transfers involving the user (as sender or receiver) const paddedAddress = "0x" + address.slice(2).padStart(64, "0"); const logs = await provider.getLogs({ address: NATIVE_USDC_EMITTER, topics: [TRANSFER_TOPIC, [paddedAddress, null], [null, paddedAddress]], fromBlock: "earliest", toBlock: "latest", }); ### [​](https://docs.arc.io/integrate/wallets#parse-the-value) Parse the value The `value` field from the system emitter uses 18 decimals (the native balance). Convert to 6-decimal USDC for display: import { parseAbi, decodeEventLog } from "viem"; const abi = parseAbi([\ "event Transfer(address indexed from, address indexed to, uint256 value)",\ ]); for (const log of logs) { const { args } = decodeEventLog({ abi, data: log.data, topics: log.topics }); const amount = Number(args.value) / 1e18; // 18-decimal native → human-readable USDC } Filtering the system emitter covers all USDC movements—native sends and the native leg of ERC-20 transfers—in one stream. Do not filter the ERC-20 contract (`0x3600…0000`) for history: plain native sends emit no log there. See [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . [​](https://docs.arc.io/integrate/wallets#step-4-handle-fees) Step 4. Handle fees ------------------------------------------------------------------------------------ Gas on Arc is denominated in USDC, not ETH. Arc uses an EIP-1559 fee model with a smoothed base fee. ### [​](https://docs.arc.io/integrate/wallets#estimate-gas-cost) Estimate gas cost const gasPrice = await provider.getGasPrice(); // Returns USDC wei (18 decimals) const gasLimit = await provider.estimateGas(tx); const feeWei = gasPrice * gasLimit; // Total fee in 18-decimal USDC wei const feeUsdc = Number(feeWei) / 1e18; // Human-readable USDC ### [​](https://docs.arc.io/integrate/wallets#ui-guidance) UI guidance | Element | Display | | --- | --- | | Fee label | ”Network fee” or “Gas fee” | | Fee denomination | USDC (for example, “0.000042 USDC”) | | Currency symbol | Do not show “ETH” or “Gwei” to users | | Insufficient funds | ”Insufficient USDC for gas” | Arc has no ETH. If your wallet warns “insufficient ETH for gas,” update that message to reference USDC instead. [​](https://docs.arc.io/integrate/wallets#step-5-send-transactions) Step 5. Send transactions ------------------------------------------------------------------------------------------------ Transaction signing on Arc is identical to Ethereum. Use secp256k1 ECDSA signatures with EIP-155 replay protection. Send USDC with a standard ERC-20 `transfer()`—the same flow as any ERC-20 token, with no choice of transfer method exposed to the user. ### [​](https://docs.arc.io/integrate/wallets#precision-and-dust) Precision and dust The ERC-20 interface uses 6 decimals, so an ERC-20 `transfer()` cannot move “dust”—amounts smaller than 1×10⁻⁶ USDC held in the 18-decimal native balance. Validating send amounts to 6 decimals client-side is sufficient for standard wallet flows. The native balance can still hold dust, and dust can be spent as gas. ### [​](https://docs.arc.io/integrate/wallets#onchain-app-interactions) Onchain app interactions Treat USDC as a standard ERC-20 when users interact with onchain applications—approvals, `transferFrom`, swaps, and liquidity provision work without special handling. Because USDC is also the native asset, support contract calls that are `payable`: an app may require USDC sent as `msg.value` (a native value transfer) alongside calldata, rather than an ERC-20 `transfer`. Pass the value through as the contract expects; no special UX is required. ### [​](https://docs.arc.io/integrate/wallets#confirmation-model) Confirmation model Arc provides deterministic finality. A transaction is either pending (in the mempool) or final (included in a block). There are no intermediate confirmation states and no reorgs. | State | Meaning | | --- | --- | | **Pending** | Transaction is in the mempool, not yet mined | | **Final** | Included in a block; irreversible | Once a transaction receipt is returned, you can immediately update the UI. No additional confirmations are needed. ### [​](https://docs.arc.io/integrate/wallets#account-abstraction) Account abstraction Arc supports ERC-4337 account abstraction for smart contract wallets. If your wallet supports AA flows (bundlers, paymasters, session keys), these work on Arc without modification. See [Account abstraction providers](https://docs.arc.io/arc/tools/account-abstraction) for compatible infrastructure including Biconomy, Pimlico, ZeroDev, and Circle Wallets. [​](https://docs.arc.io/integrate/wallets#integration-checklist) Integration checklist ----------------------------------------------------------------------------------------- Use this checklist to verify your wallet integration is complete: * [ ] Chain ID `5042002` added with correct RPC and explorer URLs * [ ] Native currency displays as “USDC” with 6 display decimals * [ ] `eth_getBalance` result converted from 18-decimal to 6-decimal for display * [ ] Single USDC balance row shown (no separate native/ERC-20 entries) * [ ] Imported USDC ERC-20 contract maps to the existing USDC asset (no duplicate entry) * [ ] No ETH references in UI labels, error messages, or fee displays * [ ] Transaction history uses the native `Transfer` event from the system emitter `0xffff...fffe` (18 decimals), not the ERC-20 contract * [ ] Gas fees displayed in USDC * [ ] Send amounts validated to 6 decimals; `payable` contract calls (USDC as `msg.value`) supported * [ ] One confirmation treated as final (no “confirming” spinner) * [ ] ERC-4337 AA flows work if your wallet supports smart accounts [​](https://docs.arc.io/integrate/wallets#see-also) See also --------------------------------------------------------------- * [EVM compatibility](https://docs.arc.io/arc/references/evm-differences) —full breakdown of differences from Ethereum * [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) —fee model and pricing mechanics * [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) —USDC contract and other deployed addresses * [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) —how Arc achieves instant settlement * [Account abstraction](https://docs.arc.io/arc/tools/account-abstraction) —ERC-4337 provider directory Was this page helpful? YesNo [Add Arc to your on/off-ramp platform](https://docs.arc.io/integrate/on-off-ramps) [Transaction Lifecycle](https://docs.arc.io/integrate/wallets/transaction-lifecycle) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Detect and Process Deposits - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/exchanges/deposits#content-area) Detect USDC deposits on Arc by generating addresses, monitoring the native USDC `Transfer` event from the system emitter `0xffff…fffe` (Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation), crediting after a single block confirmation (deterministic finality guarantees no reorgs), and sweeping funds into a hot wallet. [​](https://docs.arc.io/integrate/exchanges/deposits#prerequisites) Prerequisites ------------------------------------------------------------------------------------ Before you begin: * Access to an Arc RPC endpoint (`https://rpc.testnet.arc.network`) or WebSocket (`wss://rpc.testnet.arc.network`) * An HD wallet library for generating deposit addresses (for example, `ethers` or `viem`) * Familiarity with Ethereum JSON-RPC methods and event log filtering * A database to track processed deposits and prevent double-crediting [​](https://docs.arc.io/integrate/exchanges/deposits#steps) Steps -------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/exchanges/deposits#step-1-generate-deposit-addresses) Step 1. Generate deposit addresses Arc uses standard Ethereum addresses (0x-prefixed, 20 bytes, EIP-55 checksum). Derive deposit addresses using the same HD wallet approach as Ethereum—one unique address per user. import { HDNodeWallet, Mnemonic } from "ethers"; // Derive a deposit address for a given user index function getDepositAddress(mnemonic: string, userIndex: number): string { const hdNode = HDNodeWallet.fromMnemonic( Mnemonic.fromPhrase(mnemonic), `m/44'/60'/0'/0/${userIndex}`, ); return hdNode.address; } Store the mapping between user IDs and their derived address index. Never expose the mnemonic or private keys in client-side code. ### [​](https://docs.arc.io/integrate/exchanges/deposits#step-2-subscribe-to-new-blocks) Step 2. Subscribe to new blocks Use `eth_subscribe("newHeads")` over WebSocket for real-time block notifications, or poll `eth_blockNumber` over HTTP as a fallback. import { WebSocketProvider, JsonRpcProvider } from "ethers"; // Option A: WebSocket subscription (recommended) const wsProvider = new WebSocketProvider("wss://rpc.testnet.arc.network"); wsProvider.on("block", async (blockNumber: number) => { console.log(`New block: ${blockNumber}`); await processBlock(blockNumber); }); // Option B: HTTP polling fallback const httpProvider = new JsonRpcProvider("https://rpc.testnet.arc.network"); let lastProcessedBlock = await httpProvider.getBlockNumber(); setInterval(async () => { const currentBlock = await httpProvider.getBlockNumber(); for (let block = lastProcessedBlock + 1; block <= currentBlock; block++) { await processBlock(block); } lastProcessedBlock = currentBlock; }, 2000); ### [​](https://docs.arc.io/integrate/exchanges/deposits#step-3-detect-incoming-transfers-with-the-native-usdc-transfer-event) Step 3. Detect incoming transfers with the native USDC Transfer event Every native USDC movement emits a standard ERC-20 `Transfer` log from the system address `0xfffffffffffffffffffffffffffffffffffffffe` (Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation). This single stream covers plain native sends and the native leg of ERC-20 transfers, with values in **18 decimals**. Filter it to catch every deposit, including native sends that emit no event on the ERC-20 contract. Do not filter the ERC-20 USDC contract (`0x3600…0000`) for deposit detection. Its `Transfer` events cover only ERC-20-interface activity, so a plain native send produces no log there and the deposit is missed. Filter the system emitter (`0xffff…fffe`) instead. See [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) for the full event matrix. **Event signature:** Transfer(address indexed from, address indexed to, uint256 value) **Topic0:** `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` **Native USDC system emitter:** `0xfffffffffffffffffffffffffffffffffffffffe` Use `eth_getLogs` to filter for transfers to your deposit addresses: import { Interface, Log, JsonRpcProvider } from "ethers"; // Native USDC system emitter (EIP-7708 Transfer logs, 18 decimals) const NATIVE_USDC_EMITTER = "0xfffffffffffffffffffffffffffffffffffffffe"; 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 processBlock(blockNumber: number): Promise { const logs = await provider.getLogs({ address: NATIVE_USDC_EMITTER, topics: [\ TRANSFER_TOPIC,\ null, // any sender\ null, // any recipient—filter client-side for your addresses\ ], fromBlock: blockNumber, toBlock: blockNumber, }); for (const log of logs) { const parsed = erc20Interface.parseLog({ topics: log.topics as string[], data: log.data, }); if (!parsed) continue; const to = parsed.args.to as string; const value = parsed.args.value as bigint; // 18 decimals (native) // Check if the recipient is one of your deposit addresses if (isDepositAddress(to)) { await creditDeposit({ txHash: log.transactionHash, logIndex: log.index, to, amount: value, // 18 decimals—convert to 6 for crediting (see Step 5) blockNumber, }); } } } Do not credit the same deposit twice. An ERC-20 `transfer()` emits a log from both the system emitter (`0xffff…fffe`, 18 decimals) and the ERC-20 contract (`0x3600…0000`, 6 decimals); filter only the system emitter. Likewise, do not reconcile `eth_getBalance` against Transfer events—they represent the same balance. ### [​](https://docs.arc.io/integrate/exchanges/deposits#step-4-confirm-the-deposit) Step 4. Confirm the deposit Arc provides deterministic finality—once a transaction is included in a block, it is final with no possibility of reorg. You can safely credit deposits after 1 confirmation. import { JsonRpcProvider } from "ethers"; const provider = new JsonRpcProvider("https://rpc.testnet.arc.network"); async function isConfirmed(txHash: string): Promise { const receipt = await provider.getTransactionReceipt(txHash); if (!receipt || receipt.status === 0) { return false; // Transaction failed or not yet mined } // On Arc, 1 confirmation = final. No reorgs possible. const currentBlock = await provider.getBlockNumber(); return currentBlock >= receipt.blockNumber; } Since Arc has deterministic finality, you do not need to wait for multiple confirmations. Credit the user once the block containing their transaction is produced. ### [​](https://docs.arc.io/integrate/exchanges/deposits#step-5-handle-decimal-conversion) Step 5. Handle decimal conversion The native system `Transfer` event emits values with 18 decimals. Convert to 6-decimal USDC (divide by 10^12) before crediting user balances. // Native system Transfer values use 18 decimals—convert to 6 for crediting function formatDeposit(eventValue: bigint): string { const sixDecimals = eventValue / 10n ** 12n; // 18-decimal native → 6-decimal USDC const whole = sixDecimals / 1_000_000n; const fractional = (sixDecimals % 1_000_000n).toString().padStart(6, "0"); return `${whole}.${fractional}`; } The native system `Transfer` event and `eth_getBalance` both use 18 decimals; the ERC-20 contract’s `Transfer` and `balanceOf` use 6. Never mix representations—convert 18-decimal values by dividing by 10^12 before displaying or crediting. ### [​](https://docs.arc.io/integrate/exchanges/deposits#step-6-sweep-deposits-to-a-hot-wallet) Step 6. Sweep deposits to a hot wallet Consolidate deposited funds from individual user addresses into your hot wallet. Use EIP-1559 transactions with Arc’s minimum base fee of 20 Gwei. import { Wallet, JsonRpcProvider, parseUnits } from "ethers"; const provider = new JsonRpcProvider("https://rpc.testnet.arc.network"); async function sweepDeposit( depositPrivateKey: string, hotWalletAddress: string, amount: bigint, ): Promise { const wallet = new Wallet(depositPrivateKey, provider); const feeData = await provider.getFeeData(); const maxFeePerGas = feeData.maxFeePerGas ?? parseUnits("30", "gwei"); const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? parseUnits("1", "gwei"); // Estimate gas for an ERC-20 transfer (approximately 21,000 for native sends) const gasLimit = 65_000n; const tx = await wallet.sendTransaction({ to: hotWalletAddress, value: amount * 10n ** 12n, // Convert 6-decimal amount to 18-decimal native value type: 2, // EIP-1559 maxFeePerGas, maxPriorityFeePerGas, gasLimit, }); const receipt = await tx.wait(); return receipt!.hash; } When sweeping via a native USDC send, convert from 6-decimal amounts to 18-decimal `value` fields. Alternatively, call the ERC-20 `transfer` function on the USDC contract, which accepts 6-decimal amounts directly. [​](https://docs.arc.io/integrate/exchanges/deposits#common-mistakes) Common mistakes ---------------------------------------------------------------------------------------- **Avoid these pitfalls when implementing deposits:** * **Filtering the wrong emitter:** Plain native USDC sends emit no `Transfer` on the ERC-20 contract (`0x3600…0000`). Filter the system emitter (`0xffff…fffe`) so you do not miss native deposits. * **Double-counting:** An ERC-20 `transfer()` logs from both emitters. Filter only the system emitter, and do not reconcile `eth_getBalance` against Transfer events for the same address. * **Decimal mismatch:** The system emitter’s values use 18 decimals. Treating them as 6-decimal amounts inflates credited balances by 10^12—divide by 10^12 first. * **Waiting for multiple confirmations:** Arc has deterministic finality. Waiting for 6+ confirmations adds unnecessary latency with no security benefit. * **Hardcoded keys:** Never embed private keys in source code. Use environment variables or a secrets manager for sweep wallet credentials. Was this page helpful? YesNo [Overview](https://docs.arc.io/integrate/exchanges) [Process withdrawals](https://docs.arc.io/integrate/exchanges/withdrawals) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Process Withdrawals - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/exchanges/withdrawals#content-area) Send USDC withdrawals from an exchange hot wallet on Arc by validating addresses, estimating gas, building EIP-1559 transactions, and confirming with deterministic finality. Send withdrawals as **native USDC** transfers—the recommended path for exchanges: cheaper gas (~21,000 vs ~65,000 units) and receivable by any address. The Memo contract lets you attach compliance metadata to the same transaction. [​](https://docs.arc.io/integrate/exchanges/withdrawals#prerequisites) Prerequisites --------------------------------------------------------------------------------------- Before you begin, ensure you have: * An Arc Testnet RPC endpoint (`https://rpc.testnet.arc.network`) * A funded hot wallet with USDC for both transfer amounts and gas fees * The USDC ERC-20 contract address: `0x3600000000000000000000000000000000000000` * [viem](https://viem.sh/) installed (`npm install viem`) [​](https://docs.arc.io/integrate/exchanges/withdrawals#steps) Steps ----------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/exchanges/withdrawals#step-1-validate-the-destination-address) Step 1. Validate the destination address Verify that the withdrawal address is a valid EIP-55 checksum-validated Ethereum address before building the transaction. This prevents sending funds to malformed addresses. import { getAddress, isAddress } from "viem"; function validateDestination(address: string): string { if (!isAddress(address)) { throw new Error(`Invalid address: ${address}`); } // Return EIP-55 checksum-validated address return getAddress(address); } const destination = validateDestination( "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28", ); The address must be: * 20 bytes (40 hex characters) with a `0x` prefix * Valid per EIP-55 checksum rules ### [​](https://docs.arc.io/integrate/exchanges/withdrawals#step-2-check-the-blocklist) Step 2. Check the blocklist Arc enforces a blocklist at the protocol level. If the destination address is blocklisted, the transaction reverts at runtime. Check before sending to avoid wasted gas. import { createPublicClient, http, parseAbi } from "viem"; const client = createPublicClient({ transport: http("https://rpc.testnet.arc.network"), }); const USDC_ADDRESS = "0x3600000000000000000000000000000000000000" as const; async function isBlocklisted(address: string): Promise { const blocked = await client.readContract({ address: USDC_ADDRESS, abi: parseAbi(["function isBlacklisted(address) view returns (bool)"]), functionName: "isBlacklisted", args: [address as `0x${string}`], }); return blocked; } const blocked = await isBlocklisted(destination); if (blocked) { throw new Error(`Destination ${destination} is blocklisted`); } ### [​](https://docs.arc.io/integrate/exchanges/withdrawals#step-3-estimate-gas-units) Step 3. Estimate gas units Call `eth_estimateGas` to determine the gas units required for the native USDC send. The `value` field is denominated in 18-decimal native wei. // 1 USDC in 18-decimal native wei. If you track 6-decimal balances, // convert with: amount = amount6 * 10n ** 12n const amount = 1_000_000_000_000_000_000n; const gasEstimate = await client.estimateGas({ account: hotWalletAddress, to: destination as `0x${string}`, value: amount, }); console.log(`Estimated gas units: ${gasEstimate}`); // Typical native USDC send: ~21,000 gas units `eth_estimateGas` returns gas units, not a cost in USDC. To calculate the cost, multiply by the effective gas price. A native USDC send uses approximately 21,000 gas units, while an ERC-20 `transfer()` call uses approximately 65,000. ### [​](https://docs.arc.io/integrate/exchanges/withdrawals#step-4-calculate-gas-cost) Step 4. Calculate gas cost Use `eth_gasPrice` to get the current suggested gas price, then compute the total fee. const gasPrice = await client.getGasPrice(); // Gas cost formula: gas_units * gas_price = cost in USDC wei (18 decimals) const estimatedCost = gasEstimate * gasPrice; // Convert to human-readable USDC (18 decimals for native gas accounting) const costInUsdc = Number(estimatedCost) / 1e18; console.log(`Estimated fee: ${costInUsdc} USDC`); **Gas cost formula:** cost_usdc_wei = gas_used * effective_gas_price cost_usdc = cost_usdc_wei / 10^18 For example, a native USDC send using 21,000 gas at 20 Gwei: 21,000 * 20,000,000,000 = 420,000,000,000,000 wei = 0.00042 USDC ### [​](https://docs.arc.io/integrate/exchanges/withdrawals#step-5-build-the-eip-1559-transaction) Step 5. Build the EIP-1559 transaction Construct a type-2 (EIP-1559) transaction with `maxFeePerGas` set to at least 20 Gwei. The fee fields and the `value` field are all denominated in USDC wei (18 decimals). import { createWalletClient, http, parseGwei } from "viem"; const walletClient = createWalletClient({ account: hotWalletAccount, // Your signing account transport: http("https://rpc.testnet.arc.network"), }); const txHash = await walletClient.sendTransaction({ to: destination as `0x${string}`, value: amount, // 18-decimal native USDC gas: gasEstimate, maxFeePerGas: parseGwei("25"), // Must be >= 20 Gwei maxPriorityFeePerGas: parseGwei("1"), chain: { id: 5042002, name: "Arc Testnet", nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 18 }, rpcUrls: { default: { http: ["https://rpc.testnet.arc.network"] } }, }, }); console.log(`Transaction hash: ${txHash}`); Transactions with `maxFeePerGas` below 20 Gwei may remain pending or fail to execute. Send withdrawals as native USDC (a plain value transfer). It is cheaper than an ERC-20 `transfer()` and any address can receive it. Native sends still emit a `Transfer` log from the system emitter (`0xffff…fffe`), so indexers and block explorers still capture them. Reach for the ERC-20 `transfer()` only when you need 6-decimal exactness or ERC-20 call semantics. ### [​](https://docs.arc.io/integrate/exchanges/withdrawals#step-6-confirm-inclusion) Step 6. Confirm inclusion Arc provides deterministic finality. Once a transaction is included in a block, it is final—no reorgs, no need to wait for additional confirmations. const receipt = await client.waitForTransactionReceipt({ hash: txHash }); if (receipt.status === "success") { console.log(`Withdrawal confirmed in block ${receipt.blockNumber}`); // Credit the withdrawal as complete—no further checks needed } else { console.error("Transaction reverted"); // Handle failure (see Step 7) } Unlike other blockchains, you do not need to wait for multiple block confirmations. A single block inclusion is final on Arc. ### [​](https://docs.arc.io/integrate/exchanges/withdrawals#step-7-handle-failures) Step 7. Handle failures Common failure scenarios and how to address them: | Failure | Cause | Resolution | | --- | --- | --- | | Transaction reverts | Destination is blocklisted | Check the blocklist before sending (Step 2) | | Transaction pending | `maxFeePerGas` too low | Resubmit with `maxFeePerGas >= 20 Gwei` | | Out of gas | Gas estimate too low | Add a buffer (for example, multiply estimate by 1.2) | | Insufficient balance | Hot wallet underfunded | Top up the hot wallet—USDC covers both the transfer and gas | async function processWithdrawal(to: string, amount: bigint): Promise { const validAddress = validateDestination(to); if (await isBlocklisted(validAddress)) { throw new Error(`Address ${validAddress} is blocklisted`); } // amount is 18-decimal native USDC wei const gas = await client.estimateGas({ account: hotWalletAddress, to: validAddress as `0x${string}`, value: amount, }); const txHash = await walletClient.sendTransaction({ to: validAddress as `0x${string}`, value: amount, gas: (gas * 120n) / 100n, // 20% buffer maxFeePerGas: parseGwei("25"), maxPriorityFeePerGas: parseGwei("1"), }); const receipt = await client.waitForTransactionReceipt({ hash: txHash }); if (receipt.status !== "success") { throw new Error(`Withdrawal failed: tx ${txHash} reverted`); } return txHash; } [​](https://docs.arc.io/integrate/exchanges/withdrawals#attach-memos-for-compliance) Attach memos for compliance ------------------------------------------------------------------------------------------------------------------- Use the Memo contract to attach metadata (such as internal withdrawal IDs or compliance references) to transfers. The Memo contract wraps the encoded USDC transfer call, routes it through CallFrom so the USDC transfer still sees your hot wallet as `msg.sender`, and emits a `Memo` event for reconciliation. **Memo contract address:** `0x5294E9927c3306DcBaDb03fe70b92e01cCede505` For the full viem, ethers, Python, and cURL flow, see [Send USDC with a transaction memo](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo) . Store a deterministic `memoId` or encoded memo value that links the onchain transfer to your internal withdrawal record. [​](https://docs.arc.io/integrate/exchanges/withdrawals#see-also) See also ----------------------------------------------------------------------------- * [Gas and fees](https://docs.arc.io/arc/references/gas-and-fees) —Fee model details and base fee mechanics * [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) —Why single-block confirmation is safe * [Detect deposits](https://docs.arc.io/integrate/exchanges/deposits) —The corresponding deposit detection guide * [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) —All system contract addresses Was this page helpful? YesNo [Detect and process deposits](https://docs.arc.io/integrate/exchanges/deposits) [Bridge USDC with CCTP](https://docs.arc.io/integrate/exchanges/cctp-bridging) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Deploying a node as a service - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/deploy-node-as-service#content-area) v0.7.2 (latest) v0.7.2 (latest) **v0.7.2: Arc Testnet hardfork activates 2026-06-18 14:00:00 UTC.** The Zero7 hardfork activates at Unix timestamp `1781791200`. Testnet nodes must be on v0.7.2 by that timestamp; earlier versions stop syncing after activation. See [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) for those defaults, or [CHANGELOG.md](https://github.com/circlefin/arc-node/blob/main/CHANGELOG.md) and [BREAKING\_CHANGES.md](https://github.com/circlefin/arc-node/blob/main/BREAKING_CHANGES.md#v072) . Configure your Arc node’s Execution Layer and Consensus Layer as systemd services so they auto-restart on failure and survive reboots. [​](https://docs.arc.io/arc/tutorials/deploy-node-as-service#prerequisites) Prerequisites -------------------------------------------------------------------------------------------- * You have installed Arc and completed the [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) tutorial with both layers syncing * You have a Linux machine with systemd * You have root or `sudo` access The unit files below reference `$USER` and `$HOME`. The shell expands these to your current username and home directory **before** the file is written. After running each `sudo tee` command, review the generated unit with `sudo cat /etc/systemd/system/arc-execution.service` to confirm the paths are correct. [​](https://docs.arc.io/arc/tutorials/deploy-node-as-service#step-1-create-the-execution-layer-unit-file) Step 1: Create the Execution Layer unit file --------------------------------------------------------------------------------------------------------------------------------------------------------- Write the systemd unit file to `/etc/systemd/system/arc-execution.service`: sudo tee /etc/systemd/system/arc-execution.service > /dev/null < /dev/null < /etc/systemd/system/arc-execution.service. Created symlink /etc/systemd/system/multi-user.target.wants/arc-consensus.service -> /etc/systemd/system/arc-consensus.service. [​](https://docs.arc.io/arc/tutorials/deploy-node-as-service#step-4-verify-the-services-are-running) Step 4: Verify the services are running ----------------------------------------------------------------------------------------------------------------------------------------------- Check the status of both services: sudo systemctl status arc-execution sudo systemctl status arc-consensus Each command prints output similar to: ● arc-execution.service - Arc Node - Execution Layer Loaded: loaded (/etc/systemd/system/arc-execution.service; enabled) Active: active (running) since ... Both services display `Active: active (running)` in their output. If a service shows `failed` or `activating`, check its logs: sudo journalctl -u arc-execution --no-pager -n 50 sudo journalctl -u arc-consensus --no-pager -n 50 **Common issues:** * **Incorrect file paths in `ExecStart`:** Verify that the binary paths in the unit files match your installation directory. * **Permission errors on data directories:** Confirm that the `User` specified in the unit file owns `$HOME/.arc/`. * **Consensus Layer connection error:** The Execution Layer may not be ready yet. Wait for it to reach `active (running)`, then run `sudo systemctl restart arc-consensus`. To confirm sync progress, view logs, and verify the Prometheus metrics endpoints on ports `9001` (Execution Layer) and `29000` (Consensus Layer), see [Monitor a node](https://docs.arc.io/arc/tutorials/monitor-a-node) . To set up local Prometheus and Grafana dashboards on the same host, see [Set up Prometheus and Grafana for an Arc node](https://docs.arc.io/arc/tutorials/set-up-node-monitoring) . Was this page helpful? YesNo [Run an Arc node](https://docs.arc.io/arc/tutorials/run-an-arc-node) [Monitor a node](https://docs.arc.io/arc/tutorials/monitor-a-node) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Bridge USDC with CCTP - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/exchanges/cctp-bridging#content-area) [Circle’s Cross-Chain Transfer Protocol (CCTP)](https://developers.circle.com/cctp) uses a burn-and-mint model to transfer native USDC between blockchains without wrapped or bridged token variants. Arc’s CCTP domain is `26`. Transfers into Arc mint USDC directly to your recipient address; transfers out burn USDC and mint on the destination blockchain. Because Arc has instant finality, outbound transfers reach attestation faster than transfers from blockchains that require multiple confirmations. [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#prerequisites) Prerequisites ----------------------------------------------------------------------------------------- Before you begin, ensure you have: * An RPC endpoint for Arc Testnet (`https://rpc.testnet.arc.network`) * An RPC endpoint for the source or destination blockchain (for example, Ethereum Sepolia) * USDC balance on the sending blockchain * Familiarity with the [CCTP developer docs](https://developers.circle.com/stablecoins/cctp-getting-started) * A TypeScript environment with `viem` installed [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#contract-addresses) Contract addresses --------------------------------------------------------------------------------------------------- | Contract | Address | Domain | | --- | --- | --- | | **TokenMessengerV2** (Arc) | [`0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA`](https://testnet.arcscan.app/address/0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA) | 26 | | **MessageTransmitterV2** (Arc) | [`0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275`](https://testnet.arcscan.app/address/0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275) | 26 | | **USDC ERC-20** (Arc) | [`0x3600000000000000000000000000000000000000`](https://testnet.arcscan.app/address/0x3600000000000000000000000000000000000000) | — | For CCTP contract addresses on other blockchains, see the [CCTP contract addresses](https://developers.circle.com/stablecoins/evm-smart-contracts) reference. [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#bridge-usdc-between-arc-and-other-blockchains) Bridge USDC between Arc and other blockchains --------------------------------------------------------------------------------------------------------------------------------------------------------- Use the inbound workflow to fund your exchange hot wallet on Arc from another blockchain, and the outbound workflow to rebalance liquidity from Arc to another blockchain. * Inbound (into Arc) * Outbound (out of Arc) ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-1-format-the-recipient-address) Step 1. Format the recipient address CCTP requires the recipient as a `bytes32` value. Left-pad the 20-byte Ethereum address with zeros: import { pad, type Address } from "viem"; const recipientAddress: Address = "0xYourArcHotWalletAddress"; const mintRecipient = pad(recipientAddress, { size: 32 }); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-2-approve-usdc-on-the-source-blockchain) Step 2. Approve USDC on the source blockchain Approve the source blockchain’s TokenMessengerV2 contract to spend your USDC: import { parseUnits } from "viem"; const amount = parseUnits("10000", 6); // 10,000 USDC const approvalTx = await sourceWalletClient.writeContract({ address: SOURCE_USDC_ADDRESS, abi: [\ {\ name: "approve",\ type: "function",\ inputs: [\ { name: "spender", type: "address" },\ { name: "amount", type: "uint256" },\ ],\ outputs: [{ type: "bool" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "approve", args: [SOURCE_TOKEN_MESSENGER_ADDRESS, amount], }); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-3-call-depositforburn-on-the-source-blockchain) Step 3. Call `depositForBurn` on the source blockchain Burn USDC on the source blockchain, targeting Arc (domain `26`): const ARC_DOMAIN = 26; const burnTx = await sourceWalletClient.writeContract({ address: SOURCE_TOKEN_MESSENGER_ADDRESS, abi: [\ {\ name: "depositForBurn",\ type: "function",\ inputs: [\ { name: "amount", type: "uint256" },\ { name: "destinationDomain", type: "uint32" },\ { name: "mintRecipient", type: "bytes32" },\ { name: "burnToken", type: "address" },\ ],\ outputs: [{ type: "uint64" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "depositForBurn", args: [amount, ARC_DOMAIN, mintRecipient, SOURCE_USDC_ADDRESS], }); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-4-retrieve-the-message-hash) Step 4. Retrieve the message hash After the burn transaction confirms, extract the `MessageSent` event to get the message hash: const burnReceipt = await sourcePublicClient.waitForTransactionReceipt({ hash: burnTx, }); const messageSentEvent = burnReceipt.logs.find( (log) => log.topics[0] === "0x2fa9ca894982930190727e75500a97d8dc500233a5065e0f3126c48fbe0343c0", ); const messageBytes = messageSentEvent?.data; const messageHash = keccak256(messageBytes!); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-5-wait-for-attestation) Step 5. Wait for attestation Poll Circle’s attestation service until the attestation is available: async function getAttestation(messageHash: string): Promise { const url = `https://iris-api.circle.com/v2/attestations/${messageHash}`; while (true) { const response = await fetch(url); const data = await response.json(); if (data.status === "complete") { return data.attestation; } // Poll every 10 seconds await new Promise((resolve) => setTimeout(resolve, 10_000)); } } const attestation = await getAttestation(messageHash); Attestation typically takes approximately 60 seconds but varies based on the source blockchain’s finality time. Transfers from blockchains with longer finality (such as Ethereum) take longer than those from blockchains with fast finality. ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-6-call-receivemessage-on-arc) Step 6. Call `receiveMessage` on Arc Submit the message and attestation to Arc’s MessageTransmitterV2 to mint USDC: const ARC_MESSAGE_TRANSMITTER = "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275"; const receiveTx = await arcWalletClient.writeContract({ address: ARC_MESSAGE_TRANSMITTER, abi: [\ {\ name: "receiveMessage",\ type: "function",\ inputs: [\ { name: "message", type: "bytes" },\ { name: "attestation", type: "bytes" },\ ],\ outputs: [{ type: "bool" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "receiveMessage", args: [messageBytes, attestation], }); Once the transaction confirms, USDC is minted to the recipient address on Arc. ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-1-format-the-recipient-address-2) Step 1. Format the recipient address Pad the destination address to `bytes32`: import { pad, type Address } from "viem"; const destinationAddress: Address = "0xYourDestinationAddress"; const mintRecipient = pad(destinationAddress, { size: 32 }); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-2-approve-usdc-on-arc) Step 2. Approve USDC on Arc Approve Arc’s TokenMessengerV2 to spend your USDC: import { parseUnits } from "viem"; const ARC_USDC = "0x3600000000000000000000000000000000000000"; const ARC_TOKEN_MESSENGER = "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA"; const amount = parseUnits("10000", 6); // 10,000 USDC const approvalTx = await arcWalletClient.writeContract({ address: ARC_USDC, abi: [\ {\ name: "approve",\ type: "function",\ inputs: [\ { name: "spender", type: "address" },\ { name: "amount", type: "uint256" },\ ],\ outputs: [{ type: "bool" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "approve", args: [ARC_TOKEN_MESSENGER, amount], }); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-3-call-depositforburn-on-arc) Step 3. Call `depositForBurn` on Arc Burn USDC on Arc, targeting the destination domain: // Example: Ethereum = 0, Avalanche = 1, Arbitrum = 3 const DESTINATION_DOMAIN = 0; // Replace with your target domain const burnTx = await arcWalletClient.writeContract({ address: ARC_TOKEN_MESSENGER, abi: [\ {\ name: "depositForBurn",\ type: "function",\ inputs: [\ { name: "amount", type: "uint256" },\ { name: "destinationDomain", type: "uint32" },\ { name: "mintRecipient", type: "bytes32" },\ { name: "burnToken", type: "address" },\ ],\ outputs: [{ type: "uint64" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "depositForBurn", args: [amount, DESTINATION_DOMAIN, mintRecipient, ARC_USDC], }); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-4-retrieve-the-message-hash-2) Step 4. Retrieve the message hash Extract the message from the burn transaction receipt: const burnReceipt = await arcPublicClient.waitForTransactionReceipt({ hash: burnTx, }); const messageSentEvent = burnReceipt.logs.find( (log) => log.topics[0] === "0x2fa9ca894982930190727e75500a97d8dc500233a5065e0f3126c48fbe0343c0", ); const messageBytes = messageSentEvent?.data; const messageHash = keccak256(messageBytes!); ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-5-wait-for-attestation-2) Step 5. Wait for attestation Poll for the attestation. Because Arc has instant finality, attestation for outbound transfers is typically faster: const attestation = await getAttestation(messageHash); Outbound transfers from Arc benefit from instant finality. The attestation service can process the message once the block is produced, without waiting for additional confirmations. ### [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#step-6-call-receivemessage-on-the-destination-blockchain) Step 6. Call `receiveMessage` on the destination blockchain Submit the message and attestation to the destination blockchain’s MessageTransmitterV2: const receiveTx = await destinationWalletClient.writeContract({ address: DESTINATION_MESSAGE_TRANSMITTER, abi: [\ {\ name: "receiveMessage",\ type: "function",\ inputs: [\ { name: "message", type: "bytes" },\ { name: "attestation", type: "bytes" },\ ],\ outputs: [{ type: "bool" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "receiveMessage", args: [messageBytes, attestation], }); Once confirmed, USDC is minted to the recipient on the destination blockchain. [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#monitor-transfer-status) Monitor transfer status ------------------------------------------------------------------------------------------------------------- Track a CCTP transfer by polling the attestation API: import { keccak256 } from "viem"; async function checkTransferStatus(messageHash: string) { const response = await fetch( `https://iris-api.circle.com/v2/attestations/${messageHash}`, ); const data = await response.json(); // Possible statuses: "pending", "complete" return data.status; } Each CCTP message can only be received once. If `receiveMessage` reverts, verify that the message has not already been processed by checking the `usedNonces` mapping on the destination MessageTransmitterV2 contract. [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#cctp-v2-depositforburnwithhook) CCTP V2 `depositForBurnWithHook` ----------------------------------------------------------------------------------------------------------------------------- CCTP V2 introduces `depositForBurnWithHook`, which allows you to specify a destination caller and attach a hook for post-mint actions. This is useful for triggering automated workflows (such as depositing into a vault) immediately after USDC is minted: const burnWithHookTx = await arcWalletClient.writeContract({ address: ARC_TOKEN_MESSENGER, abi: [\ {\ name: "depositForBurnWithHook",\ type: "function",\ inputs: [\ { name: "amount", type: "uint256" },\ { name: "destinationDomain", type: "uint32" },\ { name: "mintRecipient", type: "bytes32" },\ { name: "burnToken", type: "address" },\ { name: "destinationCaller", type: "bytes32" },\ { name: "hookData", type: "bytes" },\ ],\ outputs: [{ type: "uint64" }],\ stateMutability: "nonpayable",\ },\ ], functionName: "depositForBurnWithHook", args: [\ amount,\ DESTINATION_DOMAIN,\ mintRecipient,\ ARC_USDC,\ destinationCaller, // bytes32-padded address authorized to call receiveMessage\ hookData, // Encoded calldata for post-mint execution\ ], }); When `destinationCaller` is set to a non-zero value, only that address can call `receiveMessage` for this transfer on the destination blockchain. Set it to `0x000000` to allow any address to complete the transfer. [​](https://docs.arc.io/integrate/exchanges/cctp-bridging#see-also) See also ------------------------------------------------------------------------------- * [Contract addresses](https://docs.arc.io/arc/references/contract-addresses) : full list of CCTP and other deployed contracts on Arc * [CCTP developer docs](https://developers.circle.com/stablecoins/cctp-getting-started) : attestation API reference and supported domains * [Exchange integration overview](https://docs.arc.io/integrate/exchanges) : deposits, withdrawals, and liquidity management * [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) : how Arc’s instant finality affects crosschain transfers Was this page helpful? YesNo [Process withdrawals](https://docs.arc.io/integrate/exchanges/withdrawals) [Custody platforms](https://docs.arc.io/integrate/exchanges/custody) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Monitor contract events - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/monitor-contract-events#content-area) Track contract events and get event logs with the Circle Contracts API. [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#prerequisites) Prerequisites --------------------------------------------------------------------------------------------- You need a deployed contract to monitor. If you completed the [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) tutorial, you can continue with that contract. If your contract was deployed elsewhere, import it in [Step 3](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-3-import-a-contract-optional) . [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-1-update-your-project) Step 1. Update your project ------------------------------------------------------------------------------------------------------------------------ If you haven’t already, add run scripts for monitoring contract events to your `package.json`: npm pkg set scripts.webhook="tsx webhook-receiver.ts" npm pkg set scripts.import-contract="tsx --env-file=.env import-contract.ts" npm pkg set scripts.create-monitor="tsx --env-file=.env create-monitor.ts" npm pkg set scripts.get-event-logs="tsx --env-file=.env get-event-logs.ts" If you completed the Deploy contracts tutorial, your project already has the required SDKs installed. The npm scripts previously listed work with your existing setup. [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-2-set-up-your-webhook) Step 2. Set up your webhook ------------------------------------------------------------------------------------------------------------------------ Event monitors send real-time updates to your webhook endpoint when events happen. * webhook.site * ngrok 1. Visit [webhook.site](https://webhook.site/) 2. Copy your unique webhook URL (for example, `https://webhook.site/your-uuid`) 1. Install `ngrok` from [ngrok.com](https://ngrok.com/) 2. Create a webhook receiver script: webhook-receiver.ts webhook\_receiver.py import express, { Request, Response } from "express"; const app = express(); app.use(express.json()); app.post("/webhook", (req: Request, res: Response) => { console.log("Received webhook:"); console.log(JSON.stringify(req.body, null, 2)); res.status(200).json({ received: true }); }); const PORT = 3000; app.listen(PORT, () => { console.log(`Webhook receiver listening on port ${PORT}`); console.log(`Endpoint: http://localhost:${PORT}/webhook`); }); from flask import Flask, request, jsonify import json app = Flask(__name__) @app.route("/webhook", methods=["POST"]) def webhook(): data = request.get_json() print("Received webhook:") print(json.dumps(data, indent=2)) return jsonify({"received": True}), 200 if __name__ == "__main__": PORT = 3000 print(f"Webhook receiver listening on port {PORT}") print(f"Endpoint: http://localhost:{PORT}/webhook") app.run(port=PORT) 3. Start the webhook receiver: Node.js Python npm run webhook python webhook_receiver.py 4. In a separate terminal, start `ngrok`: ngrok http 3000 5. Copy the HTTPS forwarding URL (for example, `https://abc123.ngrok-free.app/webhook`) If using `ngrok` for local testing, you can optionally set `WEBHOOK_URL` in your `.env` file to store your `ngrok` forwarding URL. [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-3-register-your-webhook-in-console) Step 3. Register your webhook in Console -------------------------------------------------------------------------------------------------------------------------------------------------- Register your webhook URL in the Developer Console: 1. Go to [Developer Console](https://console.circle.com/) 2. Navigate to **Webhooks** (left sidebar) 3. Click **Add a webhook** 4. Enter your webhook URL (from Step 1) and create the webhook Register your webhook before creating event monitors. This allows Circle to send notifications to your endpoint. [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-4-import-a-contract-optional) Step 4. Import a contract (optional) ---------------------------------------------------------------------------------------------------------------------------------------- If your contract was deployed elsewhere and is not yet available in the Developer Console, import it first. If you deployed a contract using Circle Contracts, including the [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) tutorial, skip this step. Your contract is already available in the Console. import-contract.ts import\_contract.py import { initiateSmartContractPlatformClient } from "@circle-fin/smart-contract-platform"; const contractClient = initiateSmartContractPlatformClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); async function importContract() { try { const response = await contractClient.importContract({ blockchain: "ARC-TESTNET", address: process.env.CONTRACT_ADDRESS, name: "MyContract", }); console.log(JSON.stringify(response.data, null, 2)); } catch (error) { console.error("Error importing contract:", error.message); throw error; } } importContract(); from circle.web3 import utils, smart_contract_platform import os import json client = utils.init_smart_contract_platform_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) def import_contract(): try: contracts_api = smart_contract_platform.ContractsApi(client) import_request = smart_contract_platform.ImportContractRequest( blockchain="ARC-TESTNET", address=os.getenv("CONTRACT_ADDRESS"), name="MyContract", ) response = contracts_api.import_contract(import_contract_request=import_request) print(json.dumps(response.data.to_dict(), indent=2)) except Exception as error: print(f"Error importing contract: {error}") raise error import_contract() **Run the script:** Node.js Python npm run import-contract python import_contract.py If the contract is already imported, you’ll see an error: `contract already exists`. This means the contract is already available in the Console and you can proceed to create an event monitor. [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-5-create-an-event-monitor) Step 5. Create an event monitor -------------------------------------------------------------------------------------------------------------------------------- Event monitors track specific contract events. They send updates to your webhook endpoint. This example monitors `Transfer` events: create-monitor.ts create\_monitor.py import { initiateSmartContractPlatformClient } from "@circle-fin/smart-contract-platform"; const contractClient = initiateSmartContractPlatformClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); async function createEventMonitor() { try { const response = await contractClient.createEventMonitor({ blockchain: "ARC-TESTNET", contractAddress: process.env.CONTRACT_ADDRESS, eventSignature: "Transfer(address,address,uint256)", }); console.log(JSON.stringify(response.data, null, 2)); } catch (error) { console.error("Error creating event monitor:", error.message); throw error; } } createEventMonitor(); from circle.web3 import utils, smart_contract_platform import os import json client = utils.init_smart_contract_platform_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) def create_event_monitor(): try: event_monitors_api = smart_contract_platform.EventMonitorsApi(client) monitor_request = smart_contract_platform.CreateEventMonitorRequest( blockchain="ARC-TESTNET", contract_address=os.getenv("CONTRACT_ADDRESS"), event_signature="Transfer(address,address,uint256)", ) response = event_monitors_api.create_event_monitor( create_event_monitor_request=monitor_request ) print(json.dumps(response.data.to_dict(), indent=2)) except Exception as error: print(f"Error creating event monitor: {error}") raise create_event_monitor() **Run the script:** Node.js Python npm run create-monitor python create_monitor.py **Response:** { "eventMonitor": { "id": "019bf984-b4da-7026-a3d2-674ce371a933", "contractName": "TestERC20Token", "contractId": "019bf8be-7be5-7a3e-89cc-05bcd7413f20", "contractAddress": "0x281156899e5bd6fecf1c0831ee24894eeeaea2f8", "blockchain": "ARC-TESTNET", "eventSignature": "Transfer(address,address,uint256)", "eventSignatureHash": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "isEnabled": true, "createDate": "2026-01-26T08:56:22.490638Z", "updateDate": "2026-01-26T08:56:22.490638Z" } } [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-6-receive-webhook-notifications) Step 6. Receive webhook notifications -------------------------------------------------------------------------------------------------------------------------------------------- When events occur, Circle sends updates to your endpoint. Here is what a `Transfer` event looks like: { "subscriptionId": "f0332621-a117-4b7b-bdf0-5c61a4681826", "notificationId": "5c5eea9f-398f-426f-a4a5-1bdc28b36d2c", "notificationType": "contracts.eventLog", "notification": { "contractAddress": "0x4abcffb90897fe7ce86ed689d1178076544a021b", "blockchain": "ARC-TESTNET", "txHash": "0xe15d6dbb50178f60930b8a3e3e775f3c022505ea2e351b6c2c2985d2405c8ebc", "userOpHash": "0x78c3e8185ff9abfc7197a8432d9b79566123616c136001e609102c97e732e55e", "blockHash": "0x0ad6bf57a110d42620defbcb9af98d6223f060de588ed96ae495ddeaf3565c8d", "blockHeight": 22807198, "eventSignature": "Transfer(address,address,uint256)", "eventSignatureHash": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "topics": [\ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\ "0x0000000000000000000000000000000000000000000000000000000000000000",\ "0x000000000000000000000000bcf83d3b112cbf43b19904e376dd8dee01fe2758"\ ], "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", "firstConfirmDate": "2026-01-21T06:53:12Z" }, "timestamp": "2026-01-21T06:53:13.194467201Z", "version": 2 } **Key fields:** * `notificationType`: Always `"contracts.eventLog"` for event monitor webhooks * `notification.eventSignature`: The event that was emitted * `notification.contractAddress`: Address of the contract that emitted the event * `notification.blockchain`: The blockchain network (for example, `ARC-TESTNET`) * `notification.txHash`: Transaction hash where the event occurred * `notification.userOpHash`: User operation hash (for smart contract accounts) * `notification.blockHash`: Hash of the block containing the transaction * `notification.blockHeight`: Block number where the event occurred * `notification.eventSignatureHash`: Keccak256 hash of the event signature * `notification.topics`: Indexed event parameters (for example, `from` and `to` addresses) * `notification.data`: Non-indexed event parameters (for example, token amount) * `notification.firstConfirmDate`: Timestamp when the event was first confirmed * `timestamp`: Timestamp when the webhook was sent * `version`: Webhook payload version You can verify webhook delivery status in the [Developer Console](https://console.circle.com/) under Contracts → Monitoring. [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#step-7-retrieve-event-logs) Step 7. Retrieve event logs ------------------------------------------------------------------------------------------------------------------------ You can also query event logs with the API. This is useful for past events or if you prefer polling. **Webhooks vs Polling**: Webhooks send real-time updates (push). Polling needs periodic API calls (pull). Use webhooks for production and polling for testing or past queries. get-event-logs.ts get\_event\_logs.py import { initiateSmartContractPlatformClient } from "@circle-fin/smart-contract-platform"; const contractClient = initiateSmartContractPlatformClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); async function getEventLogs() { try { const response = await contractClient.listEventLogs({ contractAddress: process.env.CONTRACT_ADDRESS, blockchain: "ARC-TESTNET", pageSize: 10, }); console.log(JSON.stringify(response.data, null, 2)); } catch (error) { console.error("Error fetching event logs:", error.message); throw error; } } getEventLogs(); from circle.web3 import utils, smart_contract_platform import os import json client = utils.init_smart_contract_platform_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) event_monitors_api = smart_contract_platform.EventMonitorsApi(client) def get_event_logs(): try: response = event_monitors_api.list_event_logs( contract_address=os.getenv("CONTRACT_ADDRESS"), blockchain="ARC-TESTNET", page_size=10 ) print(json.dumps(response.data.to_dict(), indent=2, default=str)) except Exception as error: print(f"Error fetching event logs: {error}") raise get_event_logs() **Run the script:** Node.js Python npm run get-event-logs python get_event_logs.py Replace `CONTRACT_ADDRESS` with your contract address. You can get this address when you deploy the contract, or by listing your contracts with `listContracts()`. **Response:** { "eventLogs": [\ {\ "id": "019bf987-f901-7145-9e95-55f177b05b24",\ "subscriptionId": "019bf984-b4da-7026-a3d2-674ce371a933",\ "contractId": "019bf8be-7be5-7a3e-89cc-05bcd7413f20",\ "contractName": "TestERC20Token",\ "blockchain": "ARC-TESTNET",\ "txHash": "0x3bfbab5d5ce0d1a5d682cbc742d3940cf59db0369d173b71ba2a3b8f43bfbcb1",\ "logIndex": "50",\ "blockHash": "0x7d12148f9331556b31f84f58a41b7ff16eaaa47940f9e86733037d7ab74d858e",\ "blockHeight": 23686153,\ "contractAddress": "0x281156899e5bd6fecf1c0831ee24894eeeaea2f8",\ "eventSignature": "Transfer(address,address,uint256)",\ "eventSignatureHash": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\ "topics": [\ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\ "0x0000000000000000000000000000000000000000000000000000000000000000",\ "0x000000000000000000000000bcf83d3b112cbf43b19904e376dd8dee01fe2758"\ ],\ "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",\ "decodedTopics": null,\ "decodedData": null,\ "userOpHash": "0x66befac1a371fcdddf1566215e4677127e111dff9253f306f7096fed8642a208",\ "firstConfirmDate": "2026-01-26T08:59:55Z",\ "createDate": "2026-01-26T08:59:56.545962Z",\ "updateDate": "2026-01-26T08:59:56.545962Z"\ }\ ] } You can view, update, and delete event monitors with the Circle Contracts API. See the [API Reference](https://developers.circle.com/api-reference/contracts/smart-contract-platform/get-event-monitors) for details on managing your monitors. [​](https://docs.arc.io/arc/tutorials/monitor-contract-events#summary) Summary --------------------------------------------------------------------------------- After completing this tutorial, you’ve successfully: * Set up webhook endpoints using webhook.site or `ngrok` * Registered webhooks in the Developer Console * Created event monitors for specific contract events * Received real-time webhook updates for contract events * Retrieved past event logs with the Circle SDK Was this page helpful? YesNo [Interact with contracts](https://docs.arc.io/arc/tutorials/interact-with-contracts) [Port a contract](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # How to: Index Arc Events - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/integrate/infrastructure/indexing-events#content-area) Monitor the native USDC `Transfer` event from the system address `0xffff…fffe` to capture every native USDC movement in one stream (Arc’s EIP-7708 implementation). Index the ERC-20 USDC contract, Memo, blocklist, and CCTP events from their respective contracts for full transaction coverage. Skip reorg-handling logic entirely; Arc’s deterministic finality means every block is permanent. Use block number (not timestamp) as your ordering key because sub-second blocks can share the same `block.timestamp`. [​](https://docs.arc.io/integrate/infrastructure/indexing-events#prerequisites) 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 JSON-RPC methods (`eth_getLogs`, `eth_subscribe`) * A database or indexing pipeline that supports high-throughput block ingestion * TypeScript environment with `ethers` or `viem` installed [​](https://docs.arc.io/integrate/infrastructure/indexing-events#steps) Steps -------------------------------------------------------------------------------- ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-1-connect-to-the-block-stream) Step 1. Connect to the block stream Use `eth_subscribe("newHeads")` over WebSocket for real-time block notifications. For historical back-fills, use `eth_getLogs` with `fromBlock`/`toBlock` ranges. import { WebSocketProvider, JsonRpcProvider } from "ethers"; // Real-time streaming const wsProvider = new WebSocketProvider("wss://rpc.testnet.arc.network"); wsProvider.on("block", async (blockNumber: number) => { await indexBlock(blockNumber); }); // Historical backfill const httpProvider = new JsonRpcProvider("https://rpc.testnet.arc.network"); async function backfill(startBlock: number, endBlock: number): Promise { const BATCH_SIZE = 1000; for (let from = startBlock; from <= endBlock; from += BATCH_SIZE) { const to = Math.min(from + BATCH_SIZE - 1, endBlock); const logs = await httpProvider.getLogs({ fromBlock: from, toBlock: to }); await processLogs(logs); } } Arc produces sub-second blocks. Your ingestion pipeline must handle bursts of many blocks per second without falling behind. ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-2-index-native-usdc-transfer-events-eip-7708) Step 2. Index native USDC Transfer events (EIP-7708) Every native USDC movement emits a standard ERC-20 `Transfer` log from the system address `0xfffffffffffffffffffffffffffffffffffffffe` (Arc’s [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) implementation). This single stream covers native sends, the native leg of ERC-20 transfers, and mint and burn, with values in **18 decimals**. It is the single source of truth for USDC balance changes. The ERC-20 USDC contract at `0x3600…0000` also emits its own `Transfer` (6 decimals) for ERC-20-interface activity, so an ERC-20 `transfer()` produces a log from both emitters. Distinguish them by emitter address and never count the same movement twice. For the complete event matrix, the mint and burn mapping, and the historical pre-Zero5 events, see [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) . | Property | Value | | --- | --- | | Emitter | `0xfffffffffffffffffffffffffffffffffffffffe` | | Event | `Transfer(address indexed from, address indexed to, uint256 value)` | | topic0 | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | | Decimals | 18 | import { Interface, JsonRpcProvider, Log } from "ethers"; // Native USDC system emitter (EIP-7708 Transfer logs, 18 decimals) const NATIVE_USDC_EMITTER = "0xfffffffffffffffffffffffffffffffffffffffe"; // ERC-20 USDC contract (emits its own 6-decimal Transfer and blocklist events) const USDC_ADDRESS = "0x3600000000000000000000000000000000000000"; const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; const erc20Interface = new Interface([\ "event Transfer(address indexed from, address indexed to, uint256 value)",\ ]); const provider = new JsonRpcProvider("https://rpc.testnet.arc.network"); async function indexUsdcTransfers( fromBlock: number, toBlock: number, ): Promise { const logs = await provider.getLogs({ address: NATIVE_USDC_EMITTER, topics: [TRANSFER_TOPIC], fromBlock, toBlock, }); for (const log of logs) { const parsed = erc20Interface.parseLog({ topics: log.topics as string[], data: log.data, }); if (!parsed) continue; const from = parsed.args.from as string; const to = parsed.args.to as string; const value = parsed.args.value as bigint; // 18 decimals (native) await saveTransfer({ blockNumber: log.blockNumber, txHash: log.transactionHash, logIndex: log.index, from, to, value, }); } } Filtering the native system emitter (`0xffff…fffe`) captures all native USDC movements in one stream: native sends, the native leg of ERC-20 transfers, and mint and burn. If you also index the ERC-20 USDC contract (`0x3600…0000`) for its 6-decimal `Transfer` events, match on the emitter address so you do not count ERC-20 transfers twice. To backfill history across the Zero5 hard fork, read the historical `NativeCoin*` events from `0x1800…0000` for blocks before activation and `Transfer` from `0xffff…fffe` at and after it. See [USDC system events](https://docs.arc.io/arc/references/usdc-system-events#historical-events-before-zero5) for signatures and the activation reference. ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-3-index-eurc-and-other-erc-20-token-transfers) Step 3. Index EURC and other ERC-20 token transfers EURC and other ERC-20 tokens emit standard `Transfer` events from their own contract addresses. Index these separately from USDC. | Token | Contract | Decimals | | --- | --- | --- | | EURC | `0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a` | 6 | const EURC_ADDRESS = "0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a"; async function indexEurcTransfers( fromBlock: number, toBlock: number, ): Promise { const logs = await provider.getLogs({ address: EURC_ADDRESS, topics: [TRANSFER_TOPIC], fromBlock, toBlock, }); for (const log of logs) { const parsed = erc20Interface.parseLog({ topics: log.topics as string[], data: log.data, }); if (!parsed) continue; await saveTransfer({ blockNumber: log.blockNumber, txHash: log.transactionHash, logIndex: log.index, from: parsed.args.from as string, to: parsed.args.to as string, value: parsed.args.value as bigint, }); } } ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-4-index-memo-contract-events) Step 4. Index Memo contract events The Memo contract lets an account attach an arbitrary memo to a forwarded call (for example, a USDC transfer) for correlation and reconciliation. It executes the call through the CallFrom precompile, preserving the original `msg.sender`, and emits a `Memo` event with the metadata. Index `Memo` events to store memo payloads alongside the calls they annotate. The Memo contract is available on Arc testnet as of June 18, 2026. | Property | Value | | --- | --- | | Contract | `0x5294E9927c3306DcBaDb03fe70b92e01cCede505` | | Event | `Memo(address indexed sender, address indexed target, bytes32 callDataHash, bytes32 indexed memoId, bytes memo, uint256 memoIndex)` | const MEMO_CONTRACT = "0x5294E9927c3306DcBaDb03fe70b92e01cCede505"; const memoInterface = new Interface([\ "event Memo(address indexed sender, address indexed target, bytes32 callDataHash, bytes32 indexed memoId, bytes memo, uint256 memoIndex)",\ ]); async function indexMemoEvents( fromBlock: number, toBlock: number, ): Promise { const logs = await provider.getLogs({ address: MEMO_CONTRACT, fromBlock, toBlock, }); for (const log of logs) { const parsed = memoInterface.parseLog({ topics: log.topics as string[], data: log.data, }); if (!parsed) continue; // skips the contract's BeforeMemo events const sender = parsed.args.sender as string; // original caller (msg.sender) const target = parsed.args.target as string; // contract the call was forwarded to const callDataHash = parsed.args.callDataHash as string; // hash of the forwarded calldata const memoId = parsed.args.memoId as string; // caller-supplied identifier (bytes32) const memoBytes = parsed.args.memo as string; // hex-encoded bytes const memoIndex = parsed.args.memoIndex as bigint; // sequential memo index // Decode the memo payload as UTF-8 text if applicable const memoText = Buffer.from(memoBytes.slice(2), "hex").toString("utf-8"); await saveMemo({ blockNumber: log.blockNumber, txHash: log.transactionHash, logIndex: log.index, sender, target, callDataHash, memoId, memo: memoText, memoIndex, }); } } Correlate `Memo` events with the `Transfer` (or other target-contract) events they annotate by matching on `transactionHash`, or use the caller-supplied `memoId`. To match a specific target call, compare `callDataHash` with the hash of the calldata your application submitted. The memo provides context (such as an invoice ID or payment reference) for the forwarded call. ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-5-index-blocklist-events) Step 5. Index blocklist events The USDC contract emits `Blocklisted` and `UnBlocklisted` events when addresses are added to or removed from the blocklist. Track these to maintain an accurate set of restricted addresses. | Event | Signature | | --- | --- | | Address blocked | `Blocklisted(address indexed account)` | | Address unblocked | `UnBlocklisted(address indexed account)` | const blocklistInterface = new Interface([\ "event Blocklisted(address indexed account)",\ "event UnBlocklisted(address indexed account)",\ ]); const BLOCKLISTED_TOPIC = blocklistInterface.getEvent("Blocklisted")!.topicHash; const UNBLOCKLISTED_TOPIC = blocklistInterface.getEvent("UnBlocklisted")!.topicHash; async function indexBlocklistEvents( fromBlock: number, toBlock: number, ): Promise { const logs = await provider.getLogs({ address: USDC_ADDRESS, topics: [[BLOCKLISTED_TOPIC, UNBLOCKLISTED_TOPIC]], fromBlock, toBlock, }); for (const log of logs) { const parsed = blocklistInterface.parseLog({ topics: log.topics as string[], data: log.data, }); if (!parsed) continue; const account = parsed.args.account as string; const isBlocked = parsed.name === "Blocklisted"; await updateBlocklist({ blockNumber: log.blockNumber, txHash: log.transactionHash, account, isBlocked, }); } } ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-6-index-cctp-crosschain-events) Step 6. Index CCTP crosschain events The Cross-Chain Transfer Protocol (CCTP) uses two contracts on Arc: `TokenMessengerV2` for outbound burns and `MessageTransmitterV2` for inbound mints. | Direction | Contract | Address | Event | | --- | --- | --- | --- | | Outbound (burn) | TokenMessengerV2 | `0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA` | `DepositForBurn` | | Inbound (mint) | MessageTransmitterV2 | `0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275` | `MessageReceived` | const TOKEN_MESSENGER = "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA"; const MESSAGE_TRANSMITTER = "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275"; const cctpInterface = new Interface([\ "event DepositForBurn(uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller)",\ "event MessageReceived(address indexed caller, uint32 sourceDomain, uint64 indexed nonce, bytes32 sender, bytes messageBody)",\ ]); async function indexCctpEvents( fromBlock: number, toBlock: number, ): Promise { // Index outbound burns const burnLogs = await provider.getLogs({ address: TOKEN_MESSENGER, topics: [cctpInterface.getEvent("DepositForBurn")!.topicHash], fromBlock, toBlock, }); for (const log of burnLogs) { const parsed = cctpInterface.parseLog({ topics: log.topics as string[], data: log.data, }); if (!parsed) continue; await saveCctpBurn({ blockNumber: log.blockNumber, txHash: log.transactionHash, nonce: parsed.args.nonce, amount: parsed.args.amount, depositor: parsed.args.depositor, destinationDomain: parsed.args.destinationDomain, }); } // Index inbound mints const mintLogs = await provider.getLogs({ address: MESSAGE_TRANSMITTER, topics: [cctpInterface.getEvent("MessageReceived")!.topicHash], fromBlock, toBlock, }); for (const log of mintLogs) { const parsed = cctpInterface.parseLog({ topics: log.topics as string[], data: log.data, }); if (!parsed) continue; await saveCctpMint({ blockNumber: log.blockNumber, txHash: log.transactionHash, nonce: parsed.args.nonce, sourceDomain: parsed.args.sourceDomain, }); } } ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-7-use-block-number-as-your-ordering-key) Step 7. Use block number as your ordering key Multiple blocks can share the same `block.timestamp` because Arc produces sub-second blocks that fall in the same wall-clock second. Always use `blockNumber` (and `logIndex` in a block) as your canonical ordering key. interface IndexedEvent { blockNumber: number; // Primary ordering key logIndex: number; // Secondary ordering key in a block txHash: string; // ... event-specific fields } // Correct: order by block number function compareEvents(a: IndexedEvent, b: IndexedEvent): number { if (a.blockNumber !== b.blockNumber) { return a.blockNumber - b.blockNumber; } return a.logIndex - b.logIndex; } Do not use `block.timestamp` for ordering. Two consecutive blocks (for example, block 100 and block 101) may both have `timestamp = 1700000000`. Sorting by timestamp produces ambiguous ordering. ### [​](https://docs.arc.io/integrate/infrastructure/indexing-events#step-8-simplify-your-pipeline%E2%80%94no-reorg-handling-required) Step 8. Simplify your pipeline—no reorg handling required Arc provides [deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) . Once a block appears, it is permanent. You can remove the following from your indexing pipeline: * Reorg detection and rollback logic * Confirmation-depth delays (no need to wait for N confirmations) * Uncle/ommer block handling * Chain reorganization event listeners // No need for confirmation buffers or reorg watchers. // Process each block exactly once as it arrives. async function indexBlock(blockNumber: number): Promise { // This block is final—it will never be reverted. await indexUsdcTransfers(blockNumber, blockNumber); await indexEurcTransfers(blockNumber, blockNumber); await indexMemoEvents(blockNumber, blockNumber); await indexBlocklistEvents(blockNumber, blockNumber); await indexCctpEvents(blockNumber, blockNumber); await markBlockProcessed(blockNumber); } If your indexer restarts, resume from the last processed block number. You do not need to re-validate previously indexed blocks because they cannot be reverted. [​](https://docs.arc.io/integrate/infrastructure/indexing-events#event-reference) Event reference ---------------------------------------------------------------------------------------------------- | Contract | Address | Events | | --- | --- | --- | | Native USDC (system) | `0xfffffffffffffffffffffffffffffffffffffffe` | `Transfer` (18 decimals, EIP-7708) | | USDC (ERC-20) | `0x3600000000000000000000000000000000000000` | `Transfer` (6 decimals), `Blocklisted`, `UnBlocklisted` | | EURC | `0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a` | `Transfer` | | Memo | `0x5294E9927c3306DcBaDb03fe70b92e01cCede505` | `Memo` | | TokenMessengerV2 | `0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA` | `DepositForBurn` | | MessageTransmitterV2 | `0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275` | `MessageReceived` | [​](https://docs.arc.io/integrate/infrastructure/indexing-events#see-also) See also -------------------------------------------------------------------------------------- * [USDC system events](https://docs.arc.io/arc/references/usdc-system-events) —full event matrix, emitter addresses, decimals, and historical pre-Zero5 events * [Infrastructure overview](https://docs.arc.io/integrate/infrastructure) —key differences from Ethereum and chain metadata * [Deterministic finality](https://docs.arc.io/arc/concepts/deterministic-finality) —why reorgs never occur on Arc * [Detect and process deposits](https://docs.arc.io/integrate/exchanges/deposits) —exchange-specific deposit workflow Was this page helpful? YesNo [Overview](https://docs.arc.io/integrate/infrastructure) [Add Arc to your bridge](https://docs.arc.io/integrate/infrastructure/bridges) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Terms of use - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/terms#content-area) _**Last Updated: May 15, 2026**_ These Arc Network Terms of Use (these “Terms” or this “Agreement”) constitute a binding legal agreement between you (“you,” “your”) and the applicable Circle entity offering the relevant network instance: Circle Technology Services, LLC (“CTS”) with respect to the Arc Network Testnet (the “Testnet”), and Arc Network Services LLC (“ANS”) with respect to the Arc Network Mainnet (“Arc” or the “Network”). CTS, with respect to the Testnet, and ANS, with respect to Arc, each as applicable, is referred to in these Terms as the “Company,” “we,” “our,” or “us.” These Terms govern your access to and use of Arc and the Testnet, including any access to or use of Arc or the Testnet by any AI Agent (defined below) or other automated system acting on your behalf or using your credentials, access rights, or permissions. Arc is a permissionless open source L1 blockchain initially deployed by ANS and intended over time to be operated, validated, and governed by a broader set of Validators (defined below) and other participants. Arc uses a Proof-of-Authority consensus mechanism, under which permissioned Validators secure the Network and produce blocks. The Company makes no representation or commitment as to the timing, manner, or extent of any change in the consensus mechanism, validator set, governance, or operation of Arc or the Testnet. The Testnet is a testing environment deployed by CTS solely for development, experimentation, research and other non-production purposes. The Testnet is not a production network, and does not process, transfer, or store real money or digital assets of value. You are permitted to access and use the Testnet only to test and improve the experience, security, and design of Arc. The Company may change, discontinue, or terminate, temporarily or permanently, all or any part of the Testnet, at any time and without notice. If you are an individual accessing or using Arc and/or the Testnet on behalf of, or for the benefit of, any corporation, partnership or other entity with which you are associated (an “Organization”), then you are agreeing to this Agreement on behalf of yourself and such Organization, and you represent and warrant that you have the legal authority to bind such Organization to this Agreement. By using or accessing Arc and the Testnet you agree to be bound by these Terms and any documentation and guidelines accompanying Arc, the Testnet and their features or functionalities, and all other terms, policies, and guidelines applicable to your use. You acknowledge and agree that your use of Arc, the Testnet and their features and functionalities is at your own risk and the Company is not responsible for any losses that occur as a result of your use of Arc, the Testnet and their features and functionalities. **THESE TERMS CONTAIN A MANDATORY [ARBITRATION PROVISION AND CLASS ACTION WAIVER THAT](https://docs.arc.io/terms#dispute-resolution%3B-binding-individual-arbitration%3B-class-action-waiver) , AS FURTHER SET FORTH BELOW, REQUIRES THE USE OF ARBITRATION ON AN INDIVIDUAL BASIS TO RESOLVE DISPUTES, RATHER THAN JURY TRIALS OR ANY OTHER COURT PROCEEDINGS, OR CLASS ACTIONS OF ANY KIND.** 1. [​](https://docs.arc.io/terms#eligibility-acknowledgements-and-conditions-of-use) Eligibility, Acknowledgements, and Conditions of Use ----------------------------------------------------------------------------------------------------------------------------------------- By accessing or using Arc, Testnet and their features or functionalities, you acknowledge and agree to the following: 1. You must be at least 18 years old and be legally capable of forming a binding contract in your country of residence. 2. You are not a person barred under the laws of the United States, your place of residence, or any other applicable jurisdiction. 3. You agree that you have read, understand and accept (i) all of the terms and conditions contained in these Terms and (ii) the Company’s [Privacy Policy](https://www.circle.com/legal/privacy-policy) , [Cookie Policy](https://www.circle.com/legal/cookie-policy) , and [E-Sign Consent Policy](https://www.circle.com/legal/esign-consent) . You acknowledge and agree that you will be bound by these Terms and any other applicable agreements and policies. 4. You are knowledgeable, experienced, and sophisticated in using and evaluating blockchain and related technologies and digital assets, including Arc. 5. You acknowledge and agree that: (i) transactions on Arc involve real-world value and may result in the permanent loss of digital assets; (ii) once confirmed on Arc, transactions are irreversible and cannot be canceled, reversed, or recalled, (iii) the Company does not custody, control, or have access to your digital assets, private keys, or wallets, (iv) the Company does not have the ability to recover digital assets or correct transaction errors, (v) data recorded on Arc is immutable and cannot be altered or deleted by the Company, and (vi) Arc is an open, permissionless ledger that any user may read from and (subject to these Terms and applicable Legal Requirements) submit transactions to without authorization from the Company; (vii) the Company may, from time to time and in its sole discretion, propose, deploy or facilitate protocol upgrades, parameter changes, software releases or other changes to the Network, in each case subject to the Network’s then-current consensus mechanism and governance; (viii) the Company’s role in operating, validating, securing or governing the Network may diminish, increase, change in scope, transfer to one or more third parties, or cease entirely over time, and you have no claim or recourse against the Company in respect of any such change; (ix) the Company has no obligation or ability to reverse, recall, recover, modify, correct, exclude, censor or otherwise act upon any confirmed transaction or asset on Arc, and is not responsible for the validation, ordering, inclusion or finality of any transaction; and (x) you assume sole responsibility for, and the Company shall have no liability with respect to, any acts, omissions, gains or losses arising out of or relating to your use of, or access to, Arc or the Testnet. Without limiting the foregoing, the Company has no duty to continue operating any Company-operated interface, support function, validator role, governance role, security function, documentation set, developer tool, or other resource relating to Arc or the Testnet, and no duty to support, maintain, or pursue any particular roadmap, feature, token design, gas mechanism, validator composition, governance framework, third-party ecosystem activity, or other aspect of network evolution. 6. Your use is subject to certain limitations on access and use as set forth in these Terms, any documentation accompanying Arc and Testnet’s features or functionalities or as otherwise provided to you by the Company. 7. You agree that: (i) you will not misrepresent your identity, or use any tool, technique or service (including any privacy-enhancing technology, virtual private network, mixer, anonymizing relay or similar service) for the purpose of (A) circumventing applicable Legal Requirements, including any Sanctions or Data Protection Laws, (B) defrauding, deceiving or harming any other person, or (C) evading detection of activity that would otherwise violate these Terms or applicable Legal Requirements (it being understood that the legitimate use of privacy-preserving features made available on or in connection with Arc is not, by itself, a violation of this clause); (ii) you will not engage in any intentional, negligent, unethical or unlawful conduct; and (iii) you will not intentionally or negligently make any false or misleading public statement that the Company has endorsed, sponsored or approved any of your products, services or activities. If the Company believes, in its sole discretion, that you have breached or attempted to breach or circumvent these Terms, the Company may, without prior notice, suspend, restrict or terminate your access to any Company-operated interface, front-end, support channel, developer tooling or other Company-controlled resource through which you access Arc or the Testnet. The Company does not, by these Terms, undertake any obligation or assert any general ability to block, censor, reorder, exclude or reverse transactions on Arc at the protocol or validator layer, and any Company action with respect to validator-level or protocol-level controls (if any) is governed solely by the then-current network policies, applicable Legal Requirements and the Company’s sole discretion. 8. You may receive updates and information from the Company that constitute Confidential Information. “Confidential Information” means any information or data, regardless of whether it is in tangible form, that is disclosed or otherwise made available by the Company to you and that is (i) in tangible form and labeled or marked confidential or proprietary, (ii) if disclosed orally is designated as confidential or proprietary at time of disclosure, or (iii) information that a reasonable person knows or should have known to be confidential or proprietary given the nature of the information and the circumstances surrounding disclosure. Any information disclosed by an affiliate of the Company shall be treated as if disclosed by the Company. All Confidential Information is the sole and exclusive property of the Company and may be used by you only for assisting us in resolving any security issue you have reported to us. You agree not to disclose such Confidential Information or any announcement that the Company notes as embargoed without the Company’s prior written consent. You may disclose Confidential Information when compelled to do so by Legal Requirements if you provide us with reasonable prior notice unless a court orders that we not receive notice. 9. You acknowledge and understand that: (i) activity on Arc and Testnet is public and may be viewed or recorded by anyone, and information written to Arc may be permanently public, searchable and impossible to fully delete, and may persist on Arc, the Testnet or any third-party fork or copy thereof indefinitely, even after termination of these Terms or any change to the features available on Arc; (ii) you should not input personal, confidential, or proprietary information on Arc or Testnet, and you are solely responsible for determining the lawfulness and appropriateness of any information you choose to input or transmit on or through Arc or Testnet (including through any privacy-preserving feature); and (iii) the Company and its affiliates, contractors and service providers may collect, generate, aggregate, derive and use telemetry, technical, diagnostic, usage and other data in connection with the operation, security, maintenance, improvement, analysis and provision of Arc, the Testnet and other products and services, for the purposes of operating, securing, maintaining and evaluating Arc and Testnet, as further described in the [Privacy Policy](https://www.circle.com/legal/privacy-policy) . 10. If applicable, the Company may use your data and information you may provide solely to the extent necessary to fulfill its obligations under these Terms and to comply with applicable Legal Requirements. 11. You agree to comply with any and all applicable “Data Protection Law(s)” (which means, collectively, all Legal Requirements that apply to processing of personal data under or in connection with these Terms, including applicable international, national, federal, state, provincial, and local laws, rules, regulations, directives and governmental requirements relating to privacy, data protection, or security) that may apply to performing your obligations under these Terms. The obligations include, without limitation: 1. providing notices of data breach as required by applicable Data Protection Laws; and 2. processing data only in accordance with the Company’s [Privacy Policy](https://www.circle.com/legal/privacy-policy) , as well as your own privacy policies. 12. You and the Company agree that neither is the data processor of the other party under any applicable Data Protection Law, nor are you and the Company acting together as joint data controllers. To the extent any personal data is processed in connection with Arc, each party acts as an independent controller with respect to its own processing of such personal data. You and the Company further agree that no monetary or other valuable consideration is provided to either party in exchange for any personal data associated with Arc and that data sharing conducted pursuant to these Terms does not constitute a sale of personal data under any applicable Data Protection Law. 13. You agree to be solely responsible for the accuracy, quality, integrity and legality of all information you share with the Company or its affiliates. 14. You acknowledge and agree that, in deploying Arc or Testnet, the Company is not acting in any capacity as a money transmitter or money services business (or equivalent regulated entity under applicable Legal Requirements), and the Company does not provide any regulated financial services in connection with Arc, an open source permissionless infrastructure, or the Testnet. 15. You are responsible for obtaining the data network access necessary to use Arc and Testnet. Mobile network data and messaging rates and fees may apply if you access or use Arc or Testnet from a mobile device. You are responsible for acquiring and updating compatible hardware or devices necessary to access and use Arc, Testnet and their features and functionalities and any updates thereto. The Company does not guarantee that Arc or Testnet, or any portion thereof, will function on any particular hardware or devices. In addition, Arc and Testnet and their features and functionalities may be subject to malfunctions and delays inherent in the use of the Internet and electronic communications. We are not responsible for any delays, delivery failures, or damage, loss or injury resulting from any such issues. 16. Third parties may elect to support, utilize or integrate Arc or Testnet on their platforms without any authorization or approval by the Company or anyone else. For example, as a result of the open source nature of Arc and Testnet, it is possible that a party unaffiliated with the Company could create an alternative version of the blockchain (a “fork”). Arc or Testnet access or support on any third-party platform does not imply any endorsement by the Company that such third-party services are valid, legal, stable or otherwise appropriate. The Company is not responsible for any losses or other issues you might encounter using Arc or Testnet in connection with any third-party forks, platforms, blockchains, protocols, technologies, products or services. The Company does not have any ability or obligation to prevent or mitigate attacks or resolve any other issues that might arise with any third-party forks, platforms, blockchains, protocols, technologies, products or services. Any such attacks or issues related to any third party might materially impact you, and the Company shall bear no responsibility for any losses that result from such attacks or issues. It is your responsibility to ensure that you are accessing Arc and Testnet through your intended user interface. 17. You are aware of and accept the risk of operational challenges with the Network launch, Arc’s operation, and Testnet. The Company may experience sophisticated cyber-attacks, unexpected surges in activity or other operational or technical difficulties that may cause interruptions to Arc or Testnet. You understand that Arc and Testnet may experience operational issues that lead to delays. You agree to accept the risk of any issues resulting from unanticipated or heightened technical difficulties, including those resulting from sophisticated attacks. You agree not to hold the Company accountable for any related losses. 18. You represent, warrant, and covenant that (i) you shall comply with all applicable Legal Requirements; (ii) your use of Arc and Testnet is and will at all times comply with all Legal Requirements and these Terms; (iii) you will not use Arc or Testnet, or permit the use of the foregoing by any third party, in any manner that is fraudulent, unlawful, deceptive or abusive; (iv) you will not use Arc or Testnet from a Restricted Territory or any jurisdiction that we have, in our sole discretion, or a relevant Regulatory Authority has determined is a jurisdiction where the use of Arc and Testnet is prohibited under Legal Requirements; (v) you are not a Sanctions Target; and (vi) you will not use Arc or Testnet to benefit or support any Restricted Territories or Sanctions Targets. For the purposes of these Terms: (1) “Legal Requirement” means applicable federal, state, and local laws, statutes, and regulations, and all applicable orders, judgments, decisions, rules, policies, opinions, attorney general opinions, or guidelines passed or issued by any Regulatory Authority or any competent court; Data Protection Laws; Sanctions; anti-corruption laws (including the Foreign Corrupt Practices Act and the UK Bribery Act); and all foreign laws regarding the same, relating to these Terms or otherwise applicable to either you or us, as the same may be amended and in effect from time to time; (2) “Restricted Territory” means a region, territory or country subject to Sanctions; (3) “Regulatory Authority” means any governmental, regulatory authority or law enforcement department, court, agency, commission, board, tribunal, crown corporation or other law, rule or regulation making entity (including any stock exchange or central bank) that either you or we submit to or are subject to the jurisdiction of in respect of these Terms, and any successor or replacement of any of the foregoing; (4) “Sanctions” means any Legal Requirement imposing sanctions, restrictions, or prohibitions on financial transactions or other business dealings that is administered or enforced by the U.S. Government (including the U.S. Department of Treasury’s Office of Foreign Assets Control, the U.S. Department of Commerce, or the U.S. Department of State and including designation as a “specially designated national” or blocked person), the United Nations Security Council, and all other relevant international sanctions authority, including any executive orders issued in relation to the imposition of sanctions; (5) “Sanctions Target” means any person that is: (A) included on any list of designated persons maintained by any Regulatory Authority pursuant to Sanctions, (B) organized, located or resident in a Restricted Territory, or (C) otherwise the target of any Sanctions such that a person is prohibited from dealing with such person, including as a result of being owned or controlled by any person or persons described in the foregoing subsection (A) or (B). 19. For the avoidance of doubt, enforcement of the Terms is solely in our discretion and the absence of enforcement of these Terms in some instances does not constitute a waiver of our right to enforce the Terms in other instances. These Terms may be enforced by the Company or by any of its affiliates. In addition, these Terms do not create any private right of action on the part of any third party or any reasonable expectation or promise that either Arc or Testnet will not contain any content that is prohibited by the Terms. 2. [​](https://docs.arc.io/terms#access-prohibitions) Access Prohibitions ------------------------------------------------------------------------- You will only access Arc, the Testnet and their features and functionalities following the implementation instructions and other requirements specified in the documentation or as otherwise provided by the Company. You agree that you will not, and will not permit another person or entity to: 1. resell, rent, lease, sublicense or otherwise commercialize access to any Company-operated interface, front-end, API, RPC endpoint or other Company-controlled resource through which Arc or the Testnet is accessed, except as expressly permitted by the Company in writing or under a separate written agreement with the Company (it being understood that this clause is not intended to prohibit the operation of independent infrastructure services, including third-party node, RPC, indexing or block-explorer services, that interact with Arc through publicly available means and not through Company-operated resources); 2. use or create a service that functions substantially the same as Arc or Testnet, for the purpose of or with the effect of: (i) spoofing or simulating Arc or Testnet; (ii) misrepresenting your or any third party’s association with the Company; (iii) misleading or confusing users about the origin, ownership, or operation of any feature or functionality; or (iv) falsely implying sponsorship, endorsement, or certification by the Company; 3. infringe or violate the intellectual property rights or any other rights of anyone else (including the Company) or attempt to decompile, disassemble, or reverse engineer Arc or Testnet; 4. violate any applicable law or regulation, including without limitation, and any applicable anti-money laundering laws, anti-terrorism laws, export control laws, end user restrictions, privacy laws or economic sanctions law/regulations, including those administered by the U.S. Department of the Treasury’s Office of Foreign Assets Control; 5. encourage, facilitate, or promote illegal activity, or use in a way that is dangerous, harmful, misleading, deceptive, threatening, harassing, defamatory, obscene, or otherwise objectionable; 6. commit a tort while using Arc or Testnet; 7. use Arc or Testnet in any manner that could interfere with, defraud, attack, disrupt, negatively affect, or inhibit other users from fully enjoying Arc or Testnet, or that could damage, disable, overburden, or impair the functioning of Arc or Testnet in any manner, including by (i) making any unsolicited offer or advertisement to another user of Arc or Testnet; (ii) attempting to collect personal information about another user or third party without consent; or (iii) interfering with or disrupting any network, equipment, or server connected to or used to provide Arc or Testnet, or violating any regulation, policy, or procedure of any such network, equipment, or server; 8. attempt to circumvent any content filtering techniques or security measures that the Company employs on Arc or Testnet, or attempt to access any features or functionalities that you are not authorized to access; 9. upload or introduce any malware, virus, Trojan horse, worm, logic bomb, drop-dead device, backdoor, shutdown mechanism or other harmful material; 10. provide false, inaccurate, or misleading information to the Company or otherwise on or in connection with your use; 11. post content or communications through your use of Arc or Testnet that are, in our sole discretion, libelous, defamatory, profane, obscene, pornographic, sexually explicit, indecent, lewd, vulgar, suggestive, harassing, hateful, threatening, offensive, discriminatory, bigoted, abusive, inflammatory, fraudulent, deceptive or otherwise objectionable; 12. post content through your use of Arc or Testnet containing unsolicited promotions, political campaigning, or commercial messages or any chain messages or user content designed to deceive or trick the user of Arc or Testnet; or 13. encourage or induce any third party to engage in any of the activities prohibited under these Terms. For the purpose of these Terms, Validators are considered third parties, regardless of whether the Company, any of its affiliates, or any of their respective personnel operates one or more validator nodes from time to time. The Company is not responsible for, does not endorse, and shall not be held liable in connection with, the acts, omissions, decisions, transactions, software, hardware, infrastructure or business practices of any Validator (including any Validator that may be affiliated with the Company), and no agency, employment, partnership, joint venture, or fiduciary relationship is created or implied by these Terms between the Company and any Validator. For avoidance of doubt, a “Validator” means any person or entity that operates one or more validator nodes (the combination of software and hardware) on the Network for the purpose of participating in the verification, validation, ordering and inclusion of transactions and the production of blocks on the Network in accordance with the Network’s then-current consensus mechanism, as that mechanism may evolve from time to time under the Network’s then-current governance. 3. [​](https://docs.arc.io/terms#connecting-a-digital-wallet) Connecting a Digital Wallet ----------------------------------------------------------------------------------------- You may be required to connect your digital wallet through a compatible third-party software wallet. Third-party digital wallets constitute third-party features and services and the Company is not responsible for, does not endorse, shall not be held liable in connection with and does not make any warranties, whether express or implied, as to the third-party digital wallet used by you. You are solely responsible for selecting, evaluating, configuring, securing, and monitoring any digital wallet, RPC provider, bridge, explorer, custody provider, or other third-party product or service that you use in connection with Arc or the Testnet, including any associated outages, errors, hacks, insolvencies, security failures, or compliance failures of any such third party. Except where applicable to your use of certain Circle wallet features and functionalities (which are subject to their own [terms and conditions](https://console.circle.com/legal/service-terms) ): (i) the Company never receives access to or control over your digital wallet; and (ii) therefore, you are solely responsible for (and we are not liable for any failure in) securing your digital wallet and credentials thereto, including seed phrases and private keys. You may disconnect your digital wallet from Arc or the Testnet at any time. 4. [​](https://docs.arc.io/terms#gas-fees) Gas Fees --------------------------------------------------- 1. All transactions on Arc will incur a gas fee. Gas fees consist of the following fees, and are paid directly to the Validator that successfully validates the applicable block: 1. **Base Fee.** All transactions on Arc will be subject to the Base Fee which is a gas fee calculated based on the amount of computational resources required to process a transaction and network demand. 2. **Priority Fee.** Priority Fee means an additional gas fee that a user may choose to pay to increase the relative prioritization of a transaction on Arc compared to other transactions. 2. Arc Network gas fees will be denominated in USDC. To use Arc, you will need to obtain USDC to cover computational resources required to perform a transaction on Arc. It is your responsibility to: (i) ensure that you have a sufficient balance of USDC stored at your digital wallet address to complete any transaction on Arc before initiating such Arc transaction; and (ii) confirm the amount of USDC applicable to a particular Arc transaction before authorizing that transaction. 3. Gas fees, including the rate, denomination, components, calculation methodology, payment recipient(s) and other parameters thereof, are determined under the Network’s then-current consensus mechanism, protocol parameters and governance, each of which may change from time to time. The Company makes no representation, warranty or commitment with respect to gas fee levels, fee market dynamics, the availability or value of any digital asset (including USDC) used to pay gas fees, or the continued use of any particular digital asset for gas fees, and is not responsible for any impact on you arising out of any change in any of the foregoing. 5. [​](https://docs.arc.io/terms#ai-agent-access) AI Agent Access ----------------------------------------------------------------- If you access or use Arc, the Testnet and their features and functionalities through an AI Agent or other automated system, you remain solely responsible for that access and use and for all acts, omissions, instructions, decisions, and transactions of such AI Agent or automated system as if you had taken them directly. Any AI Agent that accesses or uses Arc, Testnet or their features or functionalities does so solely on your behalf, and you remain fully responsible for all resulting activity. For purposes of this Section, an “AI Agent” means any artificial intelligence agent, model, bot, script, or other automated system that accesses, uses, interfaces with, or acts in connection with Arc or the Testnet on your behalf or using your credentials, access rights, or permissions. 6. [​](https://docs.arc.io/terms#third-party-services-and-content) Third-Party Services and Content --------------------------------------------------------------------------------------------------- Arc, the Testnet and their features and functionalities may be made available or accessed and controlled by third parties with different terms of use and privacy policies. We do not endorse these third-party services and content, and we are not responsible or liable for any of their products or services. Without limiting the foregoing, you acknowledge and agree that the Company has no responsibility for, and no duty to monitor, support, verify, approve, update, remediate, or maintain, any third-party service, content, protocol, application, interface, integration, or infrastructure that may interact with Arc or the Testnet, and you assume all risk arising from your use of any of the foregoing. 7. [​](https://docs.arc.io/terms#no-agency) No Agency ----------------------------------------------------- You will not make statements or represent yourself as an agent of the Company or mislead or deceive any third party with respect to your relationship with the Company. Nothing in these Terms creates, and you shall not assert that there exists, any agency, employment, partnership, joint venture, fiduciary, advisory or similar relationship (i) between you and the Company, (ii) between the Company and any Validator (including any Validator that may be affiliated with the Company), (iii) between the Company and any other user of Arc or the Testnet, or (iv) between the Company and any developer, operator or other participant in the broader Arc ecosystem. The Company is not responsible for the acts or omissions of any Validator or any other third party participating in or interacting with the Network. 8. [​](https://docs.arc.io/terms#no-financial-services) No Financial Services ----------------------------------------------------------------------------- Using Arc, the Testnet or their features or functionalities does not create a financial account, custodial relationship, broker-dealer relationship, fiduciary relationship, advisory relationship, partnership, joint venture, agency or investment relationship between you and the Company. The Company is not acting as a broker, dealer, exchange, money transmitter, money services business, investment adviser, custodian or other regulated financial intermediary, and is not offering financial services or products through access to Arc or Testnet. No statement made by the Company in connection with Arc or the Testnet, and no feature or functionality of Arc or the Testnet, is intended as, or should be construed as, an offer or solicitation to buy or sell any security, commodity, derivative or other financial instrument, or as investment, legal, tax or accounting advice. You are solely responsible for determining whether any digital asset or activity on Arc or the Testnet is appropriate for you, and for the legal and tax characterization of any such asset or activity in your jurisdiction. You further acknowledge and agree that you are not relying on the Company, any Validator, or any of their respective affiliates, personnel, or representatives for any legal, regulatory, tax, economic, technical, investment, or commercial advice, analysis, diligence, or conclusion relating to Arc, the Testnet, any digital asset, or any third-party product or service. 9. [​](https://docs.arc.io/terms#non-solicitation-and-no-professional-advice) Non-Solicitation and No Professional Advice ------------------------------------------------------------------------------------------------------------------------- You agree and understand that all information provided by the Company is for informational purposes only and should not be construed as legal, financial, or tax advice. You should not take, or refrain from taking, any action based on any information contained therein. You acknowledge that any decision to access, use, rely on, transact on, build on, or otherwise engage with Arc, the Testnet, or any related digital asset, protocol, application, or third-party service is made solely by you and at your sole risk. 10. [​](https://docs.arc.io/terms#submission-of-user-content) Submission of User Content --------------------------------------------------------------------------------------- Certain features may allow for the submission of your own information (“User Content”), and except as expressly provided in these terms, the Company does not acquire any ownership of any intellectual property rights that you hold in the User Content that you submit using the features described in these Terms. By submitting, posting, or displaying User Content under these Terms, (a) you grant the Company and each Validator a perpetual, irrevocable, worldwide, royalty-free, and non-exclusive license to use, reproduce, adapt, modify, translate, publish, publicly perform, publicly display and distribute such User Content to facilitate the Company’s provision of its features and functionalities, in each case only in accordance with the [Privacy Policy](https://www.circle.com/legal/privacy-policy) and (b) you grant Arc and Testnet users a non-exclusive license to access and use that User Content as permitted by these Terms and the functionality of Arc and Testnet. You represent that, before you submit User Content via Arc or Testnet, you have the necessary rights (including any necessary rights acquired from related end users) to grant us the license. You are solely responsible for your User Content and the consequences of posting or publishing User Content. We are under no obligation to monitor, review, edit, or control User Content that you or other users post or publish, and will not be in any way responsible or liable for User Content. We may, however, at any time and without prior notice introduce certain features and functionalities to screen, remove, edit, or block any User Content that in our sole judgment violates these Terms or is otherwise objectionable. We may also preserve, access, use, or disclose any User Content or related information where we determine, in our sole discretion, that doing so is appropriate for compliance, security, legal, operational, trust and safety, reputational, or investigatory purposes, and we will have no liability to you arising from any such action. You understand that you will be exposed to User Content from a variety of sources and acknowledge that User Content may be inaccurate, offensive, indecent, or objectionable. YOU AGREE TO WAIVE, AND DO HEREBY WAIVE, ANY LEGAL OR EQUITABLE RIGHT OR REMEDY YOU HAVE OR MAY HAVE AGAINST US AND THE VALIDATORS WITH RESPECT TO USER CONTENT. WE EXPRESSLY DISCLAIM ANY AND ALL LIABILITY IN CONNECTION WITH USER CONTENT. If notified by a user or content owner that User Content allegedly does not conform to these Terms, we may investigate and take additional steps as we determine to be appropriate in our sole discretion. YOU WAIVE AND HOLD HARMLESS THE COMPANY (AND ITS EMPLOYEES, DIRECTORS, AGENTS, AFFILIATES, AND REPRESENTATIVES) AND EACH VALIDATOR FROM AND AGAINST ANY CLAIMS OR LOSSES RESULTING FROM ANY ACTION TAKEN BY ANY OF THE FOREGOING PARTIES DURING, OR TAKEN AS A CONSEQUENCE OF, INVESTIGATIONS BY EITHER SUCH PARTIES OR LAW ENFORCEMENT AUTHORITIES. For clarity, we do not knowingly permit copyright-infringing activities on Arc or the Testnet. 11. [​](https://docs.arc.io/terms#use-of-the-company-marks) Use of the Company Marks ----------------------------------------------------------------------------------- We may periodically make available certain “Arc” logos, trademarks, or other identifiers for your use as set forth in the Arc Brand Kit, currently available at [arc.io](https://arc.io/) , (“Arc Marks”). The Arc Brand Kit and Circle Brand Use Policy (“Brand Use Policy”), each as amended by the Company from time to time, are hereby incorporated into these Terms by reference. The Company may, at any time and in its sole discretion, require you to modify, suspend, discontinue, or remove any use of any Arc Mark that the Company determines is misleading, confusing, inaccurate, non-compliant, unlawful, objectionable, or otherwise inconsistent with the Company’s branding, legal, regulatory, or policy requirements, and you agree to comply promptly with any such request. The Company may modify, limit, suspend or revoke your right to use any Arc Mark at any time, in its sole discretion and without prior notice. You agree to use the Arc Marks only in strict accordance with the then-current Arc Brand Kit and the Brand Use Policy, and to immediately discontinue any non-conforming use upon notice from the Company. All rights in the Arc Marks not expressly granted in the Arc Brand Kit or the Brand Use Policy are reserved by the Company, and nothing in these Terms grants you any goodwill or other rights in or to the Arc Marks. 12. [​](https://docs.arc.io/terms#ownership-feedback) Ownership; Feedback ------------------------------------------------------------------------ Arc is a permissionless open source neutral infrastructure. The Company grants you a non-exclusive, limited, revocable, terminable, personal, non-assignable, and non-sublicensable license to access certain features and functionalities on Arc, in strict accordance with these Terms. This license shall expire upon the Company’s sole discretion or your failure to adhere to these Terms. You acknowledge and agree that as between you and the Company, the Company owns all right, title and interest in and to the Arc Marks, and any Arc features and functionalities (and any derivative works or enhancements thereof), including all intellectual property rights therein. You agree not to do anything inconsistent with this Section or these Terms. Any rights not expressly granted herein are withheld. If you submit any comment or idea about improvements to the Network (“Feedback”) to the Company, you acknowledge and agree that your submission was voluntary, unsolicited by the Company, and delivered to the Company without any restrictions or confidentiality obligations on the Company’s use of the Feedback. You hereby grant the Company a perpetual, irrevocable, fully-paid, royalty-free, freely transferable and sublicensable (through multiple tiers) worldwide right and license to use any Feedback that you submit or provide to the Company in any manner or medium and for any purpose, whether or not the submitted Feedback is protectable by intellectual property laws of your or the Company’s related jurisdiction. You agree that the Company has no fiduciary or any other obligation to you in connection with any Feedback that you submit or provide to the Company, and that the Company is free to use, copy, display, perform, distribute, modify and re-format such Feedback in any manner that the Company may determine, without any attribution or compensation to you. Moreover, you acknowledge and agree that the Company has no control or special privileges over the Network and as such may not be able to use or implement your Feedback. 13. [​](https://docs.arc.io/terms#termination) Termination --------------------------------------------------------- We may terminate these Terms or introduce certain features and functionalities to suspend or terminate your use of Arc or Testnet (or any portion thereof) at any time for any reason. We may add or remove, suspend, stop, delete, discontinue or impose conditions on certain features or functionalities related to Arc or Testnet. If these Terms or your use of these features or functionalities is terminated or suspended for any reason or no reason: (a) the license and any other rights granted under these Terms and any other applicable terms will end, (b) we may (but have no obligation to other than to the extent required by applicable Legal Requirements) delete your information stored on our servers, and (c) the Company shall not be liable to you or any third party for compensation, reimbursement, or damages for any termination or suspension of your use of Arc, Testnet or for deletion of your information, including information that you may disclose on Arc or Testnet over which Company has no control or special privileges. If your use of certain features or functionalities on Arc or Testnet are terminated or suspended, you agree to continue to be bound by these Terms to the extent such provisions survive termination, including under Section 23 (Survival). For the avoidance of doubt, the Company may, at any time and in its sole discretion, transfer, transition or wind down any of the Company-operated services, infrastructure or other resources used in connection with the Network (including, without limitation, in connection with any transition of the Network to a security council, validator-driven governance, or other third-party governance arrangement), and any such action will not constitute a breach of these Terms or give rise to any claim against the Company. 14. [​](https://docs.arc.io/terms#no-warranties) No Warranties ------------------------------------------------------------- ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES ARE PROVIDED “AS IS” AND “AS AVAILABLE” WITHOUT REPRESENTATION, WARRANTY OR CONDITION OF ANY KIND, WHETHER EXPRESS, IMPLIED, OR STATUTORY. WITHOUT LIMITING THE FOREGOING, THE COMPANY AND EACH VALIDATOR SPECIFICALLY DISCLAIM ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THE COMPANY DOES NOT WARRANT OR GUARANTEE THAT ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES AND THE INFORMATION AVAILABLE ON ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES: (A) IS ACCURATE, RELIABLE OR CORRECT; (B) WILL MEET YOUR REQUIREMENTS; OR (C) WILL BE AVAILABLE AT ANY PARTICULAR TIME OR LOCATION, WILL BE UNINTERRUPTED, WILL BE ERROR-FREE, OR WITHOUT DEFECT OR SECURE. THE COMPANY FURTHER DOES NOT WARRANT OR GUARANTEE THAT ANY DEFECTS OR ERRORS WILL BE CORRECTED; OR THAT ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES ARE FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. ANY DATA DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES ARE DOWNLOADED AT YOUR OWN RISK AND YOU WILL BE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR PROPERTY OR LOSS OF DATA THAT RESULTS FROM SUCH DOWNLOAD. YOU ACKNOWLEDGE THAT TRANSACTIONS ON ARC INVOLVE REAL VALUE AND ARE IRREVERSIBLE, THAT TRANSACTION FINALITY IS DETERMINED BY THE UNDERLYING NETWORK AND NETWORK CONDITIONS, AND THAT THE COMPANY DOES NOT CONTROL, OPERATE, MAINTAIN, VALIDATE, OR GUARANTEE SUCH OUTCOMES AND COMPANY HAS NO SPECIAL PRIVILEGES OVER SAME. THE COMPANY DOES NOT CONTROL OR OPERATE ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES OR THE THIRD PARTY WALLETS THAT YOU MAY USE TO ACCESS OR ENABLE FEATURES ON ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES. The Company does not warrant, endorse, guarantee, or assume responsibility for any products or services advertised or offered by a third party. You also understand and agree that the Company does not control any products or services offered by third parties building, deploying, integrating or using Arc, Testnet and their features and functionalities. The Company is not liable for any losses or issues that may arise from such third-party products or services, including failure to comply with applicable Legal Requirements, the quality and delivery of such products and services, or your satisfaction with any products or services. If you are not satisfied with any goods or services made available by a third party on Arc or Testnet you must handle those issues directly with such third-party. TO THE EXTENT ANY DOCUMENTATION, DEVELOPER TOOL, SAMPLE CODE, REFERENCE IMPLEMENTATION, SUPPORT RESOURCE OR OTHER ANCILLARY MATERIAL THE COMPANY MAKES AVAILABLE IN CONNECTION WITH ARC OR THE TESTNET INCORPORATES OR LEVERAGES ARTIFICIAL INTELLIGENCE FEATURES, YOU ACKNOWLEDGE THAT SUCH ARTIFICIAL INTELLIGENCE SYSTEMS ARE A RAPIDLY EVOLVING FIELD AND THAT, BECAUSE OF THEIR PROBABILISTIC NATURE, SUCH FEATURES MAY PROVIDE INACCURATE, INCOMPLETE OR UNINTENDED OUTPUTS (INCLUDING SO-CALLED HALLUCINATIONS). NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, ARE MADE BY THE COMPANY WITH RESPECT TO THE OUTPUTS OF ANY SUCH AI FEATURES, AND YOU ARE SOLELY RESPONSIBLE FOR REVIEWING, VALIDATING AND DETERMINING THE APPROPRIATENESS AND ACCURACY OF ANY SUCH OUTPUT BEFORE RELYING ON OR ACTING ON IT. FOR THE AVOIDANCE OF DOUBT, THE ARC NETWORK PROTOCOL ITSELF, AND THE VALIDATION, ORDERING AND FINALITY OF TRANSACTIONS ON ARC, DO NOT INCORPORATE ARTIFICIAL INTELLIGENCE SYSTEMS. 15. [​](https://docs.arc.io/terms#limitation-of-liability) Limitation of Liability --------------------------------------------------------------------------------- TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE COMPANY BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES, INCLUDING DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA, OR OTHER INTANGIBLE LOSSES, THAT RESULT FROM THE USE OF, INABILITY TO USE, OR UNAVAILABILITY OF ARC, ARC TESTNET AND THEIR FEATURES AND FUNCTIONALITIES. IN ALL CASES, THE COMPANY WILL NOT BE LIABLE FOR ANY LOSS OR DAMAGE THAT IS NOT REASONABLY FORESEEABLE. UNDER NO CIRCUMSTANCES WILL THE COMPANY BE RESPONSIBLE FOR ANY DAMAGE, LOSS, OR INJURY RESULTING FROM HACKING, TAMPERING, OR OTHER UNAUTHORIZED ACCESS OR USE OF ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES, OR THE INFORMATION CONTAINED THEREIN. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, THE COMPANY ASSUMES NO LIABILITY OR RESPONSIBILITY FOR ANY (I) ERRORS, MISTAKES, OR INACCURACIES ON ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES; (II) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO OR USE OF ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES; (III) ANY UNAUTHORIZED ACCESS TO OR USE OF ANY COMPANY-OPERATED INFRASTRUCTURE AND/OR ANY AND ALL PERSONAL INFORMATION STORED THEREIN; (IV) ANY INTERRUPTION OR CESSATION OF TRANSMISSION TO OR FROM ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES; (V) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE THAT MAY BE TRANSMITTED TO, THROUGH OR DEPLOYED ON ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES BY ANY THIRD PARTY; (VI) ANY ERRORS OR OMISSIONS IN ANY DATA OR FOR ANY LOSS OR DAMAGE INCURRED AS A RESULT OF THE USE OF ANY DATA POSTED, EMAILED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE THROUGH ARC, TESTNET AND THEIR FEATURES AND FUNCTIONALITIES; (VII) ANY ACT, OMISSION, DECISION OR TRANSACTION OF ANY VALIDATOR OR OTHER THIRD PARTY ON OR IN CONNECTION WITH ARC OR THE TESTNET, INCLUDING ANY TRANSACTION ORDERING, INCLUSION, EXCLUSION OR CENSORSHIP, ANY MAXIMAL EXTRACTABLE VALUE OR SIMILAR ACTIVITY, OR ANY FORK, VALIDATOR-LEVEL DECISION OR PROTOCOL-LEVEL EVENT; OR (VIII) ANY AIRDROP, REWARD, INCENTIVE, FEE OR OTHER VALUE THAT YOU MAY HAVE EXPECTED OR HOPED TO RECEIVE. WITHOUT LIMITING THE FOREGOING, THE COMPANY SHALL HAVE NO LIABILITY FOR ANY LOSSES RESULTING FROM: (a) TRANSACTIONS THAT ARE IRREVERSIBLE, FINAL, OR NOT CONFIRMED; (b) ERRORS IN TRANSACTION DETAILS (INCLUDING ADDRESSES OR AMOUNTS) SUBMITTED BY YOU; (c) UNAUTHORIZED ACCESS TO YOUR WALLETS, PRIVATE KEYS, OR CREDENTIALS; (d) NETWORK FAILURES, CONGESTION, FORKS, OR PROTOCOL-LEVEL EVENTS; OR (e) THE LOSS, THEFT, OR INACCESSIBILITY OF DIGITAL ASSETS. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE COMPANY DOES NOT HAVE THE ABILITY TO REVERSE TRANSACTIONS, RECOVER DIGITAL ASSETS, RECOVER PRIVATE KEYS OR MODIFY OR DELETE DATA RECORDED ON ARC. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, THE AGGREGATE TOTAL LIABILITY OF THE COMPANY AND ITS AFFILIATES, EACH VALIDATOR, AND THEIR RESPECTIVE OFFICERS, DIRECTORS, EMPLOYEES, AGENTS, REPRESENTATIVES AND LICENSORS, ARISING OUT OF OR IN CONNECTION WITH THESE TERMS, ARC OR THE TESTNET IS LIMITED TO THE GREATER OF (X) THE GAS FEES (IF ANY) PAID BY YOU TO THE COMPANY (BUT NOT TO ANY VALIDATOR) IN THE TWELVE (12) MONTHS PRECEDING THE EVENT GIVING RISE TO LIABILITY AND (Y) $100, IN EACH CASE IN THE AGGREGATE FOR ALL CLAIMS OF EVERY KIND. THIS LIMITATION OF LIABILITY SECTION APPLIES WHETHER THE ALLEGED LIABILITY IS BASED ON CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY, OR ANY OTHER BASIS, EVEN IF THE COMPANY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE FOREGOING LIMITATION OF LIABILITY SHALL APPLY TO THE FULLEST EXTENT PERMITTED BY LAW IN THE APPLICABLE JURISDICTION. 16. [​](https://docs.arc.io/terms#indemnity) Indemnity ----------------------------------------------------- You will indemnify, defend, and hold us and each Validator (and our and their respective officers, employees, directors, agents, affiliates, licensors, contractors, successors and assigns and representatives) harmless from and against any and all claims, costs, losses, damages, judgments, tax assessments, penalties, interest, and expenses (including reasonable attorneys’ fees and costs of investigation) arising out of any claim, action, audit, investigation, inquiry, or other proceeding instituted by a person or entity that arises out of or relates to: (a) any actual or alleged breach of your representations, warranties, or obligations set forth in these Terms, including any violation of our policies; (b) your wrongful or improper use of Arc, Testnet and their features and functionalities; (c) your violation of any third-party right, including any right of privacy, publicity rights or intellectual property rights; (d) any transaction initiated by you, or by any AI Agent or other automated system acting on your behalf or using your credentials, access rights, or permissions, including errors, failures, or unintended consequences; (e) your violation of any Legal Requirement of the United States or any other country, including any Sanctions, Data Protection Laws or anti-money-laundering laws; (f) any other party’s access and/or use of Arc, Testnet and their features and functionalities with your password, private key, passkey or other credentials; (g) any User Content submitted, posted or transmitted by you or your AI Agent, or any of your acts or omissions in connection with any digital wallet, private key, validator or third-party service used by you or your AI Agent in connection with Arc or the Testnet; or (h) your willful misconduct, gross negligence or fraud. The Company or its designees will have the right, but not the obligation, to assume the exclusive defense and control of any matter otherwise subject to indemnification by you, in which event you agree to cooperate with the Company’s defense and not to settle any such matter without the Company’s prior written consent. 17. [​](https://docs.arc.io/terms#modification-of-terms) Modification of Terms ----------------------------------------------------------------------------- We may amend these Terms or modify certain features or functionalities on Arc, Testnet and their features and functionalities, including any applicable accompanying documentation and guidelines, at any time with notice that we deem to be reasonable in the circumstances, by posting the revised version on our website (each a “Revised Version”). We will update the “Last Updated” date at the top of the Terms to reflect the Revised Version. The Revised Version will be effective immediately as of the time it is posted, but will not apply retroactively. Your continued use of and access to Arc, Testnet and their features and functionalities after the posting of a Revised Version constitutes your acceptance of such Revised Version. Any Dispute (as defined in Section 18) that arose before the changes will be governed by the terms of service in place when the Dispute arose. You may not amend these Terms without our prior written consent. Regardless of where you access Arc or the Testnet, you are contracting with the Company. The Company may, in its sole discretion, assign these Terms (and its rights and obligations hereunder) to a successor entity, including any security council, foundation or other governance body that may, in the future, be designated to operate, govern or oversee the Network or any portion thereof; following any such assignment, references in these Terms to “the Company” will be read to refer to the assignee with respect to actions taken by such assignee, and your continued use of Arc or the Testnet thereafter will constitute your acceptance of these Terms as so assigned. The amendment rights of the Company under this Section 17 may be exercised by the Company or, after any such assignment, by such assignee. 18. [​](https://docs.arc.io/terms#dispute-resolution-binding-individual-arbitration-class-action-waiver) Dispute Resolution; Binding Individual Arbitration; Class Action Waiver ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- “Disputes” are defined as any claim, controversy, or dispute between you and the Company, its suppliers, service providers or licensors (or their respective affiliates, agents, directors or employees), whether arising before or during the effective period of these Terms, and including any claim, controversy, or dispute based on any conduct of you or the Company that occurred before the effective date of these Terms, including any claims arising from or relating in any way to these Terms or any matter relating to Arc, Testnet and their features and functionalities. 1. **General** You and the Company agree that any and all Disputes, except those that are resolved informally or brought in a small claims court, will be arbitrated by a neutral arbitrator who has the power to award the same individual damages and individual relief that a court can. ANY ARBITRATION UNDER THESE TERMS WILL ONLY BE ON AN INDIVIDUAL BASIS; CLASS ARBITRATIONS, CLASS ACTIONS, REPRESENTATIVE ACTIONS, AND CONSOLIDATION WITH OTHER ARBITRATIONS ARE NOT PERMITTED. YOU WAIVE ANY RIGHT TO HAVE YOUR CASE DECIDED BY A JURY AND YOU WAIVE ANY RIGHT TO PARTICIPATE IN A CLASS ACTION AGAINST THE COMPANY. If any provision of this arbitration agreement is found unenforceable, the unenforceable provision will be severed, and the remaining arbitration terms will be enforced (but in no case will there be a class or representative arbitration). 2. **Pre-Filing Requirement to Attempt to Resolve Disputes** Before an arbitration is commenced, you or the Company agree to attempt to avoid the costs of formal dispute resolution by giving each other a full and fair opportunity to address and resolve a Dispute informally. Both parties recognize that this is an important requirement, and that breach of this requirement would be a material breach of the Terms. To provide this opportunity, before commencing any arbitration or suit, each party agrees to send to the other party a written Notice (“Notice”). Any Notice to the Company should be sent by email to `arbitration@circle.com` or by mail to Arc Network Services, LLC, Attn: Arbitration Provision, 3101 Park Boulevard, Suite #02W102, Palo Alto, California 94306. Any Notice sent to you will be sent to the address on file for your account. The Notice must: (i) include your name and account number; (ii) provide detailed information sufficient to evaluate the merits of the claiming party’s individualized claim and for the other party to determine if an amicable resolution is possible; and (iii) set forth the specific relief sought, including whatever amount of money is demanded and the means by which the demanding party calculated the claimed damages. Both parties agree that they will attempt to resolve a dispute through an informal negotiation within sixty (60) days from the date the Notice is sent. After that sixty (60) day period and not before, either party may commence arbitration. Each party agrees that state or federal courts in New York, New York, USA referenced below, may enter injunctive relief to enforce the pre-filing requirements of this paragraph, including an injunction to stay an arbitration that has been commenced in violation of this paragraph. 3. **Scope of Arbitration** If we are not able to resolve the Dispute by informal negotiation or, as provided below, in a small claims court, all Disputes will be resolved finally and exclusively by binding individual arbitration with a single arbitrator (the “Arbitrator”) administered by JAMS Mediation, Arbitration, ADR services (“JAMS”) in accordance with The Comprehensive Arbitration Rules and Procedures of JAMS (available from JAMS on its website at [www.jams.com](http://www.jams.com/) ). You and the Company will have the right to file early or summary dispositive motions and to request that the relevant expedited procedures apply regardless of the claim amount. An arbitration will be conducted by a single, neutral arbitrator and shall take place in New York, New York, USA unless otherwise mutually agreed by the parties. Except as set forth above, the Arbitrator shall be responsible for determining all threshold arbitrability issues, including issues relating to whether the Terms are enforceable, unconscionable or illusory and any defense to arbitration, including waiver, delay, laches, or estoppel. 4. **Small Claims Court** Subject to applicable jurisdictional requirements, either party may elect to pursue a Dispute in a local small-claims court rather than through arbitration so long as the matter remains in small claims court and proceeds only on an individual basis. If a party has already submitted an arbitration demand to JAMS, the other party may, in its sole discretion, inform JAMS that it chooses to have the Dispute heard in small claims court. At that time, JAMS will close the arbitration and the Dispute will be heard in the appropriate small claims court, with no fees due from the arbitration respondent. 5. **Arbitration Procedures** The Federal Arbitration Act, 9 U.S.C. §§ 1-16, including its procedural provisions, fully applies. Any arbitration hearing will occur in New York, New York, at another mutually agreeable location or, if both parties agree, by telephone or videoconference. Any arbitration shall be conducted in English. The Arbitrator’s award will be binding on the parties and may be entered as a judgment in any court of competent jurisdiction. The Company values your privacy, particularly with respect to your financial transactions and data. Each of the parties shall maintain the confidential nature of the arbitration and shall not (without the prior written consent of the other party) disclose to any third party the fact, existence, content, award, or other result of the arbitration, except as may be necessary to enforce, enter, or challenge such award in a court of competent jurisdiction or as otherwise required by applicable Legal Requirements. While an arbitrator may award declaratory or injunctive relief, the Arbitrator may do so only with respect to the individual party seeking relief and only to the extent necessary to provide relief warranted by the individual party’s claim. The Arbitrator’s decision and judgment thereon will not have a precedential or collateral estoppel effect. 6. **Arbitration Fees** In accordance with JAMS, the party initiating the arbitration (either you or us) is responsible for paying the applicable filing fee. For purposes of this arbitration provision, references to you and the Company also include respective subsidiaries, affiliates, agents, employees, predecessors, successors and assigns as well as authorized users or beneficiaries of the features and functionalities. 7. **Opt Out** You may reject this provision, in which case only a court may be used to resolve any Dispute. To reject this provision, you must send us an opt-out notice (the “Opt Out”) within thirty (30) days after you first access Arc, Testnet and their features and functionalities, or we first provide you with the right to reject this provision. The Opt Out may be mailed to Arc Network Services, LLC, Attn: Arbitration Provision, 3101 Park Boulevard, Suite #02W102, Palo Alto, California 94306. Alternatively, you may send an email with the subject line “Opt Out of Arbitration” to `arbitration@circle.com`. Any Opt Out notice must include your name, address, phone number and the wallet address you used to use Arc or the Testnet. You must send such a notice in order to opt out of this provision. Opting out will not affect any other aspect of the Terms or the Network and will have no effect on any other or future agreements you may reach to arbitrate with us. 8. **Court Proceedings** Subject to and without waiver of the arbitration provisions above, you agree that any judicial proceedings (other than small claims actions as discussed above) will be brought in and you hereby consent to the exclusive jurisdiction and venue in the state or federal courts in New York, New York. 19. [​](https://docs.arc.io/terms#governing-law) Governing Law ------------------------------------------------------------- These Terms are governed by the laws of the State of Delaware, without regard to its conflicts-of-law principles, and applicable federal law of the United States. Any arbitration related to any Dispute will be governed by the Federal Arbitration Act, as set forth above. 20. [​](https://docs.arc.io/terms#limitation-on-time-to-initiate-a-dispute) Limitation on Time to Initiate a Dispute ------------------------------------------------------------------------------------------------------------------- Any action or proceeding by you relating to any Dispute must commence within one (1) year after the cause of action accrues. 21. [​](https://docs.arc.io/terms#assignment-change-of-control) Assignment; Change of Control -------------------------------------------------------------------------------------------- These Terms and any rights and licenses granted hereunder may not be transferred or assigned by you (whether by operation of law or otherwise) and any attempted transfer or assignment will be null and void. We may freely assign, novate or otherwise transfer these Terms (or any of our rights or obligations hereunder), in whole or in part, without your consent and without notice to you, to (i) any of our affiliates or subsidiaries, (ii) any successor in interest in connection with any merger, acquisition, reorganization, sale of assets or change of control, (iii) any security council, foundation, decentralized autonomous organization, validator-driven governance body or other entity that may, in the future, be designated to operate, govern, oversee or steward the Network or any portion thereof, or (iv) any other person or entity to which we, in our sole discretion, designate, including in connection with any transition of operational, governance or other responsibilities relating to the Network. References in these Terms to “the Company” will, after any such assignment, be read to refer to the assignee with respect to the rights and obligations so assigned, and the Company will have no further liability with respect to any obligation that has been so assigned and assumed by an assignee. 22. [​](https://docs.arc.io/terms#other-provisions) Other Provisions ------------------------------------------------------------------- These Terms and any other applicable terms or policies are a complete statement of the agreement between you and the Company regarding Arc, Testnet and their features and functionalities. If any provision of these Terms is invalid or unenforceable under applicable law, then it will be changed and interpreted to accomplish the objectives of such provision to the greatest extent possible under applicable law, and the remaining provisions will continue in full force and effect. These Terms do not limit any rights that the Company may have under trade secret, copyright, patent, or other laws. No waiver of any term of these Terms shall be deemed a further or continuing waiver of such term or any other term. Neither the Company nor any of its affiliates shall be liable for any failure or delay in performance under these Terms to the extent caused by any event or circumstance beyond its reasonable control, including any act of God, natural disaster, epidemic or pandemic, war, terrorism, civil unrest, governmental action or order, change in Legal Requirements, sanctions designation, internet, telecommunications or power failure, denial-of-service or other cyber-attack, smart-contract exploit, blockchain network failure, congestion, fork or protocol-level event, or act or omission of any Validator or other third party. From time to time, we may request you to certify, in writing, that you agree to and adhere to these Terms and all other applicable terms and policies, and the purpose or use of Arc, Testnet and their features and functionalities and related data that you have access to, and that each such purpose or use complies with these Terms and all other applicable terms and policies. All such certifications and attestations must be provided by an authorized representative of yours in writing. For purposes of interpreting this Agreement, unless otherwise specifically stated: (a) the singular includes the plural, and the plural includes the singular; (b) the words “herein”, “hereunder” and “hereof” and other words of similar import refer to this Agreement as a whole and not to any particular section or paragraph; (c) the words “include” and “including” will not be construed as terms of limitation, and will therefore mean “including but not limited to” and “including without limitation”; (d) the words “writing” or “written” mean preserved or presented in retrievable or reproducible form, whether electronic (including email but excluding voice mail) or hard copy; (e) the captions and section and paragraph headings used in this Agreement are inserted for convenience only and will not affect the meaning or interpretation of this Agreement; and (f) the references herein to the parties will refer to their permitted successors and assigns. 23. [​](https://docs.arc.io/terms#survival) Survival --------------------------------------------------- The following sections of these Terms survive and remain in effect in accordance with their terms upon termination of these Terms: 1(e) through 1(r) (inclusive), 5 (AI Agent Access), 6 (Third Party Services and Content), 7 (No Agency), 8 (No Financial Services), 9 (Non-Solicitation and No Professional Advice), 10 (Submission of User Content), 11 (Use of the Company Marks), 12 (Ownership; Feedback), 13 (Termination), 14 (No Warranties), 15 (Limitation of Liability), 16 (Indemnity), 17 (Modification of Terms), 18 (Dispute Resolution; Binding Individual Arbitration; Class Action Waiver), 19 (Governing Law), 20 (Limitation on Time to Initiate a Dispute), 21 (Assignment; Change of Control), 22 (Other Provisions) and 23 (Survival), in each case together with any other provision the survival of which is necessary to give effect to its terms. Was this page helpful? YesNo ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Send batch USDC transfers - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#content-area) Use the `Multicall3From` contract to batch multiple USDC transfers into one Arc transaction. This tutorial shows the full flow with viem, ethers.js, Python, and curl. You will configure a client, encode two ERC-20 `transfer(...)` subcalls, submit them through `Multicall3From.aggregate3(...)`, and verify the resulting `Transfer` events. To learn how `Multicall3From` preserves your wallet as the sender, see [Batched transactions](https://docs.arc.io/arc/concepts/batched-transactions) . [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#prerequisites) Prerequisites ------------------------------------------------------------------------------------------ Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) for the TypeScript examples, or Python 3.10+ for the Python example. * Created an [Arc Testnet wallet](https://docs.arc.io/arc/references/connect-to-arc) . * Funded the wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/) . * Chosen two recipient addresses on Arc Testnet. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-1-set-up-the-project) Step 1. Set up the project ------------------------------------------------------------------------------------------------------------------- Create a new project and install the dependencies for the client library you want to use: Node.js Python mkdir arc-batched-transfers cd arc-batched-transfers npm init -y npm pkg set type=module npm install dotenv ethers tsx typescript viem mkdir arc-batched-transfers cd arc-batched-transfers python3 -m venv .venv source .venv/bin/activate pip install web3 python-dotenv Create an `.env` file: Shell touch .env Add your configuration: .env PRIVATE_KEY=YOUR_PRIVATE_KEY RECIPIENT_ONE_ADDRESS=RECIPIENT_ONE_ADDRESS RECIPIENT_TWO_ADDRESS=RECIPIENT_TWO_ADDRESS RPC_URL=https://rpc.testnet.arc.network Replace `YOUR_PRIVATE_KEY` with the `0x`\-prefixed private key for the funded wallet. Replace each recipient value with an Arc Testnet address. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-2-review-the-contract-address-and-abi) Step 2. Review the contract address and ABI ----------------------------------------------------------------------------------------------------------------------------------------------------- Arc Testnet uses the following predeployed contracts for this tutorial: | Contract | Address | | --- | --- | | `Multicall3From` | [`0x522fAf9A91c41c443c66765030741e4AaCe147D0`](https://testnet.arcscan.app/address/0x522fAf9A91c41c443c66765030741e4AaCe147D0) | | `USDC` | [`0x3600000000000000000000000000000000000000`](https://testnet.arcscan.app/address/0x3600000000000000000000000000000000000000) | For the full address list, see [contract addresses](https://docs.arc.io/arc/references/contract-addresses#transaction-extensions) . Create `multicall3from-abi.json` in your project: multicall3from-abi.json [\ {\ "type": "function",\ "name": "aggregate3",\ "stateMutability": "nonpayable",\ "inputs": [\ {\ "name": "calls",\ "type": "tuple[]",\ "components": [\ { "name": "target", "type": "address" },\ { "name": "allowFailure", "type": "bool" },\ { "name": "callData", "type": "bytes" }\ ]\ }\ ],\ "outputs": [\ {\ "name": "returnData",\ "type": "tuple[]",\ "components": [\ { "name": "success", "type": "bool" },\ { "name": "returnData", "type": "bytes" }\ ]\ }\ ]\ }\ ] The script you build in the next steps loads this ABI file from the project root. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-3-configure-the-client-connection) Step 3. Configure the client connection --------------------------------------------------------------------------------------------------------------------------------------------- Create the script file for your client library. The first chunk reads the wallet and recipient configuration from `.env`, sets the contract addresses, loads the `Multicall3From` ABI, and creates the clients that read chain state and submit transactions. * Viem * ethers.js * Python Create `viem-batch.ts`: TypeScript import "dotenv/config"; import { readFileSync } from "node:fs"; import { type Address, createPublicClient, createWalletClient, defineChain, encodeFunctionData, erc20Abi, getAddress, http, parseEventLogs, parseUnits, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; const rpcUrl = process.env.RPC_URL ?? "https://rpc.testnet.arc.network"; const privateKey = process.env.PRIVATE_KEY as `0x${string}`; const recipients = [\ getAddress(process.env.RECIPIENT_ONE_ADDRESS as Address),\ getAddress(process.env.RECIPIENT_TWO_ADDRESS as Address),\ ]; const arcTestnet = defineChain({ id: 5042002, name: "Arc Testnet", nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 18 }, rpcUrls: { default: { http: [rpcUrl] } }, blockExplorers: { default: { name: "ArcScan", url: "https://testnet.arcscan.app" }, }, testnet: true, }); const multicall3FromAddress = "0x522fAf9A91c41c443c66765030741e4AaCe147D0"; const usdcAddress = "0x3600000000000000000000000000000000000000"; const multicall3FromAbi = JSON.parse( readFileSync("multicall3from-abi.json", "utf8"), ); const account = privateKeyToAccount(privateKey); const publicClient = createPublicClient({ chain: arcTestnet, transport: http(rpcUrl), }); const walletClient = createWalletClient({ account, chain: arcTestnet, transport: http(rpcUrl), }); Create `ethers-batch.ts`: TypeScript import "dotenv/config"; import { readFileSync } from "node:fs"; import { ethers } from "ethers"; const rpcUrl = process.env.RPC_URL ?? "https://rpc.testnet.arc.network"; const privateKey = process.env.PRIVATE_KEY as string; const recipients = [\ ethers.getAddress(process.env.RECIPIENT_ONE_ADDRESS as string),\ ethers.getAddress(process.env.RECIPIENT_TWO_ADDRESS as string),\ ]; const multicall3FromAddress = "0x522fAf9A91c41c443c66765030741e4AaCe147D0"; const usdcAddress = "0x3600000000000000000000000000000000000000"; const explorerUrl = "https://testnet.arcscan.app"; const multicall3FromAbi = JSON.parse( readFileSync("multicall3from-abi.json", "utf8"), ); const erc20Abi = [\ "function transfer(address to, uint256 amount) returns (bool)",\ "event Transfer(address indexed from, address indexed to, uint256 value)",\ ]; const provider = new ethers.JsonRpcProvider(rpcUrl, { chainId: 5042002, name: "arc-testnet", }); const wallet = new ethers.Wallet(privateKey, provider); const multicall3FromInterface = new ethers.Interface(multicall3FromAbi); const erc20Interface = new ethers.Interface(erc20Abi); Create `python-batch.py`: Python import json import os from dotenv import load_dotenv from web3 import Web3 from web3.logs import DISCARD load_dotenv() rpc_url = os.getenv("RPC_URL", "https://rpc.testnet.arc.network") private_key = os.environ["PRIVATE_KEY"] recipients = [\ Web3.to_checksum_address(os.environ["RECIPIENT_ONE_ADDRESS"]),\ Web3.to_checksum_address(os.environ["RECIPIENT_TWO_ADDRESS"]),\ ] multicall3_from_address = Web3.to_checksum_address( "0x522fAf9A91c41c443c66765030741e4AaCe147D0" ) usdc_address = Web3.to_checksum_address("0x3600000000000000000000000000000000000000") explorer_url = "https://testnet.arcscan.app" with open("multicall3from-abi.json", encoding="utf-8") as abi_file: multicall3_from_abi = json.load(abi_file) erc20_abi = [\ {\ "type": "function",\ "name": "transfer",\ "stateMutability": "nonpayable",\ "inputs": [\ {"name": "to", "type": "address"},\ {"name": "amount", "type": "uint256"},\ ],\ "outputs": [{"name": "", "type": "bool"}],\ },\ {\ "type": "event",\ "name": "Transfer",\ "anonymous": False,\ "inputs": [\ {"name": "from", "type": "address", "indexed": True},\ {"name": "to", "type": "address", "indexed": True},\ {"name": "value", "type": "uint256", "indexed": False},\ ],\ },\ ] w3 = Web3(Web3.HTTPProvider(rpc_url)) account = w3.eth.account.from_key(private_key) multicall3_from = w3.eth.contract( address=multicall3_from_address, abi=multicall3_from_abi, ) usdc = w3.eth.contract(address=usdc_address, abi=erc20_abi) The public clients read chain state, the wallet or account objects sign transactions, and the contract interfaces encode the `Multicall3From` and ERC-20 calls. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-4-encode-the-transfer-calls) Step 4. Encode the transfer calls --------------------------------------------------------------------------------------------------------------------------------- Add a chunk that sets the USDC amount and builds one ERC-20 `transfer(...)` subcall for each recipient. Each subcall targets the USDC contract, sets `allowFailure` to `false`, and includes the encoded transfer calldata. * Viem * ethers.js * Python TypeScript const amount = parseUnits("1", 6); const calls = recipients.map((recipient) => ({ target: usdcAddress, allowFailure: false, callData: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [recipient, amount], }), })); TypeScript const amount = ethers.parseUnits("1", 6); const calls = recipients.map((recipient) => ({ target: usdcAddress, allowFailure: false, callData: erc20Interface.encodeFunctionData("transfer", [recipient, amount]), })); const aggregateData = multicall3FromInterface.encodeFunctionData("aggregate3", [\ calls,\ ]); Python amount = 1_000_000 calls = [] for recipient in recipients: transfer_data = usdc.functions.transfer( recipient, amount, )._encode_transaction_data() calls.append( ( usdc_address, False, bytes.fromhex(transfer_data[2:]), ) ) `1000000` is `1` USDC in base units because the USDC ERC-20 interface uses 6 decimals. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-5-confirm-multicall3from-is-deployed) Step 5. Confirm `Multicall3From` is deployed ----------------------------------------------------------------------------------------------------------------------------------------------------- Before sending a transaction, check that the configured `Multicall3From` address has deployed bytecode. * Viem * ethers.js * Python TypeScript const multicall3FromCode = await publicClient.getCode({ address: multicall3FromAddress, }); if (!multicall3FromCode || multicall3FromCode === "0x") { throw new Error(`Multicall3From is not deployed at ${multicall3FromAddress}`); } TypeScript const multicall3FromCode = await provider.getCode(multicall3FromAddress); if (multicall3FromCode === "0x") { throw new Error(`Multicall3From is not deployed at ${multicall3FromAddress}`); } Python multicall3_from_code = w3.eth.get_code(multicall3_from_address) if multicall3_from_code == b"": raise RuntimeError( f"Multicall3From is not deployed at {multicall3_from_address}" ) If the bytecode check returns empty code, confirm that your `RPC_URL` points to Arc Testnet and that the address matches the table in Step 2. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-6-simulate-and-send-the-batch) Step 6. Simulate and send the batch ------------------------------------------------------------------------------------------------------------------------------------- Simulate the `aggregate3(...)` call before sending the transaction. The simulation confirms that each encoded subcall can succeed from your wallet. * Viem * ethers.js * Python TypeScript const simulation = await publicClient.simulateContract({ account, address: multicall3FromAddress, abi: multicall3FromAbi, functionName: "aggregate3", args: [calls], }); const simulatedResults = simulation.result as { success: boolean; returnData: `0x${string}`; }[]; if (!simulatedResults.every((result) => result.success)) { throw new Error( `Expected all simulated subcalls to succeed: ${JSON.stringify( simulatedResults, )}`, ); } const hash = await walletClient.writeContract(simulation.request); const receipt = await publicClient.waitForTransactionReceipt({ hash }); if (receipt.status !== "success") { throw new Error(`Batched transaction reverted: ${hash}`); } TypeScript const [simulatedResults] = multicall3FromInterface.decodeFunctionResult( "aggregate3", await provider.call({ from: wallet.address, to: multicall3FromAddress, data: aggregateData, }), ) as unknown as [{ success: boolean; returnData: string }[]]; if (!simulatedResults.every((result) => result.success)) { throw new Error( `Expected all simulated subcalls to succeed: ${JSON.stringify( simulatedResults, )}`, ); } const tx = await wallet.sendTransaction({ to: multicall3FromAddress, data: aggregateData, }); const receipt = await tx.wait(); if (!receipt) { throw new Error("Transaction was not mined"); } if (receipt.status !== 1) { throw new Error(`Batched transaction reverted: ${tx.hash}`); } Python simulated_results = multicall3_from.functions.aggregate3(calls).call( {"from": account.address} ) if not all(result[0] for result in simulated_results): raise RuntimeError(f"Expected all simulated subcalls to succeed: {simulated_results}") latest_block = w3.eth.get_block("latest") priority_fee = w3.to_wei(1, "gwei") base_fee = latest_block["baseFeePerGas"] transaction = multicall3_from.functions.aggregate3(calls).build_transaction( { "from": account.address, "nonce": w3.eth.get_transaction_count(account.address), "chainId": w3.eth.chain_id, "maxPriorityFeePerGas": priority_fee, "maxFeePerGas": (base_fee * 2) + priority_fee, } ) signed = account.sign_transaction(transaction) tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash) if receipt["status"] != 1: raise RuntimeError(f"Batched transaction reverted: {Web3.to_hex(tx_hash)}") The receipt is reused in the next step to check the emitted USDC `Transfer` events. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-7-verify-the-transfer-events) Step 7. Verify the transfer events ----------------------------------------------------------------------------------------------------------------------------------- Decode the USDC logs from the receipt and confirm that each expected transfer is present. * Viem * ethers.js * Python TypeScript const transferLogs = parseEventLogs({ abi: erc20Abi, eventName: "Transfer", logs: receipt.logs.filter( (log) => log.address.toLowerCase() === usdcAddress.toLowerCase(), ), }); for (const recipient of recipients) { const transfer = transferLogs.find( (log) => getAddress(log.args.from) === account.address && getAddress(log.args.to) === recipient && log.args.value === amount, ); if (!transfer) { throw new Error(`Missing expected USDC Transfer log to ${recipient}`); } } console.log( "Transaction:", `${arcTestnet.blockExplorers.default.url}/tx/${hash}`, ); console.log("Block:", receipt.blockNumber.toString()); console.log("Sender:", account.address); console.log( "Transfers:", recipients.map((recipient) => ({ to: recipient, amount: amount.toString(), })), ); TypeScript const transferEvents: ethers.LogDescription[] = []; for (const log of receipt.logs) { if (log.address.toLowerCase() !== usdcAddress.toLowerCase()) continue; const parsed = erc20Interface.parseLog(log); if (parsed?.name === "Transfer") transferEvents.push(parsed); } for (const recipient of recipients) { const transfer = transferEvents.find( (event) => event.args.from.toLowerCase() === wallet.address.toLowerCase() && event.args.to.toLowerCase() === recipient.toLowerCase() && event.args.value === amount, ); if (!transfer) { throw new Error(`Missing expected USDC Transfer log to ${recipient}`); } } console.log("Transaction:", `${explorerUrl}/tx/${tx.hash}`); console.log("Block:", receipt.blockNumber); console.log("Sender:", wallet.address); console.log( "Transfers:", recipients.map((recipient) => ({ to: recipient, amount: amount.toString(), })), ); Python transfer_events = usdc.events.Transfer().process_receipt(receipt, errors=DISCARD) for recipient in recipients: transfer = next( ( event for event in transfer_events if Web3.to_checksum_address(event["args"]["from"]) == account.address and Web3.to_checksum_address(event["args"]["to"]) == recipient and event["args"]["value"] == amount ), None, ) if transfer is None: raise RuntimeError(f"Missing expected USDC Transfer log to {recipient}") print("Transaction:", f"{explorer_url}/tx/{Web3.to_hex(tx_hash)}") print("Block:", receipt["blockNumber"]) print("Sender:", account.address) print( "Transfers:", [{"to": recipient, "amount": str(amount)} for recipient in recipients], ) The `from` value in each expected `Transfer` event is your wallet address. That check confirms that the target USDC contract saw your wallet as the sender for each subcall. [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-8-run-the-script) Step 8. Run the script ----------------------------------------------------------------------------------------------------------- Run the command for the script you created: viem ethers.js Python npx tsx --env-file=.env viem-batch.ts npx tsx --env-file=.env ethers-batch.ts python python-batch.py Successful output includes the transaction URL, block number, sender, and both recipient transfers: Transaction: https://testnet.arcscan.app/tx/0x... Block: 123456 Sender: 0xYourWallet... Transfers: [\ { to: "0xRecipientOne...", amount: "1000000" },\ { to: "0xRecipientTwo...", amount: "1000000" }\ ] [​](https://docs.arc.io/arc/tutorials/batch-usdc-transfers#step-9-check-json-rpc-directly-with-curl) Step 9. Check JSON-RPC directly with curl ------------------------------------------------------------------------------------------------------------------------------------------------- Use curl for read-only JSON-RPC checks, such as verifying deployed bytecode or reading a transaction receipt. Use a client library such as viem, ethers.js, or web3.py to sign and submit the transaction itself. eth\_getCode eth\_getTransactionReceipt MULTICALL3FROM_ADDRESS=0x522fAf9A91c41c443c66765030741e4AaCe147D0 RPC_URL=https://rpc.testnet.arc.network curl --request POST "$RPC_URL" \ --header "content-type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_getCode", "params": ["'"$MULTICALL3FROM_ADDRESS"'", "latest"], "id": 1 }' TRANSACTION_HASH=0xYOUR_TRANSACTION_HASH RPC_URL=https://rpc.testnet.arc.network curl --request POST "$RPC_URL" \ --header "content-type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_getTransactionReceipt", "params": ["'"$TRANSACTION_HASH"'"], "id": 1 }' Was this page helpful? YesNo [Send USDC with a transaction memo](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo) [Register your first AI agent](https://docs.arc.io/arc/tutorials/register-your-first-ai-agent) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Send USDC with a transaction memo - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#content-area) Use the `Memo` contract to add a memo to a USDC transfer on Arc Testnet. This tutorial shows the full flow with viem, ethers, Python, and curl. You will encode the inner USDC transfer, submit it with `Memo.memo(...)`, decode the receipt, and query past memo events by `memoId`. To learn how the `Memo` contract preserves your wallet as the sender and orders its events, see [Transaction memos](https://docs.arc.io/arc/concepts/transaction-memos) . [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------------- Before you begin, ensure that you’ve: * Installed [Node.js v22+](https://nodejs.org/) for the TypeScript examples, or Python 3.10+ for the Python example. * Created an [Arc Testnet wallet](https://docs.arc.io/arc/references/connect-to-arc) controlled by an externally owned account (EOA). Smart contract wallets aren’t supported. See [Wallet types](https://docs.arc.io/arc/concepts/transaction-memos#wallet-types) for details. * Funded the wallet with testnet USDC from the [Circle Faucet](https://faucet.circle.com/) . * Chosen a recipient address on Arc Testnet. [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-1-set-up-the-project) Step 1. Set up the project ------------------------------------------------------------------------------------------------------------------------------ Create a new project and install the dependencies for the client library you want to use: Node.js Python mkdir arc-transaction-memo cd arc-transaction-memo npm init -y npm install dotenv ethers tsx typescript viem mkdir arc-transaction-memo cd arc-transaction-memo python3 -m venv .venv source .venv/bin/activate pip install web3 python-dotenv Create an `.env` file: Shell touch .env Add your configuration: .env PRIVATE_KEY=YOUR_PRIVATE_KEY RECIPIENT_ADDRESS=RECIPIENT_ADDRESS RPC_URL=https://rpc.testnet.arc.network Replace `YOUR_PRIVATE_KEY` with the `0x`\-prefixed private key for the funded wallet. Replace `RECIPIENT_ADDRESS` with the wallet that should receive USDC. [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-2-review-the-contract-address-and-abi) Step 2. Review the contract address and ABI ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Arc Testnet uses the following predeployed transaction memo contracts: | Contract | Address | | --- | --- | | `Memo` | [`0x5294E9927c3306DcBaDb03fe70b92e01cCede505`](https://testnet.arcscan.app/address/0x5294E9927c3306DcBaDb03fe70b92e01cCede505) | | `USDC` | [`0x3600000000000000000000000000000000000000`](https://testnet.arcscan.app/address/0x3600000000000000000000000000000000000000) | For the full address list, see [contract addresses](https://docs.arc.io/arc/references/contract-addresses#transaction-extensions) . Create `memo-abi.json` in your project: memo-abi.json [\ {\ "type": "function",\ "name": "memo",\ "stateMutability": "nonpayable",\ "inputs": [\ { "name": "target", "type": "address" },\ { "name": "data", "type": "bytes" },\ { "name": "memoId", "type": "bytes32" },\ { "name": "memoData", "type": "bytes" }\ ],\ "outputs": []\ },\ {\ "type": "event",\ "name": "BeforeMemo",\ "anonymous": false,\ "inputs": [{ "name": "memoIndex", "type": "uint256", "indexed": true }]\ },\ {\ "type": "event",\ "name": "Memo",\ "anonymous": false,\ "inputs": [\ { "name": "sender", "type": "address", "indexed": true },\ { "name": "target", "type": "address", "indexed": true },\ { "name": "callDataHash", "type": "bytes32", "indexed": false },\ { "name": "memoId", "type": "bytes32", "indexed": true },\ { "name": "memo", "type": "bytes", "indexed": false },\ { "name": "memoIndex", "type": "uint256", "indexed": false }\ ]\ }\ ] The script you build in the next steps loads this ABI file from the project root. [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-3-configure-the-client-connection) Step 3. Configure the client connection -------------------------------------------------------------------------------------------------------------------------------------------------------- Create the script file for your client library. The first chunk reads the wallet and recipient configuration from `.env`, sets the contract addresses, loads the `Memo` ABI, and creates the clients that sign and send requests. * Viem * Ethers * Python The example imports the `arcTestnet` chain definition from `viem/chains`, which requires viem v2.38 or later.Create `viem-memo.ts`: TypeScript import "dotenv/config"; import { readFileSync } from "node:fs"; import { type Abi, type Address, createPublicClient, createWalletClient, encodeFunctionData, erc20Abi, getAddress, http, keccak256, parseAbiItem, parseEventLogs, parseUnits, stringToHex, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { arcTestnet } from "viem/chains"; const rpcUrl = process.env.RPC_URL ?? "https://rpc.testnet.arc.network"; const privateKey = process.env.PRIVATE_KEY as `0x${string}`; const recipient = getAddress(process.env.RECIPIENT_ADDRESS as Address); const memoAddress = "0x5294E9927c3306DcBaDb03fe70b92e01cCede505"; const usdcAddress = "0x3600000000000000000000000000000000000000"; const memoAbi = JSON.parse(readFileSync("memo-abi.json", "utf8")) as Abi; const account = privateKeyToAccount(privateKey); const publicClient = createPublicClient({ chain: arcTestnet, transport: http(rpcUrl), }); const walletClient = createWalletClient({ account, chain: arcTestnet, transport: http(rpcUrl), }); The public client reads chain state, and the wallet client signs and submits transactions from your funded account. Create `ethers-memo.ts`: TypeScript import "dotenv/config"; import { readFileSync } from "node:fs"; import { ethers } from "ethers"; const rpcUrl = process.env.RPC_URL ?? "https://rpc.testnet.arc.network"; const privateKey = process.env.PRIVATE_KEY as string; const recipient = ethers.getAddress(process.env.RECIPIENT_ADDRESS as string); const memoAddress = "0x5294E9927c3306DcBaDb03fe70b92e01cCede505"; const usdcAddress = "0x3600000000000000000000000000000000000000"; const explorerUrl = "https://testnet.arcscan.app"; const memoAbi = JSON.parse(readFileSync("memo-abi.json", "utf8")); const erc20Abi = [\ "function transfer(address to, uint256 amount) returns (bool)",\ ]; const provider = new ethers.JsonRpcProvider(rpcUrl, { chainId: 5042002, name: "arc-testnet", }); const wallet = new ethers.Wallet(privateKey, provider); const memoInterface = new ethers.Interface(memoAbi); const erc20Interface = new ethers.Interface(erc20Abi); The provider reads chain state, the wallet signs transactions, and the two interfaces encode and decode `Memo` and ERC-20 calldata. Create `python-memo.py`: Python import json import os from dotenv import load_dotenv from web3 import Web3 from web3.logs import DISCARD load_dotenv() rpc_url = os.getenv("RPC_URL", "https://rpc.testnet.arc.network") private_key = os.environ["PRIVATE_KEY"] recipient = Web3.to_checksum_address(os.environ["RECIPIENT_ADDRESS"]) memo_address = Web3.to_checksum_address("0x5294E9927c3306DcBaDb03fe70b92e01cCede505") usdc_address = Web3.to_checksum_address("0x3600000000000000000000000000000000000000") explorer_url = "https://testnet.arcscan.app" with open("memo-abi.json", encoding="utf-8") as abi_file: memo_abi = json.load(abi_file) erc20_abi = [\ {\ "type": "function",\ "name": "transfer",\ "stateMutability": "nonpayable",\ "inputs": [\ {"name": "to", "type": "address"},\ {"name": "amount", "type": "uint256"},\ ],\ "outputs": [{"name": "", "type": "bool"}],\ },\ ] w3 = Web3(Web3.HTTPProvider(rpc_url)) account = w3.eth.account.from_key(private_key) memo = w3.eth.contract(address=memo_address, abi=memo_abi) usdc = w3.eth.contract(address=usdc_address, abi=erc20_abi) The `Web3` instance connects to the RPC endpoint, and the two contract objects encode and decode `Memo` and ERC-20 calldata. [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-4-encode-the-transfer-and-memo-values) Step 4. Encode the transfer and memo values ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Add the values that define the memo transfer. The encoded ERC-20 `transfer` call becomes the inner call that `Memo.memo(...)` forwards to USDC. The `memoId` is a 32-byte identifier your application uses to look the memo up later, and the memo bytes carry the metadata itself. * Viem * Ethers * Python Append to `viem-memo.ts`: TypeScript const transferData = encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [recipient, parseUnits("1", 6)], }); const callDataHash = keccak256(transferData); const memoId = keccak256(stringToHex("invoice-2026-0001")); const memoBytes = stringToHex("order=2026-0001"); Append to `ethers-memo.ts`: TypeScript const transferData = erc20Interface.encodeFunctionData("transfer", [\ recipient,\ ethers.parseUnits("1", 6),\ ]); const callDataHash = ethers.keccak256(transferData); const memoId = ethers.id("invoice-2026-0001"); const memoBytes = ethers.toUtf8Bytes("order=2026-0001"); Append to `python-memo.py`: Python transfer_data = usdc.functions.transfer(recipient, 1_000_000)._encode_transaction_data() transfer_bytes = bytes.fromhex(transfer_data[2:]) call_data_hash = Web3.keccak(hexstr=transfer_data) memo_id = Web3.keccak(text="invoice-2026-0001") memo_bytes = b"order=2026-0001" The transfer amount is `1` USDC, expressed with six decimals. The script also hashes the transfer calldata so a later step can match it against the `callDataHash` field of the emitted `Memo` event. [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-5-confirm-the-memo-contract-is-deployed) Step 5. Confirm the `Memo` contract is deployed ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- Before you send the transaction, check that the `Memo` address has deployed bytecode. If `eth_getCode` returns `0x`, the contract is not available on the RPC endpoint you are using, and the script stops with an error. * Viem * Ethers * Python Append to `viem-memo.ts`: TypeScript const memoCode = await publicClient.getCode({ address: memoAddress }); if (!memoCode || memoCode === "0x") { throw new Error(`Memo contract is not deployed at ${memoAddress}`); } Append to `ethers-memo.ts`: TypeScript const memoCode = await provider.getCode(memoAddress); if (memoCode === "0x") { throw new Error(`Memo contract is not deployed at ${memoAddress}`); } Append to `python-memo.py`: Python memo_code = w3.eth.get_code(memo_address) if memo_code == b"": raise RuntimeError(f"Memo contract is not deployed at {memo_address}") [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-6-send-the-memo-transaction) Step 6. Send the memo transaction -------------------------------------------------------------------------------------------------------------------------------------------- Call `Memo.memo(...)` with the four arguments from Step 4: the USDC contract as the target, the encoded transfer calldata, the `memoId`, and the memo bytes. Then wait for the receipt and confirm the transaction succeeded. * Viem * Ethers * Python Append to `viem-memo.ts`: TypeScript const hash = await walletClient.writeContract({ address: memoAddress, abi: memoAbi, functionName: "memo", args: [usdcAddress, transferData, memoId, memoBytes], }); const receipt = await publicClient.waitForTransactionReceipt({ hash }); if (receipt.status !== "success") { throw new Error(`Memo transaction reverted: ${hash}`); } Append to `ethers-memo.ts`: TypeScript const tx = await wallet.sendTransaction({ to: memoAddress, data: memoInterface.encodeFunctionData("memo", [\ usdcAddress,\ transferData,\ memoId,\ memoBytes,\ ]), }); const receipt = await tx.wait(); if (!receipt) { throw new Error("Transaction was not mined"); } if (receipt.status !== 1) { throw new Error(`Memo transaction reverted: ${tx.hash}`); } console.log("Transaction:", `${explorerUrl}/tx/${tx.hash}`); console.log("Block:", receipt.blockNumber); web3.py builds the transaction explicitly, so this chunk also sets the nonce and the EIP-1559 fee fields before signing and sending: Python latest_block = w3.eth.get_block("latest") priority_fee = w3.to_wei(1, "gwei") base_fee = latest_block.get("baseFeePerGas", w3.eth.gas_price) transaction = memo.functions.memo( usdc_address, transfer_bytes, memo_id, memo_bytes, ).build_transaction( { "from": account.address, "nonce": w3.eth.get_transaction_count(account.address), "chainId": w3.eth.chain_id, "maxPriorityFeePerGas": priority_fee, "maxFeePerGas": (base_fee * 2) + priority_fee, } ) signed = account.sign_transaction(transaction) raw_transaction = getattr(signed, "raw_transaction", None) or signed.rawTransaction tx_hash = w3.eth.send_raw_transaction(raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash) if receipt["status"] != 1: raise RuntimeError(f"Memo transaction reverted: {Web3.to_hex(tx_hash)}") print("Transaction:", f"{explorer_url}/tx/{Web3.to_hex(tx_hash)}") print("Block:", receipt["blockNumber"]) [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-7-decode-and-verify-the-memo-events) Step 7. Decode and verify the memo events ------------------------------------------------------------------------------------------------------------------------------------------------------------ A successful memo transfer emits one `BeforeMemo` event and one `Memo` event alongside the USDC `Transfer`. Decode the receipt logs and verify that the `Memo` event carries the sender, target, calldata hash, `memoId`, and memo bytes the script sent. * Viem * Ethers * Python Append to `viem-memo.ts`: TypeScript const events = parseEventLogs({ abi: memoAbi, logs: receipt.logs, }); const beforeMemoEvents = events.filter( (event) => event.eventName === "BeforeMemo", ); const memoEvents = events.filter((event) => event.eventName === "Memo"); if (beforeMemoEvents.length !== 1 || memoEvents.length !== 1) { throw new Error("Expected exactly one BeforeMemo event and one Memo event"); } const memoArgs = memoEvents[0].args as { sender: Address; target: Address; callDataHash: `0x${string}`; memoId: `0x${string}`; memo: `0x${string}`; memoIndex: bigint; }; if (getAddress(memoArgs.sender) !== account.address) { throw new Error(`Unexpected memo sender: ${memoArgs.sender}`); } if (getAddress(memoArgs.target) !== getAddress(usdcAddress)) { throw new Error(`Unexpected memo target: ${memoArgs.target}`); } if (memoArgs.callDataHash !== callDataHash) { throw new Error(`Unexpected callDataHash: ${memoArgs.callDataHash}`); } if (memoArgs.memoId !== memoId || memoArgs.memo !== memoBytes) { throw new Error("Memo event did not include the expected memoId and memo"); } console.log( "Transaction:", `${arcTestnet.blockExplorers.default.url}/tx/${hash}`, ); console.log("Block:", receipt.blockNumber.toString()); console.log("Decoded memo events:", events); Append to `ethers-memo.ts`: TypeScript const beforeMemoEvents: ethers.LogDescription[] = []; const memoEvents: ethers.LogDescription[] = []; for (const log of receipt.logs) { if (log.address.toLowerCase() !== memoAddress.toLowerCase()) continue; const parsed = memoInterface.parseLog(log); if (!parsed) continue; if (parsed.name === "BeforeMemo") beforeMemoEvents.push(parsed); if (parsed.name === "Memo") memoEvents.push(parsed); console.log(parsed.name, parsed.args); } if (beforeMemoEvents.length !== 1 || memoEvents.length !== 1) { throw new Error("Expected exactly one BeforeMemo event and one Memo event"); } const memoArgs = memoEvents[0].args; if (memoArgs.sender.toLowerCase() !== wallet.address.toLowerCase()) { throw new Error(`Unexpected memo sender: ${memoArgs.sender}`); } if (memoArgs.target.toLowerCase() !== usdcAddress.toLowerCase()) { throw new Error(`Unexpected memo target: ${memoArgs.target}`); } if (memoArgs.callDataHash !== callDataHash) { throw new Error(`Unexpected callDataHash: ${memoArgs.callDataHash}`); } if ( memoArgs.memoId !== memoId || ethers.hexlify(memoArgs.memo) !== ethers.hexlify(memoBytes) ) { throw new Error("Memo event did not include the expected memoId and memo"); } Append to `python-memo.py`: Python before_memo_events = memo.events.BeforeMemo().process_receipt(receipt, errors=DISCARD) memo_events = memo.events.Memo().process_receipt(receipt, errors=DISCARD) if len(before_memo_events) != 1 or len(memo_events) != 1: raise RuntimeError("Expected exactly one BeforeMemo event and one Memo event") memo_args = memo_events[0]["args"] if Web3.to_checksum_address(memo_args["sender"]) != account.address: raise RuntimeError(f"Unexpected memo sender: {memo_args['sender']}") if Web3.to_checksum_address(memo_args["target"]) != usdc_address: raise RuntimeError(f"Unexpected memo target: {memo_args['target']}") if memo_args["callDataHash"] != call_data_hash: raise RuntimeError(f"Unexpected callDataHash: {memo_args['callDataHash'].hex()}") if memo_args["memoId"] != memo_id or memo_args["memo"] != memo_bytes: raise RuntimeError("Memo event did not include the expected memoId and memo") print("BeforeMemo:", dict(before_memo_events[0]["args"])) print("Memo:", dict(memo_args)) [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-8-query-memo-events-by-memoid) Step 8. Query memo events by `memoId` -------------------------------------------------------------------------------------------------------------------------------------------------- `memoId` is an indexed event field, so you can find the memo again later without the transaction hash. Query the `Memo` logs for the `memoId` the script sent and confirm exactly one match. * Viem * Ethers * Python Append to `viem-memo.ts`: TypeScript const memoEvent = parseAbiItem( "event Memo(address indexed sender,address indexed target,bytes32 callDataHash,bytes32 indexed memoId,bytes memo,uint256 memoIndex)", ); const matchingLogs = await publicClient.getLogs({ address: memoAddress, event: memoEvent, args: { memoId }, fromBlock: receipt.blockNumber, toBlock: receipt.blockNumber, }); if (matchingLogs.length !== 1) { throw new Error( `Expected one Memo log for memoId, found ${matchingLogs.length}`, ); } console.log("Memo events matching memoId:", matchingLogs); Append to `ethers-memo.ts`: TypeScript const memoTopic = memoInterface.getEvent("Memo")?.topicHash; if (!memoTopic) { throw new Error("Memo event topic not found"); } const matchingLogs = await provider.getLogs({ address: memoAddress, topics: [memoTopic, null, null, memoId], fromBlock: receipt.blockNumber, toBlock: receipt.blockNumber, }); if (matchingLogs.length !== 1) { throw new Error( `Expected one Memo log for memoId, found ${matchingLogs.length}`, ); } console.log( "Memo events matching memoId:", matchingLogs.map((log) => memoInterface.parseLog(log)), ); Append to `python-memo.py`: Python matching_logs = memo.events.Memo().get_logs( argument_filters={"memoId": memo_id}, from_block=receipt["blockNumber"], to_block=receipt["blockNumber"], ) if len(matching_logs) != 1: raise RuntimeError(f"Expected one Memo log for memoId, found {len(matching_logs)}") print("Memo events matching memoId:", matching_logs) [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-9-run-the-script) Step 9. Run the script ---------------------------------------------------------------------------------------------------------------------- Run the completed script for your client library: Viem Ethers Python npx tsx --env-file=.env viem-memo.ts npx tsx --env-file=.env ethers-memo.ts python python-memo.py The exact formatting differs by client library, but successful output includes the transaction URL, block number, decoded `BeforeMemo` and `Memo` event data, and one historical log match for the same `memoId`: Transaction: https://testnet.arcscan.app/tx/0x... Block: 123456 BeforeMemo: { memoIndex: 42 } Memo: { sender: "0xYourWallet...", target: "0x3600000000000000000000000000000000000000", callDataHash: "0x...", memoId: "0x...", memo: "0x...", memoIndex: 42 } Memo events matching memoId: [ ... ] [​](https://docs.arc.io/arc/tutorials/send-usdc-with-transaction-memo#step-10-check-json-rpc-directly-with-curl) Step 10. Check JSON-RPC directly with curl -------------------------------------------------------------------------------------------------------------------------------------------------------------- Use curl for read-only JSON-RPC checks, such as verifying deployed bytecode, reading a transaction receipt, or querying `Memo` logs. Use a client library such as viem, ethers, or web3.py to sign and submit the transaction itself. eth\_getCode eth\_getTransactionReceipt eth\_getLogs MEMO_ADDRESS=0x5294E9927c3306DcBaDb03fe70b92e01cCede505 RPC_URL=https://rpc.testnet.arc.network curl --request POST "$RPC_URL" \ --header "content-type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_getCode", "params": ["'"$MEMO_ADDRESS"'", "latest"], "id": 1 }' TRANSACTION_HASH=0xYOUR_TRANSACTION_HASH RPC_URL=https://rpc.testnet.arc.network curl --request POST "$RPC_URL" \ --header "content-type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_getTransactionReceipt", "params": ["'"$TRANSACTION_HASH"'"], "id": 1 }' MEMO_ADDRESS=0x5294E9927c3306DcBaDb03fe70b92e01cCede505 MEMO_EVENT_TOPIC=0xeb15ee720798341c37739df41be53acfbbf70ae6802dade35457beec6e47a5e4 MEMO_ID=0xYOUR_32_BYTE_MEMO_ID FROM_BLOCK=0xYOUR_RECEIPT_BLOCK_NUMBER TO_BLOCK=0xYOUR_RECEIPT_BLOCK_NUMBER RPC_URL=https://rpc.testnet.arc.network curl --request POST "$RPC_URL" \ --header "content-type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_getLogs", "params": [{\ "address": "'"$MEMO_ADDRESS"'",\ "fromBlock": "'"$FROM_BLOCK"'",\ "toBlock": "'"$TO_BLOCK"'",\ "topics": ["'"$MEMO_EVENT_TOPIC"'", null, null, "'"$MEMO_ID"'"]\ }], "id": 1 }' You can now attach memos to USDC transfers and reconcile them from the emitted events. For the event schema, nested memo behavior, and the guardrails that constrain memo calls, see [Transaction memos](https://docs.arc.io/arc/concepts/transaction-memos) . Was this page helpful? YesNo [Port a contract](https://docs.arc.io/arc/tutorials/porting-contracts-to-arc) [Send batch USDC transfers](https://docs.arc.io/arc/tutorials/batch-usdc-transfers) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Adapter setups - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/app-kit/tutorials/adapter-setups#content-area) The App Kit SDK uses adapters to connect SDK calls to a client and signer. The client reads blockchain data and submits signed transactions while the signer authorizes transactions from a private key, browser wallet, or wallet provider. The following adapters are supported: * [`viem`](https://viem.sh/) v2 for EVM blockchains * [`ethers`](https://ethers.org/) v6 for EVM blockchains * [`solana`](https://solana.com/) for the Solana blockchain * [`circle-wallets`](https://developers.circle.com/wallets/dev-controlled) for Circle-managed wallets Before setting up an adapter, you should already have created the wallet, account, API keys, and policies in your wallet provider. Pick your adapter to see its setup options. * Viem * Ethers * Solana * Circle Wallets Use the Viem adapter for EVM apps that use `viem` accounts or wallet clients. [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#server-side-wallet) Server-side wallet -------------------------------------------------------------------------------------------------- Use a server-side wallet when your backend signs transactions. The signer can be a private key or a wallet provider. * Private key * Turnkey Create one adapter from your wallet private key. The adapter works across EVM blockchains and uses built-in public RPC endpoints. Default RPC URLs are shared and may be rate-limited. For a more stable connection, configure a [custom RPC](https://docs.arc.io/app-kit/tutorials/adapter-setups#custom-rpc) . TypeScript import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2"; const adapter = createViemAdapterFromPrivateKey({ privateKey: process.env.PRIVATE_KEY as string, }); Use Turnkey Company Wallets with the Viem adapter for backend signing. The setup uses `@turnkey/viem` to create a Turnkey-backed `viem` account, then passes that account to the Viem adapter.Set these environment variables from your Turnkey account: .env TURNKEY_API_PUBLIC_KEY=YOUR_TURNKEY_API_PUBLIC_KEY TURNKEY_API_PRIVATE_KEY=YOUR_TURNKEY_API_PRIVATE_KEY TURNKEY_ORGANIZATION_ID=YOUR_TURNKEY_ORGANIZATION_ID TURNKEY_WALLET_ADDRESS=YOUR_TURNKEY_ETHEREUM_WALLET_ADDRESS Adapter setup: TypeScript import { AppKit } from "@circle-fin/app-kit"; import { ViemAdapter } from "@circle-fin/adapter-viem-v2"; import { Turnkey as TurnkeyServerSDK } from "@turnkey/sdk-server"; import { createAccount } from "@turnkey/viem"; import { createPublicClient, createWalletClient, http } from "viem"; const turnkey = new TurnkeyServerSDK({ apiBaseUrl: "https://api.turnkey.com", apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!, }); const account = await createAccount({ client: turnkey.apiClient(), organizationId: process.env.TURNKEY_ORGANIZATION_ID!, signWith: process.env.TURNKEY_WALLET_ADDRESS!, }); const appKit = new AppKit(); const supportedChains = (await appKit.getSupportedChains()).filter( (chain) => chain.type === "evm", ); const adapter = new ViemAdapter( { getPublicClient: ({ chain }) => createPublicClient({ chain, transport: http(), }), getWalletClient: ({ chain }) => createWalletClient({ account, chain, transport: http(), }), }, { addressContext: "user-controlled", supportedChains, }, ); **What to know about this setup**: * Turnkey supports both `viem` and `ethers`. This setup uses `viem` because `@turnkey/viem` creates a Turnkey-backed account directly, which keeps the adapter setup shorter. * Use this setup only on your backend. `TURNKEY_API_PRIVATE_KEY` authenticates requests to Turnkey and must not be exposed in browser code. * For crosschain flows, create one adapter per blockchain if your wallet client or RPC setup is blockchain-specific. [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#browser-wallet) Browser wallet ------------------------------------------------------------------------------------------ This setup expects a wallet extension in the browser (for example `window.ethereum` or `window.solana`), not a Node.js server. You can use wallets like [MetaMask](https://metamask.io/) or [Phantom](https://phantom.com/) : * MetaMask (EIP-6963) * window.ethereum Use EIP-6963 to discover injected browser wallets, select a provider, request access to the user’s accounts, then pass the provider to the Viem adapter.The example selects MetaMask by reverse-DNS identifier. To use any injected wallet, omit `requiredRdns`. TypeScript import { createViemAdapterFromProvider, type CreateViemAdapterFromProviderParams, } from "@circle-fin/adapter-viem-v2"; type BrowserWalletProvider = CreateViemAdapterFromProviderParams["provider"]; type EIP6963ProviderDetail = { info: { uuid: string; name: string; icon: string; rdns: string; }; provider: BrowserWalletProvider; }; declare global { interface WindowEventMap { "eip6963:announceProvider": CustomEvent; } } async function getInjectedWalletProvider( requiredRdns?: string, ): Promise { const providers = new Map(); const onAnnounce = ((event: CustomEvent) => { providers.set(event.detail.info.uuid, event.detail); }) as EventListener; window.addEventListener("eip6963:announceProvider", onAnnounce); window.dispatchEvent(new Event("eip6963:requestProvider")); await new Promise((resolve) => window.setTimeout(resolve, 250)); window.removeEventListener("eip6963:announceProvider", onAnnounce); const provider = requiredRdns ? [...providers.values()].find(({ info }) => info.rdns === requiredRdns) ?.provider : [...providers.values()][0]?.provider; if (!provider) { throw new Error( requiredRdns ? `No EIP-6963 wallet found for ${requiredRdns}` : "No EIP-6963 browser wallet found", ); } return provider; } const provider = await getInjectedWalletProvider("io.metamask"); // For any injected wallet, use: // const provider = await getInjectedWalletProvider(); await provider.request({ method: "eth_requestAccounts", params: undefined, }); const adapter = await createViemAdapterFromProvider({ provider, }); Use `window.ethereum` only when the wallet does not support EIP-6963 or your app intentionally accepts the default injected provider. TypeScript import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2"; import type { EIP1193Provider } from "viem"; declare global { interface Window { ethereum?: EIP1193Provider; } } if (!window.ethereum) { throw new Error("No wallet provider found"); } const adapter = await createViemAdapterFromProvider({ provider: window.ethereum, }); [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#custom-rpc) Custom RPC ---------------------------------------------------------------------------------- By default, adapters use built-in RPC endpoints, which may be rate-limited or unreliable. Override them with your own provider. [Alchemy](https://www.alchemy.com/) , [QuickNode](https://www.quicknode.com/) , and [chainlist.org](https://chainlist.org/) are common places to source endpoints. This example uses Alchemy.The `getPublicClient`/`getWalletClient` override pairs with any signer setup that uses `viem`, such as a private key or browser wallet. TypeScript import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2"; import { EthereumSepolia, ArcTestnet } from "@circle-fin/app-kit/chains"; import { createPublicClient, createWalletClient, http } from "viem"; const RPC_BY_CHAIN_NAME: Record = { [EthereumSepolia.name]: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, [ArcTestnet.name]: `https://arc-testnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, }; const adapter = createViemAdapterFromPrivateKey({ privateKey: process.env.PRIVATE_KEY as string, getPublicClient: ({ chain }) => { const rpcUrl = RPC_BY_CHAIN_NAME[chain.name]; if (!rpcUrl) { throw new Error(`No RPC configured for chain: ${chain.name}`); } return createPublicClient({ chain, transport: http(rpcUrl, { retryCount: 3, timeout: 10000, }), }); }, getWalletClient: ({ chain, account }) => { const rpcUrl = RPC_BY_CHAIN_NAME[chain.name]; if (!rpcUrl) { throw new Error(`No RPC configured for chain: ${chain.name}`); } return createWalletClient({ account, chain, transport: http(rpcUrl, { retryCount: 3, timeout: 10000, }), }); }, }); Use the Ethers adapter for EVM apps that use `ethers` providers or signers. [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#server-side-wallet-2) Server-side wallet ---------------------------------------------------------------------------------------------------- Use a server-side wallet when your backend signs transactions. The signer can be a private key or a wallet provider. * Private key * Privy Create one adapter from your wallet private key. The adapter works across EVM blockchains and uses built-in public RPC endpoints. Default RPC URLs are shared and may be rate-limited. For a more stable connection, configure a custom RPC. TypeScript import { createEthersAdapterFromPrivateKey } from "@circle-fin/adapter-ethers-v6"; const adapter = createEthersAdapterFromPrivateKey({ privateKey: process.env.PRIVATE_KEY as string, }); Use Privy server wallets with the Ethers adapter when Privy signs for your backend. Privy server wallets sign through the Privy wallet API by `walletId`, so the setup needs a small custom `ethers` signer.Set these environment variables from your Privy app and server wallet: .env PRIVY_APP_ID=YOUR_PRIVY_APP_ID PRIVY_APP_SECRET=YOUR_PRIVY_APP_SECRET PRIVY_AUTHORIZATION_PRIVATE_KEY=YOUR_PRIVY_AUTHORIZATION_PRIVATE_KEY PRIVY_WALLET_ID=YOUR_PRIVY_SERVER_WALLET_ID PRIVY_WALLET_ADDRESS=YOUR_PRIVY_SERVER_WALLET_ADDRESS Use a server-owned Privy wallet. `PRIVY_AUTHORIZATION_PRIVATE_KEY` must belong to the wallet owner or an authorized signer for `PRIVY_WALLET_ID`.Create the `PrivyServerSigner.ts` helper first. The adapter setup imports this helper in the next step. TypeScript import { PrivyClient, type Hex, type Quantity } from "@privy-io/server-auth"; import { ethers } from "ethers"; interface PrivyServerSignerOptions { walletId: string; walletAddress: string; privy: PrivyClient; provider: ethers.Provider; } type JsonSafe = | string | number | boolean | null | JsonSafe[] | { [key: string]: JsonSafe }; type PrivySignTransactionRequest = Parameters< PrivyClient["walletApi"]["ethereum"]["signTransaction"] >[0]; type PrivySignMessageRequest = Parameters< PrivyClient["walletApi"]["ethereum"]["signMessage"] >[0]; type PrivySignTypedDataRequest = Parameters< PrivyClient["walletApi"]["ethereum"]["signTypedData"] >[0]; export class PrivyServerSigner extends ethers.AbstractSigner { private walletId: string; private walletAddress: string; private privy: PrivyClient; constructor(options: PrivyServerSignerOptions) { super(options.provider); this.walletId = options.walletId; this.walletAddress = options.walletAddress; this.privy = options.privy; } connect(provider: ethers.Provider): PrivyServerSigner { return new PrivyServerSigner({ walletId: this.walletId, walletAddress: this.walletAddress, privy: this.privy, provider, }); } async getAddress(): Promise { return this.walletAddress; } async signTransaction(tx: ethers.TransactionRequest): Promise { const toQuantity = ( value: ethers.BigNumberish | null | undefined, ): Quantity | undefined => value != null ? (ethers.toBeHex(value) as Hex) : undefined; const toHex = (value: unknown): Hex | undefined => value != null ? (value.toString() as Hex) : undefined; const request: PrivySignTransactionRequest = { walletId: this.walletId, chainType: "ethereum", transaction: { to: toHex(tx.to), nonce: tx.nonce != null ? Number(tx.nonce) : undefined, chainId: tx.chainId != null ? Number(tx.chainId) : undefined, data: toHex(tx.data), value: toQuantity(tx.value), type: (tx.type ?? 2) as 0 | 1 | 2, gasLimit: toQuantity(tx.gasLimit), maxFeePerGas: toQuantity(tx.maxFeePerGas), maxPriorityFeePerGas: toQuantity(tx.maxPriorityFeePerGas), }, }; const { signedTransaction } = await this.privy.walletApi.ethereum.signTransaction(request); return signedTransaction; } async signMessage(message: string | Uint8Array): Promise { const request: PrivySignMessageRequest = { walletId: this.walletId, chainType: "ethereum", message: typeof message === "string" ? message : ethers.hexlify(message), }; const { signature } = await this.privy.walletApi.ethereum.signMessage(request); return signature; } async signTypedData( domain: ethers.TypedDataDomain, types: Record, value: Record, ): Promise { const toJsonSafe = (input: unknown): JsonSafe => { if (typeof input === "bigint") { return input.toString(); } if (Array.isArray(input)) { return input.map(toJsonSafe); } if (input && typeof input === "object") { return Object.fromEntries( Object.entries(input).map(([key, nestedValue]) => [\ key,\ toJsonSafe(nestedValue),\ ]), ); } return input as JsonSafe; }; const primaryType = Object.keys(types).find((key) => key !== "EIP712Domain") ?? ""; const jsonSafeDomain = toJsonSafe(domain) as Record; const jsonSafeMessage = toJsonSafe(value) as Record; const request: PrivySignTypedDataRequest = { walletId: this.walletId, typedData: { domain: jsonSafeDomain, types, message: jsonSafeMessage, primaryType, }, }; const { signature } = await this.privy.walletApi.ethereum.signTypedData(request); return signature; } } See all 144 lines After adding the helper, create the adapter: TypeScript import { AppKit } from "@circle-fin/app-kit"; import { EthersAdapter } from "@circle-fin/adapter-ethers-v6"; import { PrivyClient } from "@privy-io/server-auth"; import { ethers } from "ethers"; import { PrivyServerSigner } from "./PrivyServerSigner"; const privy = new PrivyClient( process.env.PRIVY_APP_ID!, process.env.PRIVY_APP_SECRET!, { walletApi: { authorizationPrivateKey: process.env.PRIVY_AUTHORIZATION_PRIVATE_KEY!, }, }, ); const appKit = new AppKit(); const supportedChains = (await appKit.getSupportedChains()).filter( (chain) => chain.type === "evm", ); const arcTestnet = supportedChains.find( (chain) => chain.chain === "Arc_Testnet", ); if (!arcTestnet) { throw new Error("Arc Testnet is not supported"); } const provider = new ethers.JsonRpcProvider(arcTestnet.rpcEndpoints[0]); const signer = new PrivyServerSigner({ walletId: process.env.PRIVY_WALLET_ID!, walletAddress: process.env.PRIVY_WALLET_ADDRESS!, privy, provider, }); const adapter = new EthersAdapter( { signer, getProvider: ({ chain }) => new ethers.JsonRpcProvider(chain.rpcEndpoints[0]), }, { addressContext: "user-controlled", supportedChains, }, ); **What to know about this setup**: * Privy’s stock `createEthersSigner` is built for user-linked wallets. Server wallets sign by `walletId`, which is why this setup uses `PrivyServerSigner`. * User-owned embedded wallets are not server wallets. For this setup, create a server-owned wallet or add your backend key as an authorized signer. * Automatic chain switching does not support this custom signer. For crosschain flows, create one adapter per blockchain. [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#browser-wallet-2) Browser wallet -------------------------------------------------------------------------------------------- This setup expects a wallet extension in the browser (for example `window.ethereum` or `window.solana`), not a Node.js server. You can use wallets like [MetaMask](https://metamask.io/) or [Phantom](https://phantom.com/) : In crosschain flows, use the Viem adapter for injected browser wallets. The Ethers adapter can work for same-chain browser wallet operations, but the `BrowserProvider` from `ethers` can throw a network-change error when the wallet switches blockchains during a bridge or other crosschain flow. * MetaMask (EIP-6963) * window.ethereum Use EIP-6963 to discover injected browser wallets, select a provider, request access to the user’s accounts, then pass the provider to the Ethers adapter.The example selects MetaMask by reverse-DNS identifier. To use any injected wallet, omit `requiredRdns`. TypeScript import { createEthersAdapterFromProvider, type CreateEthersAdapterFromProviderParams, } from "@circle-fin/adapter-ethers-v6"; type BrowserWalletProvider = CreateEthersAdapterFromProviderParams["provider"]; type EIP6963ProviderDetail = { info: { uuid: string; name: string; icon: string; rdns: string; }; provider: BrowserWalletProvider; }; declare global { interface WindowEventMap { "eip6963:announceProvider": CustomEvent; } } async function getInjectedWalletProvider( requiredRdns?: string, ): Promise { const providers = new Map(); const onAnnounce = ((event: CustomEvent) => { providers.set(event.detail.info.uuid, event.detail); }) as EventListener; window.addEventListener("eip6963:announceProvider", onAnnounce); window.dispatchEvent(new Event("eip6963:requestProvider")); await new Promise((resolve) => window.setTimeout(resolve, 250)); window.removeEventListener("eip6963:announceProvider", onAnnounce); const provider = requiredRdns ? [...providers.values()].find(({ info }) => info.rdns === requiredRdns) ?.provider : [...providers.values()][0]?.provider; if (!provider) { throw new Error( requiredRdns ? `No EIP-6963 wallet found for ${requiredRdns}` : "No EIP-6963 browser wallet found", ); } return provider; } const provider = await getInjectedWalletProvider("io.metamask"); // For any injected wallet, use: // const provider = await getInjectedWalletProvider(); await provider.request({ method: "eth_requestAccounts", params: undefined, }); const adapter = await createEthersAdapterFromProvider({ provider, }); Use `window.ethereum` only when the wallet does not support EIP-6963 or your app intentionally accepts the default injected provider. TypeScript import { createEthersAdapterFromProvider } from "@circle-fin/adapter-ethers-v6"; import type { Eip1193Provider } from "ethers"; declare global { interface Window { ethereum?: Eip1193Provider; } } if (!window.ethereum) { throw new Error("No wallet provider found"); } const adapter = await createEthersAdapterFromProvider({ provider: window.ethereum, }); [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#custom-rpc-2) Custom RPC ------------------------------------------------------------------------------------ By default, adapters use built-in RPC endpoints, which may be rate-limited or unreliable. Override them with your own provider. [Alchemy](https://www.alchemy.com/) , [QuickNode](https://www.quicknode.com/) , and [chainlist.org](https://chainlist.org/) are common places to source endpoints. This example uses Alchemy.The `getProvider` override pairs with any signer setup that uses `ethers`, such as a private key or browser wallet. TypeScript import { createEthersAdapterFromPrivateKey } from "@circle-fin/adapter-ethers-v6"; import { EthereumSepolia, ArcTestnet } from "@circle-fin/app-kit/chains"; import { JsonRpcProvider } from "ethers"; const RPC_BY_CHAIN_NAME: Record = { [EthereumSepolia.name]: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, [ArcTestnet.name]: `https://arc-testnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, }; const adapter = createEthersAdapterFromPrivateKey({ privateKey: process.env.PRIVATE_KEY as string, getProvider: ({ chain }) => { const rpcUrl = RPC_BY_CHAIN_NAME[chain.name]; if (!rpcUrl) { throw new Error(`No RPC configured for chain: ${chain.name}`); } return new JsonRpcProvider(rpcUrl); }, }); Use the Solana adapter for Solana wallets and RPC clients. [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#server-side-wallet-3) Server-side wallet ---------------------------------------------------------------------------------------------------- Use a server-side wallet when your backend signs transactions. ### [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#private-key) Private key Create one adapter from your Solana private key. Solana accepts Base58, Base64, or JSON array format private keys. Default RPC URLs are shared and may be rate-limited. For a more stable connection, configure a custom RPC. TypeScript import { createSolanaKitAdapterFromPrivateKey } from "@circle-fin/adapter-solana-kit"; const adapter = createSolanaKitAdapterFromPrivateKey({ privateKey: process.env.PRIVATE_KEY as string, }); [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#browser-wallet-3) Browser wallet -------------------------------------------------------------------------------------------- This setup expects a wallet extension in the browser (for example `window.ethereum` or `window.solana`), not a Node.js server. You can use wallets like [MetaMask](https://metamask.io/) or [Phantom](https://phantom.com/) : TypeScript import { createSolanaKitAdapterFromProvider } from "@circle-fin/adapter-solana-kit"; type SolanaKitWalletProvider = Parameters< typeof createSolanaKitAdapterFromProvider >[0]["provider"]; declare global { interface Window { solana?: SolanaKitWalletProvider; } } if (!window.solana) { throw new Error("No Solana wallet provider found"); } const adapter = await createSolanaKitAdapterFromProvider({ provider: window.solana, }); [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#custom-rpc-3) Custom RPC ------------------------------------------------------------------------------------ By default, adapters use built-in RPC endpoints, which may be rate-limited or unreliable. Override them with your own provider. [Alchemy](https://www.alchemy.com/) , [QuickNode](https://www.quicknode.com/) , and [chainlist.org](https://chainlist.org/) are common places to source endpoints. This example uses Alchemy.The `getRpc` override pairs with any Solana signer setup, such as a private key or browser wallet. TypeScript import { createSolanaKitAdapterFromPrivateKey } from "@circle-fin/adapter-solana-kit"; import { createSolanaRpc } from "@solana/kit"; const adapter = createSolanaKitAdapterFromPrivateKey({ privateKey: process.env.SOLANA_PRIVATE_KEY as string, getRpc: () => createSolanaRpc( `https://solana-devnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, ), }); server-side onlyUse the Circle Wallets adapter if you already [manage wallets through Circle](https://developers.circle.com/wallets/dev-controlled) . The adapter is suited for backend apps. It uses developer-controlled wallets so you can use the App Kit SDK on [supported blockchains](https://docs.arc.io/app-kit/references/supported-blockchains#adapters) without managing private keys yourself.The Circle Wallets adapter requires these credentials from the [Circle Console](https://console.circle.com/) : * **[API Key](https://developers.circle.com/w3s/circle-developer-account#creating-an-api-key-for-developer-services) :** Environment-prefixed (examples: `TEST_API_KEY:abc123:def456`, `LIVE_API_KEY:xyz:uvw`) or Base64-encoded * **[Entity Secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret) :** 64 lowercase alphanumeric characters This code block initializes the Circle Wallets adapter: TypeScript import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets"; const adapter = createCircleWalletsAdapter({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!, }); [​](https://docs.arc.io/app-kit/tutorials/adapter-setups#what-to-know-about-circle-wallets) What to know about Circle Wallets -------------------------------------------------------------------------------------------------------------------------------- When you use the Circle Wallets adapter in an App Kit operation, pass the wallet address in the operation context. The address tells the App Kit SDK which Circle Wallet acts on that blockchain: TypeScript from: { adapter, chain: "Base_Sepolia", address: walletAddress, } Use the wallet address for the same blockchain as `chain`. * The wallet address you pass to App Kit operations determines whether Circle handles the transaction as a dev-controlled externally owned account (EOA) or smart contract account (SCA) wallet. See [Account type comparison](https://developers.circle.com/wallets/account-types#account-type-comparison) to understand the difference. * **Dev-controlled EOA**: Each Circle Wallet exists on one blockchain. For crosschain flows, provision one wallet per source blockchain. The onchain addresses differ. * **Dev-controlled SCA**: Each Circle Wallet exists on one blockchain. For crosschain flows, provision one SCA per source blockchain. The Circle Wallets adapter does not support [gas sponsorship](https://developers.circle.com/wallets/gas-station) for crosschain transfers that originate on Solana. For those transactions, the user’s Solana wallet must hold sufficient SOL to pay network fees. Was this page helpful? YesNo [Supported blockchains and tokens](https://docs.arc.io/app-kit/references/supported-blockchains) [Send overview](https://docs.arc.io/app-kit/send) ⌘I Assistant Responses are generated using AI and may contain mistakes. --- # Interact with contracts - Arc Docs > Documentation Index > ------------------- > > Fetch the complete documentation index at: [/llms.txt](https://docs.arc.io/llms.txt) > > Use this file to discover all available pages before exploring further. [Skip to main content](https://docs.arc.io/arc/tutorials/interact-with-contracts#content-area) This tutorial guides you through interacting with smart contracts deployed on Arc Testnet. You’ll learn how to execute contract functions like minting tokens, transferring assets, and performing contract-specific operations for ERC-20, ERC-721, ERC-1155, and Airdrop contracts. [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#prerequisites) Prerequisites --------------------------------------------------------------------------------------------- Complete the [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) tutorial first. You’ll need a deployed contract. [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#step-1-update-your-project) Step 1. Update your project ------------------------------------------------------------------------------------------------------------------------ In this step, you update the project you created in the Deploy contracts tutorial with the additional environment variable and npm scripts needed for contract interactions. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#1-1-set-environment-variables) 1.1. Set environment variables Add this new variable to your existing `.env` file (from the Deploy contracts tutorial): .env RECIPIENT_WALLET_ADDRESS=YOUR_RECIPIENT_ADDRESS * `RECIPIENT_WALLET_ADDRESS` is the wallet address that receives transferred tokens during the interaction examples. Your `.env` file should already have `CIRCLE_API_KEY`, `CIRCLE_ENTITY_SECRET`, `WALLET_ID`, `WALLET_ADDRESS`, and `CONTRACT_ADDRESS` from the Deploy contracts tutorial. You’re only adding 1 new variable here. The npm run commands in this tutorial load variables from `.env` using Node.js native env-file support. Prefer editing `.env` files in your IDE or editor so credentials are not leaked to your shell history. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#1-2-add-npm-scripts) 1.2. Add npm scripts Add run scripts for contract interactions to your `package.json`: npm pkg set scripts.interact-erc20="tsx --env-file=.env interact-erc20.ts" npm pkg set scripts.interact-erc721="tsx --env-file=.env interact-erc721.ts" npm pkg set scripts.interact-erc1155="tsx --env-file=.env interact-erc1155.ts" npm pkg set scripts.interact-airdrop="tsx --env-file=.env interact-airdrop.ts" [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#step-2-interact-with-contracts) Step 2. Interact with contracts -------------------------------------------------------------------------------------------------------------------------------- Select the contract type you want to interact with from the tabs below. * ERC-20 * ERC-721 * ERC-1155 * Airdrop [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#interact-with-erc-20-contracts) Interact with ERC-20 contracts ------------------------------------------------------------------------------------------------------------------------------- ERC-20 tokens support standard fungible token operations. You’ll learn to mint new tokens and transfer them between addresses. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#mint-tokens) Mint tokens Use the `mintTo` function to mint tokens. The wallet must have `MINTER_ROLE`. Node.js Python cURL import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); const mintResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "mintTo(address,uint256)", abiParameters: [\ process.env.WALLET_ADDRESS,\ "1000000000000000000", // 1 token with 18 decimals\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(mintResponse.data, null, 2)); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) mint_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "mintTo(address,uint256)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ "1000000000000000000"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) mint_response = api_instance.create_developer_transaction_contract_execution(mint_request) print(json.dumps(mint_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "mintTo(address,uint256)", "abiParameters": [\ "",\ "1000000000000000000"\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } **Token decimals**: ERC-20 tokens typically use 18 decimals. To mint 1 token, use `1000000000000000000` (1 × 10^18). ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#transfer-tokens) Transfer tokens Use the `transfer` function to send tokens to another address. Node.js Python cURL const transferResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "transfer(address,uint256)", abiParameters: [\ process.env.RECIPIENT_WALLET_ADDRESS,\ "1000000000000000000", // 1 token with 18 decimals\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(transferResponse.data, null, 2)); transfer_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "transfer(address,uint256)", "abiParameters": [\ os.getenv("RECIPIENT_WALLET_ADDRESS"),\ "1000000000000000000"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) transfer_response = api_instance.create_developer_transaction_contract_execution(transfer_request) print(json.dumps(transfer_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "transfer(address,uint256)", "abiParameters": [\ "",\ "1000000000000000000"\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#full-erc-20-interaction-script) Full ERC-20 interaction script Here’s the full script combining mint and transfer operations: interact-erc20.ts interact\_erc20.py import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); async function main() { // Mint tokens const mintResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "mintTo(address,uint256)", abiParameters: [\ process.env.WALLET_ADDRESS,\ "1000000000000000000", // 1 token with 18 decimals\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(mintResponse.data, null, 2)); // Transfer tokens const transferResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "transfer(address,uint256)", abiParameters: [\ process.env.RECIPIENT_WALLET_ADDRESS,\ "1000000000000000000", // 1 token with 18 decimals\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(transferResponse.data, null, 2)); } main(); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) # Mint tokens mint_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "mintTo(address,uint256)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ "1000000000000000000"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) mint_response = api_instance.create_developer_transaction_contract_execution(mint_request) print(json.dumps(mint_response.data.to_dict(), indent=2)) # Transfer tokens transfer_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "transfer(address,uint256)", "abiParameters": [\ os.getenv("RECIPIENT_WALLET_ADDRESS"),\ "1000000000000000000"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) transfer_response = api_instance.create_developer_transaction_contract_execution(transfer_request) print(json.dumps(transfer_response.data.to_dict(), indent=2)) **Run the script:** Node.js Python npm run interact-erc20 python interact_erc20.py [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#interact-with-erc-721-contracts) Interact with ERC-721 contracts --------------------------------------------------------------------------------------------------------------------------------- ERC-721 tokens are unique tokens. Each token has a unique ID and can have associated metadata stored on IPFS or other storage. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#mint-tokens-2) Mint tokens Use the `mintTo` function to mint tokens. The wallet must have `MINTER_ROLE`. Node.js Python cURL import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); const mintResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "mintTo(address,string)", abiParameters: [\ process.env.WALLET_ADDRESS,\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei",\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(mintResponse.data, null, 2)); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) mint_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "mintTo(address,string)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) mint_response = api_instance.create_developer_transaction_contract_execution(mint_request) print(json.dumps(mint_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "mintTo(address,string)", "abiParameters": [\ "",\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei"\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } **Metadata URI**: The second parameter is the token metadata URI. It typically points to an IPFS hash containing the token’s metadata (name, description, image, etc.). You can use the example IPFS URI from the code sample for testing. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#transfer-tokens-2) Transfer tokens Use the `transferFrom` or `safeTransferFrom` function to transfer tokens between addresses. Node.js Python cURL const transferResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "safeTransferFrom(address,address,uint256)", abiParameters: [\ "",\ "",\ "1", // Token ID\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(transferResponse.data, null, 2)); transfer_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "safeTransferFrom(address,address,uint256)", "abiParameters": [\ "",\ "",\ "1"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) transfer_response = api_instance.create_developer_transaction_contract_execution(transfer_request) print(json.dumps(transfer_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "safeTransferFrom(address,address,uint256)", "abiParameters": [\ "",\ "",\ "1"\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#full-erc-721-interaction-script) Full ERC-721 interaction script Here’s the full script combining mint and transfer operations: interact-erc721.ts interact\_erc721.py import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); async function main() { // Mint token const mintResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "mintTo(address,string)", abiParameters: [\ process.env.WALLET_ADDRESS,\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei",\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(mintResponse.data, null, 2)); // Transfer token (token ID 1) const transferResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "safeTransferFrom(address,address,uint256)", abiParameters: [\ process.env.WALLET_ADDRESS,\ process.env.RECIPIENT_WALLET_ADDRESS,\ "1", // Token ID\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(transferResponse.data, null, 2)); } main(); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) # Mint token mint_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "mintTo(address,string)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) mint_response = api_instance.create_developer_transaction_contract_execution(mint_request) print(json.dumps(mint_response.data.to_dict(), indent=2)) # Transfer token (token ID 1) transfer_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "safeTransferFrom(address,address,uint256)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ os.getenv("RECIPIENT_WALLET_ADDRESS"),\ "1"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) transfer_response = api_instance.create_developer_transaction_contract_execution(transfer_request) print(json.dumps(transfer_response.data.to_dict(), indent=2)) **Run the script:** Node.js Python npm run interact-erc721 python interact_erc721.py **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#interact-with-erc-1155-contracts) Interact with ERC-1155 contracts ----------------------------------------------------------------------------------------------------------------------------------- ERC-1155 contracts support multiple token types in a single contract. Each token has a unique ID and can be fungible or non-fungible. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#mint-tokens-3) Mint tokens Use the `mintTo` function to mint tokens. The wallet must have `MINTER_ROLE`. The first mint requires the maximum uint256 value to create token ID 0. For subsequent mints, always use `0` which creates the next token ID. Node.js Python cURL import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); const mintResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "mintTo(address,uint256,string,uint256)", abiParameters: [\ process.env.WALLET_ADDRESS,\ "115792089237316195423570985008687907853269984665640564039457584007913129639935", // Max uint256 = ID 0\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei",\ "1", // Amount\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(mintResponse.data, null, 2)); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) mint_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "mintTo(address,uint256,string,uint256)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ "115792089237316195423570985008687907853269984665640564039457584007913129639935",\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei",\ "1"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) mint_response = api_instance.create_developer_transaction_contract_execution(mint_request) print(json.dumps(mint_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "mintTo(address,uint256,string,uint256)", "abiParameters": [\ "",\ "115792089237316195423570985008687907853269984665640564039457584007913129639935",\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei",\ "1"\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } **ERC-1155 Token ID Creation**: The first mint of each token ID requires passing the maximum uint256 value (`2^256 - 1` or `115792089237316195423570985008687907853269984665640564039457584007913129639935`) to create token ID 0 in the contract. For all subsequent mints, use `0` which creates the next sequential token ID (1, 2, 3, etc.). This is an ERC-1155 standard requirement for lazy minting, where token IDs are created on demand rather than pre-initialized. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#batch-transfer-tokens) Batch transfer tokens Use the `safeBatchTransferFrom` function to transfer multiple token types in a single transaction. Node.js Python cURL const transferResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)", abiParameters: [\ "",\ "",\ ["0"], // Token IDs\ ["1"], // Amounts\ "0x", // Empty bytes\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(transferResponse.data, null, 2)); transfer_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)", "abiParameters": [\ "",\ "",\ ["0"],\ ["1"],\ "0x"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) transfer_response = api_instance.create_developer_transaction_contract_execution(transfer_request) print(json.dumps(transfer_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)", "abiParameters": [\ "",\ "",\ ["0"],\ ["1"],\ "0x"\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#full-erc-1155-interaction-script) Full ERC-1155 interaction script Here’s the full script combining mint and batch transfer operations: interact-erc1155.ts interact\_erc1155.py import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); async function main() { // Mint tokens (token ID 0) const mintResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "mintTo(address,uint256,string,uint256)", abiParameters: [\ process.env.WALLET_ADDRESS,\ "115792089237316195423570985008687907853269984665640564039457584007913129639935", // Max uint256 = ID 0\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei",\ "1", // Amount\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(mintResponse.data, null, 2)); // Batch transfer tokens const transferResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)", abiParameters: [\ process.env.WALLET_ADDRESS,\ process.env.RECIPIENT_WALLET_ADDRESS,\ ["0"], // Token IDs\ ["1"], // Amounts\ "0x", // Empty bytes\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(transferResponse.data, null, 2)); } main(); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) # Mint tokens (token ID 0) mint_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "mintTo(address,uint256,string,uint256)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ "115792089237316195423570985008687907853269984665640564039457584007913129639935",\ "ipfs://bafkreibdi6623n3xpf7ymk62ckb4bo75o3qemwkpfvp5i25j66itxvsoei",\ "1"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) mint_response = api_instance.create_developer_transaction_contract_execution(mint_request) print(json.dumps(mint_response.data.to_dict(), indent=2)) # Batch transfer tokens transfer_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)", "abiParameters": [\ os.getenv("WALLET_ADDRESS"),\ os.getenv("RECIPIENT_WALLET_ADDRESS"),\ ["0"],\ ["1"],\ "0x"\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) transfer_response = api_instance.create_developer_transaction_contract_execution(transfer_request) print(json.dumps(transfer_response.data.to_dict(), indent=2)) **Run the script:** Node.js Python npm run interact-erc1155 python interact_erc1155.py **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#execute-airdrop-operations) Execute airdrop operations ----------------------------------------------------------------------------------------------------------------------- The Airdrop contract enables mass token distribution to multiple recipients. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#prerequisites-2) Prerequisites Before executing an airdrop, you need: 1. **A token contract address** - Deploy one using the [ERC-20](https://docs.arc.io/arc/tutorials/deploy-contracts#erc-20) , [ERC-721](https://docs.arc.io/arc/tutorials/deploy-contracts#erc-721) , or [ERC-1155](https://docs.arc.io/arc/tutorials/deploy-contracts#erc-1155) templates, or use an existing token 2. **Token balance** - Your wallet must hold enough tokens to distribute 3. **Token approval** - Call the `approve` or `setApprovalForAll` function on your token contract to allow the airdrop contract to transfer tokens ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#execute-an-erc-20-airdrop) Execute an ERC-20 airdrop Use the `airdropERC20` function to distribute ERC-20 tokens to multiple recipients. Node.js Python cURL import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); const airdropResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "airdropERC20(address,(address,uint256)[])", abiParameters: [\ "", // ERC-20 token contract address\ [\ ["", "1000000000000000000"],\ ["", "2000000000000000000"],\ ],\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(airdropResponse.data, null, 2)); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) airdrop_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "airdropERC20(address,(address,uint256)[])", "abiParameters": [\ "",\ [\ ["", "1000000000000000000"],\ ["", "2000000000000000000"]\ ]\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) airdrop_response = api_instance.create_developer_transaction_contract_execution(airdrop_request) print(json.dumps(airdrop_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "airdropERC20(address,(address,uint256)[])", "abiParameters": [\ "",\ [\ ["", "1000000000000000000"],\ ["", "2000000000000000000"]\ ]\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } **Token contract**: The first parameter is the address of the ERC-20 token contract you want to airdrop. You must deploy this contract first using the [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) tutorial. ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#execute-an-erc-721-airdrop) Execute an ERC-721 airdrop Use the `airdropERC721` function to distribute tokens to multiple recipients. Node.js Python cURL const airdropResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "airdropERC721(address,(address,uint256)[])", abiParameters: [\ "", // ERC-721 token contract address\ [\ ["", "1"], // Token ID 1\ ["", "2"], // Token ID 2\ ],\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(airdropResponse.data, null, 2)); airdrop_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "airdropERC721(address,(address,uint256)[])", "abiParameters": [\ "",\ [\ ["", "1"],\ ["", "2"]\ ]\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) airdrop_response = api_instance.create_developer_transaction_contract_execution(airdrop_request) print(json.dumps(airdrop_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "airdropERC721(address,(address,uint256)[])", "abiParameters": [\ "",\ [\ ["", "1"],\ ["", "2"]\ ]\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#execute-an-erc-1155-airdrop) Execute an ERC-1155 airdrop Use the `airdropERC1155` function to distribute ERC-1155 tokens to multiple recipients. Node.js Python cURL const airdropResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "airdropERC1155(address,(address,uint256,uint256)[])", abiParameters: [\ "", // ERC-1155 token contract address\ [\ ["", "0", "10"], // Token ID 0, amount 10\ ["", "1", "5"], // Token ID 1, amount 5\ ],\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(airdropResponse.data, null, 2)); airdrop_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "airdropERC1155(address,(address,uint256,uint256)[])", "abiParameters": [\ "",\ [\ ["", "0", "10"],\ ["", "1", "5"]\ ]\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) airdrop_response = api_instance.create_developer_transaction_contract_execution(airdrop_request) print(json.dumps(airdrop_response.data.to_dict(), indent=2)) curl --request POST \ --url 'https://api.circle.com/v1/w3s/developer/transactions/contractExecution' \ --header 'authorization: Bearer ' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data '{ "idempotencyKey": "", "entitySecretCiphertext": "", "walletId": "", "abiFunctionSignature": "airdropERC1155(address,(address,uint256,uint256)[])", "abiParameters": [\ "",\ [\ ["", "0", "10"],\ ["", "1", "5"]\ ]\ ], "contractAddress": "", "feeLevel": "MEDIUM" }' ### [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#full-airdrop-interaction-script) Full airdrop interaction script Here’s the full script for executing an ERC-20 airdrop. You can adapt it for ERC-721 or ERC-1155 by changing the function signature and parameters as shown in the examples previously: interact-airdrop.ts interact\_airdrop.py import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets"; const circleDeveloperSdk = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY, entitySecret: process.env.CIRCLE_ENTITY_SECRET, }); async function main() { // Execute ERC-20 airdrop const airdropResponse = await circleDeveloperSdk.createContractExecutionTransaction({ walletId: process.env.WALLET_ID, abiFunctionSignature: "airdropERC20(address,(address,uint256)[])", abiParameters: [\ process.env.TOKEN_CONTRACT_ADDRESS, // ERC-20 token contract address\ [\ [process.env.RECIPIENT_ADDRESS_1, "1000000000000000000"],\ [process.env.RECIPIENT_ADDRESS_2, "2000000000000000000"],\ ],\ ], contractAddress: process.env.CONTRACT_ADDRESS, fee: { type: "level", config: { feeLevel: "MEDIUM", }, }, }); console.log(JSON.stringify(airdropResponse.data, null, 2)); // For ERC-721 airdrop, use: // abiFunctionSignature: "airdropERC721(address,(address,uint256)[])" // abiParameters: [tokenAddress, [[recipient1, tokenId1], [recipient2, tokenId2]]] // For ERC-1155 airdrop, use: // abiFunctionSignature: "airdropERC1155(address,(address,uint256,uint256)[])" // abiParameters: [tokenAddress, [[recipient1, tokenId, amount], [recipient2, tokenId, amount]]] } main(); from circle.web3 import utils, developer_controlled_wallets import os import json client = utils.init_developer_controlled_wallets_client( api_key=os.getenv("CIRCLE_API_KEY"), entity_secret=os.getenv("CIRCLE_ENTITY_SECRET") ) api_instance = developer_controlled_wallets.TransactionsApi(client) # Execute ERC-20 airdrop airdrop_request = developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict({ "walletId": os.getenv("WALLET_ID"), "abiFunctionSignature": "airdropERC20(address,(address,uint256)[])", "abiParameters": [\ os.getenv("TOKEN_CONTRACT_ADDRESS"),\ [\ [os.getenv("RECIPIENT_ADDRESS_1"), "1000000000000000000"],\ [os.getenv("RECIPIENT_ADDRESS_2"), "2000000000000000000"]\ ]\ ], "contractAddress": os.getenv("CONTRACT_ADDRESS"), "feeLevel": "MEDIUM", }) airdrop_response = api_instance.create_developer_transaction_contract_execution(airdrop_request) print(json.dumps(airdrop_response.data.to_dict(), indent=2)) # For ERC-721 airdrop, use: # abiFunctionSignature: "airdropERC721(address,(address,uint256)[])" # abiParameters: [token_address, [[recipient1, token_id1], [recipient2, token_id2]]] # For ERC-1155 airdrop, use: # abiFunctionSignature: "airdropERC1155(address,(address,uint256,uint256)[])" # abiParameters: [token_address, [[recipient1, token_id, amount], [recipient2, token_id, amount]]] **Run the script:** Node.js Python npm run interact-airdrop python interact_airdrop.py **Response:** { "id": "601a0815-f749-41d8-b193-22cadd2a8977", "state": "INITIATED" } * * * [​](https://docs.arc.io/arc/tutorials/interact-with-contracts#summary) Summary --------------------------------------------------------------------------------- After completing this tutorial, you’ve learned how to: * Execute contract functions using the Circle SDKs * Mint and transfer tokens for your deployed contracts * Perform contract-specific operations based on token type Was this page helpful? YesNo [Deploy contracts](https://docs.arc.io/arc/tutorials/deploy-contracts) [Monitor contract events](https://docs.arc.io/arc/tutorials/monitor-contract-events) ⌘I Assistant Responses are generated using AI and may contain mistakes. ---