Skip to content
This repository was archived by the owner on Mar 17, 2026. It is now read-only.

Commit 3d41d89

Browse files
authored
Merge pull request #41 from smartcontractkit/feature/ethers-adapter-integration-tests
Add ethers.js adapter support
2 parents c33ad78 + bbf67b0 commit 3d41d89

File tree

6 files changed

+654
-3
lines changed

6 files changed

+654
-3
lines changed

packages/ccip-js/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ const walletClient = createWalletClient({
117117
transport: custom(window.ethereum!),
118118
})
119119

120+
// Using ethers.js signer & provider
121+
import { ethers } from 'ethers'
122+
import { ethersSignerToWalletClient, ethersProviderToPublicClient } from '@chainlink/ccip-js'
123+
124+
const ethersProvider = new ethers.JsonRpcProvider('https://rpc.example.com')
125+
const ethersSigner = new ethers.Wallet(PRIVATE_KEY, ethersProvider)
126+
127+
const ethersWalletClient = await ethersSignerToWalletClient(ethersSigner, mainnet)
128+
const ethersPublicClient = ethersProviderToPublicClient(ethersProvider, mainnet)
129+
130+
// The SDK methods accept Viem clients. The adapters above allow
131+
// passing ethers providers and signers by converting them to Viem clients.
132+
120133
// Approve Router to transfer tokens on user's behalf
121134
const { txHash, txReceipt } = await ccipClient.approveRouter({
122135
client: walletClient,
@@ -605,7 +618,15 @@ folder. From there, the relevant ABI arrays must be manually moved to `./src/abi
605618

606619
#### Running tests
607620

608-
1. Integration tests against testnets are favored in the ccip-js package.
621+
1. Integration tests against testnets are favored in the ccip-js package. They require the you set the following environment variables:
622+
623+
```
624+
PRIVATE_KEY=
625+
AVALANCHE_FUJI_RPC_URL
626+
SEPOLIA_RPC_URL
627+
HEDERA_TESTNET_RPC_URL
628+
629+
```
609630

610631
2. Start by cd into `packages/ccip-js` and then run `pnpm install` OR from the project root you can run `pnpm i -w`
611632

packages/ccip-js/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"build": "tsc && hardhat compile",
1515
"lint": "eslint 'src/**/*.{ts,js}'",
1616
"format": "prettier --write 'src/**/*.{ts,js,json,md}'",
17-
"t:int": "jest --coverage -u --testMatch=\"**/integration-testnet.test.ts\" --detectOpenHandles",
17+
"t:int": "jest --coverage -u --testMatch=\"**/integration-testnet**.test.ts\" --detectOpenHandles",
18+
"t:int:viem": "jest --coverage -u --testMatch=\"**/integration-testnet.test.ts\" --detectOpenHandles",
19+
"t:int:ethers": "jest --coverage -u --testMatch=\"**/integration-testnet-ethers.test.ts\" --detectOpenHandles",
1820
"t:unit": "jest --coverage -u --testMatch=\"**/unit.test.ts\" ",
1921
"test:hh": "hardhat test"
2022
},

packages/ccip-js/src/api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import { TRANSFER_STATUS_FROM_BLOCK_SHIFT, ExecutionStateChangedABI } from './co
1919
import { parseAbi } from 'viem'
2020

2121
export { IERC20ABI }
22+
export {
23+
ethersProviderToTransport,
24+
ethersSignerToAccount,
25+
ethersProviderToPublicClient,
26+
ethersSignerToWalletClient,
27+
} from './ethers-adapters'
2228

2329
/** An object containing methods for cross-chain transfer management.
2430
* @typedef {Object} Client */
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { Provider, Signer, TypedDataField } from 'ethers'
2+
import type {Address, Hash, Transport, WalletClient, PublicClient } from 'viem'
3+
4+
import { custom, createPublicClient, createWalletClient } from 'viem'
5+
import { toAccount } from 'viem/accounts'
6+
7+
/** Convert an ethers provider to a viem transport. */
8+
export function ethersProviderToTransport(provider: Provider): Transport {
9+
return custom({
10+
async request({ method, params }) {
11+
return (provider as any).send(method, params as any)
12+
},
13+
})
14+
}
15+
16+
/** Convert an ethers signer to a viem LocalAccount. */
17+
export async function ethersSignerToAccount(signer: Signer) {
18+
return toAccount({
19+
address: (await signer.getAddress()) as unknown as Address,
20+
async signMessage({ message }) {
21+
const data = typeof message === 'string' ? message : new TextDecoder().decode(message as any)
22+
return signer.signMessage(data) as unknown as Hash
23+
},
24+
async signTransaction(txn) {
25+
return signer.signTransaction({
26+
chainId: txn.chainId,
27+
data: txn.data,
28+
gasLimit: txn.gas,
29+
gasPrice: txn.gasPrice,
30+
nonce: txn.nonce,
31+
to: txn.to,
32+
value: txn.value,
33+
type: txn.type === 'legacy' ? 0 : txn.type === 'eip2930' ? 1 : txn.type === 'eip1559' ? 2 : undefined,
34+
...(txn.type && txn.accessList ? { accessList: txn.accessList } : {}),
35+
...(txn.maxPriorityFeePerGas ? { maxPriorityFeePerGas: txn.maxPriorityFeePerGas } : {}),
36+
...(txn.maxFeePerGas ? { maxFeePerGas: txn.maxFeePerGas } : {}),
37+
} as any) as unknown as Hash
38+
},
39+
async signTypedData({ domain, types, message }) {
40+
const { EIP712Domain: _removed, ...rest } = types as any
41+
const signTypedData = (signer as any)._signTypedData ?? (signer as any).signTypedData
42+
return signTypedData(domain ?? {}, rest as Record<string, TypedDataField[]>, message) as unknown as Hash
43+
},
44+
})
45+
}
46+
47+
/** Create a viem PublicClient from an ethers provider. */
48+
export function ethersProviderToPublicClient(provider: Provider, chain: any): PublicClient {
49+
return createPublicClient({ chain: chain as any, transport: ethersProviderToTransport(provider) }) as unknown as PublicClient
50+
}
51+
52+
/** Create a viem WalletClient from an ethers signer. */
53+
export async function ethersSignerToWalletClient(
54+
signer: Signer & { provider: Provider | null },
55+
chain: any,
56+
): Promise<WalletClient> {
57+
if (!signer.provider) {
58+
throw new Error('ethers signer must be connected to a provider')
59+
}
60+
return createWalletClient({
61+
chain: chain as any,
62+
transport: ethersProviderToTransport(signer.provider),
63+
account: await ethersSignerToAccount(signer),
64+
}) as unknown as WalletClient
65+
}

0 commit comments

Comments
 (0)