Skip to main content

What We’re Building

A React (Next.js) app that connects Dynamic’s embedded wallets to Circle Gateway infrastructure, allowing users to:
  • Deposit USDC into Circle Gateway across multiple domains
  • Transfer USDC cross-chain between Sepolia, Base Sepolia, and Arc Testnet
  • View USDC balances across all domains in a unified interface
  • Sign EIP-712 messages for secure cross-chain attestations
If you want to take a quick look at the final code, check out the GitHub repository (built on the legacy React SDK with wagmi, so use it for the Circle Gateway logic; this page shows the same app wired to the JavaScript SDK).

Key Components

  • Embedded wallets: Non-custodial, MPC-backed wallets with seamless auth
  • Circle Gateway: Cross-chain infrastructure for USDC transfers between domains
  • EIP-712 Signing: Secure typed data signing for attestation messages

Supported Networks

This guide demonstrates Circle Gateway integration with Circle Arc testnet, along with Sepolia (Domain 0) and Base Sepolia (Domain 6). Circle Arc is a new Layer-1 blockchain network designed as an Economic Operating System for the internet, featuring predictable dollar-based fees, sub-second finality, and EVM compatibility.

How It Works

Cross-Chain Transfer Flow

Circle Gateway uses a burn-and-mint mechanism for cross-chain USDC transfers:
  1. Deposit: Users deposit USDC into the Gateway Wallet contract on the source chain
  2. Burn Intent: When transferring, the app constructs a burn intent specifying the source domain, destination domain, amount, and recipient
  3. EIP-712 Signing: The burn intent is signed using EIP-712 typed data signing, which provides cryptographic proof of the user’s intent
  4. Attestation: The signed burn intent is submitted to Circle Gateway API, which validates the signature and returns an attestation
  5. Mint: The app switches to the destination chain and calls the Gateway Minter contract with the attestation to mint USDC

EIP-712 Typed Data

EIP-712 provides a standard way to sign structured data. The burn intent includes:
  • Transfer Specification: Source and destination domains, contracts, tokens, and amounts
  • Burn Intent: Maximum block height, maximum fee, and the transfer spec
  • Domain Separator: Ensures signatures are chain-specific and cannot be replayed
This secure signing mechanism ensures that cross-chain transfers are authorized by the wallet owner and cannot be tampered with.

Building the Application

Project setup

Follow the React Quickstart (JS SDK): the JavaScript SDK’s EVM extension covers external and embedded EVM wallets directly, so you don’t need wagmi. This recipe’s example uses Next.js, so scaffold with create-next-app and apply the same packages and provider wiring as in the quickstart below.
Developer console: Under Chains & Networks, enable the testnets this guide uses (Sepolia, Base Sepolia, and Arc Testnet) and any other EVM networks you need. Under Sign-in Methods and Wallets, enable what your flow needs (including Embedded wallets). Under Security → Allowed Origins, add the origin where the app runs (for example http://localhost:3000).

Configure Dynamic Environment

Create a .env.local file with your Dynamic environment ID:
.env.local
You can find your Environment ID in the Dynamic developer console under Developer Settings → SDK & API Keys.

Add Chain Configuration

Create src/lib/chains.ts to define the Circle Gateway domain IDs and contract addresses. This file contains all the chain-specific configuration needed for the Gateway integration.
src/lib/chains.ts
This defines the three testnet domains, their USDC contract addresses, the shared Gateway contracts, and the network ID the SDK uses for each chain.

Create the Dynamic Client

Create src/lib/dynamic-client.ts. Importing this module at the app root registers the EVM extension before any component renders.
src/lib/dynamic-client.ts

Wire Up the Providers

Create src/components/providers.tsx to wrap the app in QueryClientProvider and DynamicProvider, and to create the embedded wallet as soon as a user signs in. Mount QueryClientProvider outside DynamicProvider.
src/components/providers.tsx
Wrap your root layout’s children in <Providers> (for example in src/app/layout.tsx), and import src/lib/dynamic-client at the app root so extensions register before any component renders.

Create a Login Component

The JavaScript SDK is headless, so there’s no built-in login form. Create src/components/dynamic/dynamic-login.tsx with a minimal email OTP form:
src/components/dynamic/dynamic-login.tsx

Create Gateway Utilities

Create src/lib/gateway.ts to handle Circle Gateway API calls and EIP-712 signing. This file contains all the API communication logic for interacting with the Circle Gateway service. It provides functions to build burn intents, create EIP-712 typed data structures for signing, and handle address conversions required for cross-chain transfers. The GatewayAPI object provides methods for making HTTP requests to the Circle Gateway API, while the burnIntent and burnIntentTypedData functions construct the structured data needed for EIP-712 message signing, which is required for secure cross-chain attestations.
src/lib/gateway.ts
This handles Circle Gateway API calls and creates EIP-712 typed data for signing cross-chain transfers. Nothing here depends on Dynamic: it’s the same Circle Gateway logic as any other client.

Build Main Gateway Component

Create src/components/GatewayApp.tsx for the main interface. This component serves as the central hub for the Gateway integration, displaying USDC balances across all three domains and providing access to deposit and transfer functionality. The component:
  • Fetches balances: Retrieves USDC balances from Circle Gateway API for all supported domains
  • Displays unified view: Shows balances across Sepolia, Base Sepolia, and Arc Testnet in a single interface
  • Manages state: Handles loading states and error handling for API calls
  • Integrates forms: Includes deposit and transfer forms for user interactions
The JavaScript SDK doesn’t track a “current” wallet, so this page picks the first EVM wallet account with isEvmWalletAccount and passes it down to the deposit and transfer forms:
The full GatewayApp.tsx page below wires this into the balance grid and the deposit/transfer forms:
src/components/GatewayApp.tsx
This component fetches and displays USDC balances across all three domains, plus the deposit and transfer forms. It gates the whole UI behind useInitStatus().data === 'finished' and shows the login form until useUser() returns a user; once signed in, it waits for the embedded wallet created by WaasBootstrap before rendering the forms.

Create Deposit Form

Create src/components/DepositForm.tsx for depositing USDC. This component handles the two-step deposit process required by Circle Gateway:
  1. Approve Gateway Wallet: Grants the Gateway Wallet contract permission to spend the user’s USDC
  2. Deposit to Gateway: Transfers USDC into the Gateway Wallet contract on the selected chain
The core of this flow (checking the active network, switching if needed, and sending the approve + deposit transactions) is the same whether or not you’re in React:
The React tab is the actual DepositForm.tsx used in this recipe’s Next.js app: it wraps the same approve-then-deposit flow in a form, comparing the active network from useGetActiveNetworkData against the selected source chain and calling useSwitchActiveNetwork’s switchNetwork first if they don’t match, then waiting for the approval transaction to confirm (via a Viem PublicClient created with createPublicClientFromNetworkData) before proceeding with the deposit. Users can select which chain they want to deposit on (Sepolia, Base Sepolia, or Arc Testnet), and the component will switch to that network if needed. It validates that the amount is positive before starting. Once the deposit succeeds, the transaction hash is displayed so users can track it on the blockchain explorer. This makes the USDC available in the Gateway Wallet contract, ready for cross-chain transfers.

Create Transfer Form

This form builds a burn intent, signs it with EIP-712 typed data, submits it to Circle Gateway for attestation, then switches to the destination chain and mints. The signing and minting logic is the same whether or not you’re in React:
Both tabs implement the same cross-chain transfer flow using Circle Gateway’s burn-and-mint mechanism: build a burn intent specifying the source and destination domains, contract addresses, amount, and recipient; convert it to EIP-712 typed data and sign it with a Viem wallet client from createWalletClientForWalletAccount. Signing happens on the source chain’s active network, so no switch is needed yet. The signed burn intent is sent to Circle Gateway’s API, which validates the signature and returns an attestation. The flow then switches to the destination chain and uses the attestation to mint USDC there. The React tab is the actual TransferForm.tsx used in this recipe’s Next.js app, wrapping the same flow in a form.

Update Main Page

Update src/app/page.tsx to use the GatewayApp component. This is the entry point for the application that renders the main Gateway interface. QueryClientProvider and DynamicProvider already wrap the tree from Providers in your root layout, so this page only needs the Gateway UI itself.
src/app/page.tsx

Enable Transaction Simulation

Dynamic’s transaction simulation shows detailed transaction previews before execution. To enable:
  1. Go to your Dynamic developer console
  2. Navigate to Developer Settings → Embedded Wallets → Dynamic
  3. Enable “Show Confirmation UI” and “Transaction Simulation” toggles
When enabled, users see a transaction preview when depositing or transferring USDC:

Configure CORS

Add http://localhost:3000 to CORS origins in your Dynamic developer console under Developer Settings > CORS Origins.

Run the Application

Start the development server:
The application will be available at http://localhost:3000.

Conclusion

If you want to take a look at the full source code, check out the GitHub repository. This integration demonstrates how Dynamic’s embedded wallets connect to Circle Gateway for secure cross-chain USDC transfers using EIP-712 signing.

Additional Resources

Last modified on October 9, 2026