> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge USDC to Arc with Para and CCTP

> Use a Para wallet with Circle Bridge Kit to move USDC from Ethereum to Arc Mainnet.

export const Card = ({imgUrl, title, description, href, horizontal = false, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  const handleClick = e => {
    e.preventDefault();
    if (newTab) {
      window.open(href, '_blank', 'noopener,noreferrer');
    } else {
      window.location.href = href;
    }
  };
  return <div className={`not-prose relative my-2 p-[1px] rounded-xl transition-all duration-300 ${isHovered ? 'bg-gradient-to-r from-[#FF4E00] to-[#874AE3]' : 'bg-gray-200'}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      <a href={href} onClick={handleClick} className={`not-prose flex ${horizontal ? 'flex-row' : 'flex-col'} font-normal h-full bg-white overflow-hidden w-full cursor-pointer rounded-[11px] no-underline`}>
        {imgUrl && <div className={`relative overflow-hidden flex-shrink-0 ${horizontal ? 'w-[30%] rounded-l-[11px]' : 'w-full'}`} onClick={e => e.stopPropagation()}>
            <img src={imgUrl} alt={title} className="w-full h-full object-cover pointer-events-none select-none" draggable="false" />
            <div className="absolute inset-0 pointer-events-none" />
          </div>}
        <div className={`flex-grow px-6 py-5 ${horizontal ? 'w-[70%]' : 'w-full'} flex flex-col ${horizontal && imgUrl ? 'justify-center' : 'justify-start'}`}>
          {title && <h2 className="font-semibold text-base text-gray-800 m-0">{title}</h2>}
          {description && <div className={`font-normal text-gray-500 re leading-6 ${horizontal || !imgUrl ? 'mt-0' : 'mt-1'}`}>
              <p className="m-0 text-xs">{description}</p>
            </div>}
        </div>
      </a>
    </div>;
};

export const Link = ({href, label, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  return <a href={href} target={newTab ? '_blank' : '_self'} rel={newTab ? 'noopener noreferrer' : undefined} className="not-prose inline-block relative text-black font-semibold cursor-pointer border-b-0 no-underline" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      {label}
      <span className={`absolute left-0 bottom-0 w-full rounded-sm bg-gradient-to-r from-orange-600 to-purple-600 transition-all duration-300 ${isHovered ? 'h-0.5' : 'h-px'}`} />
    </a>;
};

Bridge USDC from Ethereum Mainnet to Arc Mainnet with Circle's CCTP V2. Para authenticates the user and signs transactions on both chains. Bridge Kit handles the approval, burn, attestation, and mint steps.

## Before you start

Start with a Next.js app that has `ParaProvider`, a login flow, and an EVM wallet for the signed-in user. These Para guides cover that setup:

<CardGroup cols={2}>
  <Card title="Set up Para in Next.js" description="Add ParaProvider and authentication to your app." href="/v3/react/setup/nextjs" />

  <Card title="Use Para with viem" description="Connect the signed-in wallet to a viem client." href="/v3/react/guides/web3-operations/evm/setup-libraries" />
</CardGroup>

The wallet needs native USDC to bridge and ETH for Ethereum gas. It also needs enough USDC on Arc to pay for a destination transaction, since Arc uses USDC as its gas token. Review the transfer amount and network fees with the user before submitting a mainnet transaction.

<Note>
  This route uses CCTP domain `0` for Ethereum and domain `26` for Arc. These are protocol domain IDs, not EVM chain IDs. Bridge Kit's `Ethereum` and `Arc` chain definitions carry the domains and CCTP contract addresses; the example does not hardcode them.
</Note>

## Install the bridge packages

From your Para Next.js application, install Bridge Kit, Circle's viem adapter, and the Para viem integration. The versions below are the ones used for this guide.

```bash theme={null}
npm install @circle-fin/bridge-kit@1.15.1 @circle-fin/adapter-viem-v2@1.18.0 @getpara/viem-v2-integration@3.19.0 viem@2.56.8
```

Set `NEXT_PUBLIC_ETHEREUM_RPC_URL` and `NEXT_PUBLIC_ARC_RPC_URL` to RPC endpoints for the two mainnets. The values are public browser configuration, so do not put private RPC credentials in them.

## Connect Para to Bridge Kit

Use <Link label="useClient()" href="/v3/references/hooks/useClient" /> and <Link label="useAccount()" href="/v3/references/hooks/useAccount" /> inside your existing `ParaProvider` to check the signed-in Para wallet. Confirm the session with <Link label="isFullyLoggedIn()" href="/v3/references/core/isfullyloggedin" />. <Link label="createParaViemAccount()" href="/v3/references/signers/evm/viem-v2/create-para-viem-account" /> selects the EVM wallet, and <Link label="createParaViemClient()" href="/v3/references/signers/evm/viem-v2/create-para-viem-client" /> gives Circle's `ViemAdapter` a wallet client for each chain. Both clients use the same Para account.

```tsx src/hooks/useCctpAdapter.ts theme={null}
"use client";

import { useAccount, useClient } from "@getpara/react-sdk";
import {
  createParaViemAccount,
  createParaViemClient,
} from "@getpara/viem-v2-integration";
import { ViemAdapter } from "@circle-fin/adapter-viem-v2";
import { Arc, Ethereum } from "@circle-fin/bridge-kit/chains";
import { createPublicClient, http } from "viem";

const ethereumRpc = process.env.NEXT_PUBLIC_ETHEREUM_RPC_URL;
const arcRpc = process.env.NEXT_PUBLIC_ARC_RPC_URL;

function rpcFor(chainId: number) {
  const rpc = chainId === 1 ? ethereumRpc : chainId === 5042 ? arcRpc : undefined;
  if (!rpc) throw new Error(`Missing RPC URL for chain ${chainId}`);
  return rpc;
}

export function useCctpAdapter() {
  const para = useClient();
  const { isConnected } = useAccount();

  async function createAdapter() {
    if (!para || !isConnected || !(await para.isFullyLoggedIn())) {
      throw new Error("Sign in with Para before bridging USDC");
    }

    const account = createParaViemAccount({ para });
    const adapter = new ViemAdapter(
      {
        getPublicClient: ({ chain }) =>
          createPublicClient({ chain, transport: http(rpcFor(chain.id)) }),
        getWalletClient: ({ chain }) =>
          createParaViemClient({
            para,
            walletClientConfig: {
              account,
              chain,
              transport: http(rpcFor(chain.id)),
            },
          }),
      },
      { addressContext: "user-controlled", supportedChains: [Ethereum, Arc] },
    );

    return adapter;
  }

  return { createAdapter };
}
```

<Warning>
  Keep the authenticated Para session available until the bridge finishes. Do not replace the Para account with a private-key viem account: that would bypass Para signing.
</Warning>

## Bridge Ethereum USDC to Arc

Call `bridge()` in response to a user action. Bridge Kit uses CCTP V2 to burn USDC on Ethereum, waits for Circle's attestation, then mints USDC on Arc. Setting `useForwarder: false` keeps the Arc mint signed by Para. The result contains each step and the overall state. `amount` is a human-readable USDC amount.

```tsx src/hooks/useCctpBridge.ts theme={null}
"use client";

import { BridgeKit, type BridgeResult } from "@circle-fin/bridge-kit";
import { useCctpAdapter } from "./useCctpAdapter";

function summarize(result: BridgeResult) {
  const burn = result.steps.find(
    (step) => step.name === "burn" || step.name === "depositForBurn",
  );
  const mint = result.steps.find((step) => step.name === "mint");

  return {
    status:
      result.state === "error"
        ? "needs_attention"
        : result.state === "success" &&
            burn?.state === "success" &&
            burn.txHash &&
            mint?.state === "success" &&
            mint.txHash
          ? "complete"
          : "pending",
    failedStep: result.steps.find((step) => step.state === "error"),
    sourceBurn: burn?.txHash,
    sourceExplorer: burn?.explorerUrl,
    destinationMint: mint?.txHash,
    destinationExplorer: mint?.explorerUrl,
  };
}

export function useCctpBridge() {
  const { createAdapter } = useCctpAdapter();

  async function bridgeUsdc(amount: string) {
    const adapter = await createAdapter();
    const kit = new BridgeKit();
    kit.on("*", (event) => console.info(event.method, event.values));

    const result = await kit.bridge({
      from: { adapter, chain: "Ethereum" },
      to: { adapter, chain: "Arc", useForwarder: false },
      amount,
      token: "USDC",
    });
    return { result, ...summarize(result) };
  }

  async function resumeTransfer(result: BridgeResult) {
    if (result.state !== "error") return { result, ...summarize(result) };

    const adapter = await createAdapter();
    const resumed = await new BridgeKit().retry(result, {
      from: adapter,
      to: adapter,
    });
    return { result: resumed, ...summarize(resumed) };
  }

  return { bridgeUsdc, resumeTransfer };
}
```

In a client component, call `bridgeUsdc("1.00")` from a transfer button. It returns the Bridge Kit `result` and a summary with `status`, `failedStep`, and the burn and mint transaction hashes and explorer links. Show a transfer as complete only when `status` is `complete`. For a failed transfer, read `failedStep?.name` and `failedStep?.errorMessage`. Handle errors thrown before Bridge Kit returns a result separately. Open the Ethereum burn and Arc mint explorer links to check their receipts and transferred amounts. The `fetchAttestation` event records progress between those transactions; it is not itself an onchain transaction.

## Resume an interrupted transfer

If `result.state` is `error`, inspect `result.steps` before asking the user to try again. An approved or burned transfer may already have spent gas or destroyed the source USDC. Keep that result and pass it to `resumeTransfer(result)` from a separate Resume action. The hook gives Bridge Kit's `retry()` a fresh authenticated Para adapter and returns the updated result and status summary.

Do not start a second `bridge()` call to recover a transfer whose burn succeeded. If a retry still fails, keep the burn transaction hash and investigate the failed step before attempting another transaction. A `pending` result is not a confirmed destination mint.

## Check the route

For a production check, record both the Ethereum burn transaction and the Arc mint transaction from the same Bridge Kit result. The source receipt alone confirms only the burn. The destination explorer receipt confirms the mint. For a testnet trial before using mainnet funds, follow Circle's <Link label="Ethereum Sepolia to Arc Testnet quickstart" href="https://docs.arc.io/app-kit/quickstarts/bridge-tokens-across-blockchains" newTab />. Circle's <Link label="Bridge Kit documentation" href="https://docs.arc.io/app-kit/bridge" newTab /> covers fees, supported routes, and recovery behavior.

## Keep building with Para

Use the same signed-in Para wallet for other EVM actions:

<CardGroup cols={3}>
  <Card title="Send tokens" description="Move tokens with a Para-signed transaction." href="/v3/react/guides/web3-operations/evm/send-tokens" />

  <Card title="Call a contract" description="Use a Para wallet for contract interactions." href="/v3/react/guides/web3-operations/evm/interact-with-contracts" />

  <Card title="Explore Arc" description="Review Arc network setup and wallet details." href="/v3/walkthroughs/arc" />
</CardGroup>
