diff --git a/examples/nextjs/app/ccip-js-ethers/page.tsx b/examples/nextjs/app/ccip-js-ethers/page.tsx new file mode 100644 index 0000000..f0ad3c0 --- /dev/null +++ b/examples/nextjs/app/ccip-js-ethers/page.tsx @@ -0,0 +1,10 @@ +import { CCIPEthers } from "@/components/ccip-with-ethers"; +import { Providers } from "./providers"; + +export default function CCIPJsPage() { + return ( + + + + ); +} diff --git a/examples/nextjs/app/ccip-js/providers.tsx b/examples/nextjs/app/ccip-js-ethers/providers.tsx similarity index 100% rename from examples/nextjs/app/ccip-js/providers.tsx rename to examples/nextjs/app/ccip-js-ethers/providers.tsx diff --git a/examples/nextjs/app/ccip-js/page.tsx b/examples/nextjs/app/ccip-js-wagmi/page.tsx similarity index 73% rename from examples/nextjs/app/ccip-js/page.tsx rename to examples/nextjs/app/ccip-js-wagmi/page.tsx index 0b00b06..7c8b8e6 100644 --- a/examples/nextjs/app/ccip-js/page.tsx +++ b/examples/nextjs/app/ccip-js-wagmi/page.tsx @@ -1,4 +1,4 @@ -import { CCIP } from "@/components/ccip"; +import { CCIP } from "@/components/ccip-with-wagmi"; import { Providers } from "./providers"; export default function CCIPJsPage() { diff --git a/examples/nextjs/app/ccip-js-wagmi/providers.tsx b/examples/nextjs/app/ccip-js-wagmi/providers.tsx new file mode 100644 index 0000000..91787dc --- /dev/null +++ b/examples/nextjs/app/ccip-js-wagmi/providers.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { WagmiProvider } from 'wagmi'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactNode } from 'react'; +import { wagmiConfig } from '@/config/wagmiConfig'; + +const queryClient = new QueryClient(); + +export function Providers({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/examples/nextjs/app/layout.tsx b/examples/nextjs/app/layout.tsx index bf73ccd..8353c1b 100644 --- a/examples/nextjs/app/layout.tsx +++ b/examples/nextjs/app/layout.tsx @@ -34,9 +34,15 @@ export default function RootLayout({ - CCIP-JS + CCIP-JS (Wagmi) + + + CCIP-JS (Ethers)
diff --git a/examples/nextjs/components/ccip-with-ethers.tsx b/examples/nextjs/components/ccip-with-ethers.tsx new file mode 100644 index 0000000..a92e2c9 --- /dev/null +++ b/examples/nextjs/components/ccip-with-ethers.tsx @@ -0,0 +1,1166 @@ +"use client"; + +import { createClient, IERC20ABI, RateLimiterState, TransferStatus, SupportedClient } from "@chainlink/ccip-js"; +import { + Address, + encodeAbiParameters, + encodeFunctionData, + Hash, + Hex, + parseEther, + PublicClient, + TransactionReceipt, + WalletClient, +} from "viem"; +import { useState, useEffect } from "react"; // Added useEffect + +import { ethers, BrowserProvider, Signer } from "ethers"; // Added BrowserProvider, Signer + +const ccipClient = createClient(); + +export function CCIPEthers() { + const [ethersProvider, setEthersProvider] = useState(undefined); + const [ethersSigner, setEthersSigner] = useState(undefined); + const [ethersErrorMessage, setEthersErrorMessage] = useState(null); + + return ( +
+ {!ethersProvider && !ethersSigner && ( + + )} + {ethersErrorMessage && ( +
{ethersErrorMessage}
+ )} + {ethersProvider && ( + <> + + + + + + + + + + + )} + {ethersSigner && ( + <> + + + + + + )} +
+ ); +} + +interface ConnectWalletProps { + setEthersProvider: React.Dispatch>; + setEthersSigner: React.Dispatch>; + setEthersErrorMessage: React.Dispatch>; +} + +function ConnectWallet({ setEthersProvider, setEthersSigner, setEthersErrorMessage }: ConnectWalletProps) { + const [currentAccount, setCurrentAccount] = useState(null); + const [currentChainId, setCurrentChainId] = useState(null); + const [connectionStatus, setConnectionStatus] = useState("Disconnected"); + const [connectError, setConnectError] = useState(null); + const [switchChainError, setSwitchChainError] = useState(null); + + const availableChains = [ + { id: 43113, name: "Avalanche Fuji", hexId: "0xA28A" }, // Example: Avalanche Fuji testnet + { id: 11155111, name: "Sepolia", hexId: "0xAA36A7" }, // Example: Sepolia testnet + // Add other chains as needed + ]; + + useEffect(() => { + const initEthersConnection = async () => { + if (typeof window !== "undefined" && window.ethereum) { + try { + setConnectionStatus("Connecting..."); + const accounts = await window.ethereum.request({ method: "eth_accounts" }); + if (accounts.length > 0) { + setCurrentAccount(accounts[0]); + const provider = new ethers.BrowserProvider(window.ethereum); + setEthersProvider(provider); + const signer = await provider.getSigner(); + setEthersSigner(signer); + const network = await provider.getNetwork(); + setCurrentChainId(`${network.chainId}`); + setConnectionStatus("Connected"); + setConnectError(null); + } else { + setConnectionStatus("Disconnected"); + setCurrentAccount(null); + setEthersProvider(undefined); + setEthersSigner(undefined); + } + + const handleAccountsChanged = (accounts: string[]) => { + console.log('Ethers: Accounts changed:', accounts); + if (accounts.length === 0) { + setCurrentAccount(null); + setEthersProvider(undefined); + setEthersSigner(undefined); + setConnectionStatus("Disconnected"); + setEthersErrorMessage("MetaMask disconnected or no accounts available."); + } else { + setCurrentAccount(accounts[0]); + initEthersConnection(); // Re-initialize provider/signer with new account + setEthersErrorMessage(null); + } + }; + + const handleChainChanged = (chainId: string) => { + console.log('Ethers: Chain changed:', chainId); + setCurrentChainId(parseInt(chainId, 16).toString()); + initEthersConnection(); // Re-initialize provider/signer for new chain + setEthersErrorMessage(null); + }; + + window.ethereum.on('accountsChanged', handleAccountsChanged); + window.ethereum.on('chainChanged', handleChainChanged); + + return () => { + window.ethereum.removeListener('accountsChanged', handleAccountsChanged); + window.ethereum.removeListener('chainChanged', handleChainChanged); + }; + } catch (error: any) { + console.error("Ethers: Failed to connect or initialize:", error); + setConnectionStatus("Disconnected"); + const errorMessage = `Ethers: Failed to connect or initialize: ${error.message || error}`; + setConnectError(errorMessage); + setEthersErrorMessage(errorMessage); + } + } else { + setConnectionStatus("Disconnected"); + const errorMessage = "Ethers: MetaMask or compatible wallet not detected."; + setConnectError(errorMessage); + setEthersErrorMessage(errorMessage); + } + }; + + initEthersConnection(); + }, []); + + const handleConnectWallet = async () => { + if (typeof window !== "undefined" && window.ethereum) { + try { + setConnectionStatus("Connecting..."); + setConnectError(null); + await window.ethereum.request({ method: "eth_requestAccounts" }); + const provider = new ethers.BrowserProvider(window.ethereum); + setEthersProvider(provider); + const signer = await provider.getSigner(); + setEthersSigner(signer); + const network = await provider.getNetwork(); + setCurrentChainId(`${network.chainId}`); + setCurrentAccount(await signer.getAddress()); + setConnectionStatus("Connected"); + setEthersErrorMessage(null); + } catch (error: any) { + console.error("Ethers: Failed to connect to MetaMask:", error); + setConnectionStatus("Disconnected"); + const errorMessage = `Ethers: Failed to connect to MetaMask: ${error.message || error}`; + setConnectError(errorMessage); + setEthersErrorMessage(errorMessage); + } + } else { + const errorMessage = "Ethers: MetaMask or compatible wallet not detected."; + setConnectError(errorMessage); + setEthersErrorMessage(errorMessage); + } + }; + + const handleSwitchChain = async (newChainId: string) => { + if (window.ethereum) { + try { + setSwitchChainError(null); + const targetChain = availableChains.find(c => `${c.id}` === newChainId); + if (!targetChain) { + throw new Error(`Chain with id ${newChainId} not found.`); + } + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: targetChain.hexId }], + }); + // ChainChanged event listener will handle updating currentChainId and re-initializing provider/signer + } catch (error: any) { + console.error("Ethers: Failed to switch chain:", error); + const errorMessage = `Ethers: Failed to switch chain: ${error.message || error}`; + setSwitchChainError(errorMessage); + setEthersErrorMessage(errorMessage); + } + } + }; + + return ( +
+

Connect Wallet (Ethers.js):

+
+ +
+ {connectError &&

{connectError}

} + {currentAccount &&

{`Address: ${currentAccount}`}

} + {currentChainId && ( + <> +

{`Connected to Chain ID: ${currentChainId} (Status: ${connectionStatus})`}

+
+ + +
+ {switchChainError &&

{switchChainError}

} + + )} +
+ ); +} + +function ApproveRouter({ ethersSigner }: { ethersSigner: SupportedClient }) { // Changed WalletClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [tokenAddress, setTokenAddress] = useState(); + const [amount, setAmount] = useState(); + const [txHash, setTxHash] = useState(); + + return ( +
+

Approve Transfer

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setTokenAddress(target.value)} + /> +
+
+ + setAmount(target.value)} + /> +
+ + {txHash && ( +
+ + {txHash} +
+ )} +
+ ); +} + +function TransferTokensAndMessage({ ethersSigner }: { ethersSigner: SupportedClient }) { // Changed WalletClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [tokenAddress, setTokenAddress] = useState(); + const [amount, setAmount] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [destinationAccount, setDestinationAccount] = useState(); + const [data, setData] = useState(); + const [messageId, setMessageId] = useState(); + const [txHash, setTxHash] = useState(); + + return ( +
+

Transfer Tokens

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setTokenAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+
+ + setDestinationAccount(target.value)} + /> +
+
+ + setData(encodeAbiParameters([{ type: "string", name: "data" }], [target.value]))} + /> +
+
+ + setAmount(target.value)} + /> +
+ + {txHash && ( +
+ + {txHash} +
+ )} + {messageId && ( +
+ + {messageId} +
+ )} +
+ ); +} + +function SendCCIPMessage({ ethersSigner }: { ethersSigner: SupportedClient }) { // Changed WalletClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [destinationAccount, setDestinationAccount] = useState(); + const [data, setData] = useState(); + const [messageId, setMessageId] = useState(); + const [txHash, setTxHash] = useState(); + + return ( +
+

Send Message

+
+ + setRouterAddress(target.value)} + /> +
+ +
+ + setDestinationChainSelector(target.value)} + /> +
+
+ + setDestinationAccount(target.value)} + /> +
+
+ + setData(encodeAbiParameters([{ type: "string", name: "data" }], [target.value]))} + /> +
+ + {txHash && ( +
+ + {txHash} +
+ )} + {messageId && ( +
+ + {messageId} +
+ )} +
+ ); +} + +function SendFunctionData({ ethersSigner }: { ethersSigner: SupportedClient }) { // Changed WalletClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [destinationAccount, setDestinationAccount] = useState(); + const [amount, setAmount] = useState(); + const [data, setData] = useState(); + const [messageId, setMessageId] = useState(); + const [txHash, setTxHash] = useState(); + + return ( +
+

Send Function Data

+

Using ERC20 transfer function

+
+ + setRouterAddress(target.value)} + /> +
+ +
+ + setDestinationChainSelector(target.value)} + /> +
+
+ + setDestinationAccount(target.value)} + /> +
+
+ + setAmount(target.value)} + /> +
+ + {txHash && ( +
+ + {txHash} +
+ )} + {messageId && ( +
+ + {messageId} +
+ )} +
+ ); +} + +function GetAllowance({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [tokenAddress, setTokenAddress] = useState(); + const [account, setAccount] = useState(); + const [allowance, setAllowance] = useState(); + return ( +
+

Get allowance:

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setTokenAddress(target.value)} + /> +
+ +
+ + setAccount(target.value)} + /> +
+ + + {allowance && ( +
+ + {allowance} +
+ )} +
+ ); +} + +function GetOnRampAddress({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [onRamp, setOnRamp] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + return ( +
+

Get On-ramp address:

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+ + + {onRamp && ( +
+ + {onRamp} +
+ )} +
+ ); +} + +function GetSupportedFeeTokens({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [supportedFeeTokens, setSupportedFeeTokens] = useState(); + + return ( +
+

Get supported fee tokens:

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+ + {supportedFeeTokens && supportedFeeTokens.length > 0 && ( +
+ + + {supportedFeeTokens.map(address => ( +
+                                {address}
+                            
+ ))} +
+
+ )} +
+ ); +} + +function GetLaneRateRefillLimits({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [rateLimits, setRateLimits] = useState(); + + return ( +
+

Get lane rate refil limits:

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+ + {rateLimits && ( +
+ + +
+                            {`Tokens: ${rateLimits.tokens.toLocaleString()}`}
+                        
+
+                            {`Last updated: ${new Date(rateLimits.lastUpdated * 1000).toLocaleString()}`}
+                        
+
{`Is enabled: ${rateLimits.isEnabled.toString()}`}
+
{`Capacity: ${rateLimits.capacity.toLocaleString()}`}
+
{`Rate: ${rateLimits.rate.toLocaleString()}`}
+
+
+ )} +
+ ); +} + +function GetTokenRateLimitByLane({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [tokenAddress, setTokenAddress] = useState(); + const [tokenRateLimits, setTokenRateLimits] = useState(); + + return ( +
+

Get token rate limit by lane:

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+
+ + setTokenAddress(target.value)} + /> +
+ + {tokenRateLimits && ( + <> +
+ + +
+                                {`Tokens: ${tokenRateLimits.tokens.toLocaleString()}`}
+                            
+
+                                {`Last updated: ${new Date(tokenRateLimits.lastUpdated * 1000).toLocaleString()}`}
+                            
+
{`Is enabled: ${tokenRateLimits.isEnabled.toString()}`}
+
{`Capacity: ${tokenRateLimits.capacity.toLocaleString()}`}
+
{`Rate: ${tokenRateLimits.rate.toLocaleString()}`}
+
+
+ + )} +
+ ); +} + +function IsTokenSupported({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [tokenAddress, setTokenAddress] = useState(); + const [isTokenSupported, setIsTokenSupported] = useState(); + + return ( +
+

Is token supported:

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+
+ + setTokenAddress(target.value)} + /> +
+ + {isTokenSupported && ( +
+ + {isTokenSupported.toLocaleString()} +
+ )} +
+ ); +} + +function GetTokenAdminRegistry({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [tokenAddress, setTokenAddress] = useState(); + const [tokenAdminRegistry, setTokenAdminRegistry] = useState(); + return ( +
+

Token admin registry:

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+
+ + setTokenAddress(target.value)} + /> +
+ + {tokenAdminRegistry && ( +
+ + {tokenAdminRegistry.toLocaleString()} +
+ )} +
+ ); +} + +function GetTransactionReceipt({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [hash, setHash] = useState(); + const [transactionReceipt, setTransactionReceipt] = useState(); + + return ( +
+

Get transaction receipt:

+ +
+ + setHash(target.value)} + /> +
+ + + {transactionReceipt && ( + <> +

{`Block Number: ${transactionReceipt.blockNumber.toString()}`}

+

{`From: ${transactionReceipt.from}`}

+

{`To: ${transactionReceipt.to}`}

+

{`Status: ${transactionReceipt.status}`}

+
+ + +
+                                {`Block Number: ${transactionReceipt.blockNumber.toString()}`}
+                            
+
{`From: ${transactionReceipt.from}`}
+
{`To: ${transactionReceipt.to}`}
+
{`Status: ${transactionReceipt.status}`}
+
+
+ + )} +
+ ); +} + +function GetFee({ ethersProvider }: { ethersProvider: SupportedClient }) { // Changed PublicClient to SupportedClient + const [routerAddress, setRouterAddress] = useState(); + const [tokenAddress, setTokenAddress] = useState(); + const [amount, setAmount] = useState(); + const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [destinationAccount, setDestinationAccount] = useState(); + const [data, setData] = useState(); + const [fee, setFee] = useState(); + + return ( +
+

Get fee

+
+ + setRouterAddress(target.value)} + /> +
+
+ + setTokenAddress(target.value)} + /> +
+
+ + setDestinationChainSelector(target.value)} + /> +
+
+ + setDestinationAccount(target.value)} + /> +
+
+ + setData(encodeAbiParameters([{ type: "string", name: "data" }], [target.value]))} + /> +
+
+ + setAmount(target.value)} + /> +
+ + {fee && ( +
+ + {fee} +
+ )} +
+ ); +} diff --git a/examples/nextjs/components/ccip.tsx b/examples/nextjs/components/ccip-with-wagmi.tsx similarity index 99% rename from examples/nextjs/components/ccip.tsx rename to examples/nextjs/components/ccip-with-wagmi.tsx index 7609f6a..fa5c83d 100644 --- a/examples/nextjs/components/ccip.tsx +++ b/examples/nextjs/components/ccip-with-wagmi.tsx @@ -23,7 +23,7 @@ export function CCIP() { return (
- + {!publicClient && !walletClient && } {publicClient && ( <> diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 9013ba3..31039d6 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -13,9 +13,10 @@ "lint": "next lint" }, "dependencies": { - "@chainlink/ccip-js": "^0.2.1", + "@chainlink/ccip-js": "workspace:^", "@chainlink/ccip-react-components": "^0.3.0", "@tanstack/react-query": "^5.37.1", + "ethers": "6.13.4", "next": "14.2.3", "react": "18", "react-dom": "18", @@ -32,4 +33,4 @@ "postcss": "^8", "tailwindcss": "^3.4.1" } -} +} \ No newline at end of file diff --git a/packages/ccip-js/src/adapters/ethers.ts b/packages/ccip-js/src/adapters/ethers.ts index 690febe..defe9ab 100644 --- a/packages/ccip-js/src/adapters/ethers.ts +++ b/packages/ccip-js/src/adapters/ethers.ts @@ -9,6 +9,13 @@ import type { TransactionReceipt as ViemTransactionReceipt, AbiEvent, } from 'viem' + +import type { + ContractCallArgs, + TransactionArgs, + ReceiptArgs, + LogsArgs, +} from './types' import { createPublicClient, createWalletClient, @@ -21,7 +28,7 @@ import { isAddressEqual, } from 'viem' import { toAccount } from 'viem/accounts' -import type { Provider, Signer, TypedDataField } from 'ethers' +import type { Provider, Signer, TypedDataField, BrowserProvider } from 'ethers' import { Contract, type TransactionReceipt as EthersTxReceipt, type TransactionResponse } from 'ethers' import { readContract as viemReadContract, @@ -262,7 +269,8 @@ export function isEthersSigner(signer: any): signer is Signer { /** * Union of supported client types: viem Client/WalletClient, ethers Provider, or ethers Signer. */ -export type SupportedClient = ViemClient | WalletClient | Provider | (Signer & { provider?: Provider }) + +export type SupportedClient = ViemClient | WalletClient | Provider | Signer | BrowserProvider /** * Attempts to adapt the provided client to a viem PublicClient if possible. @@ -299,13 +307,13 @@ async function toViemWalletClient(client: SupportedClient, chain?: Chain): Promi */ export async function readContractCompat( client: SupportedClient, - args: Parameters[1] & { chain?: Chain }, + args: ContractCallArgs, ) { if (isEthersProvider(client) || isEthersSigner(client)) { const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider if (!provider) throw new Error('Unsupported client for readContract: signer has no provider') - const contract = new Contract(args.address as Address, args.abi as any[], provider) - return (contract as any)[(args as any).functionName](...(((args as any).args as any[]) || [])) + const contract = new Contract(args.address, args.abi, provider) + return (contract as any)[args.functionName](...(args.args || [])) } const viemClient = toViemPublicClient(client, args.chain) if (!viemClient) throw new Error('Unsupported client for readContract') @@ -318,18 +326,18 @@ export async function readContractCompat( */ export async function writeContractCompat( client: SupportedClient, - args: Parameters[1] & { chain?: Chain }, + args: TransactionArgs, ) { if (isEthersSigner(client)) { const signer = client as Signer - const contract = new Contract(args.address as Address, args.abi as any[], signer) - const txResponse: TransactionResponse = await (contract as any)[(args as any).functionName]( - ...(((args as any).args as any[]) || []), + const contract = new Contract(args.address, args.abi, signer) + const txResponse: TransactionResponse = await (contract as any)[args.functionName]( + ...(args.args || []), { - value: (args as any).value !== undefined ? (args as any).value.toString() : undefined, - gasLimit: (args as any).gas, - gasPrice: (args as any).gasPrice, - nonce: (args as any).nonce, + value: args.value !== undefined ? args.value.toString() : undefined, + gasLimit: args.gas, + gasPrice: args.gasPrice, + nonce: args.nonce, }, ) return txResponse.hash as Hex @@ -345,15 +353,15 @@ export async function writeContractCompat( */ export async function waitForTransactionReceiptCompat( client: SupportedClient, - args: Parameters[1] & { chain?: Chain }, + args: ReceiptArgs, ) { if (isEthersProvider(client) || isEthersSigner(client)) { const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider if (!provider) throw new Error('Unsupported client for waitForTransactionReceipt: signer has no provider') const maybe = await provider.waitForTransaction( - (args as any).hash, - (args as any).confirmations, - (args as any).timeout, + args.hash, + args.confirmations, + args.timeout, ) if (!maybe) throw new Error('Transaction receipt not found') return formatEthersReceipt(maybe) @@ -369,12 +377,12 @@ export async function waitForTransactionReceiptCompat( */ export async function getTransactionReceiptCompat( client: SupportedClient, - args: Parameters[1] & { chain?: Chain }, + args: ReceiptArgs, ) { if (isEthersProvider(client) || isEthersSigner(client)) { const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider if (!provider) throw new Error('Unsupported client for getTransactionReceipt: signer has no provider') - const maybe = await provider.getTransactionReceipt((args as any).hash) + const maybe = await provider.getTransactionReceipt(args.hash) if (!maybe) throw new Error('Transaction receipt not found') return formatEthersReceipt(maybe) } @@ -405,17 +413,17 @@ export async function getBlockNumberCompat(client: SupportedClient, chain?: Chai */ export async function getLogsCompat( client: SupportedClient, - args: Parameters[1] & { chain?: Chain }, + args: LogsArgs, ) { if (isEthersProvider(client) || isEthersSigner(client)) { const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider if (!provider) throw new Error('Unsupported client for getLogs: signer has no provider') - const abiEvent = (args as any).event as AbiEvent | undefined - const address = (args as any).address as Address | undefined - const fromBlock = (args as any).fromBlock ? ((args as any).fromBlock as bigint).toString() : undefined - const toBlock = (args as any).toBlock ? ((args as any).toBlock as bigint).toString() : undefined + const abiEvent = args.event as AbiEvent | undefined + const address = args.address as Address | undefined + const fromBlock = args.fromBlock ? args.fromBlock.toString() : undefined + const toBlock = args.toBlock ? args.toBlock.toString() : undefined const topics: (string | null)[] | undefined = abiEvent - ? buildTopicsFromEventAndArgs(abiEvent, (args as any).args) + ? buildTopicsFromEventAndArgs(abiEvent, args.args) : undefined const filter: any = { address, topics, fromBlock, toBlock } const logs = await provider.getLogs(filter) diff --git a/packages/ccip-js/src/adapters/types.ts b/packages/ccip-js/src/adapters/types.ts new file mode 100644 index 0000000..6d322fe --- /dev/null +++ b/packages/ccip-js/src/adapters/types.ts @@ -0,0 +1,43 @@ +import type { Address, Chain, Hex, AbiEvent } from 'viem' + +// Custom types to eliminate 'as any' casts throughout the adapter layer +export interface ContractCallArgs { + address: Address + // Supports both readonly and mutable ABI arrays for maximum compatibility + // (readonly arrays come from parseAbi, static ABIs, etc.) + abi: readonly any[] | any[] + functionName: string + args?: any[] + chain?: Chain +} + +export interface TransactionArgs { + address: Address + // Supports both readonly and mutable ABI arrays for maximum compatibility + // (readonly arrays come from parseAbi, static ABIs, etc.) + abi: readonly any[] | any[] + functionName: string + args?: any[] + value?: bigint + gas?: bigint + gasPrice?: bigint + nonce?: number + chain?: Chain +} + +export interface ReceiptArgs { + hash: Hex + confirmations?: number + timeout?: number + chain?: Chain +} + +export interface LogsArgs { + address?: Address + event?: AbiEvent + args?: Record + fromBlock?: bigint + toBlock?: bigint + + chain?: Chain +} diff --git a/packages/ccip-js/src/api.ts b/packages/ccip-js/src/api.ts index 100262a..fbbb5a8 100644 --- a/packages/ccip-js/src/api.ts +++ b/packages/ccip-js/src/api.ts @@ -22,6 +22,7 @@ export { getTransactionReceiptCompat, getBlockNumberCompat, getLogsCompat, + SupportedClient, } from './adapters/ethers' import { @@ -734,7 +735,7 @@ export const createClient = (): Client => { ) const feeTokens = await readCompat(options.client as any, { - abi: parseAbi(['function getFeeTokens() returns (address[] feeTokens)']), // same signature for both PriceRegistry and FeeQuoter + abi: parseAbi(['function getFeeTokens() view returns (address[] feeTokens)']), // same signature for both PriceRegistry and FeeQuoter address: priceRegistryOrFeeQuoter as Viem.Address, functionName: 'getFeeTokens', }) @@ -848,7 +849,7 @@ export const createClient = (): Client => { try { const viemChain = (options.client as any)?.chain as Viem.Chain | undefined if (viemChain) return scaleFeeDecimals(fee, viemChain) - } catch {} + } catch { } return scaleFeeDecimals(fee) } diff --git a/packages/ccip-js/test/integration-testnet.test.ts b/packages/ccip-js/test/integration-testnet.test.ts index 33033f9..c406063 100644 --- a/packages/ccip-js/test/integration-testnet.test.ts +++ b/packages/ccip-js/test/integration-testnet.test.ts @@ -7,6 +7,7 @@ import { sepolia, avalancheFuji, hederaTestnet } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' import bridgeToken from '../artifacts-compile/BridgeToken.json' import { DEFAULT_ANVIL_PRIVATE_KEY } from './helpers/constants' +import { JsonRpcProvider, Wallet, Contract } from 'ethers' const ccipClient = CCIP.createClient() const bridgeTokenAbi = bridgeToken.contracts['src/contracts/BridgeToken.sol:BridgeToken'].bridgeTokenAbi @@ -20,8 +21,7 @@ const WRAPPED_HBAR = '0xb1F616b8134F602c3Bb465fB5b5e6565cCAd37Ed' const LINK_TOKEN_FUJI = '0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846' const LINK_TOKEN_HEDERA = '0x90a386d59b9A6a4795a011e8f032Fc21ED6FEFb6' -// 6m to match https://viem.sh/docs/actions/public/waitForTransactionReceipt.html#timeout-optional, -// which is called in approveRouter() + // TODO @zeuslawyer: https://prajjwaldimri.medium.com/why-is-my-jest-runner-not-closing-bc4f6632c959 - tests are passing but jest is not closing. Viem transport issue? why? // currently timeout set to 180000ms in jest.config.js @@ -36,11 +36,11 @@ const privateKey = process.env.PRIVATE_KEY as Viem.Hex if (privateKey === DEFAULT_ANVIL_PRIVATE_KEY) { throw new Error( - "Developer's PRIVATE_KEY for Ethereum Sepolia and Avalanche Fuji must be set for integration testing on", + "Developer's PRIVATE_KEY for Ethereum Sepolia and Avalanche Fuji must be set in terminal for integration testing on testnet", ) } -describe('Integration: Fuji -> Sepolia', () => { +describe('[Viem]Integration: Fuji -> Sepolia', () => { let avalancheFujiClient: Viem.WalletClient let sepoliaClient: Viem.WalletClient let bnmToken_fuji: any @@ -108,7 +108,7 @@ describe('Integration: Fuji -> Sepolia', () => { routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, }) - expect(avalancheFujiOnRampAddress).toEqual('0x75b9a75Ee1fFef6BE7c4F842a041De7c6153CF4E') + expect(avalancheFujiOnRampAddress).toEqual('0x75b9a75Ee1fFef6BE7c4F842a041De7c6153CF4E' as `0x${string}`) }) it('lists supported fee tokens', async function () { @@ -131,7 +131,7 @@ describe('Integration: Fuji -> Sepolia', () => { // this implicitly asserts that the values are defined as well. expect(typeof tokens).toBe('bigint') - expect(typeof lastUpdated).toBe('number') + expect(typeof lastUpdated).toBe('bigint') // Changed to bigint expect(typeof isEnabled).toBe('boolean') expect(typeof capacity).toBe('bigint') expect(typeof rate).toBe('bigint') @@ -335,7 +335,7 @@ describe('Integration: Fuji -> Sepolia', () => { expect(ccipSend_txReceipt).toBeDefined() expect(ccipSend_txReceipt.status).toEqual('success') expect(ccipSend_txReceipt.from.toLowerCase()).toEqual(avalancheFujiClient.account!.address.toLowerCase()) - expect(ccipSend_txReceipt.to!.toLowerCase()).toEqual(AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS.toLowerCase()) + expect(ccipSend_txReceipt.to!.toLowerCase()).toEqual(AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS.toLowerCase() as `0x${string}`) }) }) @@ -344,7 +344,7 @@ describe('Integration: Fuji -> Sepolia', () => { }) }) -describe('√ (Hedera(custom decimals) -> Sepolia) all critical functionality in CCIP Client', () => { +describe('[Viem](Hedera(custom decimals) -> Sepolia) all critical functionality in CCIP Client', () => { let hederaTestnetClient: Viem.WalletClient let sepoliaClient: Viem.WalletClient let bnmToken_hedera: any @@ -561,6 +561,323 @@ describe('√ (Hedera(custom decimals) -> Sepolia) all critical functionality in expect(ccipSend_txReceipt).toBeDefined() expect(ccipSend_txReceipt.status).toEqual('success') expect(ccipSend_txReceipt.from.toLowerCase()).toEqual(hederaTestnetClient.account!.address.toLowerCase()) - expect(ccipSend_txReceipt.to!.toLowerCase()).toEqual(HEDERA_TESTNET_CCIP_ROUTER_ADDRESS.toLowerCase()) + expect(ccipSend_txReceipt.to!.toLowerCase()).toEqual(HEDERA_TESTNET_CCIP_ROUTER_ADDRESS.toLowerCase() as `0x${string}`) + }) +}) + +describe.only('[Ethers]Integration: Fuji -> Sepolia', () => { + let avalancheFujiProvider: JsonRpcProvider + let avalancheFujiSigner: Wallet + let sepoliaProvider: JsonRpcProvider + let sepoliaSigner: Wallet + let bnmToken_fuji: any + let _messageId: `0x${string}` + let tokenTransfer_txHash: `0x${string}` + + const AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS = '0xF694E193200268f9a4868e4Aa017A0118C9a8177' + const approvedAmount = parseEther('0.000000001') + + beforeAll(async () => { + avalancheFujiProvider = new JsonRpcProvider(AVALANCHE_FUJI_RPC_URL) + try { + await avalancheFujiProvider.ready + await avalancheFujiProvider.getBlockNumber() + } catch (error) { + console.log('ERROR : avalancheFujiProvider', error) + } + + avalancheFujiSigner = new Wallet(privateKey, avalancheFujiProvider) + + sepoliaProvider = new JsonRpcProvider(SEPOLIA_RPC_URL) + sepoliaSigner = new Wallet(privateKey, sepoliaProvider) + + bnmToken_fuji = new Contract( + '0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4', // CCIP BnM on Avalanche Fuji + bridgeTokenAbi, + avalancheFujiSigner, + ) + + // Check that contract is properly instantiated + if (!bnmToken_fuji.target) { + throw new Error('Contract target is undefined - contract not properly instantiated') + } + expect(bnmToken_fuji.target).toEqual('0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4' as `0x${string}`) + + const bnmBalance = await bnmToken_fuji.balanceOf(avalancheFujiSigner.address) + if (parseInt(bnmBalance) <= approvedAmount) { + await bnmToken_fuji.drip(avalancheFujiSigner.address) + console.log(' ℹ️ | Dripped 1 CCIP BnM token to account: ', avalancheFujiSigner.address) + } + }) + + describe('√ (Fuji -> Sepolia) all critical functionality in CCIP Client', () => { + it('should approve BnM spend, given valid input', async () => { + const ccipApprove = await ccipClient.approveRouter({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + amount: approvedAmount, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + waitForReceipt: true, + }) + + // ccipApprove.txReceipt!.status == 'success' && console.log(' | Approved CCIP BnM token on Avalanche Fuji' + await expect(ccipApprove.txReceipt!.status).toEqual('success') + }) + + it('fetches token allowance', async function () { + const allowance = await ccipClient.getAllowance({ + client: avalancheFujiSigner, + account: avalancheFujiSigner.address as `0x${string}`, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + }) + expect(allowance).toEqual(approvedAmount) + }) + + it('returns on-ramp address', async function () { + const avalancheFujiOnRampAddress = await ccipClient.getOnRampAddress({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + }) + expect(avalancheFujiOnRampAddress).toEqual('0x75b9a75Ee1fFef6BE7c4F842a041De7c6153CF4E' as `0x${string}`) + }) + + it.only('lists supported fee tokens', async function () { + const result = await ccipClient.getSupportedFeeTokens({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + }) + expect(result.length).toEqual(2) + expect(result.includes(LINK_TOKEN_FUJI)).toBe(true) + expect(result.includes(WRAPPED_NATIVE_AVAX)).toBe(true) + }) + + it('fetched lane rate refill limits are defined', async function () { + const { tokens, lastUpdated, isEnabled, capacity, rate } = await ccipClient.getLaneRateRefillLimits({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + }) + + // this implicitly asserts that the values are defined as well. + expect(typeof tokens).toBe('bigint') + expect(typeof lastUpdated).toBe('bigint') // Both Ethers and Viem return bigint for timestamps + expect(typeof isEnabled).toBe('boolean') + expect(typeof capacity).toBe('bigint') + expect(typeof rate).toBe('bigint') + }) + + it('returns token rate limit by lane', async function () { + const { tokens, lastUpdated, isEnabled, capacity, rate } = await ccipClient.getTokenRateLimitByLane({ + client: avalancheFujiSigner, // Use signer for ethers + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + supportedTokenAddress: bnmToken_fuji.target as `0x${string}`, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + }) + + // this implicitly asserts that the values are defined as well. + expect(typeof tokens).toBe('bigint') + expect(typeof lastUpdated).toBe('bigint') // Both Ethers and Viem return bigint for timestamps + expect(typeof isEnabled).toBe('boolean') + expect(typeof capacity).toBe('bigint') + expect(typeof rate).toBe('bigint') + }) + + it('returns fee estimate', async function () { + const fee_link = await ccipClient.getFee({ + client: avalancheFujiSigner, // Use signer for ethers + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + amount: approvedAmount, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + destinationAccount: sepoliaSigner.address as `0x${string}`, + feeTokenAddress: LINK_TOKEN_FUJI, + }) + const fee_native = await ccipClient.getFee({ + client: avalancheFujiSigner, // Use signer for ethers + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + amount: approvedAmount, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + destinationAccount: sepoliaSigner.address as `0x${string}`, + feeTokenAddress: WRAPPED_NATIVE_AVAX, + }) + + expect(fee_link).toBeGreaterThan(1000n) + expect(fee_native).toBeGreaterThan(1000n) + }) + it('returns token admin registry', async function () { + const result = await ccipClient.getTokenAdminRegistry({ + client: avalancheFujiSigner, // Use signer for ethers + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + }) + + const CCIP_ADMIN_REGISTRY_ADDRESS = '0xA92053a4a3922084d992fD2835bdBa4caC6877e6' + expect(result).toEqual(CCIP_ADMIN_REGISTRY_ADDRESS as `0x${string}`) + }) + + it('checks if BnM token is supported for transfer', async function () { + const result = await ccipClient.isTokenSupported({ + client: avalancheFujiSigner, // Use signer for ethers + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + }) + expect(result).toBe(true) + }) + + it('transfers tokens | pay in LINK', async function () { + await ccipClient.approveRouter({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + amount: approvedAmount, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + waitForReceipt: true, + }) + + // approve LINK spend + const fee_link = await ccipClient.getFee({ + client: avalancheFujiSigner, // Use signer for ethers + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + amount: approvedAmount, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + destinationAccount: sepoliaSigner.address as `0x${string}`, + feeTokenAddress: LINK_TOKEN_FUJI, + }) + await ccipClient.approveRouter({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + amount: fee_link, + tokenAddress: LINK_TOKEN_FUJI, + waitForReceipt: true, + }) + const allowance = await ccipClient.getAllowance({ + client: avalancheFujiSigner, // Use signer for ethers + account: avalancheFujiSigner.address as `0x${string}`, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + }) + + expect(allowance).toBeGreaterThanOrEqual(approvedAmount) + + const result = await ccipClient.transferTokens({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + destinationAccount: sepoliaSigner.address as `0x${string}`, + amount: approvedAmount, + feeTokenAddress: LINK_TOKEN_FUJI, + }) + + _messageId = result.messageId + tokenTransfer_txHash = result.txHash + + expect(result.txReceipt!.status).toEqual('success') + }) + + it('transfers tokens > pays in native token', async function () { + await ccipClient.approveRouter({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + amount: approvedAmount, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + waitForReceipt: true, + }) + + const result = await ccipClient.transferTokens({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + tokenAddress: bnmToken_fuji.target as `0x${string}`, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + destinationAccount: sepoliaSigner.address as `0x${string}`, + amount: approvedAmount, + }) + + expect(result.txReceipt!.status).toEqual('success') + }) + + it('CCIP message (sending) tx OK > paid in LINK', async function () { + const testReceiverContract = '0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4' as `0x${string}` // using BnM token contract as receiver for testing + const testMessage = Viem.encodeAbiParameters( + [{ type: 'string', name: 'message' }], + ['Hello from Avalanche Fuji!'], + ) + + // Get fee in LINK and approve it + const fee_link = await ccipClient.getFee({ + client: avalancheFujiSigner, // Use signer for ethers + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + destinationAccount: testReceiverContract, + data: testMessage, + feeTokenAddress: LINK_TOKEN_FUJI, + }) + + await ccipClient.approveRouter({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + amount: fee_link, + tokenAddress: LINK_TOKEN_FUJI, + waitForReceipt: true, + }) + + const result = await ccipClient.sendCCIPMessage({ + client: avalancheFujiSigner, + routerAddress: AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS, + destinationChainSelector: SEPOLIA_CHAIN_SELECTOR, + destinationAccount: testReceiverContract, + data: testMessage, + feeTokenAddress: LINK_TOKEN_FUJI, + }) + + console.info( + `Avalanche Fuji --> Sepolia sendCCIPMessage MessageId: ${result.messageId} <> Sent to: ${testReceiverContract} on Sepolia`, + ) + + expect(result.txReceipt!.status).toEqual('success') + expect(result.messageId).toBeDefined() + expect(result.txHash).toBeDefined() + }) + + it('gets transfer status & gets transaction receipt', async function () { + // Skip if tokenTransfer_txHash is not set (previous test failed) + if (!tokenTransfer_txHash) { + console.warn('Skipping transfer status test - tokenTransfer_txHash not set') + return + } + + const ccipSend_txReceipt = await ccipClient.getTransactionReceipt({ + client: avalancheFujiSigner, // Use signer for ethers + hash: tokenTransfer_txHash, + }) + + const FUJI_CHAIN_SELECTOR = '14767482510784806043' + const SEPOLIA_ROUTER_ADDRESS = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59' + + const transferStatus = await ccipClient.getTransferStatus({ + client: sepoliaSigner, // Use signer for ethers + sourceChainSelector: FUJI_CHAIN_SELECTOR, + destinationRouterAddress: SEPOLIA_ROUTER_ADDRESS as `0x${string}`, + fromBlockNumber: ccipSend_txReceipt.blockNumber ? ccipSend_txReceipt.blockNumber : undefined, + messageId: _messageId, + }) + + expect(transferStatus).toBeDefined() + + expect(ccipSend_txReceipt).toBeDefined() + expect(ccipSend_txReceipt.status).toEqual('success') + expect(ccipSend_txReceipt.from.toLowerCase()).toEqual(avalancheFujiSigner.address.toLowerCase()) + expect(ccipSend_txReceipt.to!.toLowerCase()).toEqual(AVALANCHE_FUJI_CCIP_ROUTER_ADDRESS.toLowerCase() as `0x${string}`) + }) + }) + + afterAll(async () => { + console.info('✅ | Ethers Integration tests completed. Waiting for timeout...') }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b8c23a..c207993 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,14 +22,17 @@ importers: examples/nextjs: dependencies: '@chainlink/ccip-js': - specifier: ^0.2.1 - version: 0.2.4(a3c99c0fb0fe13a5c24ee996cead66b3) + specifier: workspace:^ + version: link:../../packages/ccip-js '@chainlink/ccip-react-components': specifier: ^0.3.0 version: 0.3.0(1ed1302eb82042231a950ad4c671505f) '@tanstack/react-query': specifier: ^5.37.1 version: 5.80.7(react@18.3.1) + ethers: + specifier: 6.13.4 + version: 6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) next: specifier: 14.2.3 version: 14.2.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -326,10 +329,6 @@ packages: '@asamuzakjp/css-color@3.1.1': resolution: {integrity: sha512-hpRD68SV2OMcZCsrbdkccTw5FXjNDLo5OuqSHyHZfwweGsDWZwDJ2+gONyNAbazZclobMirACLw0lk8WVxIqxA==} - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -342,10 +341,6 @@ packages: resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.10': - resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.27.5': resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} engines: {node: '>=6.9.0'} @@ -515,12 +510,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.25.9': - resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} engines: {node: '>=6.9.0'} @@ -3779,9 +3768,6 @@ packages: bn.js@4.12.2: resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} - bn.js@5.2.2: resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} @@ -8274,12 +8260,6 @@ snapshots: '@csstools/css-tokenizer': 3.0.3 lru-cache: 10.4.3 - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.25.9 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -8308,14 +8288,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.10': - dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.26.10 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - '@babel/generator@7.27.5': dependencies: '@babel/parser': 7.27.5 @@ -8497,11 +8469,6 @@ snapshots: '@babel/core': 7.27.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 @@ -8623,44 +8590,6 @@ snapshots: - utf-8-validate - zod - '@chainlink/ccip-js@0.2.4(a3c99c0fb0fe13a5c24ee996cead66b3)': - dependencies: - '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(chai@5.2.0)(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-toolbox': 5.0.0(13680ddddf672fc71131616f3a7f7389) - '@nomicfoundation/hardhat-viem': 2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.25.67) - '@openzeppelin/contracts': 5.2.0 - chai: 5.2.0 - ethers: 6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) - mocha: 11.7.0 - ts-jest: 29.2.6(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)))(typescript@5.8.3) - typescript: 5.8.3 - viem: 2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67) - transitivePeerDependencies: - - '@babel/core' - - '@jest/transform' - - '@jest/types' - - '@nomicfoundation/hardhat-ignition-ethers' - - '@nomicfoundation/hardhat-network-helpers' - - '@nomicfoundation/hardhat-verify' - - '@typechain/ethers-v6' - - '@typechain/hardhat' - - '@types/chai' - - '@types/mocha' - - '@types/node' - - babel-jest - - bufferutil - - esbuild - - hardhat - - hardhat-gas-reporter - - jest - - solidity-coverage - - supports-color - - ts-node - - typechain - - utf-8-validate - - zod - '@chainlink/ccip-react-components@0.3.0(1ed1302eb82042231a950ad4c671505f)': dependencies: '@chainlink/ccip-js': 0.2.4(5e3e05d9b50680c4de2bc0a3c2cf5c97) @@ -8836,7 +8765,7 @@ snapshots: package-manager-detector: 0.2.11 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.7.1 + semver: 7.7.2 spawndamnit: 3.0.1 term-size: 2.2.1 @@ -9359,7 +9288,7 @@ snapshots: dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 - bn.js: 5.2.1 + bn.js: 5.2.2 '@ethersproject/bytes@5.8.0': dependencies: @@ -9652,7 +9581,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -9665,14 +9594,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.15.32)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -9700,14 +9629,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.15.32)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -9736,7 +9665,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -9754,7 +9683,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.15.32 + '@types/node': 20.19.1 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -9776,7 +9705,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.15.32 + '@types/node': 20.19.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -9846,7 +9775,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.15.32 + '@types/node': 20.19.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -10441,16 +10370,6 @@ snapshots: - typescript - zod - '@nomicfoundation/hardhat-viem@2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.25.67)': - dependencies: - abitype: 0.9.10(typescript@5.8.3)(zod@3.25.67) - hardhat: 2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10) - lodash.memoize: 4.1.2 - viem: 2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67) - transitivePeerDependencies: - - typescript - - zod - '@nomicfoundation/hardhat-viem@2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.25.67)': dependencies: abitype: 0.9.10(typescript@5.8.3)(zod@3.25.67) @@ -11702,28 +11621,28 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.6 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.6 '@types/bn.js@5.1.6': dependencies: - '@types/node': 22.15.32 + '@types/node': 20.19.1 '@types/bn.js@5.2.0': dependencies: @@ -11762,7 +11681,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.15.32 + '@types/node': 20.19.1 '@types/istanbul-lib-coverage@2.0.6': {} @@ -12695,7 +12614,7 @@ snapshots: acorn-walk@8.3.4: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 acorn@8.14.1: {} @@ -13004,8 +12923,6 @@ snapshots: bn.js@4.12.2: {} - bn.js@5.2.1: {} - bn.js@5.2.2: {} bowser@2.11.0: {} @@ -15317,7 +15234,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -15406,7 +15323,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.15.32)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)): + jest-config@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3)): dependencies: '@babel/core': 7.27.4 '@jest/test-sequencer': 29.7.0 @@ -15431,8 +15348,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.15.32 - ts-node: 10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3) + '@types/node': 20.19.1 + ts-node: 10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -15492,7 +15409,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -15502,7 +15419,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.15.32 + '@types/node': 20.19.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -15528,7 +15445,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.27.1 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -15541,7 +15458,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -15576,7 +15493,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -15604,7 +15521,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 @@ -15625,10 +15542,10 @@ snapshots: jest-snapshot@29.7.0: dependencies: '@babel/core': 7.27.4 - '@babel/generator': 7.26.10 + '@babel/generator': 7.27.5 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.4) - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.27.4) - '@babel/types': 7.26.10 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4) + '@babel/types': 7.27.6 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -15669,7 +15586,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.15.32 + '@types/node': 20.19.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -15678,7 +15595,7 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.15.32 + '@types/node': 20.19.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1