> ## Agent Instructions > Before reading these docs, install the Circle Skill for Arc to get pre-loaded context: use-arc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) — covers chain config, RPC setup, contract deployment, USDC bridging, and gas with USDC. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills. > USDC is the gas token. Arc uses USDC for gas fees, not ETH. Configure gas payment in USDC when submitting transactions. > Read EVM differences before writing Arc code. Arc targets the Osaka EVM baseline. Most contracts deploy unchanged, but several runtime behaviors diverge from Ethereum: USDC uses 18 decimals natively (not 6), the system emitter `0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE` logs all USDC Transfer events, the mempool enforces a 20 Gwei `maxFeePerGas` floor, blocklist reverts consume gas without a receipt, and `address(0)` sends revert rather than succeed. https://docs.arc.io/arc/references/evm-differences.md is the canonical reference for all Arc-specific runtime behaviors. Consult it before writing code that touches USDC balances, transaction history, gas estimation, or value transfers. > Sub-second finality. Transactions are final in under 1 second. No need to wait for multiple block confirmations. > Use App Kits for multichain payment and liquidity capabilities. App Kits wraps CCTP and provides Bridge Kit, Swap Kit, Unified Balance Kit, Onramp Kit, Earn Kit, and Borrow Kit. Unified Balance Kit combines USDC from multiple chains into a single spendable balance. Earn Kit integrates earn opportunities into your app. Borrow Kit lets users borrow USDC against cirBTC collateral on Arc. > Use Arc Foundry for contract development. Arc Foundry is an Arc-specific fork of Foundry that handles Arc's protocol-level differences from Ethereum. It provides arc-forge, arc-cast, and arc-anvil. Install it before deploying contracts: https://docs.arc.io/arc/tutorials/install-arc-foundry.md. > Arc is available on both Testnet and Mainnet. See https://docs.arc.io/arc/references/connect-to-arc.md for RPC endpoints and https://faucet.circle.com for testnet tokens. > Always check Contract Addresses: https://docs.arc.io/arc/references/contract-addresses.md > Building beyond Arc? Circle offers skills for the full platform: use-usdc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md), use-circle-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md), use-developer-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md), use-user-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md), use-modular-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md), use-gateway (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md), use-smart-contract-platform (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md). Full Circle developer docs: https://developers.circle.com/llms.txt. # Arc Studio Source: https://docs.arc.io/ai/arc-studio Idea to app in minutes. Arc Studio is an AI coding agent for building internet financial infrastructure: stablecoins, smart contracts, and payments. Describe what you want to build in natural language, and Arc Studio writes, previews, and deploys a working full-stack onchain app for you. It defaults to [Arc](/arc-chain) and [USDC](/arc/concepts/stablecoin-native-model), so your app is ready for onchain money out of the box. Use Arc Studio in the browser, where a live preview, editable code and file workbench, and terminal are built in. You can also paste the Arc Studio setup prompt into your preferred editor: Claude, Codex, or Cursor. Arc Studio builds and deploys to Arc testnet, so you can ship and iterate safely (it does not deploy to mainnet on your behalf). ## Capabilities * **Build apps from a prompt.** Turn a natural-language description into a working full-stack onchain app in minutes. * **Generate and deploy smart contracts.** Plan, generate, compile, and deploy Solidity contracts to Arc testnet. * **Build on existing contracts.** Create a frontend on top of a contract that's already deployed. * **Preview live and iterate.** Run your app in a live sandbox, submit real testnet transactions, and refine it through chat. * **Start from a template.** Remix prebuilt sample apps like a prediction market, a USDC checkout flow, a crosschain bridge, or an onchain profile. * **Move USDC out of the box.** USDC and a built-in Arc testnet faucet are ready by default, so you can fund a wallet and move money without leaving Arc Studio. * **Connect a wallet.** Sign and send transactions from the live preview with a connected wallet. * **Export your code.** Push to GitHub to keep building or deploy elsewhere. Open Arc Studio and build your first onchain app on Arc. # Arc Studio CLI Source: https://docs.arc.io/ai/arc-studio-cli Use Arc Studio from your terminal, or let an AI coding agent drive it to write, audit, and deploy onchain code for you. The Arc Studio CLI brings [Arc Studio](/ai/arc-studio) to the command line. The agent, inference, contract pipeline, and sandbox stay server-side, so the CLI is a thin client: you send a prompt and files, and you get back deployed addresses, diffs, and a live preview. It works two ways. Run `arc-studio` on its own for an interactive session in your terminal, or use it as a subagent surface so another coding agent (Claude Code, Codex, Cursor, or an editor that speaks the Agent Client Protocol) delegates onchain work to Arc Studio and reads back structured results. Arc Studio deploys to Arc testnet only. | Command | Description | | - | - | | `arc-studio login` | Authenticate using a browser device-link flow | | `arc-studio whoami` | Print the currently signed-in user | | `arc-studio tokens list` | List issued personal access tokens | | `arc-studio tokens revoke` | Revoke a personal access token | | `arc-studio` | Open an interactive terminal session | | `arc-studio run` | Run a turn non-interactively | | `arc-studio skills install` | Register Arc Studio as a skill or subagent in a coding tool | | `arc-studio attach` | Attach to a running or detached turn | | `arc-studio pull` | Download workspace artifacts to the local directory | | `arc-studio ls` | List files in the workspace | | `arc-studio cat` | Print the contents of a workspace file | | `arc-studio clone` | Clone a public GitHub repo server-side into the app sandbox | | `arc-studio acp` | Run the CLI as an ACP stdio agent | | `arc-studio apps` | List your Arc Studio apps | | `arc-studio deployments` | List deployments and their addresses | | `arc-studio preview` | Open the app preview in a browser | | `arc-studio open` | Open the app workspace in Arc Studio web | ## Install and authenticate The CLI requires Node.js 20 or later. Install it globally: ```bash npm theme={null} npm install -g @circle-fin/arc-studio-cli ``` ```bash pnpm theme={null} pnpm add -g @circle-fin/arc-studio-cli ``` Authenticate with a browser device-link flow. The page you authorize mints a revocable personal access token, stored in the macOS Keychain (or `~/.arc-studio/credentials.json` with `0600` permissions on other platforms): ```bash theme={null} arc-studio login ``` Pass `--paste` when you're on SSH or a headless machine. Use `arc-studio whoami` to confirm you're signed in, and `arc-studio tokens list` or `arc-studio tokens revoke` to manage tokens you've issued. ## Interactive session Run the CLI with no arguments to open a session in your terminal. It walks you through onboarding on the first run, and resumes the current directory's app after that: ```bash theme={null} arc-studio ``` Responses stream as they're generated, alongside a live task checklist and the agent's file and command activity. Deployments and the preview URL are surfaced at the end of each turn, and plan-mode questions appear as an interactive picker. Type `/` for the command palette. Slash commands cover the same ground as the web workbench: | Command | Description | | - | - | | `/new` | Start a new app in the current directory | | `/sessions` | List and switch between sessions | | `/preview` | Open the running app preview in a browser | | `/open` | Open the current workspace in Arc Studio web | | `/deployments` | Show recent deployments and their addresses | | `/usage` | Show token and compute usage for the session | | `/ls` | List files in the workspace | | `/cat` | Print the contents of a workspace file | | `/pull` | Download workspace artifacts to the local directory | | `/exit` | End the session | ## Drive Arc Studio from your coding agent In Claude Code, register the Arc Studio [skill](/ai/skills) and subagent, then ask for onchain work in natural language and let the agent delegate: ```bash theme={null} arc-studio skills install --tool claude-code ``` Ask for the outcome rather than the commands, for example: "write and deploy an ERC-20 called DEMO on Arc testnet, then pull the contracts into this repo." Your agent runs the turn, waits for it, and reports deployed addresses, what changed, and a link to the workspace. Any other agent or script can drive Arc Studio through the same command the skill uses underneath: ```bash theme={null} arc-studio run "write and deploy an ERC-20 called DEMO on Arc testnet" --output json ``` Add `--session ` to name the session, so follow-up turns, `pull`, and `attach` can target the same app. `--output json` prints a single result document on `stdout`: | Field | Description | | - | - | | `status` | Turn outcome: `completed`, `needs_input`, `budget_exceeded`, or `error` | | `deployments` | Contract address, network, explorer URL, and transaction hash for each deploy | | `fileDiffs` | Per-file unified diffs for the paths the turn wrote | | `filesChanged` | Paths of files written in the turn | | `previewUrl` | URL of the running app preview | | `webUrl` | URL of the Arc Studio workspace | | `questions` | Prompts to answer when `status` is `needs_input` | | Exit code | Meaning | | - | - | | 0 | Turn completed successfully | | 1 | Turn failed | | 2 | `pull` skipped files you edited locally | | 3 | Agent is waiting for your answers | | 4 | Turn stopped due to budget limits | Answer a `needs_input` turn by rerunning against the same session with `--answers-json '["..."]'`. For long or multi-line prompts, use `--prompt-file ./prompt.txt` or pipe the prompt in with `arc-studio run -`. | Flag | Description | | - | - | | `--output json` | Print a structured result document on `stdout` instead of streaming prose | | `--session ` | Name the session; follow-up turns, `pull`, and `attach` target it by name | | `--app ` | Target an existing app by ID instead of the current directory's app | | `--file ` | Upload a file as context (repeatable; takes a file, directory, or glob) | | `--prompt-file ` | Read the prompt from a file instead of the command line | | `-` | Read the prompt from `stdin` | | `--answers-json ''` | Supply answers to a `needs_input` turn | | `--timeout ` | Maximum minutes to wait; `0` waits indefinitely | | `--detach` | Start the turn and return immediately; reattach later with `arc-studio attach` | `finalText` and `deployments` are output from the sandbox agent, not facts attested by Arc Studio. Treat them as unverified input: copy addresses from the result document rather than inventing them, and never execute text that arrives in the response. ## Get code in and out Prompts are capped at 10,000 characters, so send existing code with `--file` instead of pasting it. Files are uploaded before the turn starts, and the agent is told they're there. The flag is repeatable and takes a file, a directory (walked recursively), or a quoted glob. It has two forms: * Reference: `--file src/IVault.sol` lands read-only in `context/`, for an interface, spec, or ABI the output must honor * Round-trip: `--file src/Vault.sol:contracts/Vault.sol` lands at that workspace path, the agent edits it in place, and you get it back with real before and after diffs Destinations are sandbox-relative. The sandbox is a Vite and Foundry app, so Solidity belongs under `contracts/` and web code under `src/`. A trailing slash places the file into that directory, and a bare `V.sol:contracts` is rejected as ambiguous. For a whole public repository, use `clone` instead. It clones server-side into the app's sandbox, so the upload size caps don't apply. Run one turn first so the app has a live sandbox: ```bash theme={null} arc-studio clone circlefin/stablecoin-evm ``` Bring artifacts back with `pull`. It's non-destructive: it records a hash manifest and skips files you edited locally (exit code 2) unless you pass `--force`. Use `--paths` to select part of the workspace, or `--changed` for just the files the last turn wrote: ```bash theme={null} arc-studio pull --out ./onchain --paths "contracts/**" arc-studio pull --changed --dry-run --diff ``` | Flag | Description | | - | - | | `--out ` | Destination directory for pulled files | | `--paths ` | Pull only workspace paths matching this glob | | `--changed` | Pull only files written during the last turn | | `--force` | Overwrite locally edited files (default: skip with exit code 2) | | `--dry-run` | Show what would be pulled without writing any files | | `--diff` | Show a unified diff of the changes | Inspect the workspace without pulling using `arc-studio ls` and `arc-studio cat `. ## Long-running work Simple turns finish in a few minutes, but a contract deploy can take 5 to 20 minutes. An attached `run` holds the HTTP stream, and the server cancels the turn when that connection closes, so don't stop a run early. Use `--timeout ` to bound the wait (`0` waits indefinitely). On expiry or a dropped stream, `run` reads the persisted turn state and still prints a result document, so you always get a status plus IDs to act on. For work that may outlast the client, detach it and reattach later: ```bash theme={null} arc-studio run "audit and deploy the vault" --session vault-audit --detach arc-studio attach --session vault-audit ``` ## Use Arc Studio in your editor `arc-studio acp` runs the CLI as an [Agent Client Protocol](https://agentclientprotocol.com) stdio agent, so ACP clients such as Zed can drive Arc Studio like a native coding agent. Add it to your Zed `settings.json`: ```json theme={null} { "agent_servers": { "Arc Studio": { "command": "arc-studio", "args": ["acp"] } } } ``` Prose, tool activity, and the task list stream as ACP updates, and each turn ends with a summary of deployments, the workspace link, and files changed. Files you `@`-mention are forwarded as context files, the same channel as `--file`. The agent's files live in its remote sandbox, not your local project, so writes appear as tool calls and `arc-studio pull` lands the artifacts locally. ## Continue an app you started on the web An app built at [studio.arc.io](https://studio.arc.io/) has no local session, so target it by ID. List your apps, then pass `--app`: ```bash theme={null} arc-studio apps --json arc-studio run "add a pause guard to the vault" --app ``` `run`, `pull`, `ls`, `cat`, `deployments`, `preview`, and `open` all accept `--app`. Note that `run --app` opens a new thread: the app's files persist, but the chat history doesn't, so restate the context the turn needs. # Arc MCP server Source: https://docs.arc.io/ai/mcp Connect your AI coding tools to Arc documentation using the Model Context Protocol (MCP) server for search and full-page retrieval. The Arc Model Context Protocol (MCP) server gives AI tools direct access to Arc documentation so they can search for relevant content and retrieve full pages during conversations. It is hosted at `https://docs.arc.io/mcp` and requires no authentication. The server exposes two tools: * **Search**: finds relevant documentation snippets based on a query. * **Get page**: retrieves the full content of a specific documentation page. For a machine-readable index of all documentation pages, see the [`llms.txt` file](https://docs.arc.io/llms.txt). ## Claude Code Run the following command to add the Arc MCP server: ```bash theme={null} claude mcp add --transport http arc-docs https://docs.arc.io/mcp ``` Claude Code automatically discovers the server's tools on the next conversation. ## Claude Desktop 1. Open **Settings** and navigate to **Connectors**. 2. Select **Add custom connector**. 3. Enter `Arc Docs` as the name and `https://docs.arc.io/mcp` as the URL. 4. During a chat, use the attachments button to select the Arc Docs connector. ## Cursor Add the following to your `mcp.json` file (accessible via **Cursor Settings > MCP**): ```json theme={null} { "mcpServers": { "arc-docs": { "url": "https://docs.arc.io/mcp" } } } ``` ## VS Code (Copilot) Create or update `.vscode/mcp.json` in your project root: ```json theme={null} { "servers": { "arc-docs": { "type": "http", "url": "https://docs.arc.io/mcp" } } } ``` ## Windsurf Add the following to your Windsurf MCP configuration: ```json theme={null} { "mcpServers": { "arc-docs": { "serverUrl": "https://docs.arc.io/mcp" } } } ``` ## Other MCP clients Any MCP-compatible client can connect using the HTTP transport at `https://docs.arc.io/mcp`. Most clients require only the server URL and transport type (`http`). Refer to your client's documentation for the exact configuration format. ## Verify the connection After adding the server, confirm the connection by asking your AI tool a question about Arc, such as "What smart contract standards does Arc support?" The tool should return content sourced from Arc documentation. If it does not: * **Check the URL**: confirm it is exactly `https://docs.arc.io/mcp` with no trailing path. * **Check network access**: the server must be reachable over HTTPS from your machine. * **Restart the client**: some tools only detect new MCP servers after a restart or new session. # AI skills for building on Arc Source: https://docs.arc.io/ai/skills Use Circle's open source AI skills to accelerate development on Arc with AI-assisted IDEs. Skills give AI coding tools specialized knowledge for building on Arc and with Circle's products, including USDC, agent wallets, crosschain transfers, and smart contracts. Skills are available in the [circlefin/skills](https://github.com/circlefin/skills) repository. ## Installation Install Circle Skills with the command-line. ```shell Claude Code icon="https://mintcdn.com/arcio-docs/DcVxxFlqh75sTiVQ/images/claude-logo.png?fit=max&auto=format&n=DcVxxFlqh75sTiVQ&q=85&s=b0262a5319054feab00173c330431e02" theme={null} /plugin marketplace add circlefin/skills /plugin install circle-skills@circle ``` ```shell Vercel Skills CLI icon="https://mintcdn.com/arcio-docs/DcVxxFlqh75sTiVQ/images/vercel_logo.png?fit=max&auto=format&n=DcVxxFlqh75sTiVQ&q=85&s=9a66052e003d6b18378e0d8a1e0f35f5" theme={null} npx skills add circlefin/skills ``` ## Available skills | Skill | What it covers | | :- | :- | | [`use-arc`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) | Chain config, contract deployment, viem/wagmi integration, bridging via CCTP | | [`use-circle-cli`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-cli/SKILL.md) | Unified CLI for agent wallets, x402 payments, and crosschain transfers | | [`use-agent-wallet`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-agent-wallet/SKILL.md) | Email/OTP login, wallet creation, status checks, balance inspection | | [`fund-agent-wallet`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/fund-agent-wallet/SKILL.md) | Fund with USDC via fiat on-ramp, crypto transfer, or Gateway deposit | | [`agent-wallet-policy`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/agent-wallet-policy/SKILL.md) | View and reset per-tx, daily, weekly, and monthly USDC spending caps | | [`pay-via-agent-wallet`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/pay-via-agent-wallet/SKILL.md) | Pay paid APIs in USDC via x402 (search, market data, weather, news, sports) | | [`use-usdc`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md) | Balances, transfers, approvals, verification across EVM and Solana | | [`bridge-stablecoin`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md) | CCTP bridging with App Kit, progress tracking, EVM and Solana | | [`swap-tokens`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/swap-tokens/SKILL.md) | Same-chain swaps via App Kit or Swap Kit; combine with bridge for crosschain | | [`use-gateway`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md) | Gateway unified balance for sub-500ms crosschain transfers | | [`unify-balance`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/unify-balance/SKILL.md) | Crosschain USDC balance via App Kit or Unified Balance Kit | | [`use-circle-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md) | Choose between developer-controlled, user-controlled, and modular wallets | | [`use-developer-controlled-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md) | Custodial wallets for payouts, treasury, subscriptions, automation | | [`use-user-controlled-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md) | Embedded wallets with social login or OTP/PIN, no seed phrases | | [`use-modular-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md) | Passkey auth and gasless transactions; ERC-4337 and ERC-6900 | | [`use-smart-contract-platform`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md) | Deploy/import/call/monitor contracts; ERC-20/721/1155 templates | # App Kits Source: https://docs.arc.io/app-kit Build payment and liquidity workflows across blockchains with App Kits App Kits is a suite of SDKs for composing multichain payment and liquidity flows without orchestrating separate, low-level protocol integrations for each blockchain or use case. The App Kit SDK is the all-in-one core package. Install it to access every capability (Send, Bridge, Swap, Unified Balance, Onramp, Earn, and Borrow) behind one type-safe interface. Or, you can install Bridge Kit, Swap Kit, Unified Balance Kit, Onramp Kit, Earn Kit, or Borrow Kit separately. ## Quick install To get started quickly, install the App Kit SDK and the Viem adapter: ```bash npm theme={null} npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem ``` ```bash yarn theme={null} yarn add @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem ``` Need a different adapter or individual kits? See the full [installation](/app-kit/tutorials/installation) guide. ## Core capabilities Combine and use any of the App Kit SDK's built-in core capabilities in your app. Bridge, Swap, Unified Balance, Onramp, Earn, and Borrow are also available as individual kits. Transfer USDC and EURC across blockchains. Exchange one token for another on the same blockchain or across blockchains. Transfer tokens between wallets on the same blockchain. Create a chain-abstracted balance and spend it instantly. Embed a widget so users can buy stablecoins on Arc with fiat. Deposit USDC or EURC to earn returns from supported lending protocols. Borrow USDC against cirBTC collateral on Arc. ## Key benefits * **Simple setup**: Get up and running with minimal configuration and a few lines of code. * **Application monetization**: Collect a custom fee from end users without writing new code. * **Flexible configurations**: Specify custom RPC endpoints and wallet clients. * **Broad compatibility**: Works with Viem, Ethers, Solana, and Circle Wallets, integrating smoothly with existing developer workflows. * **Protocol abstraction**: Build against a single interface over underlying protocols such as [Gateway](https://developers.circle.com/gateway) and [CCTP](https://developers.circle.com/cctp). * **Composable workflows**: Combine multiple capabilities in one product flow without stitching together separate protocol integrations. ## Quick look The following examples show how each capability can be integrated with a single method call. ```typescript TypeScript theme={null} // Transfer 1.00 USDC from Ethereum to Arc const result = await kit.bridge({ from: { adapter: viemAdapter, chain: "Ethereum_Sepolia" }, to: { adapter: viemAdapter, chain: "Arc_Testnet" }, amount: "1.00", }); ``` Ready to start bridging? Follow the [Bridge tokens across blockchains](/app-kit/quickstarts/bridge-tokens-across-blockchains) quickstart. ```typescript TypeScript theme={null} // Swap 1.00 USDC for EURC on Arc Testnet const result = await kit.swap({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, tokenIn: "USDC", tokenOut: "EURC", amountIn: "1.00", config: { kitKey: process.env.KIT_KEY as string, // Your kit key from the Circle Console }, }); ``` Ready to start swapping? Follow the [Swap tokens on a blockchain](/app-kit/quickstarts/swap-tokens-same-chain) quickstart. ```typescript TypeScript theme={null} // Deposit 1.00 USDC into the Unified Balance from Base const depositBase = await kit.unifiedBalance.deposit({ from: { adapter: viemAdapter, chain: "Base_Sepolia" }, amount: "1.00", token: "USDC", }); // Deposit 1.00 USDC into the Unified Balance from Arbitrum const depositArb = await kit.unifiedBalance.deposit({ from: { adapter: viemAdapter, chain: "Arbitrum_Sepolia" }, amount: "1.00", token: "USDC", }); // Spend 1.50 USDC from the Unified Balance on Arc const spendResult = await kit.unifiedBalance.spend({ from: { adapter: viemAdapter }, amountIn: "1.50", to: { adapter: viemAdapter, chain: "Arc_Testnet", recipientAddress: "0xRecipientAddress", }, }); ``` Ready to start using a Unified Balance? Follow the quickstarts: * [Deposit and spend a Unified Balance](/app-kit/quickstarts/unified-balance-deposit-and-spend) * [Use a delegate to deposit and spend a Unified Balance](/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend) ```typescript TypeScript theme={null} // Send 1.00 USDC from one wallet to another on Arc Testnet const result = await kit.send({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, to: "RECIPIENT_ADDRESS", amount: "1.00", token: "USDC", }); ``` Ready to start sending tokens? Follow the [Send tokens across wallets](/app-kit/quickstarts/send-tokens-same-chain) quickstart. ```typescript TypeScript theme={null} // Mint a session on your server, then mount the Onramp widget inline const session = await kit.onramp.fetchSession({ url: "/api/onramp/sessions", body: { userId: "user-123", destinationAddress: "USER_WALLET_ADDRESS" }, }); const widget = kit.onramp.mountIframe({ session, container: document.getElementById("onramp-root")!, }); ``` Ready to start onramping? Follow the [Embed the Onramp widget](/app-kit/quickstarts/onramp-embed-widget) quickstart. ```typescript TypeScript theme={null} // Deposit 100.00 USDC into an Earn vault on Arc const result = await kit.earn.deposit({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, vaultAddress: "0xVaultAddress", amount: "100.00", }); ``` Ready to add earning to your app? Follow the [Deposit into an Earn vault](/app-kit/quickstarts/earn-deposit) quickstart. ```typescript TypeScript theme={null} // Borrow 1,000 USDC against cirBTC on Arc Testnet const result = await kit.borrow.borrow({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, marketId: "0x1111111111111111111111111111111111111111111111111111111111111111", borrowAmount: "1000.0", }); ``` Ready to start borrowing? Follow the [Originate a loan](/app-kit/quickstarts/originate-a-loan) quickstart. # App Kit SDK: Borrow Source: https://docs.arc.io/app-kit/borrow Originate and manage cirBTC-collateralized USDC loans on Arc with the App Kit SDK's Borrow capability The [App Kit SDK](/app-kit) includes the Borrow capability, which lets your users borrow USDC against cirBTC collateral without you integrating with a lending protocol yourself. Your users post cirBTC, borrow USDC, and repay to reclaim their collateral. ## How it works A user supplies cirBTC as collateral and receives USDC in return, all in a single atomic transaction on Arc. If you subscribe to [webhooks](/app-kit/howtos/borrow/monitor-a-loan#subscribe-to-health-updates), the SDK posts events when a loan's health changes, so you can prompt the user to act before liquidation. ```mermaid theme={null} flowchart LR U[User wallet] -->|"cirBTC collateral"| L[(Loan)] L -->|"USDC"| U L -->|"webhook events"| Y[Your backend] ``` For how loan health is measured and when a position becomes liquidatable, see [How loan health works](/app-kit/concepts/borrow-loan-health). ## Quick look This code snippet opens a loan by borrowing 1,000 USDC against cirBTC on Arc Testnet: ```typescript TypeScript theme={null} const result = await kit.borrow.borrow({ from: { adapter: viemAdapter, chain: "Arc_Testnet" }, marketId: "0x1111111111111111111111111111111111111111111111111111111111111111", borrowAmount: "1000.0", }); ``` For a complete end-to-end flow, follow the [Originate a loan](/app-kit/quickstarts/originate-a-loan) quickstart. ## Installation [Install the App Kit SDK](/app-kit/tutorials/installation) to use Borrow. If you only need to borrow and don't want to install the full App Kit SDK, install the standalone Borrow Kit for your preferred adapter: Borrow writes require an adapter with atomic-batch support. Use Viem or Circle Wallets. ```bash npm theme={null} npm install @circle-fin/borrow-kit @circle-fin/adapter-viem-v2 viem ``` ```bash yarn theme={null} yarn add @circle-fin/borrow-kit @circle-fin/adapter-viem-v2 viem ``` ```bash npm theme={null} npm install @circle-fin/borrow-kit @circle-fin/adapter-circle-wallets ``` ```bash yarn theme={null} yarn add @circle-fin/borrow-kit @circle-fin/adapter-circle-wallets ``` # App Kit SDK: Bridge Source: https://docs.arc.io/app-kit/bridge Transfer USDC and EURC across blockchains with the App Kit SDK's Bridge capability The [App Kit SDK](/app-kit) includes the Bridge capability that lets you move USDC and EURC across blockchains in a few lines of code. It abstracts the underlying [CCTP](https://developers.circle.com/cctp) flow so you can bridge without orchestrating the low-level burn, attestation, and mint steps yourself. ## How it works Bridge moves USDC or EURC from a wallet on the source blockchain to a wallet on the destination blockchain. The SDK handles the underlying [CCTP](https://developers.circle.com/cctp) burn, attestation, and mint steps for you. ```mermaid theme={null} flowchart LR subgraph ChainA[Source blockchain] A[Wallet] end subgraph ChainB[Destination blockchain] B[Wallet] end A -->|"bridge assets"| B ``` ## Quick look This code snippet bridges USDC between an EVM and non-EVM chain in a single method call: ```typescript TypeScript theme={null} // Transfer 1.00 USDC from Solana to Arc // Defaults to USDC when `token` is omitted const result = await kit.bridge({ from: { adapter: solanaAdapter, chain: "Solana_Devnet" }, to: { adapter: viemAdapter, chain: "Arc_Testnet" }, amount: "1.00", }); ``` For a complete end-to-end flow, follow the [quickstart](/app-kit/quickstarts/bridge-tokens-across-blockchains). ## Installation [Install the App Kit SDK](/app-kit/tutorials/installation) to use Bridge. If you only need to bridge and don't want to install the full App Kit SDK, follow the steps below to install the standalone Bridge Kit. ```bash npm theme={null} npm install @circle-fin/bridge-kit ``` ```bash yarn theme={null} yarn add @circle-fin/bridge-kit ``` Install the [adapters](/app-kit/tutorials/adapter-setups) you need for the chains you plan to bridge between. ```bash npm theme={null} npm install @circle-fin/adapter-viem-v2 viem ``` ```bash yarn theme={null} yarn add @circle-fin/adapter-viem-v2 viem ``` ```bash npm theme={null} npm install @circle-fin/adapter-ethers-v6 ethers ``` ```bash yarn theme={null} yarn add @circle-fin/adapter-ethers-v6 ethers ``` ```bash npm theme={null} npm install @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js ``` ```bash yarn theme={null} yarn add @circle-fin/adapter-solana-kit @solana/kit @solana/web3.js ``` ```bash npm theme={null} npm install @circle-fin/adapter-circle-wallets ``` ```bash yarn theme={null} yarn add @circle-fin/adapter-circle-wallets ``` # How borrow fees work Source: https://docs.arc.io/app-kit/concepts/borrow-fees How Circle and integrator fees apply when originating a loan with the App Kit SDK Two fees can apply when a loan is originated, including an [integrator fee](/app-kit/howtos/borrow/configure-integrator-fee) you can configure. Fees apply only at origination, not on subsequent operations against the same loan, and both fees come out of the loan proceeds rather than adding to the borrower's debt. ## Fees breakdown Each origination can include the following fees: | Fee | When it applies | Amount | Recipient | | - | - | - | - | | Circle fee | Always. On every origination. | 0%, subject to change | Circle | | Integrator fee | Conditionally. When you [configure a non-zero rate](/app-kit/howtos/borrow/configure-integrator-fee). | You define, in basis points, up to 10,000. | 90% to your fee recipient; 10% to Arc | A fee applies only when the loan's debt is zero at execution time: a first borrow, or a borrow after a full repayment. Borrowing more against a loan that already carries debt adds no fee. Repayments, collateral additions, collateral withdrawals, and closes never charge a fee. A zero-rate fee produces no fee entry and no onchain transfer. ## Loan proceeds and funds flow The following example shows what happens when a borrower originates a 1,000 USDC loan against cirBTC collateral on Arc, and you have configured a 25 basis point integrator fee: The borrower confirms a 1,000 USDC borrow. This is the full principal the loan will carry. The loan is opened for 1,000 USDC of principal owed to the underlying lending protocol. This is the amount the borrower repays over time, regardless of any fees applied at origination. See [best practices](#best-practices-for-integrator-fees) for what to show the borrower before they sign. The Circle fee is 0%, so no USDC is deducted for this leg. Your 25 basis point (0.25%) integrator fee applies to the 1,000 USDC principal, carving 2.5 USDC out of the loan proceeds. Fees round down to the nearest USDC atom, and any remainder stays with the borrower. The 2.5 USDC integrator fee is transferred onchain in two legs: * 2.25 USDC (90% of the fee) to your configured fee recipient. * 0.25 USDC (10% of the fee) to Arc. The borrower's wallet receives 997.5 USDC: * Loan principal: 1,000 USDC. * Deduct 2.5 USDC integrator fee. * Net proceeds: 997.5 USDC. This flow is illustrated in the following diagram: Structured summary for tools that read raw MDX (Mermaid is not machine-parseable): Nodes: Loan (1,000 USDC principal), CircleFee (0 USDC), IntegratorFee (2.5 USDC = 25 bps of 1,000 USDC), FeeRecipient (your address, 2.25 USDC = 90% of fee), Arc (0.25 USDC = 10% of fee), Net (997.5 USDC), Borrower (borrower's wallet). Flow: Loan principal splits into Circle fee (0), integrator fee (2.5 USDC, which splits 90/10 between your fee recipient and Arc), and net proceeds (997.5 USDC to borrower). Borrower owes the full 1,000 USDC principal over the life of the loan. ```mermaid theme={null} flowchart TB Loan["Loan principal:
1,000 USDC"] CircleFee["Circle fee:
0 USDC (0%)"] IntegratorFee["Integrator fee:
-2.5 USDC (25 bps)"] Net["Net proceeds:
997.5 USDC"] Loan --> CircleFee Loan --> IntegratorFee Loan --> Net IntegratorFee --> FeeRecipient["Your fee recipient:
2.25 USDC (90%)"] IntegratorFee --> Arc["Arc:
0.25 USDC (10%)"] Net --> Borrower["Borrower's wallet"] ``` ## Best practices for integrator fees Follow these best practices when setting an integrator fee: * Set the fee once during app setup with [`kit.borrow.setIntegratorConfig`](/app-kit/howtos/borrow/configure-integrator-fee). The rate and recipient apply to every subsequent origination and do not need to be passed per call. * Use a fee recipient address on the same blockchain where the loan is originated. * Preview the fee amount and net proceeds before the borrower signs. The [borrow quote](/app-kit/howtos/borrow/preview-a-loan-operation) returns a `fees` array with each active leg's `type`, `token`, and `amount`. * Before the borrower confirms an origination, show: * Loan summary: principal owed, integrator fee, and net proceeds to the borrower's wallet. ```text Example UI display theme={null} Loan principal (owed): 1,000.00 USDC Circle fee: - 0.00 USDC Integrator fee (0.25%): - 2.50 USDC Net proceeds to wallet: 997.50 USDC ``` * Return human-readable decimal strings. For example, return `"1000"` rather than `"1000000000"` for 1,000 USDC. The App Kit SDK handles base-unit conversion internally. # How loan health works Source: https://docs.arc.io/app-kit/concepts/borrow-loan-health How loan-to-value, health factor, health bands, and liquidation work for Borrow loans Borrow loans are issued through an underlying lending protocol on Arc, and each market sets a liquidation threshold. The App Kit SDK exposes your position's distance from that threshold as a loan-to-value ratio (LTV) and a health factor, grouping the health factor into bands you can act on before liquidation. ## Health fields [Check loan state](/app-kit/howtos/borrow/monitor-a-loan#check-loan-state) to see these health fields: * `ltv`: the current [loan-to-value ratio](#loan-to-value-ratio-ltv) * `healthFactor`: the current [health factor](#health-factor) * `healthFactorBand`: the current [health factor band](#health-factor-bands) * `liquidationPrice`: the cirBTC price at which the loan would cross into [`LIQUIDATABLE`](#liquidation) ## Loan-to-value ratio (LTV) Loan-to-value ratio is the ratio of your outstanding debt to the current value of your collateral, denominated in the borrowed asset. For example, a loan that carries 3,000 USDC in debt against cirBTC worth 10,000 USDC has an LTV of `0.30`. LTV rises as debt accrues interest, as the value of cirBTC falls, or both. ## Health factor Health factor is the inverse safety measure of LTV. It equals `LLTV / LTV`, where `LLTV` (liquidation loan-to-value) is the market's liquidation threshold. The health factor tells you how close a position is to liquidation: * Above `1.0`: the position is not liquidatable. * At `1.0`: the position is at the liquidation boundary. * Below `1.0`: a third-party liquidator can repay part of the debt and seize collateral to bring the position back in line. ## Health factor bands The health factor maps to five bands. Use the band to drive alerts and to gate operations that would move the loan further toward liquidation. | Band | Health factor | What it means | | :- | :- | :- | | `SAFE` | >= 1.20 | The position has ample buffer above liquidation. | | `WARN` | 1.10 to \< 1.20 | The buffer is shrinking. Consider adding collateral. | | `URGENT` | 1.05 to \< 1.10 | Small price movements can push the loan into liquidation. | | `IMMINENT` | 1.00 to \< 1.05 | Liquidation is likely without a repayment. | | `LIQUIDATABLE` | \< 1.00 | The position can be liquidated at any time. | ## Liquidation When a loan crosses into `LIQUIDATABLE`, the underlying lending protocol lets a liquidator repay part or all of the debt in exchange for the collateral at a discount. Liquidation does not go through the SDK. It is a permissionless protocol action. Every position reports a `liquidationPrice`, the cirBTC price (denominated in USDC) at which the loan would cross into `LIQUIDATABLE`. Show it to users so they can act before the market reaches it. # How bridge fees work Source: https://docs.arc.io/app-kit/concepts/bridge-fees How custom fees, CCTP protocol fees, Forwarding Service fees, and source-paid (upfront) fees apply when bridging USDC with the App Kit SDK This guide explains which fees apply when bridging, how funds move through a transaction, and the best practices to follow when [implementing custom fees](/app-kit/tutorials/bridge/collect-bridge-fee). ## Fee breakdown The following fees can apply: | Fee | When it applies | Amount | Recipient | | - | - | - | - | | Custom fee | Conditionally. When you [implement custom bridge fees](/app-kit/tutorials/bridge/collect-bridge-fee). | You define (on top of the bridge amount). | 90% to your fee recipient; 10% to Arc | | Cross-Chain Transfer Protocol (CCTP) fee | Conditionally. On [`FAST`](/app-kit/tutorials/bridge/configure-transfer-speed) transfers only; `SLOW` (Standard) transfers skip this fee. | Varies by source blockchain. See [CCTP fees](https://developers.circle.com/cctp/technical-guide#fees). | [Circle CCTP](https://developers.circle.com/cctp) (the underlying protocol) | | Forwarding Service fee | Conditionally. When you enable the [Forwarding Service](/app-kit/tutorials/bridge/use-forwarding-service). | Per [Forwarding Service fees](https://developers.circle.com/cctp/concepts/forwarding-service#fees-and-execution). Deducted from mint on destination by default. | [Circle CCTP](https://developers.circle.com/cctp) | ## Pay fees on the source chain By default, CCTP Fast Transfer and Forwarding Service fees reduce the amount minted on the destination. You can instead [pay those fees on the source chain](/app-kit/tutorials/bridge/pay-fees-on-source) (`config.feePayment: "source"`) so the recipient receives the exact bridge amount. Source-paid fees are incompatible with custom fees. ## How funds flow through a transfer The following example shows what happens when a user wants 1,000 USDC to arrive at the destination after a Fast Transfer, with a 10 USDC custom fee on that transfer, and the [Forwarding Service](/app-kit/tutorials/bridge/use-forwarding-service) enabled. The bridge amount is 1,000.30 USDC so that after the example CCTP protocol fee (0.10 USDC) and Forwarding Service fee (0.20 USDC), 1,000 USDC is credited to the recipient: The user initiates a 1,000.30 USDC bridge transfer on the source blockchain (sized for 1,000 USDC net to the destination after the example fees). You add a 10 USDC (about 1%) custom fee. The source wallet signs a transaction for 1,010.30 USDC (bridge amount + custom fee). The 10 USDC custom fee is split on the source blockchain: * Arc receives 1 USDC (10%). * Your fee recipient receives 9 USDC (remaining 90%). The 1,000.30 USDC bridge amount is forwarded to CCTP. CCTP takes a protocol fee (0.10 USDC in this example) for a Fast Transfer. The Forwarding Service deducts its fee (0.20 USDC in this example) from the amount to be minted on the destination blockchain. The destination wallet receives 1,000.00 USDC on the destination blockchain. This flow is illustrated in the following diagram: ```mermaid theme={null} flowchart TB Source["*Source wallet*#160;"] --> Approved["Approved amount:
1,010.30 USDC"] Approved --> CustomFee["Custom fee:
-10 USDC"] Approved --> Transfer["Bridge amount:
1,000.30 USDC"] CustomFee --> ArcShare["10% of custom fee:
1 USDC"] ArcShare --> Arc["*Arc*#160;"] CustomFee --> PartnerShare["90% of custom fee:
9 USDC"] PartnerShare --> Partner["*Your fee recipient*#160;"] Transfer --> CCTP["*CCTP*#160;"] CCTP --> Transfer2["Net amount after fees:
1,000.00 USDC"] Transfer2 --> Destination["*Destination wallet*#160;"] CCTP --> FastFee["CCTP protocol fee (example):
-0.10 USDC"] CCTP --> ForwarderFee["Forwarding Service fee (example):
-0.20 USDC"] FastFee --> Circle["*Circle*#160;"] ForwarderFee --> Circle ``` ## Best practices for custom fees Follow these best practices when implementing custom fees: * Treat the custom fee as an amount added on top of the bridge transfer. Do not subtract it from the bridge amount. * Validate that the user's wallet balance covers both the bridge amount and the custom fee. The following code shows an example balance check: ```typescript TypeScript theme={null} const requiredBalance = parseFloat(amount) + parseFloat(customFee); if (userBalance < requiredBalance) { throw new Error(`Insufficient balance. Need ${requiredBalance} USDC`); } ``` * Use a fee recipient address on the source blockchain. Do not use an address on the destination. * In your UI, display the following to the user before they confirm the transaction: * The total source wallet debit: bridge amount + custom fee * The full fee breakdown: bridge amount, custom fee, CCTP Fast Transfer fee (if applicable), and Forwarding Service fee (if using the [Forwarding Service](/app-kit/tutorials/bridge/use-forwarding-service)) * Return human-readable decimal strings. For example, `10` rather than `10000000` for 10 USDC. The App Kit SDK handles base-unit conversion internally. # How Earn fees work Source: https://docs.arc.io/app-kit/concepts/earn-fees Vault, deposit, and withdrawal fees that apply when using Earn with the App Kit SDK Earn deposits accrue returns generated by third-party lending protocols. Those protocols set vault fees, which are deducted from the returns. Same-chain deposits do not charge an Earn deposit fee. Crosschain deposits incur CCTP bridge fees on the source blockchain. Preview fees with `getDepositQuote` and `getWithdrawalQuote` before you submit. ## Fee breakdown The following fees can apply: | Fee | When it applies | Amount | Recipient | | - | - | - | - | | Performance fee | On returns earned while deposited | A percentage of returns; varies by vault | Vault protocol | | Management fee | While assets are deposited | An annualized percentage of deposited assets; varies by vault | Vault protocol | | Deposit fee | Only on a crosschain deposit when burning USDC on the source blockchain | Varies based on CCTP bridge fees | Circle CCTP | | Withdrawal fee | On withdrawal | 0, subject to change | Circle | ### Performance fee The performance fee is taken as a percentage of the returns generated by the underlying lending protocol. For Morpho vaults, the performance fee ranges from 0% to 50%, configurable by the vault curator. For example, a 10% performance fee on 1.00 USDC of returns would result in 0.90 USDC returned to you. The performance fee is reflected in the vault's current exchange rate between USDC and vault shares. ### Management fee The management fee is an annualized fee charged on your deposited balance and is set by the vault's underlying protocol. For Morpho vaults, the management fee ranges from 0% to 5%, configurable by the vault curator. For example, a 0.5% annual management fee on a 1,000 USDC deposit is approximately 5.00 USDC per year. The management fee is reflected in the vault's current exchange rate between USDC and vault shares. ### Deposit fees There is no deposit fee for same-chain deposits. Vault performance and management fees still apply to returns over time. Crosschain deposits incur CCTP bridge fees on the source blockchain. To preview them before depositing, see [Preview a crosschain deposit](/app-kit/tutorials/earn/preview-operations#preview-a-crosschain-deposit). The CCTP transfer speed (Standard Transfer or Fast Transfer) changes which fees apply. See CCTP [fee tables](https://developers.circle.com/cctp/concepts/fees#fee-tables) by transfer speed for the amounts per blockchain. ### Withdrawal fee The withdrawal fee is 0 and subject to change. Preview it before withdrawing with `getWithdrawalQuote` — see [Preview a withdrawal](/app-kit/tutorials/earn/preview-operations#preview-a-withdrawal). ## Who pays the fees Vault fees (performance and management) are collected from accrued returns inside the vault, so they don't touch your principal. Crosschain deposit fees are collected on the source blockchain at burn time. Any withdrawal fee is deducted from the principal returned at withdrawal. ```mermaid theme={null} flowchart TB Deposit["Wallet
Deposits 1,000 USDC"] --> Vault["Morpho vault"] Vault --> Returns["Accrued returns (example):
50 USDC"] Returns --> PerfFee["Performance fee (10%):
-5 USDC"] Returns --> MgmtFee["Management fee (0.5%):
-5 USDC"] PerfFee --> Curator["Vault curator"] MgmtFee --> Curator Returns --> NetReturns["Net returns:
40 USDC"] NetReturns --> Withdraw["On withdrawal:
1,040 USDC"] Withdraw --> CircleFee["Withdrawal fee:
0 USDC"] CircleFee --> Circle["Circle"] Withdraw --> NetReturn["Net amount returned:
1,040 USDC"] NetReturn --> WalletOut["Wallet"] ``` ## How fees affect your returned amount The amount returned when you withdraw may differ from your original deposit for two reasons: * **Upside**: Returns from the underlying lending protocol accumulate over time and are reflected in the value of your shares. * **Downside**: Vault fees reduce returns. In extreme market conditions, vault asset value can decrease enough to reduce your principal below what you originally deposited. The `currentBalance` field in [`getPosition`](/app-kit/tutorials/earn/check-position) always reflects the current redemption value of your shares after accounting for vault fees. # How swap fees work Source: https://docs.arc.io/app-kit/concepts/swap-fees How custom fees and provider fees apply when swapping tokens with the App Kit SDK This guide explains which fees apply when performing a swap, how funds move through a swap transaction, and best practices for [implementing custom swap fees](/app-kit/tutorials/swap/collect-swap-fee). ## Fee breakdown Two fees can apply to a swap: | Fee | When it applies | Amount | Recipient | | - | - | - | - | | Custom fee | Conditionally. When you [configure custom swap fees](/app-kit/tutorials/swap/collect-swap-fee). | Percentage you define on the swap amount; collected before the swap executes. | 90% to your fee recipient; 10% to Arc | | Provider fee | Always. On every swap. | 2 basis points (0.02%) of the swap amount after any custom fee (1 bp = 0.01%). | Swap service provider (third-party liquidity service that executes the swap) | Your end user pays the swap amount plus all applicable fees. ## How funds flow through a swap This example traces a 1,000 USDT-to-USDC swap with a 1% custom fee: The user requests a swap of 1,000 USDT to USDC. You add a 1% custom fee. On 1,000 USDT, this equals 10 USDT. The source wallet signs a transaction authorizing 1,000 USDT. The App Kit SDK collects the 10 USDT custom fee and splits it: * Arc receives 1 USDT (10% of the custom fee). * Your fee recipient receives 9 USDT (90% of the custom fee). The swap service provider deducts a provider fee of 2 basis points (0.02%) from the remaining 990 USDT. This equals 0.198 USDT. The provider executes the swap with 989.802 USDT and the user receives the equivalent output in USDC. **Net result:** Of the original 1,000 USDT, 10 USDT goes to custom fee recipients, 0.198 USDT goes to the swap service provider, and the remaining 989.802 USDT is swapped for USDC. This flow is illustrated in the following diagram: ```mermaid theme={null} flowchart TB SourceWallet["*Source wallet*#160;"] --> ApprovedAmount["Approved amount:
1,000 USDT"] ApprovedAmount --> CustomFeeDeduction["Custom fee:
-10 USDT (1%)"] ApprovedAmount --> PostFeeSwapAmount["Swap amount:
990 USDT"] CustomFeeDeduction --> ArcShare["10% of custom fee:
1 USDT"] CustomFeeDeduction --> PartnerShare["90% of custom fee:
9 USDT"] ArcShare --> Arc["*Arc*#160;"] PartnerShare --> FeeRecipient["*Your fee recipient*#160;"] PostFeeSwapAmount --> SwapService["*Swap service*#160;"] SwapService --> SwapTransaction["Swap transaction:
989.802 USDT to USDC"] SwapService --> ProviderFee["Swap service fee:
-0.198 USDT (2 bps)"] SwapTransaction --> SourceWalletUsdc["*Source wallet*#160;
receives USDC"] ProviderFee --> SwapServiceProvider["*Swap service provider*#160;"] ``` ## Best practices for custom fees Follow these best practices when implementing custom fees for swaps: * Use a fee recipient address in the same network context where the swap originates. * Return fee amounts in human-readable decimal format (for example, `0.20` instead of `200000` for 0.20 USDC). The App Kit SDK handles base-unit conversion internally. # How Unified Balance fees work Source: https://docs.arc.io/app-kit/concepts/unified-balance-fees How fees apply when spending from a Unified Balance and how funds move through a spend transaction Several fees can apply when you spend from a Unified Balance, including a [custom fee](/app-kit/tutorials/unified-balance/collect-custom-spend-fees) you can implement. Fees affect how funds move through a spend and what remains in the Unified Balance total. Deposits work differently. Same-chain deposits do not charge Unified Balance protocol fees. [Fast deposits](/app-kit/tutorials/unified-balance/use-fast-deposits) can incur [CCTP Fast Transfer](https://developers.circle.com/cctp/technical-guide#fees) and [Forwarding Service](https://developers.circle.com/cctp/concepts/forwarding-service#fees-and-execution) fees before funds land in the Unified Balance. ## Fees breakdown Each spend can include the following fees: | Fee | When it applies | Amount | Recipient | | - | - | - | - | | Custom spend fee | Conditionally. When you [implement custom spend fees](/app-kit/tutorials/unified-balance/collect-custom-spend-fees). | You define (carved from the spend amount). | 90% to your fee recipient; 10% to Arc | | Gateway protocol fee | Conditionally. On spends where the source and destination differ (crosschain). | 0.5 basis points (0.005%) of the spend amount from the Unified Balance at spend time; 0 if same blockchain. | [Circle Gateway](https://developers.circle.com/gateway) (protocol underlying Unified Balance) | | Gas | Always. On spends that execute burn intents on source blockchains. | Varies by source blockchain and network conditions; incurred per burn intent on source. | Source blockchain | | Forwarding Service fee | Conditionally. When you [use the Forwarding Service](/app-kit/tutorials/unified-balance/use-forwarding-service) for the destination mint. | Per [Forwarding Service fees](https://developers.circle.com/cctp/concepts/forwarding-service#fees-and-execution). Deducted from amount minted on destination. | Circle | ## Total balance and funds flow The following example shows what happens when a user wants 500 USDC to arrive at the destination from a Unified Balance of 1,000 USDC (previously deposited), you collect a 5 USDC custom fee on that spend, and the [Forwarding Service](/app-kit/tutorials/unified-balance/use-forwarding-service) is enabled: The user previously deposited 1,000 USDC from their wallet on the source blockchain into the Unified Balance. The user confirms a spend of 505.20 USDC from the Unified Balance. This is the amount needed to ensure exactly 500 USDC arrives at the destination after the custom fee and Forwarding Service fee are applied. See [best practices](#best-practices-for-custom-fees) for what to show the user before they confirm a spend. You deduct a 5 USDC custom fee from the spend amount. The user's source wallet signs three burn intents that move: * 500.20 USDC (the spend amount minus the 5 USDC custom fee) toward the destination mint. * 0.50 USDC (10% of the custom fee) to Arc. * 4.50 USDC (90% of the custom fee) to your fee recipient. Circle Gateway deducts a 0.025 USDC transfer fee from the Unified Balance (0.005% of 505.20 USDC). For a same-chain spend, this fee is 0. The source blockchains deduct 0.03 USDC from the Unified Balance as gas for the three burn intents. The Forwarding Service deducts its fee (0.20 USDC in this example) from the amount to be minted on the destination blockchain. The recipient's destination wallet receives 500 USDC on the destination blockchain: * 500.20 USDC minted. * Deduct 0.20 USDC Forwarding Service fee. * Net received: 500 USDC. After the spend, the user's Unified Balance total is 494.745 USDC: * Started at 1,000 USDC. * Deduct 505.20 USDC spend amount. * Deduct 0.025 USDC Gateway transfer fee. * Deduct 0.03 USDC gas. * Remaining: 494.745 USDC. This flow and Unified Balance running total is illustrated in the following diagram. ```mermaid theme={null} flowchart TB subgraph UB["Unified Balance total"] direction TB U0["1,000 USDC"] U1["494.80 USDC"] U2["494.775 USDC"] U3["494.745 USDC"] U0 -->|"Spend deducted:
-505.20 USDC"| U1 U1 -->|"Gateway fee:
-0.025 USDC"| U2 U2 -->|"Gas:
-0.030 USDC"| U3 end SPEND["Spend amount:
**505.20 USDC**"] B500["Spend amount:
**500.20 USDC**"] B05["10% of custom fee:
**0.50 USDC**"] B45["90% of custom fee:
**4.50 USDC**"] U0 --> SPEND SPEND --> B500 SPEND --> B05 SPEND --> B45 FWD["Forwarding fee:
**-0.20 USDC**"] GW["*Gateway*#160;"] OUT500["Spend amount:
**500 USDC**"] DEST["*Destination wallet*#160;"] B500 --> FWD FWD --> GW B500 --> OUT500 OUT500 --> DEST B05 --> ARC["*Arc*#160;"] B45 --> RECIP["*Your fee recipient*#160;"] ``` ## Best practices for custom fees Follow these best practices when implementing custom fees: * Use a fee recipient address on the source blockchain. Do not use an address on the destination. * Calculate the spend amount from the amount the user wants to receive at the destination. To receive a specific amount, the user must spend more than that from the Unified Balance to cover fees. * Before the user confirms a spend, show: * Spend summary: spend amount, fee breakdown (custom fee and Forwarding Service fee when applicable), and amount received at the destination. * Unified Balance summary: starting balance, each deduction (spend amount, Gateway transfer fee when applicable, gas), and remaining balance. ```text Example UI display theme={null} Spend amount: 505.20 USDC Forwarding Service fee: - 0.20 USDC Custom fee: - 5.00 USDC Amount received at destination: 500.00 USDC Unified Balance: 1,000.00 USDC Spend amount: - 505.20 USDC Gateway fee: - 0.025 USDC Gas (estimated): - 0.03 USDC Remaining balance: 494.745 USDC ``` * Return human-readable decimal strings. For example, return `"10"` rather than `"10000000"` for 10 USDC. The App Kit SDK handles base-unit conversion internally. * Validate that the user's Unified Balance can cover the spend amount, the Gateway protocol fee, and gas. For gas and fee estimates, see [estimate spend fees](/app-kit/tutorials/unified-balance/estimate-spend-fees). Example check: ```typescript TypeScript theme={null} // Example: calculate required spend from a target destination amount and validate balance const targetDestinationAmount = 500; // USDC the user wants to arrive at the destination const forwardingServiceFee = 0.2; // deducted from amount minted at destination const customFee = 5; // your custom fee in USDC const spendAmount = targetDestinationAmount + forwardingServiceFee + customFee; // 505.20 USDC const sameChain = false; // true when source and destination blockchain are the same const gatewayFee = sameChain ? 0 : spendAmount * 0.00005; // 0.005% when crosschain const gasEstimate = 0.05; // replace with a blockchain-appropriate estimate; the walkthrough above uses 0.03 for illustration const userBalance = 1000; // placeholder; in production parse totalConfirmedBalance from kit.unifiedBalance.getBalances const requiredBalance = spendAmount + gatewayFee + gasEstimate; if (userBalance < requiredBalance) { throw new Error(`Insufficient balance. Need ${requiredBalance} USDC`); } // requiredBalance is about 505.275 USDC here (505.20 spend + 0.025 gateway + 0.05 gas estimate) ``` # App Kit SDK: Earn Source: https://docs.arc.io/app-kit/earn Integrate Earn opportunities for USDC and EURC into your app using the App Kit SDK The [App Kit SDK](/app-kit) includes the Earn capability that lets you offer opportunities to earn USDC and EURC. Vaults are powered by underlying DeFi lending protocols. Earn handles vault discovery and share-to-asset conversion, so you don't need to integrate each vault contract directly. Earn is available for USDC and EURC on Arc. ## How it works When you deposit USDC into an Earn vault, you receive vault shares. The shares appreciate over time as the underlying lending protocol generates returns. To exit, you redeem your shares back to USDC, which by then is worth your deposit plus any returns from the protocol. ```mermaid theme={null} flowchart LR Wallet["Wallet:
1,000 USDC"] -->|deposit| Shares["Vault shares:
Appreciate over time"] Shares -->|withdraw| Out["Wallet:
1,040 USDC
(deposit + returns)"] ``` Earn is non-custodial: deposited USDC lives in the vault contract, and integrators choose which vaults to deposit into. ## Quick look This code snippet fetches vault details and deposits into a vault: ```typescript TypeScript theme={null} // Fetch vault details const { vaults } = await kit.earn.exploreVaults({ chain: "Arc_Testnet", sortBy: "apy", }); // Deposit into the first vault const result = await kit.earn.deposit({ from: { adapter, chain: "Arc_Testnet" }, vaultAddress: vaults[0].vaultAddress, amount: "10.00", }); ``` For a complete end-to-end flow, follow the [quickstart](/app-kit/quickstarts/earn-deposit). ## API key Earn accepts an optional [Circle API key](https://developers.circle.com/api-reference/keys) obtained from the [Circle Console](https://console.circle.com/api-keys). Pass one as `apiKey` in the operation config. Without one, requests share a rate limit. Production traffic typically needs a key to avoid hitting it. For a restricted key, select the "App Kits" product permission when creating the key. API keys are environment specific. Testnet and mainnet each need their own key. Keep the API key server-side. The App Kit SDK rejects an API key passed from a browser context. ## Installation [Install the App Kit SDK](/app-kit/tutorials/installation) to use Earn. If you only need Earn and don't want to install the full App Kit SDK, install the standalone Earn Kit for your preferred adapter: ```bash npm theme={null} npm install @circle-fin/earn-kit @circle-fin/adapter-viem-v2 viem ``` ```bash yarn theme={null} yarn add @circle-fin/earn-kit @circle-fin/adapter-viem-v2 viem ``` ```bash npm theme={null} npm install @circle-fin/earn-kit @circle-fin/adapter-ethers-v6 ethers ``` ```bash yarn theme={null} yarn add @circle-fin/earn-kit @circle-fin/adapter-ethers-v6 ethers ``` ```bash npm theme={null} npm install @circle-fin/earn-kit @circle-fin/adapter-circle-wallets ``` ```bash yarn theme={null} yarn add @circle-fin/earn-kit @circle-fin/adapter-circle-wallets ``` # How-to: Add collateral or borrow more against a loan Source: https://docs.arc.io/app-kit/howtos/borrow/add-collateral-or-borrow-more Post more cirBTC to strengthen an open loan, or borrow more USDC against the same loan Once a loan is open, post more cirBTC to strengthen its [health factor](/app-kit/concepts/borrow-loan-health#health-factor), or borrow more USDC for additional liquidity. Adding collateral lowers LTV while borrowing more raises it. ## Prerequisites Before you begin, ensure that you've: * [Installed the App Kit SDK](/app-kit/tutorials/installation) and an adapter * [Originated a loan](/app-kit/quickstarts/originate-a-loan) with the connected wallet ## Add collateral Call `addCollateral` with the loan's `loanId` and the cirBTC amount to add. Adding collateral improves the loan's health factor but does not change its debt. ```typescript TypeScript theme={null} const result = await kit.borrow.addCollateral({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", collateralAmount: "0.05", }); if (result.status === "confirmed") { console.log("Collateral added:", result.collateralAdded.amount); } ``` To see the resulting health factor before signing, see [Preview Borrow operations](/app-kit/howtos/borrow/preview-a-loan-operation). ## Borrow more Call `borrow` with the loan's `loanId` (not a `marketId`) and the additional USDC amount. The SDK sizes any additional collateral needed to keep the loan healthy after the borrow and pulls it from the wallet's cirBTC balance in the same batch. ```typescript TypeScript theme={null} const result = await kit.borrow.borrow({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", borrowAmount: "500.0", }); ``` To preview the borrow before signing, see [Preview a borrow](/app-kit/howtos/borrow/preview-a-loan-operation#preview-a-borrow). # How-to: Configure your integrator fee Source: https://docs.arc.io/app-kit/howtos/borrow/configure-integrator-fee Set the integrator fee rate and recipient address that apply to every loan origination The integrator fee applies to every loan origination once set, so you configure it once rather than passing it per call. For an overview of how the fee fits into loan proceeds, see [How borrow fees work](/app-kit/concepts/borrow-fees). If you use this feature, Arc keeps 10% of the custom fee you collect from your end users. ## Prerequisites Before you begin, ensure that you've: * [Installed the App Kit SDK](/app-kit/tutorials/installation) * [Configured an adapter](/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. ## Set your fee rate and recipient Call `kit.borrow.setIntegratorConfig` with your fee in basis points and the address that receives the fee. This call requires an API key from the [Circle Console](https://console.circle.com/api-keys), passed as `apiKey` on the operation config. Keep the API key server-side. It produces no wallet signature. ```typescript TypeScript theme={null} const config = await kit.borrow.setIntegratorConfig({ integratorFeeBps: 25, integratorFeeAddress: "0x1111111111111111111111111111111111111111", config: { apiKey: process.env.CIRCLE_API_KEY as string }, }); ``` `integratorFeeBps` accepts values from `0` to `10000`. ## Turn the fee off Pass `0` with a `null` address to turn off fee collection: ```typescript TypeScript theme={null} await kit.borrow.setIntegratorConfig({ integratorFeeBps: 0, integratorFeeAddress: null, config: { apiKey: process.env.CIRCLE_API_KEY as string }, }); ``` ## Read your current configuration Call `kit.borrow.getIntegratorConfig` to read the stored rate and recipient: ```typescript TypeScript theme={null} const config = await kit.borrow.getIntegratorConfig({ config: { apiKey: process.env.CIRCLE_API_KEY as string }, }); console.log(config.integratorFeeBps, config.integratorFeeAddress); ``` Before the first `setIntegratorConfig` call, `getIntegratorConfig` throws [`CONFIG_NOT_FOUND`](/app-kit/references/borrow-error-handling#input-errors). # How-to: Monitor a loan Source: https://docs.arc.io/app-kit/howtos/borrow/monitor-a-loan Read a wallet's loans and register webhooks for asynchronous health updates Monitor a Borrow loan by reading its current state on demand or by registering a webhook to receive updates when the loan enters a different [health band](/app-kit/concepts/borrow-loan-health#health-factor-bands). ## Prerequisites Before you begin, ensure that you've: * [Installed the App Kit SDK](/app-kit/tutorials/installation) and an adapter * Opened a loan whose owner matches the connected wallet ## Check loan state Call `getLoans` with a wallet address and blockchain to fetch a page of the wallet's loans (up to 100 per call), along with each loan's collateral, debt, health factor, band, LTV, and liquidation price. Pass the returned `pagination.pageAfter` cursor back to fetch the next page. If you already have a `loanId`, call `kit.borrow.getPosition({ loanId })` to read that specific loan. It returns the same shape, except the owner field is named `wallet` instead of `walletAddress`. ```typescript TypeScript theme={null} const { loans } = await kit.borrow.getLoans({ walletAddress: "0x...", chain: "Arc_Testnet", }); console.dir(loans, { depth: null }); ``` Each `Loan` looks like: ```text theme={null} { loanId: "11111111-1111-4111-8111-111111111111", chain: "Arc_Testnet", marketId: "0x...", walletAddress: "0x...", dataStatus: "READY", status: "active", collateral: { token: "cirBTC", tokenAddress: "0x...", amount: "0.05", decimals: 8 }, borrowed: { token: "USDC", tokenAddress: "0x...", amount: "1000.03", decimals: 6 }, principalBorrowed: { token: "USDC", tokenAddress: "0x...", amount: "1000.00", decimals: 6 }, accruedInterest: { token: "USDC", tokenAddress: "0x...", amount: "0.03", decimals: 6 }, borrowApy: 0.032, ltv: 0.42, healthFactor: 2.05, healthFactorBand: "SAFE", liquidationPrice: { token: "USDC", tokenAddress: "0x...", amount: "23230.00", decimals: 6 } } ``` For what the health fields mean, see [How loan health works](/app-kit/concepts/borrow-loan-health). ## Subscribe to health updates Register a webhook to receive updates when a loan enters a different health band. ### Register a webhook Call `registerWebhook` with the loan's `loanId` and your `webhookUrl`. The connected wallet must equal the loan's persisted owner. ```typescript TypeScript theme={null} const result = await kit.borrow.registerWebhook({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", webhookUrl: "https://example.com/borrow-webhook", }); ``` The wallet signs the registration. There is no onchain transaction. ### Unregister a webhook Omit `webhookUrl` (or pass `null`) to remove a loan's current webhook. ```typescript TypeScript theme={null} await kit.borrow.registerWebhook({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", }); ``` # How-to: Pay down a loan Source: https://docs.arc.io/app-kit/howtos/borrow/pay-down-a-loan Repay USDC debt, close a loan in full, and withdraw cirBTC collateral from a Borrow loan Pay down a Borrow loan with a partial repayment, a full close, or a collateral withdrawal. Each operation addresses the loan by `loanId`. ## Prerequisites Before you begin, ensure that you've: * [Installed the App Kit SDK](/app-kit/tutorials/installation) and an adapter * Opened a loan with debt or collateral to withdraw ## Repay part of a loan Call `repay` with the loan's `loanId` and the USDC amount. The SDK rejects the request before signing if `repayAmount` exceeds outstanding debt. Use `closeLoan` for a full payoff. ```typescript TypeScript theme={null} const result = await kit.borrow.repay({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", repayAmount: "100.0", }); ``` ## Close a loan in full Call `closeLoan` to repay all outstanding debt and withdraw all collateral in a single atomic batch. The SDK uses share-based payoff math and may pull more USDC than needed, up to your `slippageBps` bound, refunding any surplus. ```typescript TypeScript theme={null} const result = await kit.borrow.closeLoan({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", }); if (result.status === "confirmed") { console.log("Repaid:", result.amountRepaid.amount); console.log("Collateral withdrawn:", result.collateralWithdrawn.amount); } ``` To preview a full close before signing, see [Preview Borrow operations](/app-kit/howtos/borrow/preview-a-loan-operation#preview-a-full-close). ## Withdraw collateral Call `withdrawCollateralRepayIfNeeded` to release cirBTC from a loan. The SDK sizes any repayment needed to keep the loan healthy after the withdrawal and pays it from the wallet's USDC balance in the same atomic batch. If the repayment covers the entire debt, the withdrawal closes the loan. ```typescript TypeScript theme={null} const result = await kit.borrow.withdrawCollateralRepayIfNeeded({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", collateralAmount: "0.01", }); ``` To preview a collateral withdrawal before signing, see [Preview Borrow operations](/app-kit/howtos/borrow/preview-a-loan-operation#preview-withdrawing-collateral). # How-to: Preview Borrow operations Source: https://docs.arc.io/app-kit/howtos/borrow/preview-a-loan-operation Inspect the expected outcome and health impact of a Borrow operation before submitting a transaction Preview a Borrow operation with its paired quote method, or size a borrow with `getRequiredCollateral` or `getMaxBorrow`. For the full list of quote methods and their return shapes, see [Borrow quote methods](/app-kit/references/borrow-quote-methods). ## Prerequisites Before you begin, ensure that you've: * [Installed the App Kit SDK](/app-kit/tutorials/installation) * [Configured an adapter](/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. ## Preview a borrow `getBorrowQuote` handles both opening a new loan and borrowing more against an existing one. To preview opening a new loan, pass the `walletAddress`, `chain`, and `marketId`: ```typescript TypeScript theme={null} const quote = await kit.borrow.getBorrowQuote({ chain: "Arc_Testnet", walletAddress: "0x...", marketId: "0x1111111111111111111111111111111111111111111111111111111111111111", borrowAmount: "1000.0", }); ``` To preview borrowing more against an existing loan, pass its `loanId`. The service resolves the owner, blockchain, and market from the loan: ```typescript TypeScript theme={null} const quote = await kit.borrow.getBorrowQuote({ loanId: "11111111-1111-4111-8111-111111111111", borrowAmount: "500.0", }); ``` Either call returns the `collateralAmount` the borrow would pull, the `loanAssetAmount` it would deliver, the market's `borrowApy`, `fees`, `gasFees`, and the position's `resultingHealthFactor`, `resultingLtv`, `resultingBand`, and `liquidationPrice`. ## Preview add collateral Quote the effect of adding cirBTC to an existing loan: ```typescript TypeScript theme={null} const quote = await kit.borrow.getAddCollateralQuote({ chain: "Arc_Testnet", loanId: "11111111-1111-4111-8111-111111111111", collateralAmount: "0.05", }); ``` The quote returns `fees`, `gasFees`, and the position's resulting health fields after the collateral addition. ## Preview a repayment Quote a partial repayment. The SDK rejects repayments that exceed the loan's outstanding debt at quote time. ```typescript TypeScript theme={null} const quote = await kit.borrow.getRepayQuote({ chain: "Arc_Testnet", loanId: "11111111-1111-4111-8111-111111111111", repayAmount: "100.0", }); ``` The quote returns the requested `repayAmount`, `fees`, `gasFees`, and resulting health fields. ## Preview a full close Quote a full payoff. Because `closeLoan` uses share-based payoff math and may pull more USDC than needed (up to your `slippageBps` bound), the quote's `bundledRepayment` reflects the upper bound of what the transaction may pull. ```typescript TypeScript theme={null} const quote = await kit.borrow.getCloseLoanQuote({ chain: "Arc_Testnet", loanId: "11111111-1111-4111-8111-111111111111", }); ``` The quote returns `bundledRepayment` (the upper bound of USDC the close may pull), `collateralAmount` (the cirBTC returned), `fees`, `gasFees`, and resulting health fields. ## Preview withdrawing collateral Quote the effect of withdrawing collateral, including the USDC required for the internal repay leg: ```typescript TypeScript theme={null} const quote = await kit.borrow.getWithdrawCollateralRepayIfNeededQuote({ chain: "Arc_Testnet", loanId: "11111111-1111-4111-8111-111111111111", collateralAmount: "0.01", }); ``` The quote returns `collateralAmount` (the cirBTC released), `bundledRepayment` (the upper bound of USDC needed for the internal repay leg), `fees`, `gasFees`, and resulting health fields. Between preview and execution, the exact repayment amount can shift slightly as interest accrues on the loan. `slippageBps` (default `300`, or 3%) caps how much larger the actual repayment can be than the previewed amount. Pass a smaller `slippageBps` to tighten the ceiling, or omit it for the default. ## Size a borrow Use `getRequiredCollateral` when the user knows how much USDC they want and what health factor they want to land on: ```typescript TypeScript theme={null} const sizing = await kit.borrow.getRequiredCollateral({ chain: "Arc_Testnet", marketId: "0x1111111111111111111111111111111111111111111111111111111111111111", borrowAmount: "1000.0", targetHealthFactor: 1.5, }); ``` Use `getMaxBorrow` when the user has a fixed collateral amount and wants to see the largest borrow it supports: ```typescript TypeScript theme={null} const ceiling = await kit.borrow.getMaxBorrow({ chain: "Arc_Testnet", marketId: "0x1111111111111111111111111111111111111111111111111111111111111111", collateralAmount: "0.05", }); ``` Both return the sizing value along with `resultingHealthFactor` and `liquidationPrice`. They do not return `fees`, `gasFees`, `resultingLtv`, or `resultingBand`. # How-to: Track operation progress Source: https://docs.arc.io/app-kit/howtos/borrow/track-operation-progress Subscribe to phase events on a loan operation to drive a progress UI or log where an operation stopped `kit.borrow.borrow`, `kit.borrow.repay`, `kit.borrow.addCollateral`, `kit.borrow.withdrawCollateralRepayIfNeeded`, and `kit.borrow.closeLoan` run through several phases before they confirm onchain. Subscribe to those phases with `kit.on` to update a progress UI in real time, log operational metrics, or diagnose where an operation stopped when it fails. For the phase names and firing rules, see [Borrow phase events](/app-kit/references/borrow-phase-events). ## Prerequisites Before you begin, ensure that you've: * [Installed the App Kit SDK](/app-kit/tutorials/installation) * [Configured an adapter](/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. ## Subscribe to one phase Pass a `borrow.` name to `kit.on` to receive only that phase's events. Each payload carries `operation`, `method` (the phase name), and a `values` object with the phase `state` (`pending`, `success`, or `error`): ```typescript TypeScript theme={null} kit.on("borrow.execute", (payload) => { if (payload.values.state === "success") { console.log("Confirmed in", payload.values.txHash); } if (payload.values.state === "error") { console.error("Failed:", payload.values.errorMessage); } }); await kit.borrow.borrow({ from: { adapter, chain: "Arc_Testnet" }, marketId: "0x1111...1111", borrowAmount: "1000.0", }); ``` Only the `execute` phase carries a `txHash` when the batch confirms. For which phases carry hashes and what a stalled `pending` state means, see [Borrow phase events](/app-kit/references/borrow-phase-events). ## Subscribe to every Borrow phase Pass `"borrow.*"` to receive events for every phase across every loan operation. A bare `"*"` also matches, but it delivers bridge, earn, and unified balance events too, so filter on the payload if you only want Borrow. ```typescript TypeScript theme={null} kit.on("borrow.*", ({ operation, method, values }) => { console.log(operation, method, values.state); }); ``` ## Unsubscribe Pass the same handler reference to `kit.off` to unregister it. Inline arrow functions create a new reference each call, so store the handler if you plan to unsubscribe: ```typescript TypeScript theme={null} import type { BorrowActions } from "@circle-fin/app-kit"; const handler = ({ values }: BorrowActions["execute"]) => console.log(values.state); kit.on("borrow.execute", handler); kit.off("borrow.execute", handler); ``` # How-to: Use idempotency keys Source: https://docs.arc.io/app-kit/howtos/borrow/use-idempotency-keys Attach an idempotency key to a loan operation so the same user intent lands onchain at most once An idempotency key lets the SDK recognize when two submissions represent the same user intent and collapse them into one onchain outcome. Use it whenever the same intent might reach the service more than once. Common cases: * A caught error you decide to retry across processes. * A user who double-clicks the submit button. * A queue worker that replays after a deploy. * A serverless function that times out and reruns. * A webhook consumer that fires the same job twice. `kit.borrow.borrow`, `kit.borrow.repay`, `kit.borrow.addCollateral`, `kit.borrow.withdrawCollateralRepayIfNeeded`, and `kit.borrow.closeLoan` all accept an `idempotencyKey` on the call. ## Prerequisites Before you begin, ensure that you've: * [Installed the App Kit SDK](/app-kit/tutorials/installation) * [Configured an adapter](/app-kit/tutorials/adapter-setups) These are required so any example below runs with a valid `kit` and `adapter`. ## Generate one key per user intent Generate a UUID when the user first submits the intent, persist it alongside the request, and reuse the same key on every later submission of that intent: ```typescript TypeScript theme={null} import { randomUUID } from "node:crypto"; const idempotencyKey = randomUUID(); const result = await kit.borrow.repay({ from: { adapter, chain: "Arc_Testnet" }, loanId: "11111111-1111-4111-8111-111111111111", repayAmount: "100.0", idempotencyKey, }); ``` The service binds the key to the request parameters and to the Circle-signed execution it produces. A same-key replay behaves differently depending on the execution's state: * While the execution is unsettled and hasn't expired (default TTL: 1 minute), the replay returns the same signed execution, which gets resubmitted onchain. * If the execution has already settled onchain, the service returns `IDEMPOTENCY_KEY_ALREADY_EXECUTED`. Read this as "the first attempt succeeded, don't submit again." * If the signed execution's deadline has passed, the service returns `SIGNED_BUNDLE_EXPIRED` and the request needs a new key. For delays longer than the signed execution's TTL (queue workers replaying after a deploy, serverless functions rerunning much later), reuse the same key if the first attempt has already settled onchain, and mint a new key if it hasn't. To resume a failed operation from the same process, use [`kit.borrow.retry`](/app-kit/references/borrow-error-handling#retry-a-failed-operation) instead. # App Kit SDK: Onramp Source: https://docs.arc.io/app-kit/onramp Embed the Onramp widget so users can buy USDC or EURC on Arc with fiat. The [App Kit SDK](/app-kit) includes the Onramp capability, an embeddable widget that lets your users buy USDC or EURC on Arc with fiat. The widget is hosted on Arc and handles KYC, payment processing, and settlement. You can try the Onramp flow at [portal.arc.io/deposit/fiat](https://portal.arc.io/deposit/fiat). For local development and testing, use the [sandbox environment](/app-kit/references/onramp-hosting-requirements#sandbox). Onramp requires an [API key](#api-key) to integrate. Certain [payment methods](#supported-payment-methods-and-countries) are only available to end users after completing KYB. ## How it works Your server creates a session for a specific user and destination wallet, then your client mounts the Onramp widget with that session. Sessions are valid for 30 minutes. The widget walks the user through the purchase, then delivers the crypto to the wallet address you specified. Lifecycle events flow back to your page so you can update your UI. ```mermaid theme={null} flowchart LR S[Your server] -->|"creates session"| C[Your client] C -->|"mounts widget with session"| W[Onramp widget] W -->|"crypto"| A[(User's destination wallet)] ``` ## Supported payment methods and countries Debit card, Apple Pay, and Google Pay are available for USDC and EURC on Arc in the US, UK, select EU countries, and other countries worldwide. Enabling these methods requires completing KYB in the [Circle Console](https://console.circle.com/app-kits/). To accept them, your server must also pass [`referrerDomain`](/app-kit/tutorials/onramp/customize-session-minting#allow-your-page-to-embed-the-widget) when creating a session. Bank transfer is available for USDC on Arc in select US states and select EU countries. Credit cards aren't supported. ## API key Onramp requires a [Circle API key](https://developers.circle.com/api-reference/keys) to authenticate your server. Obtain one from the [Circle Console](https://console.circle.com/api-keys) and keep it server-side only, since it grants full access to your Onramp integration. Pass it as `apiKey` in your Onramp config. API keys are environment specific. Sandbox and production each need their own key. ## Quick look This example fetches a session from your backend and mounts the Onramp widget inline: ```typescript TypeScript theme={null} // Fetch a session minted on your server const session = await kit.onramp.fetchSession({ url: "/api/onramp/sessions", body: { appUserId: "user-123", destinationAddress: "USER_WALLET_ADDRESS", }, }); // Mount the widget inline const widget = kit.onramp.mountIframe({ session, container: document.getElementById("onramp-root")!, onDepositSettled: ({ payload }) => console.log("settled", payload), }); ``` Onramp also requires a session route on your server. Follow the [quickstart](/app-kit/quickstarts/onramp-embed-widget) for the end-to-end flow. ## Installation [Install the App Kit SDK](/app-kit/tutorials/installation) to use Onramp. If you only need Onramp and don't want to install the full App Kit SDK, install the standalone Onramp Kit. ```bash npm theme={null} npm install @circle-fin/onramp-kit ``` ```bash yarn theme={null} yarn add @circle-fin/onramp-kit ``` Onramp does not require a wallet adapter. The Onramp widget handles payment and delivery to the destination wallet. # Quickstart: Bridge tokens across blockchains Source: https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains Use the App Kit SDK to bridge tokens between blockchains with a browser wallet or Circle Wallets adapter Bridge tokens between blockchains with the wallet model that matches your application. Use a browser wallet when the end user signs in the client, or use Circle Wallets when you manage developer-controlled wallets through Circle. Use this flow to bridge tokens with a connected browser wallet. The example uses Ethereum Sepolia and Arc Testnet. ## Prerequisites Before you begin, ensure that you've: * Installed [Node.js v22+](https://nodejs.org/). * Installed a browser wallet such as [MetaMask](https://metamask.io/) and added the [Ethereum Sepolia](https://chainlist.org/chain/11155111) and [Arc Testnet](https://docs.arc.io/arc/references/connect-to-arc#wallet-setup) networks. * Funded your wallet with testnet tokens you want to bridge. Circle-issued tokens can be obtained from the [Circle Faucet](https://faucet.circle.com/). * Funded your wallet with ETH for Ethereum Sepolia from a [public faucet](https://www.alchemy.com/faucets/ethereum-sepolia). ## Step 1. Set up the project ### 1.1. Create the project and install dependencies Create a new directory, install the App Kit packages, and add local browser demo tooling: ```bash Shell theme={null} mkdir app-kit-bridge-browser-wallet cd app-kit-bridge-browser-wallet npm init -y npm pkg set type=module npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem npm install --save-dev typescript vite ``` Only need to bridge and want a lighter install than the full App Kit SDK? Install the standalone Bridge Kit instead: `@circle-fin/bridge-kit` ### 1.2. Configure TypeScript (optional) This step is optional. It helps prevent missing types in your IDE or editor. Create a `tsconfig.json` file configured for ESM and Node: ```bash Shell theme={null} cat <<'EOF' > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["node"] } } EOF ``` ## Step 2. Connect a browser wallet This step shows the core browser wallet integration flow: discover an [`EIP-6963`](https://eips.ethereum.org/EIPS/eip-6963) provider, create an App Kit adapter from the selected provider, and pass that adapter into an App Kit SDK method. The snippets below keep wallet discovery, wallet connection, and adapter setup in small helper functions for readability. ### 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 TypeScript theme={null} 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()]; } ``` ### 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 TypeScript theme={null} 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. ### 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 TypeScript theme={null} 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. ## Step 3. Bridge tokens ### 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 TypeScript theme={null} import { AppKit } from "@circle-fin/app-kit"; const kit = new AppKit(); async function bridgeWithBrowserWallet() { 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; } ``` This example omits `token`, which causes the bridge call to default to USDC. A [non-USDC token](https://developers.circle.com/cctp/expanded-assets) is any configured ERC-20, such as EURC. To bridge a non-USDC token, pass a [token supported by Bridge](/app-kit/references/supported-blockchains#supported-tokens) (such as `EURC`) as `token`. For any other configured asset, pass its `tokenId` instead. ```typescript TypeScript theme={null} import { AppKit } from "@circle-fin/app-kit"; const kit = new AppKit(); async function bridgeWithBrowserWallet() { const { adapter, connectedAddress, walletName } = await connectBrowserWallet(); let result = await kit.bridge({ from: { adapter, chain: "Ethereum_Sepolia" }, to: { adapter, chain: "Arc_Testnet" }, amount: "1.00", token: "EURC", // or "wETH" | "cirBTC" | bytes32 tokenId }); if (result.state === "error") { result = await kit.retryBridge(result, { from: adapter, to: adapter, }); } console.log(`Submitted bridge with ${walletName}`, { connectedAddress, result, }); return result; } ``` These routes are EVM-to-EVM only. Both blockchains must support [CCTP for non-USDC](https://developers.circle.com/cctp/expanded-assets/concepts/supported-chains-and-domains), and the token must be configured on both. Token deployment on a chain does not mean every bridge route is live yet. To see which routes a token supports, query Iris with that token's `tokenId` (sandbox example for EURC): ```bash theme={null} curl "https://iris-api-sandbox.circle.com/v2/cctpx/tokens?tokenid=0x2587821a0ee7daa174b95436b5dab1731cfa1844775b010217d3c0dd02a4eecd" ``` `amount` is a human-readable decimal string in whole-token units (same shape as USDC). Token decimals differ. For example, EURC uses 6, cirBTC uses 8, and wETH uses 18. CCTP for non-USDC tokens does not support [custom bridge fees](/app-kit/tutorials/bridge/collect-bridge-fee). To inspect the quoted fee before you submit, [estimate costs](/app-kit/tutorials/bridge/estimate-costs) and pass the returned `quote` into `kit.bridge()`. 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. ### 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 TypeScript theme={null}
    kit.on("*", (payload) => {
      console.log("Action:", payload);
    });
    ```

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

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

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

    ```bash Shell theme={null}
    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://explorer.testnet.arc.io/tx/0xdeadbeefcafebabe1234567890abcdef1234567890abcdef1234567890abcd",
      },
      method: "approve",
    }
    ```

    ## 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](/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.

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

    ### Add the Solana dependencies

    Add the Solana adapter dependency to the same project:

    ```bash Shell theme={null}
    npm install @circle-fin/adapter-solana
    ```

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

    #### 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 TypeScript theme={null}
    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,
      };
    }
    ```

    #### 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 TypeScript theme={null}
    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,
      };
    }
    ```

    #### 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 TypeScript theme={null}
    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;
    }
    ```

    #### Retry a failed bridge attempt

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

    ```typescript TypeScript theme={null}
    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.

    #### 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 TypeScript theme={null}
    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](/app-kit/tutorials/bridge/collect-bridge-fee), use the
      [Forwarding Service](/app-kit/tutorials/bridge/use-forwarding-service), or
      [estimate gas and provider fees](/app-kit/tutorials/bridge/estimate-costs)
      before bridging. Proceed only if the cost works for you.
    

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

    ```bash Shell theme={null}
    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"`.
    

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

    ## Step 1. Set up the project

    ### 1.1. Create the project and install dependencies

    Create a project directory, initialize a Node.js project with an ES module type
    and start script, then install runtime and dev dependencies:

    ```bash Shell theme={null}
    mkdir app-kit-bridge-circle-wallets
    cd app-kit-bridge-circle-wallets
    npm init -y
    npm pkg set type=module
    npm pkg set scripts.start="tsx --env-file=.env index.ts"
    npm install @circle-fin/app-kit @circle-fin/adapter-circle-wallets tsx @solana/kit
    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`
    

    ### 1.2. Configure TypeScript (optional)

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

    Create a `tsconfig.json` file configured for ESM and Node:

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

    ### 1.3. Set environment variables

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

    ```bash Shell theme={null}
    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:

    ```text .env theme={null}
    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.
    

    ## Step 2. Bridge tokens

    ### 2.1. Create the script

    Create an `index.ts` file in the project directory and add the following code.

    
      Using a different blockchain as the source or destination? Change the `chain`
      values in `kit.bridge()` and ensure the source wallet has USDC and enough native
      tokens for gas.
    

    
      
        ```typescript TypeScript theme={null}
        // 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 });
        ```
      

      
        A [non-USDC token](https://developers.circle.com/cctp/expanded-assets) is any
        configured ERC-20, such as EURC.

        To bridge a non-USDC token, pass a
        [token supported by Bridge](/app-kit/references/supported-blockchains#supported-tokens)
        (such as `EURC`) as `token`. For any other configured asset, pass its `tokenId`
        instead.

        ```typescript TypeScript theme={null}
        // 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: "Ethereum_Sepolia",
            address: process.env.EVM_WALLET_ADDRESS!,
          },
          to: {
            adapter,
            chain: "Arc_Testnet",
            address: process.env.EVM_WALLET_ADDRESS!,
          },
          amount: "1.00",
          token: "EURC", // or "wETH" | "cirBTC" | bytes32 tokenId
        });

        console.dir(result, { depth: null, colors: true });
        ```

        These routes are EVM-to-EVM only. Both blockchains must support
        [CCTP for non-USDC](https://developers.circle.com/cctp/expanded-assets/concepts/supported-chains-and-domains),
        and the token must be configured on both.

        Token deployment on a chain does not mean every bridge route is live yet. To see
        which routes a token supports, query Iris with that token's `tokenId` (sandbox
        example for EURC):

        ```bash theme={null}
        curl "https://iris-api-sandbox.circle.com/v2/cctpx/tokens?tokenid=0x2587821a0ee7daa174b95436b5dab1731cfa1844775b010217d3c0dd02a4eecd"
        ```

        `amount` is a human-readable decimal string in whole-token units (same shape as
        USDC). Token decimals differ. For example, EURC uses 6, cirBTC uses 8, and wETH
        uses 18.

        
          CCTP for non-USDC tokens does not support
          [custom bridge fees](/app-kit/tutorials/bridge/collect-bridge-fee). To inspect
          the quoted fee before you submit,
          [estimate costs](/app-kit/tutorials/bridge/estimate-costs) and pass the returned
          `quote` into `kit.bridge()`.
        
      
    

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

    ### 2.2. Run the script

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

    ```bash Shell theme={null}
    npm run start
    ```

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

    ```bash Shell theme={null}
    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://explorer.testnet.arc.io/tx/0xb4a6efb91a3a714822185b9310e54d0806040d38ef1b5282a9279522f09e6ccd",
      },
      method: "approve",
    }
    ```
  



# Quickstart: Deposit into an Earn vault
Source: https://docs.arc.io/app-kit/quickstarts/earn-deposit

Discover available vaults and deposit USDC into one on Arc Testnet

Deposit USDC into an Earn vault on Arc Testnet. Returns are generated by the
underlying lending protocol. This quickstart uses Circle Wallets, but you can
use any compatible [adapter setup](/app-kit/tutorials/adapter-setups).

## Prerequisites

Before you begin, ensure that you've:

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


  Earn operations work without an API key. For higher rate limits, pass an
  optional [API key](/app-kit/earn#api-key) as `apiKey` in the operation config.
  Keep the API key server-side. The App Kit SDK rejects an API key passed from a
  browser context.

  
    ```typescript TypeScript theme={null}
    const result = await kit.earn.deposit({
      from: { adapter, chain: "Arc_Testnet" },
      vaultAddress: "0x...",
      amount: "10.00",
      config: { apiKey: process.env.API_KEY as string },
    });
    ```
  


## Step 1. Set up the project

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

```bash Shell theme={null}
# Set up your directory and initialize a Node.js project
mkdir app-kit-earn-deposit-circle-wallets
cd app-kit-earn-deposit-circle-wallets
npm init -y
npm pkg set type=module

# Set up run scripts
npm pkg set scripts.explore-vaults="tsx --env-file=.env explore-vaults.ts"
npm pkg set scripts.deposit="tsx --env-file=.env deposit.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 Earn and want a lighter install than the full App Kit? Install the
  standalone package instead: `@circle-fin/earn-kit`


### 1.2. Configure TypeScript (optional)


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


Create a `tsconfig.json` file configured for ESM and Node:

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

### 1.3. Set environment variables

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

```bash Shell theme={null}
touch .env
```

Add your credentials. Replace `YOUR_API_KEY` with your Circle Developer API key,
`YOUR_ENTITY_SECRET` with your entity secret:

```text .env theme={null}
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.


## Step 2. Discover available vaults

`kit.earn.exploreVaults` returns the vaults available on a chain along with each
vault's address, current APY, fees, and status. Use it to pick a vault to
deposit into.

### 2.1. Create the discovery script

Create an `explore-vaults.ts` file:

```typescript explore-vaults.ts theme={null}
import { AppKit } from "@circle-fin/app-kit";

const kit = new AppKit();

async function main() {
  const { vaults, pagination } = await kit.earn.exploreVaults({
    chain: "Arc_Testnet",
    sortBy: "apy",
  });

  console.log(`Found ${pagination.totalCount} vaults`);
  console.dir(vaults, { depth: null, colors: true });
}

void main();
```


  `kit.earn.exploreVaults` accepts filter, sort, and pagination parameters such as
  `protocol`, `asset`, `minApy`, `minTvl`, `sortBy`, `page`, and `pageSize`. For
  lazy iteration across all pages, use `kit.earn.exploreVaultsIterator`.


### 2.2. Run the discovery script

In your terminal, run:

```bash Shell theme={null}
npm run explore-vaults
```

You'll see output like:

```bash Shell theme={null}
Found 4 vaults
[
  {
    vaultAddress: '0x...',
    chain: 'Arc_Testnet',
    name: 'Steakhouse USDC',
    protocol: 'MORPHO',
    asset: 'USDC',
    assetAddress: "0x...",
    currentApy: 0.042,
    nativeApy: 0,
    vaultFee: 0,
    rewards: [],
    collateral: [],
    status: "active",
    circleGuarded: false,
    totalDeposits: "0.0",
    liquidity: "1000000.0",
  },
  ...
]
```


  Only deposit into a vault with `status: 'active'`. A `'low_liquidity'` vault may
  not have enough liquidity for withdrawals.


`kit.earn.exploreVaults` returns all Morpho vaults on the blockchain. No
allowlist is applied, so integrators choose any active vault to deposit into.
Crosschain deposits are an exception: gas-sponsored crosschain routes use a
manually allowlisted set of destination vaults.

### 2.3. Pick a vault

Choose a vault from the output and copy its `vaultAddress`. You'll use it in the
next step.

## Step 3. Deposit USDC


  If your wallet is on a different blockchain than the vault, see
  [Deposit crosschain into an Earn vault](/app-kit/tutorials/earn/crosschain-deposit)
  instead.


### 3.1. Create the deposit script

Create a `deposit.ts` file. Replace `YOUR_SELECTED_VAULT_ADDRESS` with the vault
address you copied in the previous step and `YOUR_CIRCLE_WALLET_ADDRESS` with
the address of your Circle Wallets wallet. This script deposits 10.00 USDC into
the vault:


  Pass `amount` as a positive decimal string (for example `"10.00"`). Zero and
  negative values are rejected. For USDC and EURC, use at most 6 decimal places.
  Leading zeros and leading-dot forms (for example `"00.5"` or `".5"`) are
  rejected.


```typescript deposit.ts theme={null}
import { AppKit } from "@circle-fin/app-kit";
import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets";

const kit = new AppKit();
const amount = "10.00";
const vaultAddress = "YOUR_SELECTED_VAULT_ADDRESS";

const adapter = createCircleWalletsAdapter({
  apiKey: process.env.CIRCLE_API_KEY!,
  entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
});

async function main() {
  const result = await kit.earn.deposit({
    from: {
      adapter,
      chain: "Arc_Testnet",
      address: "YOUR_CIRCLE_WALLET_ADDRESS",
    },
    vaultAddress,
    amount,
  });

  console.dir(result, { depth: null, colors: true });
}

void main();
```


  Preview the expected outcome of a deposit before submitting with
  [`kit.earn.getDepositQuote`](/app-kit/tutorials/earn/preview-operations#preview-a-deposit).


### 3.2. Run the deposit script

In your terminal, run:

```bash Shell theme={null}
npm run deposit
```

When the script completes, you'll see output like:

```bash Shell theme={null}
{
  kind: "same-chain",
  txHash: "0x...",
  explorerUrl: "https://explorer.testnet.arc.io/tx/0x...",
  vaultAddress: "0x...",
  amount: "10",
}
```

Use the `txHash` to verify the transaction on the Arc Testnet block explorer. To
confirm the vault shares credited to your wallet, see
[Check your Earn position](/app-kit/tutorials/earn/check-position). When you're
ready to redeem, see
[Withdraw from an Earn vault](/app-kit/quickstarts/earn-withdraw).


# Quickstart: Withdraw from an Earn vault
Source: https://docs.arc.io/app-kit/quickstarts/earn-withdraw

Withdraw USDC from an Earn vault to recover your deposit and any returns generated by the underlying lending protocol

Redeem your vault shares back to USDC on Arc Testnet, including any returns
generated by the underlying lending protocol since your deposit. This quickstart
uses Circle Wallets, but you can use any compatible
[adapter setup](/app-kit/tutorials/adapter-setups).

## Prerequisites

Before you begin, ensure that you've:

* Completed the [deposit quickstart](/app-kit/quickstarts/earn-deposit) so you
  have USDC deposited in a vault.
* Installed [Node.js v22+](https://nodejs.org/).
* Obtained an
  [API key](https://developers.circle.com/api-reference/keys#creating-an-api-key-for-developer-services)
  and
  [entity secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret)
  from the
  [Circle Console](https://developers.circle.com/w3s/circle-developer-account).
* Created a developer-controlled wallet on Arc Testnet using the Circle Console.


  Earn operations work without an API key. For higher rate limits, pass an
  optional [API key](/app-kit/earn#api-key) as `apiKey` in the operation config.
  Keep the API key server-side. The App Kit SDK rejects an API key passed from a
  browser context.

  
    ```typescript TypeScript theme={null}
    const result = await kit.earn.deposit({
      from: { adapter, chain: "Arc_Testnet" },
      vaultAddress: "0x...",
      amount: "10.00",
      config: { apiKey: process.env.API_KEY as string },
    });
    ```
  


## Step 1. Set up the project


  If you completed the [deposit quickstart](/app-kit/quickstarts/earn-deposit),
  add the withdraw run script in step 1.1 to your existing project and skip to
  Step 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:

```bash Shell theme={null}
# Set up your directory and initialize a Node.js project
mkdir app-kit-earn-withdraw-circle-wallets
cd app-kit-earn-withdraw-circle-wallets
npm init -y
npm pkg set type=module

# Set up run scripts
npm pkg set scripts.withdraw="tsx --env-file=.env withdraw.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 Earn and want a lighter install than the full App Kit? Install the
  standalone package instead: `@circle-fin/earn-kit`


### 1.2. Configure TypeScript (optional)


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


Create a `tsconfig.json` file configured for ESM and Node:

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

### 1.3. Set environment variables

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

```bash Shell theme={null}
touch .env
```

Add your credentials. Replace `YOUR_API_KEY` with your Circle Developer API key,
`YOUR_ENTITY_SECRET` with your entity secret:

```text .env theme={null}
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.


## Step 2. Withdraw USDC

### 2.1. Create the withdrawal script

Create a `withdraw.ts` file. Replace `YOUR_SELECTED_VAULT_ADDRESS` with the
vault address you deposited into and `YOUR_CIRCLE_WALLET_ADDRESS` with the
address of your Circle Wallets wallet. This script withdraws 10.00 USDC from the
vault:

```typescript withdraw.ts theme={null}
import { AppKit } from "@circle-fin/app-kit";
import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets";

const kit = new AppKit();
const amount = "10.00";
const vaultAddress = "YOUR_SELECTED_VAULT_ADDRESS";

const adapter = createCircleWalletsAdapter({
  apiKey: process.env.CIRCLE_API_KEY!,
  entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
});

async function main() {
  const result = await kit.earn.withdraw({
    from: {
      adapter,
      chain: "Arc_Testnet",
      address: "YOUR_CIRCLE_WALLET_ADDRESS",
    },
    vaultAddress,
    amount,
  });

  console.dir(result, { depth: null, colors: true });
}

void main();
```


  The first withdrawal from a vault requires an approval transaction to allow the
  vault contract to redeem your shares. The App Kit SDK handles this
  automatically, so you may see two transactions in your wallet instead of one.


The amount you receive depends on the current vault share price at the time of
redemption.


  Preview the expected outcome of a withdrawal before submitting with
  [`kit.earn.getWithdrawalQuote`](/app-kit/tutorials/earn/preview-operations#preview-a-withdrawal).


### 2.2. Run the withdrawal script

In your terminal, run:

```bash Shell theme={null}
npm run withdraw
```

When the script completes, you'll see output like:

```bash Shell theme={null}
{
  txHash: "0x...",
  explorerUrl: "https://explorer.testnet.arc.io/tx/0x...",
  vaultAddress: "0x...",
  amount: "10.04",
}
```

The returned `amount` reflects the value of your shares at the time of
withdrawal, which may be greater than your original deposit due to accrued
returns. For details on any fees that apply, see
[How Earn fees work](/app-kit/concepts/earn-fees).

Use the `txHash` to verify the transaction on the Arc Testnet block explorer. To
confirm your updated vault balance, see
[Check your Earn position](/app-kit/tutorials/earn/check-position).


# Quickstart: Embed the Onramp widget
Source: https://docs.arc.io/app-kit/quickstarts/onramp-embed-widget

Mint a session on your server and mount the Onramp widget inline in your app so users can buy stablecoins on Arc.

Embed the [Onramp](/app-kit/onramp) widget inline in an existing web app using
the App Kit SDK. A signed-in user can open your page, complete an onramp inside
an iframe, and your page receives a settled deposit event.

The Onramp widget integration is framework-agnostic. The server handler uses the
Fetch `Request` / `Response` standard, and the browser widget only needs a
mounted DOM container. The route example uses Next.js App Router wiring, but the
same handler works in Fetch-compatible backends such as Hono, Cloudflare
Workers, Bun, Deno, and modern Node. For Express or Fastify hosts, see
[Customize session minting](/app-kit/tutorials/onramp/customize-session-minting).


  Try the Onramp flow at
  [portal.arc.io/deposit/fiat](https://portal.arc.io/deposit/fiat) before
  integrating.


## Prerequisites

Before you begin, ensure that you've:

* Installed [Node.js v22+](https://nodejs.org/).
* Set up an existing web app with a server route and a browser page.
* [Installed the App Kit SDK](/app-kit/tutorials/installation).
* Obtained an [API key](/app-kit/onramp#api-key) from the
  [Circle Console](https://console.circle.com/api-keys).
* Obtained a wallet address on Arc where users will receive their stablecoins.

## Step 1. Set your environment variables

Add your API key to your server environment. Never inline it in client code or
commit it to source control.

```bash .env theme={null}
CIRCLE_API_KEY=YOUR_API_KEY
```

## Step 2. Expose a session route on your server

Create a route that exchanges your API key for a short-lived session token. The
App Kit SDK ships `createSessionRouteHandler` as a drop-in `Request → Response`
handler. The following example uses the Next.js App Router:

```typescript app/api/onramp/sessions/route.ts theme={null}
import {
  createAppServerKit,
  createSessionRouteHandler,
} from "@circle-fin/app-kit/server";

const server = createAppServerKit({
  onramp: {
    apiKey: process.env.CIRCLE_API_KEY!,
    referrerDomain: "your.domain.com", // Required for debit card, Apple Pay, and Google Pay.
  },
});

export const POST = createSessionRouteHandler(server.onramp);
```

The handler accepts `POST` requests, validates the body, calls
`server.onramp.createSession()`, and returns the session as JSON. It maps thrown
errors to the appropriate HTTP status codes and sets `Cache-Control: no-store`
so session tokens are never cached.


  This route doesn't perform authentication. Before deploying, add an `authorize`
  callback that checks the user's session. See
  [Customize session minting](/app-kit/tutorials/onramp/customize-session-minting#add-authentication).


## Step 3. Add a container element to your page

The Onramp widget renders inside an `