Skip to content
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
17 changes: 4 additions & 13 deletions examples/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,8 @@ You can start editing the page by modifying `app/page.tsx`. The page auto-update

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More
## Example Implementation
This NextJS example app
- shows the use of both the `ccip-react-components` package as well as the `ccip-js` package.
- shows how to use the `ccipClient` exposed by `ccip-js` using viem (default, preferred) and also with an ethers provider (via an adapter).

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
178 changes: 178 additions & 0 deletions examples/nextjs/app/ccip-ethers/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"use client";

import { useMemo, useState } from "react";
import { Providers } from "../ccip-js/providers";
import { ethers } from "ethers";
import { createClient } from "@chainlink/ccip-js";
import { createPublicClient, custom } from "viem";
import { useAccount, useSwitchChain } from "wagmi";
import { ethersProviderToPublicClient, ethersSignerToWalletClient } from "@chainlink/ccip-js";

export default function CCIPEthersDemoPage() {
return (
<Providers>
<EthersDemo />
</Providers>
);
}

function EthersDemo() {
const ccipClient = useMemo(() => createClient(), []);
const [publicReady, setPublicReady] = useState(false);
const [walletReady, setWalletReady] = useState(false);
const [publicError, setPublicError] = useState<string | null>(null);
const [walletError, setWalletError] = useState<string | null>(null);
const { chain, address } = useAccount();
const { chains, switchChain, isError: isSwitchError, error: switchError } = useSwitchChain();
const [selectedChainId, setSelectedChainId] = useState<string>("");

const [routerAddress, setRouterAddress] = useState<string>("");
const [destinationChainSelector, setDestinationChainSelector] = useState<string>("");
const [onRamp, setOnRamp] = useState<string>("");
const [onRampError, setOnRampError] = useState<string | null>(null);

const [publicClient, setPublicClient] = useState<any>(null);

async function connectEthers() {
try {
setPublicError(null);
setWalletError(null);
const browserProvider = new ethers.BrowserProvider((window as any).ethereum);
const signer = await browserProvider.getSigner();

const net = await browserProvider.getNetwork();
const viemChain = {
id: Number(net.chainId),
name: net.name || "unknown",
nativeCurrency: { name: "", symbol: "", decimals: 18 },
rpcUrls: { default: { http: [] }, public: { http: [] } },
} as any;

const viemPublic =
typeof ethersProviderToPublicClient === "function"
? ethersProviderToPublicClient(browserProvider, viemChain)
: createPublicClient({ chain: viemChain, transport: custom(browserProvider as any) });

if (typeof ethersSignerToWalletClient === "function") {
await ethersSignerToWalletClient(signer, viemChain);
}

setPublicClient(viemPublic);
setPublicReady(true);
setWalletReady(true);
} catch (e: any) {
const msg = e?.message ?? String(e);
if (!publicReady) setPublicError(msg);
if (!walletReady) setWalletError(msg);
}
}

return (
<div className="m-2 p-2 w-full grid gap-2">
<div className="space-y-2 border rounded-md p-4 bg-white">
<h2 className="font-bold">Connect (ethers → viem via adapters)</h2>
<button
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
onClick={connectEthers}
>
Connect
</button>
{!publicReady && publicError && <p className="text-red-500">{publicError}</p>}
{!walletReady && walletError && <p className="text-red-500">{walletError}</p>}
{publicReady && walletReady && <p>Adapters ready.</p>}
</div>

<div className="space-y-2 border rounded-md p-4 bg-white">
<h2 className="font-bold">Network</h2>
{address && <p>{`Address: ${address}`}</p>}
{chain && <p>{`Connected to ${chain.name} (chainId: ${chain.id})`}</p>}
<div className="flex flex-col">
<label htmlFor="chainId">Switch to chain</label>
<select
className="border border-slate-300 rounded-md p-1"
name="chainId"
value={selectedChainId}
onChange={e => setSelectedChainId(e.target.value)}
>
<option value="" disabled>
Select chain
</option>
{chains.map(c => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))}
</select>
</div>
<button
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
onClick={async () => {
if (selectedChainId) {
try {
await switchChain({ chainId: Number(selectedChainId) });
await connectEthers();
} catch (e) {
// ignore, error shown below
}
}
}}
>
Switch
</button>
{isSwitchError && <p className="text-red-500">{switchError?.message}</p>}
</div>

<div className="space-y-2 border rounded-md p-4 bg-white">
<h2 className="font-bold">Get On-ramp address (ethers-adapted public client)</h2>
<div className="flex flex-col">
<label htmlFor="routerAddress">Router Address*</label>
<input
className="border border-slate-300 rounded-md p-1"
name="routerAddress"
placeholder="0x..."
onChange={({ target }) => setRouterAddress(target.value)}
/>
</div>
<div className="flex flex-col w-full">
<label htmlFor="destinationChainSelector">Destination Chain Selector*</label>
<input
className="border border-slate-300 rounded-md p-1"
name="destinationChainSelector"
placeholder="1234..."
onChange={({ target }) => setDestinationChainSelector(target.value)}
/>
</div>
<button
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
onClick={async () => {
setOnRamp("");
setOnRampError(null);
if (publicClient && routerAddress && destinationChainSelector) {
try {
const result = await ccipClient.getOnRampAddress({
client: publicClient,
routerAddress: routerAddress as any,
destinationChainSelector,
});
setOnRamp(result as string);
} catch (e: any) {
setOnRampError(e?.message ?? String(e));
}
}
}}
>
Get On-ramp
</button>
{onRampError && <p className="text-red-500">{onRampError}</p>}
{onRamp && (
<div className="flex flex-col w-full">
<label>On-ramp contract address:</label>
<code className="w-full whitespace-pre-wrap break-all">{onRamp}</code>
</div>
)}
</div>
</div>
);
}


8 changes: 7 additions & 1 deletion examples/nextjs/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ export default function RootLayout({
className="border border-slate-300 rounded-md p-2 hover:bg-slate-300 transition-colors"
href="/ccip-js"
>
CCIP-JS
CCIP-JS (viem)
</Link>
<Link
className="border border-slate-300 rounded-md p-2 hover:bg-slate-300 transition-colors"
href="/ccip-ethers"
>
CCIP-JS (ethers)
</Link>
</nav>
<main className="flex flex-col items-center justify-center bg-slate-100 grow">
Expand Down
32 changes: 20 additions & 12 deletions examples/nextjs/components/ccip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,30 @@ function ConnectWallet() {
const { chains, switchChain, error: switchError, isError: isSwitchError } = useSwitchChain();

const [chainId, setChainId] = useState<string>(`${chain?.id}`);
const [localConnectError, setLocalConnectError] = useState<string | null>(null);

return (
<div className="space-y-2 border rounded-md p-4 bg-white">
<h2 className="font-bold">Connect Wallet:</h2>
<div className="space-x-2">
{connectors.map(connector => (
<button
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
key={connector.uid}
onClick={() => connect({ connector })}
>
{connector.name}
</button>
))}
</div>
<h2 className="font-bold">Connect Wallet</h2>
<button
className="rounded-md p-2 bg-black text-white hover:bg-slate-600 transition-colors"
onClick={() => {
setLocalConnectError(null);
const preferredConnector =
connectors.find(c => c.id === "metaMask" || c.name.toLowerCase() === "metamask") ||
connectors.find(c => c.id === "injected") ||
connectors[0];
if (!preferredConnector) {
setLocalConnectError("No wallet connector available");
return;
}
connect({ connector: preferredConnector });
}}
>
Connect Wallet
</button>
{isConnectError && <p className="text-red-500">{connectError.message}</p>}
{localConnectError && <p className="text-red-500">{localConnectError}</p>}
{address && <p>{`Address: ${address}`}</p>}
{chain && (
<>
Expand Down
7 changes: 4 additions & 3 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"private": true,
"scripts": {
"bdev": "pnpm run build-components && next dev",
"bdev": "pnpm run build-ccip-js && pnpm run build-components && next dev",
"dev": "next dev",
"build-ccip-js": "pnpm --filter ccip-js run build",
"build-components": "pnpm --filter ccip-react-components run build",
Expand All @@ -13,9 +13,10 @@
"lint": "next lint"
},
"dependencies": {
"@chainlink/ccip-js": "^0.2.1",
"@chainlink/ccip-react-components": "^0.3.0",
"@chainlink/ccip-js": "workspace:^",
"@chainlink/ccip-react-components": "workspace:^",
"@tanstack/react-query": "^5.37.1",
"ethers": "^6",
"next": "14.2.3",
"react": "18",
"react-dom": "18",
Expand Down
4 changes: 3 additions & 1 deletion examples/nextjs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
}
],
"paths": {
"@/*": ["./*"]
"@/*": ["./*"],
"@chainlink/ccip-js": ["../../packages/ccip-js/dist/api.d.ts"],
"@chainlink/ccip-js/*": ["../../packages/ccip-js/dist/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
Expand Down
Loading
Loading