Skip to content

docs: Circle AppKit v1.5.x kit.bridge() and kit.send() have no documented Arc Testnet integration path — only kit.swap() / estimateSwap() are viable #88

Open

Description

@osr21

Summary

Circle AppKit v1.5.x (@circle-fin/app-kit) documents kit.bridge() and kit.send() as top-level primitives, but neither has a documented path for Arc Testnet. When building a swap DApp that needed cross-chain bridging (Ethereum Sepolia → Arc Testnet) and wallet-to-wallet token sends, only kit.swap() and kit.estimateSwap() worked reliably.

What we expected

The AppKit README implies that kit.bridge() enables USDC bridging between any two supported chains including Arc Testnet, and that kit.send() sends USDC/EURC between wallets on the same chain.

What actually happened

  • kit.bridge() — No documentation on which chain identifiers are valid for Arc Testnet (e.g. "Arc_Testnet" string constant). The CCTP attestation service used internally does not document Arc Testnet support.
  • kit.send() — No documentation on whether Arc Testnet is supported, or whether the underlying transfer is an on-chain ERC-20 transfer() or a Circle-mediated off-chain transfer.
  • Both methods require the backend wallet private key to execute, which conflicts with the client-side viem adapter pattern shown in docs — creating a security/UX architecture mismatch.

Workaround

  • Bridge: Guide users to faucet.circle.com manually. No programmatic on-chain bridge path was available.
  • Send: Implemented a direct ERC-20 transfer() call via MetaMask + viem, bypassing AppKit entirely.

Environment

  • @circle-fin/app-kit v1.5.0
  • @circle-fin/adapter-viem-v2 v1.10.0
  • Arc Testnet (Chain ID 5042002)
  • Observed: May 2025

Request

Please add to the Arc developer docs:

  1. Which AppKit methods are supported on Arc Testnet
  2. The correct chain identifier string for Arc Testnet in kit.bridge()
  3. Whether kit.send() is implemented for Arc Testnet or falls back to raw ERC-20

Activity

  1. osr21 commented on May 25, 2026

    @osr21
    Author

    Confirmed findings + working alternative for cross-chain USDC bridging

    Based on our experience building a bridge DApp on Arc Testnet:

    What works:

    • kit.swap() and kit.estimateSwap() work for same-chain USDC swaps on Arc Testnet
    • For Arc Testnet ↔ Ethereum Sepolia/Base Sepolia/Avalanche Fuji bridging, CCTP V2 directly is the only fully documented path

    What doesn't have an official Arc path:

    • kit.bridge() — no documented Arc Testnet configuration in AppKit v1.5.x
    • kit.send() — no documented Arc Testnet wallet-to-wallet send path

    Working alternative for USDC bridging (CCTP V2):
    The CCTP V2 contracts are live on Arc Testnet. All chains (Arc, Sepolia, Base Sepolia, Fuji) share the same contract addresses:

    • TokenMessenger: 0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA
    • MessageTransmitter: 0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275

    Arc Testnet's CCTP domain ID is 26. See docs/cctp-v2.md in PR #79 for a complete
    integration guide including the Arc-specific minFinalityThreshold and attestation timing.

  2. osr21 commented on Jun 16, 2026

    @osr21
    Author

    Additional findings: kit.swap() itself also fails on Arc Testnet with ONCHAIN_SIMULATION_FAILED

    Building a swap DApp (Arc Swap / live) on Arc Testnet, we found that kit.swap() — not just kit.bridge() and kit.send() — is also completely non-functional on Arc Testnet.

    Error

    KitError: ONCHAIN_SIMULATION_FAILED
      at kit.swap({ fromToken: "USDC", toToken: "EURC", amount: "1.0", walletAddress: "0x..." })
    

    The error is thrown regardless of token direction (USDC→EURC or EURC→USDC), amount, or wallet state. kit.estimateSwap() does not throw but returns wildly incorrect rates (e.g. 17 EURC per 1 USDC instead of ~0.91), which suggests the swap router contract has no real liquidity on Arc Testnet.

    Root cause

    The Circle swap router contract at 0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b appears to have no deployed liquidity pool on Arc Testnet. The on-chain simulation step inside kit.swap() therefore always fails.

    Workaround we shipped

    We replaced Circle Kit entirely with a backend-facilitated peer-to-peer model:

    1. User approves ERC-20 allowance to a backend wallet
    2. Backend calls transferFrom to pull amountIn from user
    3. Backend calculates amountOut using real market rates (Frankfurter ECB forex for USDC/EURC, CoinGecko for BTC/USD)
    4. Backend calls transfer to send amountOut to user
    5. Auto-refund if step 4 fails

    This is a workable testnet pattern but obviously not suitable for production. The underlying issue is that kit.swap() should either work on Arc Testnet or throw a clear CHAIN_NOT_SUPPORTED error rather than an opaque ONCHAIN_SIMULATION_FAILED.

    Environment

    • @circle-fin/app-kit v1.5.x
    • Arc Testnet, Chain ID 5042002
    • USDC: 0x3600000000000000000000000000000000000000
    • EURC: 0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a
  3. osr21 commented on Jun 19, 2026

    @osr21
    Author

    Confirmed: kit.swap() also fails with ONCHAIN_SIMULATION_FAILED on Arc Testnet

    Building on this issue with additional findings from Arc Swap (Uniswap V2 DEX on Arc Testnet, App Kit v1.7.0 + adapter-viem-v2 v1.11.2):

    kit.swap() status update

    In v1.7.x, kit.swap() — not just kit.bridge() and kit.send() — also fails on Arc Testnet with ONCHAIN_SIMULATION_FAILED in both directions (USDC→EURC and EURC→USDC). Arc Testnet (Chain ID 5042002) appears to not be in Circle's supported chains for any swap routing.

    kit.estimateSwap() returns rates that diverge significantly from real EUR/USD forex:

    • USDC→EURC: App Kit returns ~0.971 (real rate ~0.872)
    • EURC→USDC: App Kit returns ~1.874 (real rate ~1.146)

    These appear to reflect Circle's internal liquidity pool prices, not live market rates.

    Working alternatives for each App Kit method on Arc Testnet

    App Kit method Arc Testnet status Working alternative
    kit.swap() ❌ ONCHAIN_SIMULATION_FAILED Uniswap V2 router (frontend) or backend peer-to-peer transfer at real forex rate
    kit.estimateSwap() ❌ Wrong rates On-chain getReserves() + Frankfurter/CoinGecko
    kit.bridge() ❌ No Arc path CCTP V2 directly (MessageTransmitterV2 + TokenMessengerV2)
    kit.send() ❌ No Arc path Direct ERC-20 transfer() via viem

    Swap estimate priority that works

    // 1. On-chain pool quote via getReserves() — exact constant-product math
    const [reserve0, reserve1] = await publicClient.readContract({
      address: PAIR_ADDRESS,
      abi: PAIR_ABI,
      functionName: "getReserves",
    });
    const poolRate = Number(reserve1) / Number(reserve0);
    
    // 2. Real forex as sanity check — if pool is >5% off, use forex directly
    const forexRate = await frankfurterFetch(); // USD/EUR from api.frankfurter.app
    if (Math.abs(poolRate - forexRate) / forexRate > 0.05) {
      return forexRate; // pool imbalanced — use market rate
    }
    return poolRate; // pool price is accurate — use it (matches what router will execute)

    The 5% guard also routes backend swaps away from the pool automatically when it drifts, so users always get a rate close to real EUR/USD regardless of pool state.

  4. osr21 commented on Jun 19, 2026

    @osr21
    Author

    Update: kit.swap() is intermittent, not always broken — confirmed working tx

    Correction to my earlier comment: kit.swap() does not always fail on Arc Testnet. We observed a successful execution in production:

    Transaction: 0x237678e4eb865f07b597eb930a3194fb7ae309b513a07fd60f4f0a6fceb65a15

    The swap went through successfully — 1.99 USDC → 2.333 EURC — via Circle's App Kit Adapter. The routing chain was:

    Backend wallet
      → Circle Adapter (0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b)
        → LiFi Diamond (0xFf70F4A1d11995621854F3692acF286d8aCd04b2)
          → DexAggregatorCore (0x09AD820aaC5779683B481c4674208A4e1B024Afa)
            → CurveStableSwap (0xbbc2A38aB48fA953eC68Ee6115Bd518D3A226f6e)  
                1.993 USDC → 2.344 WUSDC  (wraps USDC first)
            → CurveStableSwap (0x942644106B073E30D72c2C5D7529D5C296ea91ab)
                2.344 WUSDC → 2.333 EURC  (unwraps to EURC)
    

    What this tells us about kit.swap() on Arc Testnet

    • App Kit does NOT use Uniswap-style AMMs for USDC/EURC — it routes through LiFi Diamond → Curve StableSwap pools with WUSDC as an intermediate token
    • ONCHAIN_SIMULATION_FAILED appears to be liquidity-conditional, not a permanent chain incompatibility — it likely occurs when the Curve pools don't have sufficient depth for the requested amount
    • The working tx above (1.99 USDC) succeeded; larger amounts or different market conditions may still fail

    Revised recommendation

    Use tryKitSwap() as the primary path but keep a fallback (peer-to-peer at market rate or Uniswap V2 pool) for when it fails. Do not treat ONCHAIN_SIMULATION_FAILED as permanent — it's a liquidity signal, not a chain compatibility error.

    // Try App Kit first — works when Curve pool liquidity is sufficient
    const kitResult = await tryKitSwap(tokenIn, tokenOut, amountIn);
    if (kitResult) {
      return kitResult; // App Kit succeeded — route: LiFi → Curve → WUSDC → EURC
    }
    // Fallback — direct Uniswap V2 router or peer-to-peer at Frankfurter rate
    return fallbackSwap(tokenIn, tokenOut, amountIn);

    Rate accuracy

    The successful tx gave 1.173 EURC/USDC vs real EUR/USD of ~1.146 — approximately 2.3% above market on testnet. For production usage, real forex validation (e.g. Frankfurter) is still recommended as a sanity check on kit.estimateSwap() output.

  5. kutluhaneth46 commented on Sep 14, 2026

    @kutluhaneth46
    Contributor

    Taking this. Official App Kit docs now document Arc Testnet for bridge/send/swap with the Arc_Testnet chain id; I will add an in-repo developer note that points at those paths and answers the three questions in this issue.

  6. kutluhaneth46 commented on Sep 14, 2026

    @kutluhaneth46
    Contributor

    Opened #404 — in-repo note for Arc_Testnet plus bridge/send/swap coverage, linked from the README.

  7. kutluhaneth46 commented on Sep 28, 2026

    @kutluhaneth46
    Contributor

    @ZhiyuCircle requesting assignment — linked PR is already open, CI-green, and mergeable. Per CONTRIBUTING the only remaining gate is assignment; happy to rebase or adjust scope once assigned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationecosystemComponent: ecosystem

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions