Skip to content

Add ethers.js adapter support #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/ccip-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions packages/ccip-js/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
63 changes: 63 additions & 0 deletions packages/ccip-js/src/ethers-adapters.ts
Original file line number Diff line number Diff line change
@@ -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<string, TypedDataField[]>, 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<WalletClient> {
return createWalletClient({
chain: chain as any,
transport: ethersProviderToTransport(signer.provider),
account: await ethersSignerToAccount(signer),
}) as unknown as WalletClient
}