diff --git a/packages/ccip-js/README.md b/packages/ccip-js/README.md index 87024ea..4f382fb 100644 --- a/packages/ccip-js/README.md +++ b/packages/ccip-js/README.md @@ -117,6 +117,22 @@ const walletClient = createWalletClient({ transport: custom(window.ethereum!), }) +// Using ethers.js signer & provider +import { ethers } from 'ethers' +import { + ethersSignerToWalletClient, + ethersProviderToPublicClient, +} from '@chainlink/ccip-js' + +const ethersProvider = new ethers.JsonRpcProvider('https://rpc.example.com') +const ethersSigner = new ethers.Wallet(PRIVATE_KEY, ethersProvider) + +const ethersWalletClient = await ethersSignerToWalletClient(ethersSigner, mainnet) +const ethersPublicClient = ethersProviderToPublicClient(ethersProvider, mainnet) + +// The SDK methods accept Viem clients. The adapters above allow +// passing ethers providers and signers by converting them to Viem clients. + // Approve Router to transfer tokens on user's behalf const { txHash, txReceipt } = await ccipClient.approveRouter({ client: walletClient, diff --git a/packages/ccip-js/src/api.ts b/packages/ccip-js/src/api.ts index 7726acb..a8a578f 100644 --- a/packages/ccip-js/src/api.ts +++ b/packages/ccip-js/src/api.ts @@ -17,6 +17,12 @@ 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, +} from './ethers-adapters' export { IERC20ABI } diff --git a/packages/ccip-js/src/ethers-adapters.ts b/packages/ccip-js/src/ethers-adapters.ts new file mode 100644 index 0000000..1cd05a7 --- /dev/null +++ b/packages/ccip-js/src/ethers-adapters.ts @@ -0,0 +1,63 @@ +import type { Address, Hash } from 'viem' +import type { Provider, Signer, TypedDataField } from 'ethers' +import { custom } from 'viem' +import type { Transport, WalletClient, PublicClient } from 'viem' +import { createPublicClient, createWalletClient } from 'viem' +import { toAccount } from 'viem/accounts' + +/** Convert an ethers provider to a viem transport. */ +export function ethersProviderToTransport(provider: Provider): Transport { + return custom({ + async request({ method, params }) { + return (provider as any).send(method, params as any) + }, + }) +} + +/** Convert an ethers signer to a viem LocalAccount. */ +export async function ethersSignerToAccount(signer: Signer) { + return toAccount({ + address: (await signer.getAddress()) as unknown as Address, + async signMessage({ message }) { + const data = typeof message === 'string' ? message : new TextDecoder().decode(message as any) + return signer.signMessage(data) as unknown as Hash + }, + async signTransaction(txn) { + return 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 : undefined, + ...(txn.type && txn.accessList ? { accessList: txn.accessList } : {}), + ...(txn.maxPriorityFeePerGas ? { maxPriorityFeePerGas: txn.maxPriorityFeePerGas } : {}), + ...(txn.maxFeePerGas ? { maxFeePerGas: txn.maxFeePerGas } : {}), + } as any) as unknown as Hash + }, + async signTypedData({ domain, types, message }) { + const { EIP712Domain: _removed, ...rest } = types as any + const signTypedData = (signer as any)._signTypedData ?? (signer as any).signTypedData + return signTypedData(domain ?? {}, rest as Record, message) as unknown as Hash + }, + }) +} + +/** Create a viem PublicClient from an ethers provider. */ +export function ethersProviderToPublicClient(provider: Provider, chain: any): PublicClient { + return createPublicClient({ chain: chain as any, transport: ethersProviderToTransport(provider) }) as unknown as PublicClient +} + +/** Create a viem WalletClient from an ethers signer. */ +export async function ethersSignerToWalletClient( + signer: Signer & { provider: Provider }, + chain: any, +): Promise { + return createWalletClient({ + chain: chain as any, + transport: ethersProviderToTransport(signer.provider), + account: await ethersSignerToAccount(signer), + }) as unknown as WalletClient +}