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

Commit f458c2f

Browse files
committed
Add ethers example to nextjs app
1 parent 2b52598 commit f458c2f

File tree

4 files changed

+258
-57
lines changed

4 files changed

+258
-57
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
"use client";
2+
3+
import { useMemo, useState } from "react";
4+
import { Providers } from "../ccip-js/providers";
5+
import { ethers } from "ethers";
6+
import { createClient } from "@chainlink/ccip-js";
7+
import { useAccount, useSwitchChain } from "wagmi";
8+
// Import adapters via compiled dist path to avoid missing type exports in older versions.
9+
// Runtime will work since the package ships these files.
10+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
11+
// @ts-ignore
12+
import { ethersProviderToPublicClient, ethersSignerToWalletClient } from "@chainlink/ccip-js/dist/ethers-adapters.js";
13+
14+
export default function CCIPEthersDemoPage() {
15+
return (
16+
<Providers>
17+
<EthersDemo />
18+
</Providers>
19+
);
20+
}
21+
22+
function EthersDemo() {
23+
const ccipClient = useMemo(() => createClient(), []);
24+
const [publicReady, setPublicReady] = useState(false);
25+
const [walletReady, setWalletReady] = useState(false);
26+
const [publicError, setPublicError] = useState<string | null>(null);
27+
const [walletError, setWalletError] = useState<string | null>(null);
28+
const { chain, address } = useAccount();
29+
const { chains, switchChain, isError: isSwitchError, error: switchError } = useSwitchChain();
30+
const [selectedChainId, setSelectedChainId] = useState<string>("");
31+
32+
const [routerAddress, setRouterAddress] = useState<string>("");
33+
const [destinationChainSelector, setDestinationChainSelector] = useState<string>("");
34+
const [onRamp, setOnRamp] = useState<string>("");
35+
const [onRampError, setOnRampError] = useState<string | null>(null);
36+
37+
const [publicClient, setPublicClient] = useState<any>(null);
38+
const [walletClient, setWalletClient] = useState<any>(null);
39+
40+
async function connectEthers() {
41+
try {
42+
setPublicError(null);
43+
setWalletError(null);
44+
const browserProvider = new ethers.BrowserProvider((window as any).ethereum);
45+
const signer = await browserProvider.getSigner();
46+
47+
const net = await browserProvider.getNetwork();
48+
const viemChain = {
49+
id: Number(net.chainId),
50+
name: net.name || "unknown",
51+
nativeCurrency: { name: "", symbol: "", decimals: 18 },
52+
rpcUrls: { default: { http: [] }, public: { http: [] } },
53+
} as any;
54+
55+
const viemPublic = ethersProviderToPublicClient(browserProvider, viemChain);
56+
const viemWallet = await ethersSignerToWalletClient(signer, viemChain);
57+
58+
setPublicClient(viemPublic);
59+
setWalletClient(viemWallet);
60+
setPublicReady(true);
61+
setWalletReady(true);
62+
} catch (e: any) {
63+
const msg = e?.message ?? String(e);
64+
if (!publicReady) setPublicError(msg);
65+
if (!walletReady) setWalletError(msg);
66+
}
67+
}
68+
69+
return (
70+
<div className="m-2 p-2 w-full grid gap-2">
71+
<div className="space-y-2 border rounded-md p-4 bg-white">
72+
<h2 className="font-bold">Connect (ethers → viem via adapters)</h2>
73+
<button
74+
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
75+
onClick={connectEthers}
76+
>
77+
Connect
78+
</button>
79+
{!publicReady && publicError && <p className="text-red-500">{publicError}</p>}
80+
{!walletReady && walletError && <p className="text-red-500">{walletError}</p>}
81+
{publicReady && walletReady && <p>Adapters ready.</p>}
82+
</div>
83+
84+
<div className="space-y-2 border rounded-md p-4 bg-white">
85+
<h2 className="font-bold">Network</h2>
86+
{address && <p>{`Address: ${address}`}</p>}
87+
{chain && <p>{`Connected to ${chain.name} (chainId: ${chain.id})`}</p>}
88+
<div className="flex flex-col">
89+
<label htmlFor="chainId">Switch to chain</label>
90+
<select
91+
className="border border-slate-300 rounded-md p-1"
92+
name="chainId"
93+
value={selectedChainId}
94+
onChange={e => setSelectedChainId(e.target.value)}
95+
>
96+
<option value="" disabled>
97+
Select chain
98+
</option>
99+
{chains.map(c => (
100+
<option key={c.id} value={c.id}>
101+
{c.name}
102+
</option>
103+
))}
104+
</select>
105+
</div>
106+
<button
107+
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
108+
onClick={async () => {
109+
if (selectedChainId) {
110+
try {
111+
await switchChain({ chainId: Number(selectedChainId) });
112+
await connectEthers();
113+
} catch (e) {
114+
// ignore, error shown below
115+
}
116+
}
117+
}}
118+
>
119+
Switch
120+
</button>
121+
{isSwitchError && <p className="text-red-500">{switchError?.message}</p>}
122+
</div>
123+
124+
<div className="space-y-2 border rounded-md p-4 bg-white">
125+
<h2 className="font-bold">Get On-ramp address (ethers-adapted public client)</h2>
126+
<div className="flex flex-col">
127+
<label htmlFor="routerAddress">Router Address*</label>
128+
<input
129+
className="border border-slate-300 rounded-md p-1"
130+
name="routerAddress"
131+
placeholder="0x..."
132+
onChange={({ target }) => setRouterAddress(target.value)}
133+
/>
134+
</div>
135+
<div className="flex flex-col w-full">
136+
<label htmlFor="destinationChainSelector">Destination Chain Selector*</label>
137+
<input
138+
className="border border-slate-300 rounded-md p-1"
139+
name="destinationChainSelector"
140+
placeholder="1234..."
141+
onChange={({ target }) => setDestinationChainSelector(target.value)}
142+
/>
143+
</div>
144+
<button
145+
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
146+
onClick={async () => {
147+
setOnRamp("");
148+
setOnRampError(null);
149+
if (publicClient && routerAddress && destinationChainSelector) {
150+
try {
151+
const result = await ccipClient.getOnRampAddress({
152+
client: publicClient,
153+
routerAddress: routerAddress as any,
154+
destinationChainSelector,
155+
});
156+
setOnRamp(result as string);
157+
} catch (e: any) {
158+
setOnRampError(e?.message ?? String(e));
159+
}
160+
}
161+
}}
162+
>
163+
Get On-ramp
164+
</button>
165+
{onRampError && <p className="text-red-500">{onRampError}</p>}
166+
{onRamp && (
167+
<div className="flex flex-col w-full">
168+
<label>On-ramp contract address:</label>
169+
<code className="w-full whitespace-pre-wrap break-all">{onRamp}</code>
170+
</div>
171+
)}
172+
</div>
173+
</div>
174+
);
175+
}
176+
177+

examples/nextjs/app/layout.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ export default function RootLayout({
3838
>
3939
CCIP-JS
4040
</Link>
41+
<Link
42+
className="border border-slate-300 rounded-md p-2 hover:bg-slate-300 transition-colors"
43+
href="/ccip-ethers"
44+
>
45+
CCIP Ethers
46+
</Link>
4147
</nav>
4248
<main className="flex flex-col items-center justify-center bg-slate-100 grow">
4349
<ClientOnly>{children}</ClientOnly>

examples/nextjs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@chainlink/ccip-js": "^0.2.1",
1717
"@chainlink/ccip-react-components": "^0.3.0",
1818
"@tanstack/react-query": "^5.37.1",
19+
"ethers": "^6",
1920
"next": "14.2.3",
2021
"react": "18",
2122
"react-dom": "18",

0 commit comments

Comments
 (0)