Skip to content
This repository was archived by the owner on Mar 17, 2026. It is now read-only.
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
10 changes: 10 additions & 0 deletions examples/nextjs/app/ccip-js-ethers/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CCIPEthers } from "@/components/ccip-with-ethers";
import { Providers } from "./providers";

export default function CCIPJsPage() {
return (
<Providers>
<CCIPEthers />
</Providers>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CCIP } from "@/components/ccip";
import { CCIP } from "@/components/ccip-with-wagmi";
import { Providers } from "./providers";

export default function CCIPJsPage() {
Expand Down
16 changes: 16 additions & 0 deletions examples/nextjs/app/ccip-js-wagmi/providers.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</WagmiProvider>
);
}
10 changes: 8 additions & 2 deletions examples/nextjs/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ export default function RootLayout({
</Link>
<Link
className="border border-slate-300 rounded-md p-2 hover:bg-slate-300 transition-colors"
href="/ccip-js"
href="/ccip-js-wagmi"
>
CCIP-JS
CCIP-JS (Wagmi)
</Link>
<Link
className="border border-slate-300 rounded-md p-2 hover:bg-slate-300 transition-colors"
href="/ccip-js-ethers"
>
CCIP-JS (Ethers)
</Link>
</nav>
<main className="flex flex-col items-center justify-center bg-slate-100 grow">
Expand Down
1,166 changes: 1,166 additions & 0 deletions examples/nextjs/components/ccip-with-ethers.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function CCIP() {

return (
<div className="m-2 p-2 w-full grid md:grid-cols-2 gap-2">
<ConnectWallet />
{!publicClient && !walletClient && <ConnectWallet />}
{publicClient && (
<>
<GetAllowance publicClient={publicClient} />
Expand Down
5 changes: 3 additions & 2 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -32,4 +33,4 @@
"postcss": "^8",
"tailwindcss": "^3.4.1"
}
}
}
58 changes: 33 additions & 25 deletions packages/ccip-js/src/adapters/ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import type {
TransactionReceipt as ViemTransactionReceipt,
AbiEvent,
} from 'viem'

import type {
ContractCallArgs,
TransactionArgs,
ReceiptArgs,
LogsArgs,
} from './types'
import {
createPublicClient,
createWalletClient,
Expand All @@ -21,7 +28,7 @@ import {
isAddressEqual,
} from 'viem'
import { toAccount } from 'viem/accounts'
import type { Provider, Signer, TypedDataField } from 'ethers'
import type { Provider, Signer, TypedDataField, BrowserProvider } from 'ethers'
import { Contract, type TransactionReceipt as EthersTxReceipt, type TransactionResponse } from 'ethers'
import {
readContract as viemReadContract,
Expand Down Expand Up @@ -262,7 +269,8 @@ export function isEthersSigner(signer: any): signer is Signer {
/**
* Union of supported client types: viem Client/WalletClient, ethers Provider, or ethers Signer.
*/
export type SupportedClient = ViemClient | WalletClient | Provider | (Signer & { provider?: Provider })

export type SupportedClient = ViemClient | WalletClient | Provider | Signer | BrowserProvider

/**
* Attempts to adapt the provided client to a viem PublicClient if possible.
Expand Down Expand Up @@ -299,13 +307,13 @@ async function toViemWalletClient(client: SupportedClient, chain?: Chain): Promi
*/
export async function readContractCompat(
client: SupportedClient,
args: Parameters<typeof viemReadContract>[1] & { chain?: Chain },
args: ContractCallArgs,
) {
if (isEthersProvider(client) || isEthersSigner(client)) {
const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider
if (!provider) throw new Error('Unsupported client for readContract: signer has no provider')
const contract = new Contract(args.address as Address, args.abi as any[], provider)
return (contract as any)[(args as any).functionName](...(((args as any).args as any[]) || []))
const contract = new Contract(args.address, args.abi, provider)
return (contract as any)[args.functionName](...(args.args || []))
}
const viemClient = toViemPublicClient(client, args.chain)
if (!viemClient) throw new Error('Unsupported client for readContract')
Expand All @@ -318,18 +326,18 @@ export async function readContractCompat(
*/
export async function writeContractCompat(
client: SupportedClient,
args: Parameters<typeof viemWriteContract>[1] & { chain?: Chain },
args: TransactionArgs,
) {
if (isEthersSigner(client)) {
const signer = client as Signer
const contract = new Contract(args.address as Address, args.abi as any[], signer)
const txResponse: TransactionResponse = await (contract as any)[(args as any).functionName](
...(((args as any).args as any[]) || []),
const contract = new Contract(args.address, args.abi, signer)
const txResponse: TransactionResponse = await (contract as any)[args.functionName](
...(args.args || []),
{
value: (args as any).value !== undefined ? (args as any).value.toString() : undefined,
gasLimit: (args as any).gas,
gasPrice: (args as any).gasPrice,
nonce: (args as any).nonce,
value: args.value !== undefined ? args.value.toString() : undefined,
gasLimit: args.gas,
gasPrice: args.gasPrice,
nonce: args.nonce,
},
)
return txResponse.hash as Hex
Expand All @@ -345,15 +353,15 @@ export async function writeContractCompat(
*/
export async function waitForTransactionReceiptCompat(
client: SupportedClient,
args: Parameters<typeof viemWaitForTransactionReceipt>[1] & { chain?: Chain },
args: ReceiptArgs,
) {
if (isEthersProvider(client) || isEthersSigner(client)) {
const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider
if (!provider) throw new Error('Unsupported client for waitForTransactionReceipt: signer has no provider')
const maybe = await provider.waitForTransaction(
(args as any).hash,
(args as any).confirmations,
(args as any).timeout,
args.hash,
args.confirmations,
args.timeout,
)
if (!maybe) throw new Error('Transaction receipt not found')
return formatEthersReceipt(maybe)
Expand All @@ -369,12 +377,12 @@ export async function waitForTransactionReceiptCompat(
*/
export async function getTransactionReceiptCompat(
client: SupportedClient,
args: Parameters<typeof viemGetTransactionReceipt>[1] & { chain?: Chain },
args: ReceiptArgs,
) {
if (isEthersProvider(client) || isEthersSigner(client)) {
const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider
if (!provider) throw new Error('Unsupported client for getTransactionReceipt: signer has no provider')
const maybe = await provider.getTransactionReceipt((args as any).hash)
const maybe = await provider.getTransactionReceipt(args.hash)
if (!maybe) throw new Error('Transaction receipt not found')
return formatEthersReceipt(maybe)
}
Expand Down Expand Up @@ -405,17 +413,17 @@ export async function getBlockNumberCompat(client: SupportedClient, chain?: Chai
*/
export async function getLogsCompat(
client: SupportedClient,
args: Parameters<typeof viemGetLogs>[1] & { chain?: Chain },
args: LogsArgs,
) {
if (isEthersProvider(client) || isEthersSigner(client)) {
const provider: Provider | undefined = (isEthersSigner(client) ? (client as any).provider : client) as Provider
if (!provider) throw new Error('Unsupported client for getLogs: signer has no provider')
const abiEvent = (args as any).event as AbiEvent | undefined
const address = (args as any).address as Address | undefined
const fromBlock = (args as any).fromBlock ? ((args as any).fromBlock as bigint).toString() : undefined
const toBlock = (args as any).toBlock ? ((args as any).toBlock as bigint).toString() : undefined
const abiEvent = args.event as AbiEvent | undefined
const address = args.address as Address | undefined
const fromBlock = args.fromBlock ? args.fromBlock.toString() : undefined
const toBlock = args.toBlock ? args.toBlock.toString() : undefined
const topics: (string | null)[] | undefined = abiEvent
? buildTopicsFromEventAndArgs(abiEvent, (args as any).args)
? buildTopicsFromEventAndArgs(abiEvent, args.args)
: undefined
const filter: any = { address, topics, fromBlock, toBlock }
const logs = await provider.getLogs(filter)
Expand Down
43 changes: 43 additions & 0 deletions packages/ccip-js/src/adapters/types.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>
fromBlock?: bigint
toBlock?: bigint

chain?: Chain
}
5 changes: 3 additions & 2 deletions packages/ccip-js/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export {
getTransactionReceiptCompat,
getBlockNumberCompat,
getLogsCompat,
SupportedClient,
} from './adapters/ethers'

import {
Expand Down Expand Up @@ -734,7 +735,7 @@ export const createClient = (): Client => {
)

const feeTokens = await readCompat(options.client as any, {
abi: parseAbi(['function getFeeTokens() returns (address[] feeTokens)']), // same signature for both PriceRegistry and FeeQuoter
abi: parseAbi(['function getFeeTokens() view returns (address[] feeTokens)']), // same signature for both PriceRegistry and FeeQuoter
address: priceRegistryOrFeeQuoter as Viem.Address,
functionName: 'getFeeTokens',
})
Expand Down Expand Up @@ -848,7 +849,7 @@ export const createClient = (): Client => {
try {
const viemChain = (options.client as any)?.chain as Viem.Chain | undefined
if (viemChain) return scaleFeeDecimals(fee, viemChain)
} catch {}
} catch { }
return scaleFeeDecimals(fee)
}

Expand Down
Loading