Arc House
Tooling + Technical
July 7, 2026

App Kit FAQs

App Kit FAQs
# App Kits
# Unified Balance Kit
# Bridge Kit
# Swap Kit

Implementation answers for building with App Kit

App Kit FAQs

Setup and wallets

Does App Kit work in a server app, a client (browser) app, or both?

Both.
  • Send, Bridge, and Unified Balance run in a server backend or a browser client app.
  • Swap runs server-side only today; client-side Swap is in progress.
Pick your adapter to match: a browser-wallet adapter for client apps, or a private key, Circle Wallets, or Turnkey adapter for servers.

How do I integrate a client-side (browser) wallet, like MetaMask or Trust Wallet?

Create the adapter from the wallet’s injected provider. Any EIP-1193 EVM browser wallet (MetaMask, Trust Wallet), or a Solana wallet such as Phantom, works:
import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2";
const adapter = await createViemAdapterFromProvider({
  provider: window.ethereum,
});
  • Ethers:  createEthersAdapterFromProvider .
  • Solana:  createSolanaKitAdapterFromProvider  with  window.solana .
Browser wallets work for Send, Bridge, and Unified Balance; Swap is server-side only for now. See the  browser wallet setup .

How do I integrate a server-side wallet, like Turnkey or Circle Wallets?

Build an adapter from your wallet’s credentials and pass it to the kit. App Kit supports these server-side options:
  • Private key:  createViemAdapterFromPrivateKey  (and the Ethers and Solana equivalents).
  • Circle Wallets:  createCircleWalletsAdapter , using a Circle API Key and Entity Secret.
  • Turnkey: a Turnkey company wallet passed into the Viem adapter through  @turnkey/viem .
Each is configured the same way you’d build any adapter, then passed to the kit. See the  server-side wallet setup .

What credentials does the Circle Wallets adapter need, and what changes for a smart-contract account?

  • It needs a Circle API Key and Entity Secret, and is server-side only (never expose these in a browser).
  • It currently supports Circle developer-controlled wallets only. User-controlled wallets and modular wallets support is in progress.
  • For a smart-contract account (SCA): set  allowanceStrategy: "approve"  for swaps and Unified Balance deposits, and use a delegate for Unified Balance spends, because an SCA can’t sign its own spends.

Does App Kit only support Arc, or other blockchains as well?

Many blockchains, not only Arc. App Kit supports a wide set of EVM blockchains (Arbitrum, Avalanche, Base, Ethereum, OP Mainnet, Polygon, Linea, and more), plus Solana and Arc, across mainnet and testnet.
Coverage differs by capability: Send and Bridge support the most blockchains, while Swap and Unified Balance support a subset. Check the matrices in  supported blockchains and tokens .

Should I configure a custom RPC for production, and how?

Yes. The default public RPC endpoints are rate-limited, so for production pass your own endpoint (such as Alchemy or QuickNode) by mapping each blockchain to its URL in the adapter callback:  getPublicClient  (Viem),  getProvider  (Ethers), or  getRpc  (Solana). See the  custom RPC setup .

Amounts and fees

How does Circle charge the service fee for App Kit?

App Kit is generally free to use. Any cost comes from the underlying provider or from optional value-add services, and varies by capability:
  • Send: free; you pay only network gas.
  • Bridge: free, with optional paid value-add services (such as Fast Transfer and the Forwarding Service).
  • Swap: pays a basic service-provider fee (the third-party liquidity provider that executes the swap).
  • Unified Balance: covers the gas cost of each spend, with optional paid value-add services (such as the Forwarding Service).
Separately, if you add a custom fee to monetize a capability, Circle takes 10% and you keep 90%.
See the per-capability fee details:  swap fees ,  bridge fees , and  Unified Balance fees .

How do I collect my own fee, and how does it differ by each capability?

Set a fee per call, or set a global policy. The per-call shape differs by capability:
// Swap: percentage-based
config: { customFee: { percentageBps: 50, recipientAddress: "0xYourWallet" } }
ďťż
// Bridge and Unified Balance spend: absolute value
config: { customFee: { value: "0.10", recipientAddress: "0xYourWallet" } }
For a global fee:
  • Swap and Bridge:  developerFee  hooks in the  AppKit  constructor.
  • Unified Balance spend:  kit.unifiedBalance.setCustomFeePolicy(...) .
Circle takes 10% of any custom fee; you keep 90%. See  collect a swap fee ,  collect a bridge fee , and  collect custom spend fees .

Swap

Why do I need a Kit Key for Swap, and how should I manage it?

Swap requires a Kit Key; Send, Bridge, and Unified Balance don’t. It’s a free credential from the  Circle Console , used mainly for authorization and security.
Manage it as a secret: store it in an environment variable, never hardcode or ship it to the browser (another reason Swap runs server-side), and pass it as  config.kitKey  per call:
const result = await kit.swap({
  from: { adapter, chain: "Arc" },
  tokenIn: "USDT",
  tokenOut: "USDC",
  amountIn: "1.00",
  config: { kitKey: process.env.KIT_KEY },
});

Can I run Swap from a browser or client-side app?

Not yet. Swap requires a server-side adapter (a private key, Circle Wallets, or Turnkey). Client-side Swap is a known limitation that the team is actively working on.
Send, Bridge, and Unified Balance already work with browser wallets such as MetaMask today. See  adapter setups .

How do I show users the exact output and minimum before executing?

Call  estimateSwap  with the same parameters as  swap ; it runs no transaction and returns:
  •  estimatedOutput : the expected amount.
  •  stopLimit : the guaranteed minimum after slippage.
  •  fees : the fee breakdown.
Both amounts are  TokenAmount  objects with  .amount  and  .token .
const estimate = await kit.estimateSwap({
  from: { adapter, chain: "Arc" },
  tokenIn: "EURC",
  tokenOut: "USDC",
  amountIn: "1.00",
  config: { kitKey: process.env.KIT_KEY },
});
console.log(estimate.estimatedOutput.amount, estimate.stopLimit.amount);
Control the floor with  slippageBps  (default 300, or 3%) or an explicit  stopLimit , which overrides slippage. See  estimate the swap rate  and  set slippage tolerance or stop limit .

What can I actually swap on testnet versus mainnet?

  • Arc Testnet: USDC, EURC, and cirBTC only, because testnet liquidity is limited.
  • Mainnet: stablecoins (USDC, EURC, USDT, USDe, DAI, PYUSD), wrapped tokens (WBTC, WETH, WSOL), and native tokens, plus any other token by contract address.
Swap also supports fewer blockchains than Bridge. Confirm with  kit.getSupportedChains('swap')  or the  supported tokens reference .

Why did my swap revert? It happens often on testnet.

On testnet, the usual cause is thin liquidity: the swap route can’t fill at your price, so the transaction reverts. To reduce reverts:
  • Stay within the supported testnet pairs (Arc Testnet: USDC, EURC, cirBTC).
  • Use small amounts, and raise  slippageBps  (avoid  0 ) or set a realistic  stopLimit .
  • Run  estimateSwap  first to confirm a route exists and see the expected output.
  • If a testnet pool is temporarily depleted, try again after a while. Circle’s liquidity provider monitors the testnet pools and replenishes tokens.
Other common causes (any network): no route or token pair, insufficient allowance (let the default  permit  run, or use  approve ), and insufficient gas. See  set slippage tolerance or stop limit .

What differs between a crosschain swap and a same-chain swap?

Both are a single  kit.swap()  call. The difference is the optional to field:
  • Same-chain: omit  to  (output goes to the source wallet), or set  to.recipientAddress  to send it to another wallet on the same blockchain.
  • Crosschain: set  to.chain  to the destination blockchain.  recipientAddress  is required for crosschain, because the destination wallet may use a different chain or address format.
// Cross-chain: swap USDC on Ethereum, receive on Base at a specific wallet
const result = await kit.swap({
  from: { adapter, chain: "Ethereum" },
  tokenIn: "USDC",
  tokenOut: "USDC",
  amountIn: "100",
  to: { chain: "Base", recipientAddress: "0xRecipient" },
  config: { kitKey: process.env.KIT_KEY },
});
A crosschain swap settles asynchronously, so check its status with  waitForSwap . It polls until the status is terminal ( DONE ,  FAILED , or  NOT_FOUND ). For a same-chain swap it returns immediately, because the swap is already terminal when  swap  resolves.
const final = await kit.waitForSwap({ result, kitKey: process.env.KIT_KEY });
Also note:
  • Custom fees are collected from the source-chain input token (same-chain output fees are taken from the estimated output).
  • Both the source and destination must be swap-supported blockchains.

Can I do a same-chain swap and send the output to a different wallet?

Yes. Set  to.recipientAddress ; the destination chain defaults to the source blockchain when you omit  to.chain . Omit  to  entirely and the swapped tokens go back to the source wallet.
const result = await kit.swap({
  from: { adapter, chain: "Arc" },
  tokenIn: "USDT",
  tokenOut: "USDC",
  amountIn: "1.00",
  to: { recipientAddress: "0xDifferentWallet" },
  config: { kitKey: process.env.KIT_KEY },
});
See  Swap .

Bridge

How do I cap or avoid the bridge fee, and who pays it?

Bridge uses the Cross-Chain Transfer Protocol (CCTP):
  • FAST (default): carries a CCTP fee that varies by source blockchain, taken from the USDC amount.
  • SLOW (standard): free, but takes longer to finalize.
  • maxFee: caps the fee on a FAST transfer; if the quote exceeds it, the transfer falls back to SLOW.
There is no separate gas token for the fee itself. See  configure transfer speed and max cost ,  estimate bridging costs , and  bridge fees .

My Arc Testnet bridge reverts with ‘Max fee must be less than amount’. Why?

A  FAST  bridge takes its fee from the transfer, so the amount must exceed the CCTP fast-transfer fee; below that, the burn step reverts with this error.
It isn’t strictly testnet-only, but in practice it mainly surfaces on Arc Testnet, where the fee is relatively high (around 1.4 USDC). On mainnet the fast-transfer fee is small, so normal amounts clear it.
Fix: send a larger amount, or use  SLOW  transfer speed, which skips the fast fee. See  configure transfer speed and max cost .

How do I track a bridge’s progress?

Listen for the lifecycle events, which App Kit namespaces with  bridge. :
kit.on("bridge.approve", (e) => console.log("Approved", e.values.txHash));
kit.on("bridge.burn", (e) => console.log("Burned", e.values.txHash));
kit.on("bridge.attestation", () => console.log("Attestation received"));
kit.on("bridge.mint", (e) => console.log("Minted", e.values.txHash));
kit.on("*", (e) => console.log(e.method, e.values)); // all events
You can also inspect the returned  result.steps  array; each step has  name ,  state ,  txHash , and  explorerUrl . Remove a handler with  kit.off(action, handler)  using the same function reference.
The standalone Bridge Kit emits the same events without the  bridge.  prefix (for example,  approve ). See the  SDK reference .

My bridge failed midway. Do I lose my funds, and how do I resume?

No, bridges are recoverable.
  • Inspect  result.steps  to find the failed step (each has  state ,  errorMessage , and  error ).
  • Call  retryBridge(result, retryContext)  to resume from that step.
  • Use the  isRetryableError  helper to decide whether to retry.

Unified Balance

What kind of wallets does Unified Balance work with, and how do I implement it?

Any adapter backed by an EOA (externally owned account) works, including browser wallets like MetaMask, a private key, Circle Wallets, and Turnkey. It is not limited to Circle Developer-Controlled Wallets.
Unified Balance requires an EOA to sign. If your main wallet is a smart-contract account (SCA), use the delegate model: delegate to an EOA, which signs spends on the SCA’s behalf. Direct support for SCA and multi-sig wallets is in progress.
To implement it, use the same adapter you’d use for Send or Bridge, then:
  1.  deposit  USDC into the Unified Balance from one or more blockchains.
  1.  spend  from the balance on any supported blockchain.
For backends or smart-contract accounts, deposit on a user’s behalf with depositFor, and register a delegate to spend without per-spend signing. See the  Unified Balance overview , the  deposit and spend quickstart , and  manage delegates .

How soon after a deposit can I spend, and what determines the wait?

Two stages, each bound by blockchain finality:
  • Deposit: reflected in the Unified Balance after the source blockchain finalizes, so depositing from a fast blockchain (such as Base or Solana) reflects sooner than from Ethereum.
  • Spend: once reflected, you can spend on any supported blockchain immediately; the recipient’s receive time depends on the destination blockchain.

How is gas paid for the spend transaction?

Gas is deducted from your Unified Balance USDC; you don’t need a separate gas token on the source blockchain. The amount varies by blockchain and network conditions. For the per-blockchain gas costs, see  Gateway fees .

How do I avoid making users sign for every deposit and spend?

  • Deposit funds ahead of time, optionally with  depositFor  (deposit on the user’s behalf), so the user only signs the spend.
  • To remove signing from the spend too, register a delegate and spend on the user’s behalf.

My spend fails right after addDelegate. Why?

Delegate status is finality-aware. After  addDelegate , the status is  pending  until the grant finalizes, and spends fail until it becomes  ready .
Poll  getDelegateStatus  and wait for  ready  before spending. See  manage delegates .

How do users withdraw funds without spending them, and why the delay?

Use the two-step trustless withdrawal:  initiateRemoveFund , then  removeFund  after the activation period (about 7 days on EVM blockchains, immediate on Solana).
This is an escape hatch; for normal payouts, use spend. See  remove funds trustlessly .

Can I combine a Unified Balance spend with another capability, such as spend and swap, in one call?

Not in a single call. Each capability is its own method, so compose them as separate steps: for example,  spend  from the Unified Balance to deliver USDC, then swap that USDC into another token on the destination blockchain. Sequence the calls and check each result before the next. See the  Unified Balance overview  and  Swap .
Comments (149)
Popular
avatar
ďťż
Dive in

Related

External Content
Unified Balance Kit: Rethinking Payment and Treasury App Architectures
Jun 18th, 2026 • Views 27.5K
Video
ArcShop with Elton: Unified Balance Kit for Crosschain USDC Flows
By Elton Tay • May 1st, 2026 • Views 143.7K
Video
Event Replay: Arcshop- Introducing Bridge Kit
By Tobias Golbs • Dec 10th, 2025 • Views 146.7K
Video
App Kits Developer Office Hours: Bridge, Swap, Send, and Monetization
By Elton Tay • Apr 21st, 2026 • Views 148.6K
External Content
Unified Balance Kit: Rethinking Payment and Treasury App Architectures
Jun 18th, 2026 • Views 27.5K
Video
Event Replay: Arcshop- Introducing Bridge Kit
By Tobias Golbs • Dec 10th, 2025 • Views 146.7K
Video
App Kits Developer Office Hours: Bridge, Swap, Send, and Monetization
By Elton Tay • Apr 21st, 2026 • Views 148.6K
Video
ArcShop with Elton: Unified Balance Kit for Crosschain USDC Flows
By Elton Tay • May 1st, 2026 • Views 143.7K
Architects Program Terms & Conditions
Your Privacy Choices