⚠️ NOTEThe AppchainBridge component is alpha release software and is provided AS-IS. Use at your own risk.
Bridge tokens to appchains with a simple, customizable interface.
Usage
Create a custom chain for your appchain using Viem’s defineChain, then add it to your Wagmi configuration.
import { defineChain } from 'viem';
export const EXAMPLE_CHAIN = defineChain({
id: 8453200000,
name: 'Your Appchain Network',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: {
default: {
http: ['https://your-rpc.appchain.base.org'],
},
},
});
Use the custom chain to create an Appchain
object. You can also render an icon in the UI using the icon
prop.
import { defineChain } from 'viem';
const EXAMPLE_CHAIN = defineChain({
id: 8453200000,
name: 'Your Appchain Network',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: {
default: {
http: ['https://your-rpc.appchain.base.org'],
},
},
});
// ---cut-before---
import type { Appchain } from '@coinbase/onchainkit/appchain';
const EXAMPLE_APPCHAIN: Appchain = {
chain: EXAMPLE_CHAIN,
icon: <img src="https://some-icon.com/icon.png" alt="Your Appchain Network" />,
};
Now, you can render the AppchainBridge
component with the chain
and appchain
props.
import { defineChain } from 'viem';
const EXAMPLE_CHAIN = defineChain({
id: 8453200000,
name: 'Your Appchain Network',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: {
default: {
http: ['https://your-rpc.appchain.base.org'],
},
},
});
import type { Appchain } from '@coinbase/onchainkit/appchain';
const EXAMPLE_APPCHAIN: Appchain = {
chain: EXAMPLE_CHAIN,
icon: <img src="https://some-icon.com/icon.png" alt="Your Appchain Network" />,
};
// ---cut-before---
import { AppchainBridge } from '@coinbase/onchainkit/appchain';
import { baseSepolia } from 'viem/chains';
<AppchainBridge chain={baseSepolia} appchain={EXAMPLE_APPCHAIN} />
Custom Bridgeable Tokens
By default, only native ETH is bridgeable. Add custom tokens using the bridgeableTokens
prop:
import { defineChain } from 'viem';
const EXAMPLE_CHAIN = defineChain({
id: 8453200000,
name: 'Your Appchain Network',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: {
default: {
http: ['https://your-rpc.appchain.base.org'],
},
},
});
import type { Appchain } from '@coinbase/onchainkit/appchain';
const EXAMPLE_APPCHAIN: Appchain = {
chain: EXAMPLE_CHAIN,
icon: <img src="https://some-icon.com/icon.png" alt="Your Appchain Network" />,
};
// ---cut-before---
import type { BridgeableToken } from '@coinbase/onchainkit/appchain';
const customBridgeableTokens: BridgeableToken[] = [
{
name: 'ETH',
address: '',
symbol: 'ETH',
decimals: 18,
image:
'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
chainId: 8453200000,
},
{
address: '0x...',
remoteToken: '0x...',
name: 'Appchain Token',
symbol: 'ATKN',
decimals: 18,
chainId: 8453200000,
image: 'https://some-icon.com/icon.png',
},
];
⚠️ What is remoteToken?The remoteToken
field represents the token address on the appchain you’re bridging to.ERC-20 tokens on the appchain must comply to the IOptimismMintableERC20
interface to be bridgeable.Follow the Optimism documentation to retrieve the remoteToken
address for your ERC-20 token.
You can then plug the customBridgeableTokens
into the AppchainBridge
component with the bridgeableTokens
prop.
import type { BridgeableToken } from '@coinbase/onchainkit/appchain';
const customBridgeableTokens: BridgeableToken[] = [
{
name: 'ETH',
address: '',
symbol: 'ETH',
decimals: 18,
image:
'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
chainId: 84532,
},
{
address: '0x...',
remoteToken: '0x...',
name: 'Sandbox Token',
symbol: 'SBOX',
decimals: 18,
chainId: 8453200058,
image: 'https://img.cryptorank.io/coins/blocklords1670492311588.png',
},
];
// ---cut-before---
import { AppchainBridge } from '@coinbase/onchainkit/appchain';
import { baseSepolia } from 'viem/chains';
<AppchainBridge
chain={baseSepolia}
appchain={SANDBOX_APPCHAIN}
bridgeableTokens={customBridgeableTokens}
/>
Custom Gas Tokens
For chains using custom gas tokens, set isCustomGasToken: true
:
import type { BridgeableToken } from '@coinbase/onchainkit/appchain';
const customGasToken: BridgeableToken[] = [
{
address: '0x...',
remoteToken: '0x...',
name: 'Appchain Token',
symbol: 'ATKN',
decimals: 18,
chainId: 8453200000,
image: 'https://some-icon.com/icon.png',
isCustomGasToken: true,
},
];
Custom Price Fetching
Override handleFetchPrice
to fetch prices for custom tokens. Called whenever the user changes the input amount:
(amount: string, token: Token) => Promise<string>;
import { AppchainBridge } from '@coinbase/onchainkit/appchain';
import type { Token } from '@coinbase/onchainkit/token';
import { baseSepolia } from 'viem/chains';
const handleFetchPrice = async (amount: string, token: Token): Promise<string> => {
// Example: fetch price from your API
const response = await fetch(`https://api.example.com/price/${token.address}`);
const priceData = await response.json();
return (parseFloat(amount) * priceData.price).toString();
};
<AppchainBridge
chain={baseSepolia}
appchain={EXAMPLE_APPCHAIN}
bridgeableTokens={customBridgeableTokens}
handleFetchPrice={handleFetchPrice}
/>
Components
AppchainBridge
- Complete bridge interface for deposits and withdrawals
AppchainBridgeProvider
- Headless provider for bridge context
AppchainBridgeInput
- Amount input component
AppchainBridgeNetwork
- Network selection and display
AppchainBridgeTransactionButton
- Transaction trigger button
AppchainBridgeWithdraw
- Withdrawal interface
AppchainNetworkToggleButton
- Network toggle button
AppchainBridgeSuccess
- Success state display
AppchainBridgeResumeTransaction
- Resume interrupted transactions
Props
Appchain
export type Appchain = {
/** The chain to bridge to. */
chain: Chain;
/** Optional icon to display for the appchain. */
icon?: React.ReactNode;
};
AppchainBridgeProps
export type AppchainBridgeProps = {
/** The source chain to bridge from. This should be Base or Base Sepolia. */
chain: Chain;
/** The appchain to bridge to. */
appchain: Appchain;
/** Optional children to render within the component. */
children?: React.ReactNode;
/** Optional className override for the component. */
className?: string;
/** Optional title for the component. */
title?: string;
/** Optional array of bridgeable tokens. */
bridgeableTokens?: BridgeableToken[];
/** Optional function to implement fetching the price of the token. */
handleFetchPrice?: (amount: string, token: Token) => Promise<string>;
};
AppchainBridgeProviderProps
export type AppchainBridgeProviderProps = {
children: ReactNode;
chain: Chain;
appchain: Appchain;
bridgeableTokens?: BridgeableToken[];
handleFetchPrice?: (amount: string, token: Token) => Promise<string>;
};
AppchainBridgeContextType
export type AppchainBridgeContextType = {
// Configuration
config: AppchainConfig;
from: ChainWithIcon;
to: ChainWithIcon;
bridgeParams: BridgeParams;
bridgeableTokens: BridgeableToken[];
// UI State
isPriceLoading: boolean;
isAddressModalOpen: boolean;
isWithdrawModalOpen: boolean;
isSuccessModalOpen: boolean;
isResumeTransactionModalOpen: boolean;
balance: string;
depositStatus: string;
withdrawStatus: string;
direction: string;
depositTransactionHash?: Hex;
finalizedWithdrawalTxHash?: Hex;
resumeWithdrawalTxHash?: Hex;
// Handler Functions
handleToggle: () => void;
handleAmountChange: (params: { amount: string; token: Token }) => void;
handleAddressSelect: (address: Address) => void;
handleResumeTransaction: (txHash: Hex) => void;
handleDeposit: () => void;
handleWithdraw: () => void;
handleOpenExplorer: () => void;
handleResetState: () => void;
waitForWithdrawal: (txHash?: Hex) => Promise<void>;
proveAndFinalizeWithdrawal: () => Promise<void>;
setIsAddressModalOpen: (open: boolean) => void;
setIsWithdrawModalOpen: (open: boolean) => void;
setIsSuccessModalOpen: (open: boolean) => void;
resetDepositStatus: () => void;
setResumeWithdrawalTxHash: (txHash: Hex) => void;
setIsResumeTransactionModalOpen: (open: boolean) => void;
};
BridgeableToken
export type BridgeableToken = Token & {
/** The address of the remote token on the appchain. */
remoteToken?: Address;
/** Optional boolean to indicate if the chain uses a custom gas token */
isCustomGasToken?: boolean;
};
ChainWithIcon
export type ChainWithIcon = Chain & {
icon: React.ReactNode;
};
AppchainConfig
export type AppchainConfig = {
chainId: number;
/** The OP Bedrock contract addresses for an appchain. These are on Base and retrieved from DeployContract. */
contracts: {
l2OutputOracle: Address;
systemConfig: Address;
optimismPortal: Address;
l1CrossDomainMessenger: Address;
l1StandardBridge: Address;
l1ERC721Bridge: Address;
optimismMintableERC20Factory: Address;
};
};
AppchainBridgeSuccessProps
export type AppchainBridgeSuccessProps = {
title?: string;
primaryButtonLabel?: string;
secondaryButtonLabel?: string;
};
BridgeParams
export type BridgeParams = {
amount: string;
amountUSD: string;
token: BridgeableToken;
recipient?: Address;
};
ChainConfigParams
export type ChainConfigParams = {
config: AppchainConfig;
chain: Chain;
};
UseDepositParams
export type UseDepositParams = {
config: AppchainConfig;
from: Chain;
bridgeParams: BridgeParams;
};
UseWithdrawParams
export type UseWithdrawParams = {
config: AppchainConfig;
chain: Chain;
bridgeParams: BridgeParams;
};
export type UseDepositButtonParams = {
depositStatus: string;
withdrawStatus: string;
bridgeParams: BridgeParams;
};
export type UseWithdrawButtonParams = {
withdrawStatus: string;
};