();
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.
---