Tooling + Technical
July 7, 2026
App Kit FAQs

# App Kits
# Unified Balance Kit
# Bridge Kit
# Swap Kit
Implementation answers for building with App Kit

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%.
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-basedconfig: { customFee: { percentageBps: 50, recipientAddress: "0xYourWallet" } }ďťż
// Bridge and Unified Balance spend: absolute valueconfig: { 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 walletconst 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 },});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 eventsYou 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:
- Â
deposit USDC into the Unified Balance from one or more blockchains.
- Â
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 .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).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 .453
Comments (149)
Popular
ďťż
Dive in
Related
External Content
Unified Balance Kit: Rethinking Payment and Treasury App Architectures
Jun 18th, 2026 ⢠Views 27.5K
49:14
Video
ArcShop with Elton: Unified Balance Kit for Crosschain USDC Flows
By Elton Tay â˘Â May 1st, 2026 ⢠Views 143.7K
37:00
Video
Event Replay: Arcshop- Introducing Bridge Kit
By Tobias Golbs â˘Â Dec 10th, 2025 ⢠Views 146.7K
51:49
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
37:00
Video
Event Replay: Arcshop- Introducing Bridge Kit
By Tobias Golbs â˘Â Dec 10th, 2025 ⢠Views 146.7K
51:49
Video
App Kits Developer Office Hours: Bridge, Swap, Send, and Monetization
By Elton Tay â˘Â Apr 21st, 2026 ⢠Views 148.6K
49:14
Video
ArcShop with Elton: Unified Balance Kit for Crosschain USDC Flows
By Elton Tay â˘Â May 1st, 2026 ⢠Views 143.7K