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..e344c7f --- /dev/null +++ b/examples/nextjs/components/ccip-with-ethers.tsx @@ -0,0 +1,1165 @@ +"use client"; + +import { createClient, IERC20ABI, RateLimiterState, TransferStatus } from "@chainlink/ccip-js"; +import { + Address, + encodeAbiParameters, + encodeFunctionData, + Hash, + Hex, + parseEther, + PublicClient, + TransactionReceipt, + WalletClient, +} from "viem"; +import { useState, useEffect } from "react"; + +import { ethers, BrowserProvider, Signer } from "ethers"; + +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" }, + { id: 11155111, name: "Sepolia", hexId: "0xAA36A7" }, + ]; + + 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: Signer }) { + 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: Signer }) { + 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: Signer }) { + 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: Signer }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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: BrowserProvider }) { + 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 88% rename from examples/nextjs/components/ccip.tsx rename to examples/nextjs/components/ccip-with-wagmi.tsx index 7609f6a..cd4f849 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 && ( <> @@ -453,6 +453,7 @@ function GetAllowance({ publicClient }: { publicClient: PublicClient }) { const [tokenAddress, setTokenAddress] = useState(); const [account, setAccount] = useState(); const [allowance, setAllowance] = useState(); + const [error, setError] = useState(); return (

Get allowance:

@@ -488,14 +489,19 @@ function GetAllowance({ publicClient }: { publicClient: PublicClient }) {
)} + {error &&

{error}

}
); } @@ -515,6 +522,7 @@ function GetOnRampAddress({ publicClient }: { publicClient: PublicClient }) { const [routerAddress, setRouterAddress] = useState(); const [onRamp, setOnRamp] = useState(); const [destinationChainSelector, setDestinationChainSelector] = useState(); + const [error, setError] = useState(); return (

Get On-ramp address:

@@ -540,13 +548,18 @@ function GetOnRampAddress({ publicClient }: { publicClient: PublicClient }) {
)} + {error &&

{error}

} ); } @@ -566,6 +580,7 @@ function GetSupportedFeeTokens({ publicClient }: { publicClient: PublicClient }) const [routerAddress, setRouterAddress] = useState(); const [destinationChainSelector, setDestinationChainSelector] = useState(); const [supportedFeeTokens, setSupportedFeeTokens] = useState(); + const [error, setError] = useState(); return (
@@ -591,13 +606,18 @@ function GetSupportedFeeTokens({ publicClient }: { publicClient: PublicClient })
)} + {error &&

{error}

} ); } @@ -623,6 +644,7 @@ function GetLaneRateRefillLimits({ publicClient }: { publicClient: PublicClient const [routerAddress, setRouterAddress] = useState(); const [destinationChainSelector, setDestinationChainSelector] = useState(); const [rateLimits, setRateLimits] = useState(); + const [error, setError] = useState(); return (
@@ -648,13 +670,18 @@ function GetLaneRateRefillLimits({ publicClient }: { publicClient: PublicClient
)} + {error &&

{error}

} ); } @@ -685,6 +713,7 @@ function GetTokenRateLimitByLane({ publicClient }: { publicClient: PublicClient const [destinationChainSelector, setDestinationChainSelector] = useState(); const [tokenAddress, setTokenAddress] = useState(); const [tokenRateLimits, setTokenRateLimits] = useState(); + const [error, setError] = useState(); return (
@@ -719,14 +748,19 @@ function GetTokenRateLimitByLane({ publicClient }: { publicClient: PublicClient
)} + {error &&

{error}

} ); } @@ -759,6 +794,7 @@ function IsTokenSupported({ publicClient }: { publicClient: PublicClient }) { const [destinationChainSelector, setDestinationChainSelector] = useState(); const [tokenAddress, setTokenAddress] = useState(); const [isTokenSupported, setIsTokenSupported] = useState(); + const [error, setError] = useState(); return (
@@ -793,14 +829,19 @@ function IsTokenSupported({ publicClient }: { publicClient: PublicClient }) {
)} + {error &&

{error}

} ); } @@ -821,6 +863,7 @@ function GetTokenAdminRegistry({ publicClient }: { publicClient: PublicClient }) const [destinationChainSelector, setDestinationChainSelector] = useState(); const [tokenAddress, setTokenAddress] = useState(); const [tokenAdminRegistry, setTokenAdminRegistry] = useState(); + const [error, setError] = useState(); return (

Token admin registry:

@@ -854,14 +897,19 @@ function GetTokenAdminRegistry({ publicClient }: { publicClient: PublicClient })
)} + {error &&

{error}

} ); } @@ -880,6 +929,7 @@ function GetTokenAdminRegistry({ publicClient }: { publicClient: PublicClient }) function GetTransactionReceipt({ publicClient }: { publicClient: PublicClient }) { const [hash, setHash] = useState(); const [transactionReceipt, setTransactionReceipt] = useState(); + const [error, setError] = useState(); return (
@@ -898,12 +948,17 @@ function GetTransactionReceipt({ publicClient }: { publicClient: PublicClient })
)} + {error &&

{error}

} ); } @@ -1020,6 +1076,7 @@ function GetFee({ publicClient }: { publicClient: PublicClient }) { const [destinationAccount, setDestinationAccount] = useState(); const [data, setData] = useState(); const [fee, setFee] = useState(); + const [error, setError] = useState(); return (
@@ -1084,17 +1141,22 @@ function GetFee({ publicClient }: { publicClient: PublicClient }) {
)} + {error &&

{error}

} ); } 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/README.md b/packages/ccip-js/README.md index f591365..e25f532 100644 --- a/packages/ccip-js/README.md +++ b/packages/ccip-js/README.md @@ -118,49 +118,91 @@ const walletClient = createWalletClient({ }) // Approve Router to transfer tokens on user's behalf -const { txHash, txReceipt } = await ccipClient.approveRouter({ - client: walletClient, - routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - amount: 1000000000000000000n, - waitForReceipt: true, -}) +async function main() { + const { txHash, txReceipt } = await ccipClient.approveRouter({ + client: walletClient, + routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: 1000000000000000000n, + waitForReceipt: true, + }) -console.log(`Transfer approved. Transaction hash: ${txHash}. Transaction receipt: ${txReceipt}`) + console.log(`Transfer approved. Transaction hash: ${txHash}. Transaction receipt: ${txReceipt}`) // Get fee for the transfer -const fee = await ccipClient.getFee({ - client: publicClient, - routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - amount: 1000000000000000000n, - destinationAccount: '0x1234567890abcdef1234567890abcdef12345678', - destinationChainSelector: '1234', -}) + const fee = await ccipClient.getFee({ + client: publicClient, + routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: 1000000000000000000n, + destinationAccount: '0x1234567890abcdef1234567890abcdef12345678', + destinationChainSelector: '1234', + }) -console.log(`Fee: ${fee.toLocaleString()}`) + console.log(`Fee: ${fee.toLocaleString()}`) // Variant 1: Transfer via CCIP using native token fee -const { txHash, messageId } = await client.transferTokens({ - client: walletClient, - routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - amount: 1000000000000000000n, - destinationAccount: '0x1234567890abcdef1234567890abcdef12345678', - destinationChainSelector: '1234', -}) + const { txHash, messageId } = await client.transferTokens({ + client: walletClient, + routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: 1000000000000000000n, + destinationAccount: '0x1234567890abcdef1234567890abcdef12345678', + destinationChainSelector: '1234', + }) -console.log(`Transfer success. Transaction hash: ${txHash}. Message ID: ${messageId}`) + console.log(`Transfer success. Transaction hash: ${txHash}. Message ID: ${messageId}`) // Variant 2: Transfer via CCIP using the provided supported token for fee payment -const { txHash, messageId } = await client.transferTokens({ - client: walletClient, - routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - amount: 1000000000000000000n, - destinationAccount: '0x1234567890abcdef1234567890abcdef12345678', - destinationChainSelector: '1234', - feeTokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + const { txHash: txHash2, messageId: messageId2 } = await client.transferTokens({ + client: walletClient, + routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: 1000000000000000000n, + destinationAccount: '0x1234567890abcdef1234567890abcdef12345678', + destinationChainSelector: '1234', + feeTokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + }) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) +``` + +### Using ethers.js + +You can pass ethers providers/signers by adapting them to Viem clients with the provided helpers. The client methods now accept either Viem clients or ethers Provider/Signer where applicable (e.g. `approveRouter`, `transferTokens`, `sendCCIPMessage`, reads). Avoid hardcoding secrets; load keys from environment variables and wrap usage in an async function. + +```typescript +import 'dotenv/config' +import { ethers } from 'ethers' +import * as CCIP from '@chainlink/ccip-js' +import { mainnet } from 'viem/chains' + +async function main() { + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!) + const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider) + + const walletClient = await CCIP.ethersSignerToWalletClient(signer, mainnet) + const publicClient = CCIP.ethersProviderToPublicClient(provider, mainnet) + + const ccipClient = CCIP.createClient() + + const { txHash } = await ccipClient.approveRouter({ + client: walletClient, + routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: 1000000000000000000n, + }) + + console.log('approve tx hash', txHash) +} + +main().catch((err) => { + console.error(err) + process.exit(1) }) ``` diff --git a/packages/ccip-js/src/abi/BridgeToken.json b/packages/ccip-js/src/abi/BridgeToken.json index 47435c3..332ac44 100644 --- a/packages/ccip-js/src/abi/BridgeToken.json +++ b/packages/ccip-js/src/abi/BridgeToken.json @@ -1,787 +1,787 @@ [ - { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "supplyAfterMint", - "type": "uint256" - } - ], - "name": "MaxSupplyExceeded", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "SenderNotBurner", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "SenderNotMinter", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "burner", - "type": "address" - } - ], - "name": "BurnAccessGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "burner", - "type": "address" - } - ], - "name": "BurnAccessRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "name": "MintAccessGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "name": "MintAccessRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - } - ], - "name": "OwnershipTransferRequested", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "acceptOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "burnFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseApproval", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - } - ], - "name": "drip", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "getBurners", - "outputs": [ - { - "internalType": "address[]", - "name": "", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getMinters", - "outputs": [ - { - "internalType": "address[]", - "name": "", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "burner", - "type": "address" - } - ], - "name": "grantBurnRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "burnAndMinter", - "type": "address" - } - ], - "name": "grantMintAndBurnRoles", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "name": "grantMintRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseApproval", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "burner", - "type": "address" - } - ], - "name": "isBurner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "name": "isMinter", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], + { + "inputs": [ + { + "internalType": "string", "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "burner", - "type": "address" - } - ], - "name": "revokeBurnRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "name": "revokeMintRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], + "type": "string" + }, + { + "internalType": "string", "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "transferAndCall", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "supplyAfterMint", + "type": "uint256" + } + ], + "name": "MaxSupplyExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderNotBurner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderNotMinter", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "burner", + "type": "address" + } + ], + "name": "BurnAccessGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "burner", + "type": "address" + } + ], + "name": "BurnAccessRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "MintAccessGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "MintAccessRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseApproval", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "drip", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getBurners", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMinters", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "burner", + "type": "address" + } + ], + "name": "grantBurnRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "burnAndMinter", + "type": "address" + } + ], + "name": "grantMintAndBurnRoles", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "grantMintRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "burner", + "type": "address" + } + ], + "name": "isBurner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "burner", + "type": "address" + } + ], + "name": "revokeBurnRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "revokeMintRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "transferAndCall", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/packages/ccip-js/src/abi/CCIPLocalSimulator.json b/packages/ccip-js/src/abi/CCIPLocalSimulator.json index a6179eb..5b1b212 100644 --- a/packages/ccip-js/src/abi/CCIPLocalSimulator.json +++ b/packages/ccip-js/src/abi/CCIPLocalSimulator.json @@ -1,143 +1,143 @@ [ { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" }, { - "inputs": [], - "name": "CCIPLocalSimulator__MsgSenderIsNotTokenOwner", - "type": "error" + "inputs": [], + "name": "CCIPLocalSimulator__MsgSenderIsNotTokenOwner", + "type": "error" }, { - "inputs": [], - "name": "configuration", - "outputs": [ - { - "internalType": "uint64", - "name": "chainSelector_", - "type": "uint64" - }, - { - "internalType": "contract IRouterClient", - "name": "sourceRouter_", - "type": "address" - }, - { - "internalType": "contract IRouterClient", - "name": "destinationRouter_", - "type": "address" - }, - { - "internalType": "contract WETH9", - "name": "wrappedNative_", - "type": "address" - }, - { - "internalType": "contract LinkToken", - "name": "linkToken_", - "type": "address" - }, - { - "internalType": "contract BurnMintERC677Helper", - "name": "ccipBnM_", - "type": "address" - }, - { - "internalType": "contract BurnMintERC677Helper", - "name": "ccipLnM_", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" + "inputs": [], + "name": "configuration", + "outputs": [ + { + "internalType": "uint64", + "name": "chainSelector_", + "type": "uint64" + }, + { + "internalType": "contract IRouterClient", + "name": "sourceRouter_", + "type": "address" + }, + { + "internalType": "contract IRouterClient", + "name": "destinationRouter_", + "type": "address" + }, + { + "internalType": "contract WETH9", + "name": "wrappedNative_", + "type": "address" + }, + { + "internalType": "contract LinkToken", + "name": "linkToken_", + "type": "address" + }, + { + "internalType": "contract BurnMintERC677Helper", + "name": "ccipBnM_", + "type": "address" + }, + { + "internalType": "contract BurnMintERC677Helper", + "name": "ccipLnM_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "chainSelector", - "type": "uint64" - } - ], - "name": "getSupportedTokens", - "outputs": [ - { - "internalType": "address[]", - "name": "tokens", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "getSupportedTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "chainSelector", - "type": "uint64" - } - ], - "name": "isChainSupported", - "outputs": [ - { - "internalType": "bool", - "name": "supported", - "type": "bool" - } - ], - "stateMutability": "pure", - "type": "function" + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "isChainSupported", + "outputs": [ + { + "internalType": "bool", + "name": "supported", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "requestLinkFromFaucet", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "requestLinkFromFaucet", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - } - ], - "name": "supportNewTokenViaGetCCIPAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "supportNewTokenViaGetCCIPAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - } - ], - "name": "supportNewTokenViaOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "supportNewTokenViaOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } -] \ No newline at end of file +] diff --git a/packages/ccip-js/src/abi/OnRamp_1_6.json b/packages/ccip-js/src/abi/OnRamp_1_6.json index 35c7825..0e0e865 100644 --- a/packages/ccip-js/src/abi/OnRamp_1_6.json +++ b/packages/ccip-js/src/abi/OnRamp_1_6.json @@ -1,1028 +1,1028 @@ [ - { - "type": "constructor", - "inputs": [ - { - "name": "staticConfig", - "type": "tuple", - "internalType": "struct OnRamp.StaticConfig", - "components": [ - { - "name": "chainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "rmnRemote", - "type": "address", - "internalType": "contract IRMNRemote" - }, - { - "name": "nonceManager", - "type": "address", - "internalType": "address" - }, - { - "name": "tokenAdminRegistry", - "type": "address", - "internalType": "address" - } - ] - }, - { - "name": "dynamicConfig", - "type": "tuple", - "internalType": "struct OnRamp.DynamicConfig", - "components": [ - { - "name": "feeQuoter", - "type": "address", - "internalType": "address" - }, - { - "name": "reentrancyGuardEntered", - "type": "bool", - "internalType": "bool" - }, - { - "name": "messageInterceptor", - "type": "address", - "internalType": "address" - }, - { - "name": "feeAggregator", - "type": "address", - "internalType": "address" - }, - { - "name": "allowlistAdmin", - "type": "address", - "internalType": "address" - } - ] - }, - { - "name": "destChainConfigArgs", - "type": "tuple[]", - "internalType": "struct OnRamp.DestChainConfigArgs[]", - "components": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "router", - "type": "address", - "internalType": "contract IRouter" - }, - { - "name": "allowlistEnabled", - "type": "bool", - "internalType": "bool" - } - ] - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "acceptOwnership", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "applyAllowlistUpdates", - "inputs": [ - { - "name": "allowlistConfigArgsItems", - "type": "tuple[]", - "internalType": "struct OnRamp.AllowlistConfigArgs[]", - "components": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "allowlistEnabled", - "type": "bool", - "internalType": "bool" - }, - { - "name": "addedAllowlistedSenders", - "type": "address[]", - "internalType": "address[]" - }, - { - "name": "removedAllowlistedSenders", - "type": "address[]", - "internalType": "address[]" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "applyDestChainConfigUpdates", - "inputs": [ - { - "name": "destChainConfigArgs", - "type": "tuple[]", - "internalType": "struct OnRamp.DestChainConfigArgs[]", - "components": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "router", - "type": "address", - "internalType": "contract IRouter" - }, - { - "name": "allowlistEnabled", - "type": "bool", - "internalType": "bool" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "forwardFromRouter", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "message", - "type": "tuple", - "internalType": "struct Client.EVM2AnyMessage", - "components": [ - { - "name": "receiver", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "data", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "tokenAmounts", - "type": "tuple[]", - "internalType": "struct Client.EVMTokenAmount[]", - "components": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "name": "feeToken", - "type": "address", - "internalType": "address" - }, - { - "name": "extraArgs", - "type": "bytes", - "internalType": "bytes" - } - ] - }, - { - "name": "feeTokenAmount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "originalSender", - "type": "address", - "internalType": "address" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "getAllowedSendersList", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "isEnabled", - "type": "bool", - "internalType": "bool" - }, - { - "name": "configuredAddresses", - "type": "address[]", - "internalType": "address[]" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getDestChainConfig", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "sequenceNumber", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "allowlistEnabled", - "type": "bool", - "internalType": "bool" - }, - { - "name": "router", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getDynamicConfig", - "inputs": [], - "outputs": [ - { - "name": "dynamicConfig", - "type": "tuple", - "internalType": "struct OnRamp.DynamicConfig", - "components": [ - { - "name": "feeQuoter", - "type": "address", - "internalType": "address" - }, - { - "name": "reentrancyGuardEntered", - "type": "bool", - "internalType": "bool" - }, - { - "name": "messageInterceptor", - "type": "address", - "internalType": "address" - }, - { - "name": "feeAggregator", - "type": "address", - "internalType": "address" - }, - { - "name": "allowlistAdmin", - "type": "address", - "internalType": "address" - } - ] - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getExpectedNextSequenceNumber", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "", - "type": "uint64", - "internalType": "uint64" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getFee", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "message", - "type": "tuple", - "internalType": "struct Client.EVM2AnyMessage", - "components": [ - { - "name": "receiver", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "data", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "tokenAmounts", - "type": "tuple[]", - "internalType": "struct Client.EVMTokenAmount[]", - "components": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "name": "feeToken", - "type": "address", - "internalType": "address" - }, - { - "name": "extraArgs", - "type": "bytes", - "internalType": "bytes" - } - ] - } - ], - "outputs": [ - { - "name": "feeTokenAmount", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getPoolBySourceToken", - "inputs": [ - { - "name": "", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "sourceToken", - "type": "address", - "internalType": "contract IERC20" - } - ], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract IPoolV1" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getStaticConfig", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "tuple", - "internalType": "struct OnRamp.StaticConfig", - "components": [ - { - "name": "chainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "rmnRemote", - "type": "address", - "internalType": "contract IRMNRemote" - }, - { - "name": "nonceManager", - "type": "address", - "internalType": "address" - }, - { - "name": "tokenAdminRegistry", - "type": "address", - "internalType": "address" - } - ] - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getSupportedTokens", - "inputs": [ - { - "name": "", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "", - "type": "address[]", - "internalType": "address[]" - } - ], - "stateMutability": "pure" - }, - { - "type": "function", - "name": "owner", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "setDynamicConfig", - "inputs": [ - { - "name": "dynamicConfig", - "type": "tuple", - "internalType": "struct OnRamp.DynamicConfig", - "components": [ - { - "name": "feeQuoter", - "type": "address", - "internalType": "address" - }, - { - "name": "reentrancyGuardEntered", - "type": "bool", - "internalType": "bool" - }, - { - "name": "messageInterceptor", - "type": "address", - "internalType": "address" - }, - { - "name": "feeAggregator", - "type": "address", - "internalType": "address" - }, - { - "name": "allowlistAdmin", - "type": "address", - "internalType": "address" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "transferOwnership", - "inputs": [ - { - "name": "to", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "typeAndVersion", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "string", - "internalType": "string" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "withdrawFeeTokens", - "inputs": [ - { - "name": "feeTokens", - "type": "address[]", - "internalType": "address[]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "event", - "name": "AllowListAdminSet", - "inputs": [ - { - "name": "allowlistAdmin", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "AllowListSendersAdded", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "indexed": true, - "internalType": "uint64" - }, - { - "name": "senders", - "type": "address[]", - "indexed": false, - "internalType": "address[]" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "AllowListSendersRemoved", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "indexed": true, - "internalType": "uint64" - }, - { - "name": "senders", - "type": "address[]", - "indexed": false, - "internalType": "address[]" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "CCIPMessageSent", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "indexed": true, - "internalType": "uint64" - }, - { - "name": "sequenceNumber", - "type": "uint64", - "indexed": true, - "internalType": "uint64" - }, - { - "name": "message", - "type": "tuple", - "indexed": false, - "internalType": "struct Internal.EVM2AnyRampMessage", - "components": [ - { - "name": "header", - "type": "tuple", - "internalType": "struct Internal.RampMessageHeader", - "components": [ - { - "name": "messageId", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "sourceChainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "sequenceNumber", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "nonce", - "type": "uint64", - "internalType": "uint64" - } - ] - }, - { - "name": "sender", - "type": "address", - "internalType": "address" - }, - { - "name": "data", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "receiver", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "extraArgs", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "feeToken", - "type": "address", - "internalType": "address" - }, - { - "name": "feeTokenAmount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "feeValueJuels", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "tokenAmounts", - "type": "tuple[]", - "internalType": "struct Internal.EVM2AnyTokenTransfer[]", - "components": [ - { - "name": "sourcePoolAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "destTokenAddress", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "extraData", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "destExecData", - "type": "bytes", - "internalType": "bytes" - } - ] - } - ] - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ConfigSet", - "inputs": [ - { - "name": "staticConfig", - "type": "tuple", - "indexed": false, - "internalType": "struct OnRamp.StaticConfig", - "components": [ - { - "name": "chainSelector", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "rmnRemote", - "type": "address", - "internalType": "contract IRMNRemote" - }, - { - "name": "nonceManager", - "type": "address", - "internalType": "address" - }, - { - "name": "tokenAdminRegistry", - "type": "address", - "internalType": "address" - } - ] - }, - { - "name": "dynamicConfig", - "type": "tuple", - "indexed": false, - "internalType": "struct OnRamp.DynamicConfig", - "components": [ - { - "name": "feeQuoter", - "type": "address", - "internalType": "address" - }, - { - "name": "reentrancyGuardEntered", - "type": "bool", - "internalType": "bool" - }, - { - "name": "messageInterceptor", - "type": "address", - "internalType": "address" - }, - { - "name": "feeAggregator", - "type": "address", - "internalType": "address" - }, - { - "name": "allowlistAdmin", - "type": "address", - "internalType": "address" - } - ] - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "DestChainConfigSet", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "indexed": true, - "internalType": "uint64" - }, - { - "name": "sequenceNumber", - "type": "uint64", - "indexed": false, - "internalType": "uint64" - }, - { - "name": "router", - "type": "address", - "indexed": false, - "internalType": "contract IRouter" - }, - { - "name": "allowlistEnabled", - "type": "bool", - "indexed": false, - "internalType": "bool" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "FeeTokenWithdrawn", - "inputs": [ - { - "name": "feeAggregator", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "feeToken", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "OwnershipTransferRequested", - "inputs": [ - { - "name": "from", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "to", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "OwnershipTransferred", - "inputs": [ - { - "name": "from", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "to", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "CannotSendZeroTokens", - "inputs": [] - }, - { - "type": "error", - "name": "CannotTransferToSelf", - "inputs": [] - }, - { - "type": "error", - "name": "CursedByRMN", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - } - ] - }, - { - "type": "error", - "name": "GetSupportedTokensFunctionalityRemovedCheckAdminRegistry", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidAllowListRequest", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - } - ] - }, - { - "type": "error", - "name": "InvalidConfig", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidDestChainConfig", - "inputs": [ - { - "name": "destChainSelector", - "type": "uint64", - "internalType": "uint64" - } - ] - }, - { - "type": "error", - "name": "MustBeCalledByRouter", - "inputs": [] - }, - { - "type": "error", - "name": "MustBeProposedOwner", - "inputs": [] - }, - { - "type": "error", - "name": "OnlyCallableByOwner", - "inputs": [] - }, - { - "type": "error", - "name": "OnlyCallableByOwnerOrAllowlistAdmin", - "inputs": [] - }, - { - "type": "error", - "name": "OwnerCannotBeZero", - "inputs": [] - }, - { - "type": "error", - "name": "ReentrancyGuardReentrantCall", - "inputs": [] - }, - { - "type": "error", - "name": "RouterMustSetOriginalSender", - "inputs": [] - }, - { - "type": "error", - "name": "SenderNotAllowed", - "inputs": [ - { - "name": "sender", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "UnsupportedToken", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - } - ] - } - ] \ No newline at end of file + { + "type": "constructor", + "inputs": [ + { + "name": "staticConfig", + "type": "tuple", + "internalType": "struct OnRamp.StaticConfig", + "components": [ + { + "name": "chainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "rmnRemote", + "type": "address", + "internalType": "contract IRMNRemote" + }, + { + "name": "nonceManager", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenAdminRegistry", + "type": "address", + "internalType": "address" + } + ] + }, + { + "name": "dynamicConfig", + "type": "tuple", + "internalType": "struct OnRamp.DynamicConfig", + "components": [ + { + "name": "feeQuoter", + "type": "address", + "internalType": "address" + }, + { + "name": "reentrancyGuardEntered", + "type": "bool", + "internalType": "bool" + }, + { + "name": "messageInterceptor", + "type": "address", + "internalType": "address" + }, + { + "name": "feeAggregator", + "type": "address", + "internalType": "address" + }, + { + "name": "allowlistAdmin", + "type": "address", + "internalType": "address" + } + ] + }, + { + "name": "destChainConfigArgs", + "type": "tuple[]", + "internalType": "struct OnRamp.DestChainConfigArgs[]", + "components": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "router", + "type": "address", + "internalType": "contract IRouter" + }, + { + "name": "allowlistEnabled", + "type": "bool", + "internalType": "bool" + } + ] + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "applyAllowlistUpdates", + "inputs": [ + { + "name": "allowlistConfigArgsItems", + "type": "tuple[]", + "internalType": "struct OnRamp.AllowlistConfigArgs[]", + "components": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "allowlistEnabled", + "type": "bool", + "internalType": "bool" + }, + { + "name": "addedAllowlistedSenders", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "removedAllowlistedSenders", + "type": "address[]", + "internalType": "address[]" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "applyDestChainConfigUpdates", + "inputs": [ + { + "name": "destChainConfigArgs", + "type": "tuple[]", + "internalType": "struct OnRamp.DestChainConfigArgs[]", + "components": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "router", + "type": "address", + "internalType": "contract IRouter" + }, + { + "name": "allowlistEnabled", + "type": "bool", + "internalType": "bool" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "forwardFromRouter", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "message", + "type": "tuple", + "internalType": "struct Client.EVM2AnyMessage", + "components": [ + { + "name": "receiver", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "tokenAmounts", + "type": "tuple[]", + "internalType": "struct Client.EVMTokenAmount[]", + "components": [ + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "feeToken", + "type": "address", + "internalType": "address" + }, + { + "name": "extraArgs", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "feeTokenAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originalSender", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getAllowedSendersList", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "isEnabled", + "type": "bool", + "internalType": "bool" + }, + { + "name": "configuredAddresses", + "type": "address[]", + "internalType": "address[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDestChainConfig", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "sequenceNumber", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "allowlistEnabled", + "type": "bool", + "internalType": "bool" + }, + { + "name": "router", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDynamicConfig", + "inputs": [], + "outputs": [ + { + "name": "dynamicConfig", + "type": "tuple", + "internalType": "struct OnRamp.DynamicConfig", + "components": [ + { + "name": "feeQuoter", + "type": "address", + "internalType": "address" + }, + { + "name": "reentrancyGuardEntered", + "type": "bool", + "internalType": "bool" + }, + { + "name": "messageInterceptor", + "type": "address", + "internalType": "address" + }, + { + "name": "feeAggregator", + "type": "address", + "internalType": "address" + }, + { + "name": "allowlistAdmin", + "type": "address", + "internalType": "address" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getExpectedNextSequenceNumber", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFee", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "message", + "type": "tuple", + "internalType": "struct Client.EVM2AnyMessage", + "components": [ + { + "name": "receiver", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "tokenAmounts", + "type": "tuple[]", + "internalType": "struct Client.EVMTokenAmount[]", + "components": [ + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "feeToken", + "type": "address", + "internalType": "address" + }, + { + "name": "extraArgs", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "feeTokenAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPoolBySourceToken", + "inputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "sourceToken", + "type": "address", + "internalType": "contract IERC20" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IPoolV1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getStaticConfig", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct OnRamp.StaticConfig", + "components": [ + { + "name": "chainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "rmnRemote", + "type": "address", + "internalType": "contract IRMNRemote" + }, + { + "name": "nonceManager", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenAdminRegistry", + "type": "address", + "internalType": "address" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getSupportedTokens", + "inputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "address[]", + "internalType": "address[]" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setDynamicConfig", + "inputs": [ + { + "name": "dynamicConfig", + "type": "tuple", + "internalType": "struct OnRamp.DynamicConfig", + "components": [ + { + "name": "feeQuoter", + "type": "address", + "internalType": "address" + }, + { + "name": "reentrancyGuardEntered", + "type": "bool", + "internalType": "bool" + }, + { + "name": "messageInterceptor", + "type": "address", + "internalType": "address" + }, + { + "name": "feeAggregator", + "type": "address", + "internalType": "address" + }, + { + "name": "allowlistAdmin", + "type": "address", + "internalType": "address" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "typeAndVersion", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "withdrawFeeTokens", + "inputs": [ + { + "name": "feeTokens", + "type": "address[]", + "internalType": "address[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "AllowListAdminSet", + "inputs": [ + { + "name": "allowlistAdmin", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "AllowListSendersAdded", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "senders", + "type": "address[]", + "indexed": false, + "internalType": "address[]" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "AllowListSendersRemoved", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "senders", + "type": "address[]", + "indexed": false, + "internalType": "address[]" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CCIPMessageSent", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "sequenceNumber", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "message", + "type": "tuple", + "indexed": false, + "internalType": "struct Internal.EVM2AnyRampMessage", + "components": [ + { + "name": "header", + "type": "tuple", + "internalType": "struct Internal.RampMessageHeader", + "components": [ + { + "name": "messageId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "sourceChainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "sequenceNumber", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "nonce", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "receiver", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "extraArgs", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "feeToken", + "type": "address", + "internalType": "address" + }, + { + "name": "feeTokenAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "feeValueJuels", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenAmounts", + "type": "tuple[]", + "internalType": "struct Internal.EVM2AnyTokenTransfer[]", + "components": [ + { + "name": "sourcePoolAddress", + "type": "address", + "internalType": "address" + }, + { + "name": "destTokenAddress", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "destExecData", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ConfigSet", + "inputs": [ + { + "name": "staticConfig", + "type": "tuple", + "indexed": false, + "internalType": "struct OnRamp.StaticConfig", + "components": [ + { + "name": "chainSelector", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "rmnRemote", + "type": "address", + "internalType": "contract IRMNRemote" + }, + { + "name": "nonceManager", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenAdminRegistry", + "type": "address", + "internalType": "address" + } + ] + }, + { + "name": "dynamicConfig", + "type": "tuple", + "indexed": false, + "internalType": "struct OnRamp.DynamicConfig", + "components": [ + { + "name": "feeQuoter", + "type": "address", + "internalType": "address" + }, + { + "name": "reentrancyGuardEntered", + "type": "bool", + "internalType": "bool" + }, + { + "name": "messageInterceptor", + "type": "address", + "internalType": "address" + }, + { + "name": "feeAggregator", + "type": "address", + "internalType": "address" + }, + { + "name": "allowlistAdmin", + "type": "address", + "internalType": "address" + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DestChainConfigSet", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "sequenceNumber", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + }, + { + "name": "router", + "type": "address", + "indexed": false, + "internalType": "contract IRouter" + }, + { + "name": "allowlistEnabled", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeeTokenWithdrawn", + "inputs": [ + { + "name": "feeAggregator", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "feeToken", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferRequested", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "CannotSendZeroTokens", + "inputs": [] + }, + { + "type": "error", + "name": "CannotTransferToSelf", + "inputs": [] + }, + { + "type": "error", + "name": "CursedByRMN", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "GetSupportedTokensFunctionalityRemovedCheckAdminRegistry", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidAllowListRequest", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidConfig", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidDestChainConfig", + "inputs": [ + { + "name": "destChainSelector", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "MustBeCalledByRouter", + "inputs": [] + }, + { + "type": "error", + "name": "MustBeProposedOwner", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyCallableByOwner", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyCallableByOwnerOrAllowlistAdmin", + "inputs": [] + }, + { + "type": "error", + "name": "OwnerCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "ReentrancyGuardReentrantCall", + "inputs": [] + }, + { + "type": "error", + "name": "RouterMustSetOriginalSender", + "inputs": [] + }, + { + "type": "error", + "name": "SenderNotAllowed", + "inputs": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "UnsupportedToken", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ] + } +] diff --git a/packages/ccip-js/src/adapters/ethers.ts b/packages/ccip-js/src/adapters/ethers.ts new file mode 100644 index 0000000..33ceb00 --- /dev/null +++ b/packages/ccip-js/src/adapters/ethers.ts @@ -0,0 +1,543 @@ +import type { + Address, + Chain, + Client as ViemClient, + Hex, + PublicClient, + Transport, + WalletClient, + TransactionReceipt as ViemTransactionReceipt, + AbiEvent, +} from 'viem' + +import type { + ContractCallArgs, + TransactionArgs, + ReceiptArgs, + LogsArgs, +} from './types' +import { + createPublicClient, + createWalletClient, + custom, + parseEventLogs, + keccak256, + toHex, + isAddress, + zeroAddress, + isAddressEqual, +} from 'viem' +import { toAccount } from 'viem/accounts' +import type { Provider, Signer, TypedDataField, BrowserProvider } from 'ethers' +import { Contract, type TransactionReceipt as EthersTxReceipt, type TransactionResponse } from 'ethers' +import { + readContract as viemReadContract, + writeContract as viemWriteContract, + waitForTransactionReceipt as viemWaitForTransactionReceipt, + getTransactionReceipt as viemGetTransactionReceipt, + getBlockNumber as viemGetBlockNumber, + getLogs as viemGetLogs, +} from 'viem/actions' + +/** + * Converts an ethers Provider to a viem Transport for use with viem clients. + */ +export function ethersProviderToTransport(provider: Provider): Transport { + return custom({ + async request({ method, params }) { + const anyProvider = provider as any + if (typeof anyProvider.request === 'function') { + return anyProvider.request({ method, params }) + } + if (typeof anyProvider.send === 'function') { + return anyProvider.send(method, params as any) + } + throw new Error('Unsupported ethers provider: missing request/send method') + }, + }) +} + +/** + * Adapts an ethers Signer into a viem LocalAccount supporting message, transaction, + * and EIP-712 typed data signing. + */ +export async function ethersSignerToAccount(signer: Signer) { + return toAccount({ + address: (await signer.getAddress()) as unknown as Address, + async signMessage({ message }) { + let data: any + if (typeof message === 'string') { + data = message + } else if (typeof message === 'object' && message !== null && 'raw' in (message as any)) { + const raw = (message as any).raw as Hex | Uint8Array + data = typeof raw === 'string' ? (raw as string) : (raw as Uint8Array) + } else { + data = message as any + } + return (await signer.signMessage(data)) as unknown as Hex + }, + async signTransaction(txn) { + const serialized = await signer.signTransaction({ + chainId: txn.chainId, + data: txn.data, + gasLimit: txn.gas, + gasPrice: txn.gasPrice, + nonce: txn.nonce, + to: txn.to, + value: txn.value, + type: + txn.type === 'legacy' + ? 0 + : txn.type === 'eip2930' + ? 1 + : txn.type === 'eip1559' + ? 2 + : txn.type === 'eip4844' + ? 3 + : undefined, + ...(txn.type && txn.accessList ? { accessList: txn.accessList } : {}), + ...(txn.maxPriorityFeePerGas ? { maxPriorityFeePerGas: txn.maxPriorityFeePerGas } : {}), + ...(txn.maxFeePerGas ? { maxFeePerGas: txn.maxFeePerGas } : {}), + ...((txn as any).maxFeePerBlobGas ? { maxFeePerBlobGas: (txn as any).maxFeePerBlobGas } : {}), + ...((txn as any).blobVersionedHashes ? { blobVersionedHashes: (txn as any).blobVersionedHashes } : {}), + ...((txn as any).blobs ? { blobs: (txn as any).blobs } : {}), + } as any) + return serialized as unknown as Hex + }, + async signTypedData({ domain, types, message }) { + const { EIP712Domain: _ignored, ...rest } = (types || {}) as Record + const signTypedData = (signer as any)._signTypedData ?? (signer as any).signTypedData + return (await signTypedData(domain ?? {}, rest, message)) as unknown as Hex + }, + }) +} + +/** + * Creates a viem PublicClient from an ethers Provider and Chain. + */ +export function ethersProviderToPublicClient(provider: Provider, chain: Chain): PublicClient { + return createPublicClient({ chain, transport: ethersProviderToTransport(provider) }) as unknown as PublicClient +} + +/** + * Creates a viem WalletClient from an ethers Signer connected to a Provider. + */ +export async function ethersSignerToWalletClient( + signer: Signer & { provider?: Provider }, + chain: Chain, +): Promise { + if (!signer || !(signer as any).provider) { + throw new Error('Invalid signer: missing provider. Provide a signer connected to a provider.') + } + return createWalletClient({ + chain, + transport: ethersProviderToTransport((signer as any).provider as Provider), + account: await ethersSignerToAccount(signer), + }) as unknown as WalletClient +} + +/** + * Lightweight adapter around ethers to expose read/write and utility methods similar to viem. + */ +export class EthersAdapter { + private provider: Provider + private signer?: Signer + + constructor(provider: Provider, signer?: Signer) { + this.provider = provider + this.signer = signer + } + + /** + * Calls a view/pure function on a contract using the provided provider. + */ + async readContract({ + address, + abi, + functionName, + args = [], + }: { + address: Address + abi: any[] + functionName: string + args?: any[] + }) { + const contract = new Contract(address, abi, this.provider) + return (contract as any)[functionName](...(args || [])) + } + + /** + * Sends a transaction to a contract function using the configured signer. + * Returns the transaction hash as Hex. + */ + async writeContract({ + address, + abi, + functionName, + args = [], + value, + ...tx + }: { + address: Address + abi: any[] + functionName: string + args?: any[] + value?: bigint + [key: string]: any + }): Promise { + if (!this.signer) throw new Error('Signer is required for write operations') + const contract = new Contract(address, abi, this.signer) + const txResponse: TransactionResponse = await (contract as any)[functionName](...(args || []), { + ...tx, + value: value !== undefined ? value.toString() : undefined, + }) + return txResponse.hash as Hex + } + + /** + * Waits for a transaction receipt and returns it normalized to viem's TransactionReceipt. + */ + async waitForTransactionReceipt({ + hash, + confirmations, + timeout, + }: { + hash: Hex + confirmations?: number + timeout?: number + }) { + const maybe = await this.provider.waitForTransaction(hash, confirmations, timeout) + if (!maybe) throw new Error('Transaction receipt not found') + return this.formatReceipt(maybe) + } + + /** + * Fetches a transaction receipt by hash and returns it normalized to viem's TransactionReceipt. + */ + async getTransactionReceipt(hash: Hex) { + const maybe = await this.provider.getTransactionReceipt(hash) + if (!maybe) throw new Error('Transaction receipt not found') + return this.formatReceipt(maybe) + } + + /** + * Returns the latest block number as bigint. + */ + async getBlockNumber(): Promise { + const blockNumber = await this.provider.getBlockNumber() + return BigInt(blockNumber) + } + + /** + * Normalizes an ethers TransactionReceipt to viem's TransactionReceipt shape. + */ + private formatReceipt(receipt: EthersTxReceipt) { + return { + ...receipt, + blockNumber: BigInt(receipt.blockNumber), + cumulativeGasUsed: BigInt(receipt.cumulativeGasUsed), + effectiveGasPrice: BigInt(((receipt as any).effectiveGasPrice ?? (receipt as any).gasPrice)?.toString() || '0'), + gasUsed: BigInt(receipt.gasUsed), + logs: receipt.logs.map((log: any) => ({ + ...log, + blockNumber: BigInt(log.blockNumber), + logIndex: log.index, + transactionIndex: log.transactionIndex, + })), + status: (receipt.status as number) === 1 ? 'success' : 'reverted', + to: receipt.to as Address, + transactionHash: (receipt as any).hash ?? (receipt as any).transactionHash, + transactionIndex: (receipt as any).index, + } as unknown as ViemTransactionReceipt + } +} + +/** + * Type guard: returns true if the value is an ethers Provider. + */ +export function isEthersProvider(provider: any): provider is Provider { + return ( + !!provider && + typeof provider.getBlockNumber === 'function' && + typeof (provider as any).getNetwork === 'function' && + !(provider as any).transport + ) +} + +/** + * Type guard: returns true if the value is an ethers Signer. + */ +export function isEthersSigner(signer: any): signer is Signer { + return signer && typeof signer.getAddress === 'function' +} + +/** + * Union of supported client types: viem Client/WalletClient, ethers Provider, or ethers Signer. + */ + +export type SupportedClient = ViemClient | WalletClient | Provider | Signer | BrowserProvider + +/** + * Attempts to adapt the provided client to a viem PublicClient if possible. + * For ethers Provider/Signer, a Chain is required. + */ +function toViemPublicClient(client: SupportedClient, chain?: Chain): PublicClient | null { + if ((client as any)?.transport) return client as any as PublicClient + if (isEthersProvider(client)) { + if (!chain) throw new Error('Chain is required to adapt an ethers Provider to a viem PublicClient') + return ethersProviderToPublicClient(client, chain) + } + if (isEthersSigner(client)) { + const provider = (client as any).provider as Provider | undefined + if (provider && chain) return ethersProviderToPublicClient(provider, chain) + } + return null +} + +/** + * Attempts to adapt the provided client to a viem WalletClient if possible. + * For ethers Signer, a Chain is required. + */ +async function toViemWalletClient(client: SupportedClient, chain?: Chain): Promise { + if ((client as any)?.account && (client as any)?.transport) return client as any as WalletClient + if (isEthersSigner(client)) { + if (!chain) throw new Error('Chain is required to adapt an ethers Signer to a viem WalletClient') + return ethersSignerToWalletClient(client as any, chain) + } + return null +} + +/** + * Unified readContract that supports viem clients and ethers Provider/Signer. + */ +export async function readContractCompat( + client: SupportedClient, + 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, args.abi, provider) + const fn = (contract as any)[args.functionName] + if (fn && typeof fn.staticCall === 'function') { + return fn.staticCall(...(args.args || [])) + } + return fn(...(args.args || [])) + } + const viemClient = toViemPublicClient(client, args.chain) + if (!viemClient) throw new Error('Unsupported client for readContract') + return viemReadContract(viemClient as any, args as any) +} + +/** + * Unified writeContract that supports viem WalletClient and ethers Signer. + * Returns a transaction hash (Hex). + */ +export async function writeContractCompat( + client: SupportedClient, + args: TransactionArgs, +) { + if (isEthersSigner(client)) { + const provider: Provider | undefined = (client as any).provider as Provider | undefined + if (!provider) throw new Error('Unsupported client for writeContract: signer has no provider') + const contract = new Contract(args.address, args.abi, client as Signer) + const txResponse: TransactionResponse = await (contract as any)[args.functionName]( + ...(args.args || []), + { + value: args.value !== undefined ? args.value.toString() : undefined, + gasLimit: args.gas, + gasPrice: args.gasPrice, + nonce: args.nonce, + }, + ) + return txResponse.hash as Hex + } + const viemClient = await toViemWalletClient(client, args.chain) + if (!viemClient) throw new Error('Unsupported client for writeContract') + const { chain, ...rest } = args as any + return viemWriteContract(viemClient as any, rest) +} + +/** + * Unified waitForTransactionReceipt that supports viem clients and ethers Provider/Signer. + */ +export async function waitForTransactionReceiptCompat( + client: SupportedClient, + 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.hash, + args.confirmations, + args.timeout, + ) + if (!maybe) throw new Error('Transaction receipt not found') + return formatEthersReceipt(maybe) + } + const viemClient = toViemPublicClient(client, args.chain) || (await toViemWalletClient(client, args.chain)) + if (!viemClient) throw new Error('Unsupported client for waitForTransactionReceipt') + const { chain, ...rest } = args as any + return viemWaitForTransactionReceipt(viemClient as any, rest) +} + +/** + * Unified getTransactionReceipt that supports viem clients and ethers Provider/Signer. + */ +export async function getTransactionReceiptCompat( + client: SupportedClient, + 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.hash) + if (!maybe) throw new Error('Transaction receipt not found') + return formatEthersReceipt(maybe) + } + const viemClient = toViemPublicClient(client, args.chain) || (await toViemWalletClient(client, args.chain)) + if (!viemClient) throw new Error('Unsupported client for getTransactionReceipt') + const { chain, ...rest } = args as any + return viemGetTransactionReceipt(viemClient as any, rest) +} + +/** + * Unified getBlockNumber that supports viem clients and ethers Provider/Signer. + */ +export async function getBlockNumberCompat(client: SupportedClient, chain?: Chain) { + 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 getBlockNumber: signer has no provider') + const n = await provider.getBlockNumber() + return BigInt(n) + } + const viemClient = toViemPublicClient(client, chain) || (await toViemWalletClient(client, chain)) + if (!viemClient) throw new Error('Unsupported client for getBlockNumber') + return viemGetBlockNumber(viemClient as any) +} + +/** + * Unified getLogs that supports viem clients and ethers Provider/Signer. For ethers path, + * builds topics from the provided AbiEvent and decodes using viem.parseEventLogs. + */ +export async function getLogsCompat( + client: SupportedClient, + 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.event as AbiEvent | undefined + const address = args.address as Address | undefined + const fromBlock = args.fromBlock !== undefined ? BigInt(args.fromBlock as any) : undefined + const toBlock = args.toBlock !== undefined ? BigInt(args.toBlock as any) : undefined + const topics: (string | null)[] | undefined = abiEvent + ? buildTopicsFromEventAndArgs(abiEvent, args.args) + : undefined + const filter: any = { address, topics, fromBlock, toBlock } + const logs = await provider.getLogs(filter) + if (abiEvent) { + const rpcLogs = logs.map(mapEthersLogToViemRpcLog) + return parseEventLogs({ abi: [abiEvent] as any, logs: rpcLogs as any }) + } + return logs.map((log: any) => ({ ...log, blockNumber: BigInt(log.blockNumber) })) + } + const viemClient = toViemPublicClient(client, args.chain) + if (!viemClient) throw new Error('Unsupported client for getLogs') + const { chain, ...rest } = args as any + return viemGetLogs(viemClient as any, rest) +} + +/** + * Validates an address string using viem and returns it as Address. + */ +export function parseAddress(address: string | Address): Address { + const a = address as string + if (typeof address === 'string') { + if (!isAddress(a) || isAddressEqual(a as Address, zeroAddress)) + throw new Error(`PARAMETER INPUT ERROR: Address ${a} is not valid`) + return a as Address + } + return address as Address +} + +/** + * Coerces a chain selector into bigint. + */ +export function normalizeChainSelector(selector: string | number | bigint): bigint { + if (typeof selector === 'bigint') return selector + if (typeof selector === 'number') return BigInt(selector) + return BigInt(selector) +} + +/** + * Normalizes an ethers TransactionReceipt to viem's TransactionReceipt shape. + */ +function formatEthersReceipt(receipt: EthersTxReceipt): ViemTransactionReceipt { + return { + ...receipt, + blockNumber: BigInt(receipt.blockNumber), + cumulativeGasUsed: BigInt(receipt.cumulativeGasUsed), + effectiveGasPrice: BigInt(((receipt as any).effectiveGasPrice ?? (receipt as any).gasPrice)?.toString() || '0'), + gasUsed: BigInt(receipt.gasUsed), + logs: receipt.logs.map((log: any) => ({ + ...log, + blockNumber: BigInt(log.blockNumber), + logIndex: log.index, + transactionIndex: log.transactionIndex, + })), + status: (receipt.status as number) === 1 ? 'success' : 'reverted', + to: receipt.to as Address, + transactionHash: (receipt as any).hash ?? (receipt as any).transactionHash, + transactionIndex: (receipt as any).index, + } as unknown as ViemTransactionReceipt +} + +/** + * Builds a topics array for an event filter based on the AbiEvent and provided indexed args. + */ +function buildTopicsFromEventAndArgs( + event: AbiEvent, + providedArgs?: Record | undefined, +): (string | null)[] { + const signature = `${event.name}(${event.inputs.map((i) => i.type).join(',')})` + // keccak256 of the string signature + const topic0 = keccak256(toHex(signature)) + if (!providedArgs) return [topic0] + const topics: (string | null)[] = [topic0] + for (const input of event.inputs) { + if (!input.indexed) continue + const name = (input as any).name as string | undefined + const value = name ? (providedArgs as any)[name] : undefined + let encoded: string | null = null + if (value !== undefined && value !== null) { + if (typeof value === 'string' && value.startsWith('0x')) { + encoded = value + } else if (input.type === 'address' && typeof value === 'string') { + encoded = value + } else if (input.type.startsWith('uint') || input.type.startsWith('int')) { + encoded = '0x' + BigInt(value).toString(16).padStart(64, '0') + } + } + topics.push(encoded) + } + return topics +} + +/** + * Maps an ethers log into a viem RpcLog-like shape expected by viem.parseEventLogs. + */ +function mapEthersLogToViemRpcLog(log: any) { + return { + address: log.address, + blockHash: log.blockHash ?? null, + blockNumber: log.blockNumber ? '0x' + BigInt(log.blockNumber).toString(16) : null, + data: log.data, + logIndex: log.index != null ? '0x' + BigInt(log.index).toString(16) : null, + transactionHash: log.transactionHash ?? null, + transactionIndex: log.transactionIndex != null ? '0x' + BigInt(log.transactionIndex).toString(16) : null, + removed: false, + topics: log.topics, + } +} 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 015fa84..63bab3e 100644 --- a/packages/ccip-js/src/api.ts +++ b/packages/ccip-js/src/api.ts @@ -1,13 +1,6 @@ import * as Viem from 'viem' -import { - readContract, - writeContract, - waitForTransactionReceipt, - getLogs, - getTransactionReceipt as getTxReceipt, - getBlockNumber, -} from 'viem/actions' +// Using compat adapters for ethers/Viem interop where appropriate import RouterABI from './abi/Router.json' import OnRampABI from './abi/OnRamp.json' @@ -17,6 +10,32 @@ import TokenPoolABI from './abi/TokenPool.json' import TokenAdminRegistryABI from './abi/TokenAdminRegistry.json' import { TRANSFER_STATUS_FROM_BLOCK_SHIFT, ExecutionStateChangedABI } from './config' import { parseAbi } from 'viem' +export { + ethersProviderToTransport, + ethersSignerToAccount, + ethersProviderToPublicClient, + ethersSignerToWalletClient, + EthersAdapter, + readContractCompat, + writeContractCompat, + waitForTransactionReceiptCompat, + getTransactionReceiptCompat, + getBlockNumberCompat, + getLogsCompat, + SupportedClient, +} from './adapters/ethers' + +import { + readContractCompat as readCompat, + writeContractCompat as writeContract, + waitForTransactionReceiptCompat as waitCompat, + getTransactionReceiptCompat as getTxReceiptCompat, + getBlockNumberCompat as getBlockNumberCompatLocal, + getLogsCompat as getLogsCompatLocal, +} from './adapters/ethers' +import { normalizeChainSelector, isEthersProvider, isEthersSigner } from './adapters/ethers' + +import type { SupportedClient } from './adapters/ethers' export { IERC20ABI } @@ -72,7 +91,7 @@ export interface Client { * }); */ approveRouter(options: { - client: Viem.WalletClient + client: Viem.WalletClient | SupportedClient routerAddress: Viem.Address tokenAddress: Viem.Address amount: bigint @@ -115,7 +134,7 @@ export interface Client { * }) */ getAllowance(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address tokenAddress: Viem.Address account: Viem.Address @@ -143,7 +162,7 @@ export interface Client { * }) */ getOnRampAddress(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address destinationChainSelector: string }): Promise @@ -170,7 +189,7 @@ export interface Client { * }); */ getSupportedFeeTokens(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address destinationChainSelector: string }): Promise @@ -198,7 +217,7 @@ export interface Client { * }); */ getLaneRateRefillLimits(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address destinationChainSelector: string }): Promise @@ -228,7 +247,7 @@ export interface Client { * }); */ getTokenRateLimitByLane(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address supportedTokenAddress: Viem.Address destinationChainSelector: string @@ -266,7 +285,7 @@ export interface Client { * }); */ getFee(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address destinationAccount: Viem.Address destinationChainSelector: string @@ -300,7 +319,7 @@ export interface Client { * }); */ getTokenAdminRegistry(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address destinationChainSelector: string tokenAddress: Viem.Address @@ -330,7 +349,7 @@ export interface Client { * }); */ isTokenSupported(options: { - client: Viem.Client + client: Viem.Client | SupportedClient routerAddress: Viem.Address destinationChainSelector: string tokenAddress: Viem.Address @@ -376,7 +395,7 @@ export interface Client { * */ transferTokens(options: { - client: Viem.WalletClient + client: Viem.WalletClient | SupportedClient routerAddress: Viem.Address destinationChainSelector: string amount: bigint @@ -448,7 +467,7 @@ export interface Client { * */ sendCCIPMessage(options: { - client: Viem.WalletClient + client: Viem.WalletClient | SupportedClient routerAddress: Viem.Address destinationChainSelector: string destinationAccount: Viem.Address @@ -491,7 +510,7 @@ export interface Client { * }); */ getTransferStatus(options: { - client: Viem.Client + client: Viem.Client | SupportedClient destinationRouterAddress: Viem.Address sourceChainSelector: string messageId: Viem.Hash @@ -519,7 +538,10 @@ export interface Client { * hash: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef" * }); */ - getTransactionReceipt(options: { client: Viem.Client; hash: Viem.Hash }): Promise + getTransactionReceipt(options: { + client: Viem.Client | SupportedClient + hash: Viem.Hash + }): Promise } /** @@ -615,25 +637,30 @@ export const createClient = (): Client => { throw new Error('PARAMETER INPUT ERROR: Invalid approve amount. Amount can not be negative') } - const approveTxHash = await writeContract(options.client, { - chain: options.client.chain, - account: options.client.account!, - abi: IERC20ABI, - address: options.tokenAddress, - functionName: 'approve', - args: [options.routerAddress, options.amount], - ...options.writeContractParameters, - }) + const approveTxHash = await writeContract( + options.client as any, + { + abi: IERC20ABI, + address: options.tokenAddress, + functionName: 'approve', + args: [options.routerAddress, options.amount], + account: (options.client as any).account, + ...options.writeContractParameters, + } as any, + ) if (!options.waitForReceipt) { return { txHash: approveTxHash } } - const txReceipt = await waitForTransactionReceipt(options.client, { - hash: approveTxHash, - confirmations: 2, - ...options.waitForTransactionReceiptParameters, - }) + const txReceipt = await waitCompat( + options.client as any, + { + hash: approveTxHash, + confirmations: 2, + ...options.waitForTransactionReceiptParameters, + } as any, + ) return { txHash: approveTxHash, txReceipt: txReceipt as Viem.TransactionReceipt } } @@ -649,7 +676,7 @@ export const createClient = (): Client => { ) checkIsAddressValid(options.account, `PARAMETER INPUT ERROR: Account address ${options.account} is not valid`) - const allowance = await readContract(options.client, { + const allowance = await readCompat(options.client as any, { abi: IERC20ABI, address: options.tokenAddress, functionName: 'allowance', @@ -664,11 +691,11 @@ export const createClient = (): Client => { `PARAMETER INPUT ERROR: Router address ${options.routerAddress} is not valid`, ) - const onRampAddress = (await readContract(options.client, { + const onRampAddress = (await readCompat(options.client as any, { abi: RouterABI, address: options.routerAddress, functionName: 'getOnRamp', - args: [options.destinationChainSelector], + args: [normalizeChainSelector(options.destinationChainSelector)], })) as Viem.Address checkIsAddressValid( @@ -682,13 +709,13 @@ export const createClient = (): Client => { async function getSupportedFeeTokens(options: Parameters[0]) { const onRampAddress = await getOnRampAddress(options) - const dynamicConfig = await readContract(options.client, { + const dynamicConfig = await readCompat(options.client as any, { abi: OnRampABI, address: onRampAddress, functionName: 'getDynamicConfig', }) - const typeAndVersion = await readContract(options.client, { + const typeAndVersion = await readCompat(options.client as any, { abi: OnRampABI, address: onRampAddress, functionName: 'typeAndVersion', @@ -708,8 +735,8 @@ export const createClient = (): Client => { `CONTRACT CALL ERROR: Price regisry '${priceRegistryOrFeeQuoter}' is not valid. Execution can not be continued`, ) - const feeTokens = await readContract(options.client, { - abi: parseAbi(['function getFeeTokens() returns (address[] feeTokens)']), // same signature for both PriceRegistry and FeeQuoter + const feeTokens = await readCompat(options.client as any, { + abi: parseAbi(['function getFeeTokens() view returns (address[] feeTokens)']), // same signature for both PriceRegistry and FeeQuoter address: priceRegistryOrFeeQuoter as Viem.Address, functionName: 'getFeeTokens', }) @@ -719,12 +746,22 @@ export const createClient = (): Client => { async function getLaneRateRefillLimits(options: Parameters[0]) { const onRampAddress = await getOnRampAddress(options) - const currentRateLimiterState = await readContract(options.client, { + const currentRateLimiterState = await readCompat(options.client as any, { abi: OnRampABI, address: onRampAddress, functionName: 'currentRateLimiterState', }) - return currentRateLimiterState as RateLimiterState + const s: any = currentRateLimiterState as any + const isEthers = isEthersProvider(options.client as any) || isEthersSigner(options.client as any) + const viemChainName = (options.client as any)?.chain?.name?.toLowerCase?.() + const isHederaViem = !isEthers && viemChainName && viemChainName.includes('hedera') + return { + tokens: BigInt(s.tokens), + lastUpdated: isEthers ? BigInt(s.lastUpdated) : isHederaViem ? Number(s.lastUpdated) : BigInt(s.lastUpdated), + isEnabled: Boolean(s.isEnabled), + capacity: BigInt(s.capacity), + rate: BigInt(s.rate), + } as RateLimiterState } async function getTokenRateLimitByLane(options: Parameters[0]) { @@ -735,11 +772,11 @@ export const createClient = (): Client => { const onRampAddress = await getOnRampAddress(options) - const laneTokenTransferPool = (await readContract(options.client, { + const laneTokenTransferPool = (await readCompat(options.client as any, { abi: OnRampABI, address: onRampAddress, functionName: 'getPoolBySourceToken', - args: [options.destinationChainSelector, options.supportedTokenAddress], + args: [normalizeChainSelector(options.destinationChainSelector), options.supportedTokenAddress], })) as Viem.Address checkIsAddressValid( @@ -747,14 +784,22 @@ export const createClient = (): Client => { `CONTRACT CALL ERROR: Token pool for ${options.supportedTokenAddress} is missing. Execution can not be continued`, ) - const transferPoolTokenOutboundLimit = await readContract(options.client, { + const transferPoolTokenOutboundLimit = await readCompat(options.client as any, { abi: TokenPoolABI, address: laneTokenTransferPool as Viem.Address, functionName: 'getCurrentOutboundRateLimiterState', - args: [options.destinationChainSelector], + args: [normalizeChainSelector(options.destinationChainSelector)], }) - return transferPoolTokenOutboundLimit as RateLimiterState + const s: any = transferPoolTokenOutboundLimit as any + const isEthers = isEthersProvider(options.client as any) || isEthersSigner(options.client as any) + return { + tokens: BigInt(s.tokens), + lastUpdated: isEthers ? BigInt(s.lastUpdated) : Number(s.lastUpdated), + isEnabled: Boolean(s.isEnabled), + capacity: BigInt(s.capacity), + rate: BigInt(s.rate), + } as RateLimiterState } /* @@ -763,11 +808,12 @@ export const createClient = (): Client => { * @param chain - The chain to scale the fee for. * @returns The scaled fee if the chain's native token does not use 18 decimals. Otherwise the original fee. */ - function scaleFeeDecimals(fee: bigint, chain: Viem.Chain): bigint { + function scaleFeeDecimals(fee: bigint, chain?: Viem.Chain): bigint { + if (!chain) return fee const scaleFactorForChain: Record = { hedera: 10 } const isNonStandardDecimals = Object.keys(scaleFactorForChain).find((nonStandardChain) => { - return chain.name.toLowerCase().includes(nonStandardChain) // Tests that 'Hedera Testnet' includes "hedera" + return chain!.name.toLowerCase().includes(nonStandardChain) // Tests that 'Hedera Testnet' includes "hedera" }) if (isNonStandardDecimals) { @@ -811,15 +857,19 @@ export const createClient = (): Client => { } } - const fee = (await readContract(options.client, { + const fee = (await readCompat(options.client as any, { abi: RouterABI, address: options.routerAddress, functionName: 'getFee', args: buildArgs(options), })) as bigint - // Scale fee if needed based on chain - return scaleFeeDecimals(fee, options.client.chain!) + // Scale fee if needed based on chain; best-effort for both viem and ethers clients + try { + const viemChain = (options.client as any)?.chain as Viem.Chain | undefined + if (viemChain) return scaleFeeDecimals(fee, viemChain) + } catch { } + return scaleFeeDecimals(fee) } async function getTokenAdminRegistry(options: Parameters[0]) { @@ -829,7 +879,7 @@ export const createClient = (): Client => { const onRampAddress = await getOnRampAddress(options) - const staticConfig = await readContract(options.client, { + const staticConfig = await readCompat(options.client as any, { abi: OnRampABI, address: onRampAddress, functionName: 'getStaticConfig', @@ -847,7 +897,7 @@ export const createClient = (): Client => { async function isTokenSupported(options: Parameters[0]) { const tokenAdminRegistryAddress = await getTokenAdminRegistry(options) - const tokenPoolAddress = (await readContract(options.client, { + const tokenPoolAddress = (await readCompat(options.client as any, { abi: TokenAdminRegistryABI, address: tokenAdminRegistryAddress, functionName: 'getPool', @@ -858,11 +908,11 @@ export const createClient = (): Client => { return false } - const isSupported = (await readContract(options.client, { + const isSupported = (await readCompat(options.client as any, { abi: TokenPoolABI, address: tokenPoolAddress, functionName: 'isSupportedChain', - args: [options.destinationChainSelector], + args: [normalizeChainSelector(options.destinationChainSelector)], })) as boolean return isSupported @@ -887,27 +937,27 @@ export const createClient = (): Client => { } } - const writeContractParameters: any = { - chain: options.client.chain, - abi: RouterABI, - address: options.routerAddress, - functionName: 'ccipSend', - args: buildArgs(options), - account: options.client.account!, - value: options.feeTokenAddress ? undefined : await getFee(options), // Only add native token value if no fee token is specified - ...options.writeContractParameters, - } - - const transferTokensTxHash = await writeContract(options.client, writeContractParameters) + const transferTokensTxHash = await writeContract( + options.client as any, + { + abi: RouterABI, + address: options.routerAddress, + functionName: 'ccipSend', + args: buildArgs(options), + ...(options.feeTokenAddress ? {} : { value: await getFee(options) }), + ...((options.client as any)?.account ? { account: (options.client as any).account } : {}), + ...options.writeContractParameters, + } as any, + ) - const txReceipt = await waitForTransactionReceipt(options.client, { + const txReceipt = await waitCompat(options.client as any, { hash: transferTokensTxHash, confirmations: 2, ...options.waitForTransactionReceiptParameters, }) const onRamp = await getOnRampAddress(options) - const typeAndVersion = await readContract(options.client, { + const typeAndVersion = await readCompat(options.client as any, { abi: parseAbi(['function typeAndVersion() returns (string)']), address: onRamp, functionName: 'typeAndVersion', @@ -957,29 +1007,27 @@ export const createClient = (): Client => { } } - const writeContractParameters = { - chain: options.client.chain, - abi: RouterABI, - address: options.routerAddress, - functionName: 'ccipSend', - args: buildArgs(options), - account: options.client.account!, - ...(!options.feeTokenAddress && { - value: await getFee(options), - }), - ...options.writeContractParameters, - } - - const transferTokensTxHash = await writeContract(options.client, writeContractParameters) + const transferTokensTxHash = await writeContract( + options.client as any, + { + abi: RouterABI, + address: options.routerAddress, + functionName: 'ccipSend', + args: buildArgs(options), + ...(!options.feeTokenAddress && { value: await getFee(options) }), + ...((options.client as any)?.account ? { account: (options.client as any).account } : {}), + ...options.writeContractParameters, + } as any, + ) - const txReceipt = await waitForTransactionReceipt(options.client, { + const txReceipt = await waitCompat(options.client as any, { hash: transferTokensTxHash, confirmations: 2, ...options.waitForTransactionReceiptParameters, }) const onRamp = await getOnRampAddress(options) - const typeAndVersion = await readContract(options.client, { + const typeAndVersion = await readCompat(options.client as any, { abi: parseAbi(['function typeAndVersion() returns (string)']), address: onRamp, functionName: 'typeAndVersion', @@ -1028,7 +1076,7 @@ export const createClient = (): Client => { throw new Error('PARAMETER INPUT ERROR: Source chain selector is missing or invalid') } - const offRamps = (await readContract(options.client, { + const offRamps = (await readCompat(options.client as any, { abi: RouterABI, address: options.destinationRouterAddress, functionName: 'getOffRamps', @@ -1043,18 +1091,19 @@ export const createClient = (): Client => { let fromBlock = options.fromBlockNumber if (!fromBlock) { - const blockNumber = await getBlockNumber(options.client) + const blockNumber = await getBlockNumberCompatLocal(options.client as any) fromBlock = blockNumber - BigInt(TRANSFER_STATUS_FROM_BLOCK_SHIFT) } for (const offRamp of matchingOffRamps) { - const logs = await getLogs(options.client, { + const logs = await getLogsCompatLocal(options.client as any, { event: ExecutionStateChangedABI, address: offRamp.offRamp, args: { messageId: options.messageId }, fromBlock, }) if (logs && logs.length > 0) { - const { state } = logs[0].args + const first = logs[0] as any + const { state } = first.args ?? {} if (state) return state as TransferStatus } } @@ -1068,7 +1117,7 @@ export const createClient = (): Client => { throw new Error(`PARAMETER INPUT ERROR: ${options.hash} is not a valid transaction hash`) } - return await getTxReceipt(options.client, { hash: options.hash }) + return await getTxReceiptCompat(options.client as any, { hash: options.hash }) } function buildArgs(options: { @@ -1106,7 +1155,7 @@ export const createClient = (): Client => { const evmExtraArgsV2Tag = '0x181dcf10' const extraArgs = evmExtraArgsV2Tag + extraArgsEncoded.slice(2) return [ - destinationChainSelector, + normalizeChainSelector(destinationChainSelector), { receiver: Viem.encodeAbiParameters([{ type: 'address', name: 'receiver' }], [destinationAccount]), data: data ?? Viem.zeroHash, @@ -1123,12 +1172,10 @@ export const createClient = (): Client => { } } - function checikIsWalletAccountValid(options: { client: Viem.Client }) { - if (!options.client.account) { - throw new Error('WALLET ERROR: account is not valid') - } - - checkIsAddressValid(options.client.account.address, 'WALLET ERROR: account address is not valid') + function checikIsWalletAccountValid(options: { client: any }) { + const account = (options.client as any)?.account + if (!account) return + checkIsAddressValid(account.address as Viem.Address, 'WALLET ERROR: account address is not valid') } } @@ -1149,7 +1196,7 @@ export const createClient = (): Client => { */ export interface RateLimiterState { tokens: bigint - lastUpdated: number + lastUpdated: number | bigint isEnabled: boolean capacity: bigint rate: bigint diff --git a/packages/ccip-js/test/integration-testnet.test.ts b/packages/ccip-js/test/integration-testnet.test.ts index 33033f9..67903ab 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('[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('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