Skip to content
This repository was archived by the owner on Mar 17, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions examples/nextjs/components/ccip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ function TransferTokensAndMessage({ walletClient }: { walletClient: WalletClient
const [data, setData] = useState<Hex>();
const [messageId, setMessageId] = useState<string>();
const [txHash, setTxHash] = useState<string>();
const [useEip7702, setUseEip7702] = useState<boolean>(true);

return (
<div className="space-y-2 border rounded-md p-4 bg-white">
Expand Down Expand Up @@ -240,16 +239,6 @@ function TransferTokensAndMessage({ walletClient }: { walletClient: WalletClient
onChange={({ target }) => setAmount(target.value)}
/>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="useEip7702"
checked={useEip7702}
onChange={({ target }) => setUseEip7702(target.checked)}
className="rounded border-slate-300"
/>
<label htmlFor="useEip7702" className="text-sm">Use EIP-7702</label>
</div>
<button
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
onClick={async () => {
Expand All @@ -262,7 +251,6 @@ function TransferTokensAndMessage({ walletClient }: { walletClient: WalletClient
destinationAccount: destinationAccount as Address,
tokenAddress: tokenAddress as Address,
data,
useEip7702: useEip7702,
});
setMessageId(result.messageId);
setTxHash(result.txHash);
Expand Down
2 changes: 0 additions & 2 deletions examples/nextjs/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,4 @@ export const config: Config = {
shape: { radius: 4 },
},
showFaucet: true,
// Enable EIP-7702 support for atomic transactions
useEip7702: true,
};
6 changes: 3 additions & 3 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
"lint": "next lint"
},
"dependencies": {
"@chainlink/ccip-js": "workspace:*",
"@chainlink/ccip-react-components": "workspace:*",
"@chainlink/ccip-js": "^0.2.1",
"@chainlink/ccip-react-components": "^0.3.0",
"@tanstack/react-query": "^5.37.1",
"next": "14.2.3",
"react": "18",
"react-dom": "18",
"typescript": "^5",
"viem": "2.31.6",
"viem": "2.21.25",
"wagmi": "2.12.7"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/ccip-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@
"mocha": "^11.1.0",
"ts-jest": "^29.2.5",
"typescript": "^5.8.2",
"viem": "2.31.6"
"viem": "2.21.25"
}
}
107 changes: 6 additions & 101 deletions packages/ccip-js/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,6 @@ export interface Client {
* @param {Viem.Address} options.feeTokenAddress - The address of the token used for paying fees. If not specified the chain's native token will be used.
* @param {Viem.Hex} options.data - Arbitrary data to send along with the transaction. ABI encoded
* @param {EVMExtraArgsV2} options.extraArgs - Pass extraArgs. Check [CCIP Docs](https://docs.chain.link/ccip/best-practices#using-extraargs) how to use it
* @param {boolean} options.useEip7702 - Try EIP‑7702 if wallet + chain say yes (default: true)
* @param {Object} options.writeContractParameters
* - Override the **optional** write contract parameters for the 'approve' method.
* @param {Object} options.waitForTransactionReceiptParameters
Expand Down Expand Up @@ -386,7 +385,6 @@ export interface Client {
feeTokenAddress?: Viem.Address
data?: Viem.Hex
extraArgs?: EVMExtraArgsV2
useEip7702?: boolean
writeContractParameters?: Partial<{
gas: bigint
gasPrice: bigint
Expand Down Expand Up @@ -889,113 +887,20 @@ export const createClient = (): Client => {
}
}

// Use EIP-7702 if user has enabled it
const use7702 = options.useEip7702 === true
var txReceipt: Viem.TransactionReceipt | undefined;
var transferTokensTxHash: `0x${string}`;
const args = buildArgs(options)
const feeValue = options.feeTokenAddress ? await getFee(options) : undefined
const approveFeeCall = {
to: options.feeTokenAddress,
chain: options.client.chain,
abi: Viem.erc20Abi,
address: options.feeTokenAddress,
functionName: 'approve',
args: [options.routerAddress, feeValue || 0n],
account: options.client.account!,
...options.writeContractParameters,
} as const;
const approveCall = {
to: options.tokenAddress,
chain: options.client.chain,
abi: Viem.erc20Abi,
address: options.tokenAddress,
functionName: 'approve',
args: [options.routerAddress, options.amount],
account: options.client.account!,
...options.writeContractParameters,
} as const;
const transferCall: any = {
to: options.routerAddress,
const writeContractParameters: any = {
chain: options.client.chain,
abi: RouterABI,
address: options.routerAddress,
functionName: 'ccipSend',
args,
args: buildArgs(options),
account: options.client.account!,
value: options.feeTokenAddress ? undefined : feeValue,
value: options.feeTokenAddress ? undefined : await getFee(options), // Only add native token value if no fee token is specified
...options.writeContractParameters,
} as const;

// try EIP7702 flow
try {
if (!use7702) throw new Error('EIP-7702 is not enabled')

console.info('transferTokens(): Using EIP-7702')

const calls = [approveCall, transferCall];
if (options.feeTokenAddress) calls.unshift(approveFeeCall);

const bundleId = (await (options.client as Viem.WalletClient).sendCalls({
calls,
experimental_fallback: true,
...options.writeContractParameters,
})).id as string
console.info('transferTokens(): EIP-7702 bundleId: ', bundleId)

const { status, receipts } = (await (options.client as Viem.WalletClient).waitForCallsStatus({ id: bundleId }));
if (status === 'failure') throw new Error('Transaction failed');

transferTokensTxHash = receipts?.[0]?.transactionHash as `0x${string}`;
if (!transferTokensTxHash) throw new Error('Transaction hash not found');

console.info('transferTokens(): EIP-7702 txHash: ', transferTokensTxHash)
}
// if EIP-7702 fails, fall back to legacy flow
catch (error: any) {
// Check if the error is due to user rejecting the upgrade
// OR if the error is due to EIP-7702 not being enabled
if (error?.name === 'AtomicReadyWalletRejectedUpgradeError' ||
error?.message?.includes('user rejected the upgrade') ||
error?.message?.includes('User rejected account upgrade') ||
error?.message?.includes('EIP-7702 is not enabled')
) {
console.warn('transferTokens(): EIP-7702 Failed, falling back to legacy flow')

// Legacy 2-step fallback flow
// First, check if approval is needed
const currentAllowance = await getAllowance({
client: options.client,
routerAddress: options.routerAddress,
tokenAddress: options.tokenAddress,
account: options.client.account!.address,
})

// If approval is needed, do the approval first
if (currentAllowance < options.amount) {
console.info('transferTokens(): Approval needed, approving tokens first')

const approveTxHash = await writeContract(options.client, approveCall)

// Wait for approval transaction to be confirmed
await waitForTransactionReceipt(options.client, {
hash: approveTxHash,
confirmations: 2,
...options.waitForTransactionReceiptParameters,
})

console.info('transferTokens(): Approval completed, proceeding with transfer')
}

transferTokensTxHash = await writeContract(options.client, transferCall)

} else {
// Re-throw other errors
throw error
}
}

txReceipt = await waitForTransactionReceipt(options.client, {
const transferTokensTxHash = await writeContract(options.client, writeContractParameters)

const txReceipt = await waitForTransactionReceipt(options.client, {
hash: transferTokensTxHash,
confirmations: 2,
...options.waitForTransactionReceiptParameters,
Expand Down
52 changes: 35 additions & 17 deletions packages/ccip-js/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,46 @@ export const ExecutionStateChangedABI = {
name: 'ExecutionStateChanged',
inputs: [
{
"name": "sequenceNumber",
"type": "uint64",
"indexed": true,
"internalType": "uint64"
name: 'sourceChainSelector',
type: 'uint64',
indexed: true,
internalType: 'uint64',
},
{
"name": "messageId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
name: 'sequenceNumber',
type: 'uint64',
indexed: true,
internalType: 'uint64',
},
{
"name": "state",
"type": "uint8",
"indexed": false,
"internalType": "enum Internal.MessageExecutionState"
name: 'messageId',
type: 'bytes32',
indexed: true,
internalType: 'bytes32',
},
{
"name": "returnData",
"type": "bytes",
"indexed": false,
"internalType": "bytes"
}
name: 'messageHash',
type: 'bytes32',
indexed: false,
internalType: 'bytes32',
},
{
name: 'state',
type: 'uint8',
indexed: false,
internalType: 'enum Internal.MessageExecutionState',
},
{
name: 'returnData',
type: 'bytes',
indexed: false,
internalType: 'bytes',
},
{
name: 'gasUsed',
type: 'uint256',
indexed: false,
internalType: 'uint256',
},
],
} as const
13 changes: 0 additions & 13 deletions packages/ccip-react-components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,21 +344,8 @@ const config: Config = { theme:
radius?: number;
};
};}

### EIP-7702 Support

You can enable EIP-7702 support for atomic transactions. When enabled, the component will use EIP-7702 `sendCalls` for single-transaction transfers instead of the legacy two-step approve + transfer flow.

```typescript
import { Config } from 'ccip-react-components';
const config: Config = {
// Enable EIP-7702 support for atomic transactions
useEip7702: true,
};
```

**Note**: When EIP-7702 is enabled, the component will attempt to use atomic transactions. If the wallet or chain doesn't support EIP-7702, it will automatically fall back to the legacy two-step flow.

### Variants

There are three variants: `default`, `compact` and `drawer`
Expand Down
3 changes: 0 additions & 3 deletions packages/ccip-react-components/lib/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export const Context = createContext<{
feeTokenBalance?: bigint;
isConnectOpen?: boolean;
setIsConnectOpen: (open: boolean) => void;
useEip7702?: boolean;
}>({
chains: [],
chainsInfo: [],
Expand Down Expand Up @@ -86,7 +85,6 @@ export const ContextProvider = ({
variant: configProp?.variant,
walletConfig: configProp?.walletConfig,
showFaucet: configProp?.showFaucet,
useEip7702: configProp?.useEip7702 ?? true,
}),
[configProp]
);
Expand Down Expand Up @@ -178,7 +176,6 @@ export const ContextProvider = ({
feeTokenBalance,
isConnectOpen,
setIsConnectOpen: (o: boolean) => setIsConnectOpen(o),
useEip7702: config.useEip7702,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export function SendButton({
feeTokenAddress,
feeAmount,
feeTokenBalance,
useEip7702,
} = useAppContext();

const { data: walletClient } = useWalletClient();
Expand Down Expand Up @@ -72,7 +71,7 @@ export function SendButton({
address: feeTokenAddress,
functionName: 'allowance',
args: [destinationAccount, routerAddress],
query: { enabled: feeTokenSymbol === 'LINK' && !useEip7702 },
query: { enabled: feeTokenSymbol === 'LINK' },
});

const { data: isTokenSupported, isPending: isTokenSupportedPending } =
Expand Down Expand Up @@ -127,7 +126,6 @@ export function SendButton({
destinationAccount: Address;
tokenAddress: Address;
feeTokenAddress?: Address;
useEip7702?: boolean;
}) => await ccipClient.transferTokens(options),
onSuccess: ({ messageId, txHash }) => {
setMessageId(messageId);
Expand Down Expand Up @@ -168,8 +166,7 @@ export function SendButton({
balance &&
amount &&
parseUnits(amount, balance.decimals) >
((tokenAllowance as bigint) || BigInt(0)) &&
!useEip7702; // Skip approval if EIP-7702 is enabled
((tokenAllowance as bigint) || BigInt(0));

if (!isTokenSupportedPending && !isTokenSupported) {
return <Error message="Token is not supported on destination chain" />;
Expand Down Expand Up @@ -273,7 +270,6 @@ export function SendButton({
tokenAddress,
destinationAccount,
feeTokenAddress,
useEip7702: useEip7702, // Use EIP-7702 if enabled
})
}
>
Expand Down
5 changes: 0 additions & 5 deletions packages/ccip-react-components/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ export type Config = {
walletConfig?: WalletConfig;
/** Display faucet button for testnet chains */
showFaucet?: boolean;
/** Enable EIP-7702 support for atomic transactions (default: true)
* When enabled, the component will use EIP-7702 sendCalls for single-transaction transfers
* instead of the legacy two-step approve + transfer flow.
*/
useEip7702?: boolean;
};

export type WalletConfig = {
Expand Down
2 changes: 1 addition & 1 deletion packages/ccip-react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.2.2",
"viem": "2.31.6",
"viem": "2.21.25",
"wagmi": "2.12.7",
"zod": "^3.23.8"
},
Expand Down
Loading