|
| 1 | +--- |
| 2 | +section: ccip |
| 3 | +date: Last Modified |
| 4 | +title: "Using the CCIP JavaScript SDK" |
| 5 | +--- |
| 6 | + |
| 7 | +import { Aside, ClickToZoom } from "@components" |
| 8 | +import { Tabs } from "@components/Tabs" |
| 9 | + |
| 10 | +The [CCIP JavaScript SDK](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main) is a tool that helps you to simplify management of cross-chain token transfers, and to integrate CCIP with the frontend of your own app. |
| 11 | + |
| 12 | +The CCIP JavaScript SDK includes two packages: |
| 13 | + |
| 14 | +- [`ccip-js`](https://github.com/smartcontractkit/ccip-javascript-sdk/blob/main/packages/ccip-js/README.md): A TypeScript library that provides a client for managing cross-chain token transfers that use CCIP routers. This package allows you to manage the steps you need to prepare before sending a CCIP message, as well as checking the transfer status afterward. |
| 15 | +- [`ccip-react-components`](https://github.com/smartcontractkit/ccip-javascript-sdk/blob/main/packages/ccip-react-components/README.md): A set of prebuilt ready-to-use UI components built on top of `ccip-js`. This package includes the following features: |
| 16 | + - Customize injected wallet providers (MetaMask and Coinbase Wallet) |
| 17 | + - Specify preselected chains and tokens that will display as defaults when the component loads |
| 18 | + - Customize the UI theme |
| 19 | + - Configure allowlists and deny lists for which chains can be used, and which chains can be used as a sources or destinations for cross-chain transfers |
| 20 | + |
| 21 | +Using both packages together, you can add a fully featured CCIP bridge to your app that can be styled to match your app design. |
| 22 | + |
| 23 | +You can also use the `ccip-js` package on its own — for example, to build a backend application. The features of the CCIP-JS package include: |
| 24 | + |
| 25 | +- _Token approvals_: Approve tokens for cross-chain transfers. |
| 26 | +- _Allowance checks_: Retrieve the allowance for token transfers. |
| 27 | +- _Rate limits_: Get rate refill limits for lanes. |
| 28 | +- _Fee calculation_: Calculate the fee required for transfers. |
| 29 | +- _Token transfers_: Transfer tokens across chains. |
| 30 | +- _Transfer status_: Retrieve the status of a transfer by transaction hash. |
| 31 | + |
| 32 | +## Install and run the SDK |
| 33 | + |
| 34 | +<Aside type="caution" title="Security Warning: Handle Error Messages with Care"> |
| 35 | + **Important**: Error messages generated by this SDK are **not escaped**. If your application uses user inputs that may end up in error messages, it is your responsibility to **escape or sanitize these messages** before displaying them to users. |
| 36 | + |
| 37 | +Failure to escape or sanitize error messages could expose your application to **Cross-Site Scripting (XSS)** vulnerabilities, especially when user-generated content is included in error handling logic. |
| 38 | + |
| 39 | +</Aside> |
| 40 | + |
| 41 | +1. [Install `pnpm`](https://pnpm.io/installation). |
| 42 | + |
| 43 | +1. Clone the `ccip-javascript-sdk` repo and navigate to the root directory of the `ccip-javascript-sdk` project: |
| 44 | + |
| 45 | + ```sh |
| 46 | + git clone https://github.com/smartcontractkit/ccip-javascript-sdk.git && cd ccip-javascript-sdk |
| 47 | + ``` |
| 48 | + |
| 49 | +1. From the project root, run one of the following commands to install the SDK: |
| 50 | + |
| 51 | + {/* prettier-ignore */} |
| 52 | + <Tabs sharedStore="installOption" client:visible> |
| 53 | + <Fragment slot="tab.1">Install SDK and run example app</Fragment> |
| 54 | + <Fragment slot="tab.2">Install SDK only</Fragment> |
| 55 | + <Fragment slot="panel.1"> |
| 56 | + ```sh |
| 57 | + pnpm install |
| 58 | + pnpm build |
| 59 | + pnpm dev-example |
| 60 | + ``` |
| 61 | + </Fragment> |
| 62 | + <Fragment slot="panel.2"> |
| 63 | + ```sh |
| 64 | + pnpm install |
| 65 | + ``` |
| 66 | + </Fragment> |
| 67 | + </Tabs> |
| 68 | + - `pnpm dev-example` runs an example NextJS app locally. Navigate to http://localhost:3000 in your browser. |
| 69 | + |
| 70 | +## Run an example app |
| 71 | + |
| 72 | +The example Next.js app included with the CCIP JavaScript SDK demonstrates the SDK's functionalities within an interactive web application, allowing you to see its features in action. |
| 73 | + |
| 74 | +To get started: |
| 75 | + |
| 76 | +1. Launch the app by using the following commands: |
| 77 | + |
| 78 | + ```sh |
| 79 | + pnpm build |
| 80 | + pnpm dev-example |
| 81 | + ``` |
| 82 | + |
| 83 | +1. In your browser, navigate to http://localhost:3000/ to see the interactive app: |
| 84 | + |
| 85 | + <ClickToZoom src="/images/ccip/ccip-sdk-example-app-ui.png" alt="CCIP JavaScript SDK example app" /> |
| 86 | + |
| 87 | +## Review a basic UI example |
| 88 | + |
| 89 | +This basic UI example shows a basic token list configuration with CCIP-BnM and CCIP-LnM test tokens, and it lists the testnets you want to use with each one. This example also includes a basic frontend configuration. |
| 90 | + |
| 91 | +Review the reference documentation: |
| 92 | + |
| 93 | +- Listing tokens in [`tokensList`](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-react-components#tokens) |
| 94 | +- Configuring the frontend components in [`Config`](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-react-components#config) |
| 95 | + - Configuring a [theme for frontend styling](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-react-components#theme) |
| 96 | + |
| 97 | +Review the basic UI example below: |
| 98 | + |
| 99 | +```ts |
| 100 | +import 'ccip-react-components/dist/style.css'; |
| 101 | +import { CCIPWidget, Config, Token } from 'ccip-react-components'; |
| 102 | +import { sepolia, optimismSepolia } from 'viem/chains'; |
| 103 | + |
| 104 | +const tokensList: Token[] = [ |
| 105 | + { |
| 106 | + symbol: 'CCIP-BnM', |
| 107 | + address: { |
| 108 | + [arbitrumSepolia.id]:'0xA8C0c11bf64AF62CDCA6f93D3769B88BdD7cb93D', |
| 109 | + [avalancheFuji.id]: '0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4', |
| 110 | + [baseSepolia.id]: '0x88A2d74F47a237a62e7A51cdDa67270CE381555e', |
| 111 | + [bscTestnet.id]: '0xbFA2ACd33ED6EEc0ed3Cc06bF1ac38d22b36B9e9', |
| 112 | + [optimismSepolia.id]: '0x8aF4204e30565DF93352fE8E1De78925F6664dA7', |
| 113 | + [polygonAmoy.id]: '0xcab0EF91Bee323d1A617c0a027eE753aFd6997E4', |
| 114 | + [sepolia.id]: '0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05' |
| 115 | + }, |
| 116 | + logoURL: 'https://smartcontract.imgix.net/tokens/ccip-bnm.webp?auto=compress%2Cformat', |
| 117 | + tags: ['chainlink', 'default'] |
| 118 | + }, |
| 119 | + { |
| 120 | + symbol: 'CCIP-LnM', |
| 121 | + address: { |
| 122 | + [optimismSepolia.id]: '0x044a6B4b561af69D2319A2f4be5Ec327a6975D0a', |
| 123 | + [sepolia.id]: '0x466D489b6d36E7E3b824ef491C225F5830E81cC1' |
| 124 | + }, |
| 125 | + logoURL: 'https://smartcontract.imgix.net/tokens/ccip-lnm.webp?auto=compress%2Cformat', |
| 126 | + tags: ['chainlink', 'default'] |
| 127 | + } |
| 128 | +]; |
| 129 | + |
| 130 | +const config: Config = { |
| 131 | + theme: { |
| 132 | + pallette: { |
| 133 | + background: '#FFFFFF', |
| 134 | + border: '#B3B7C0', |
| 135 | + text: '#000000', |
| 136 | + } |
| 137 | + shape: { |
| 138 | + radius: 6 |
| 139 | + }, |
| 140 | + } |
| 141 | +}; |
| 142 | + |
| 143 | +<CCIPWidget config={config} tokensList={tokensList} />; |
| 144 | +``` |
| 145 | + |
| 146 | +### Theme configuration |
| 147 | + |
| 148 | +You can customize the component's theme to be in line with your app design. These are all the options available for theme configuration: |
| 149 | + |
| 150 | +```ts |
| 151 | +import { Config } from 'ccip-react-components'; |
| 152 | +const config: Config = { theme: |
| 153 | + { |
| 154 | + /** Define the app colors in HEX format */ |
| 155 | + palette?: { |
| 156 | + /** Titles color and primary button background, default #000000 */ |
| 157 | + primary?: string; |
| 158 | + /** Background color, default '#FFFFFF' */ |
| 159 | + background?: string; |
| 160 | + /** Border color, default '#B3B7C0' */ |
| 161 | + border?: string; |
| 162 | + /** Text color, default '#000000' */ |
| 163 | + text?: string; |
| 164 | + /** Secondary text, inactive and placeholders color, default '#6D7480' */ |
| 165 | + muted?: string; |
| 166 | + /** Input fields background color, default '#FFFFFF' */ |
| 167 | + input?: string; |
| 168 | + /** Popovers, dropdowns and select fields background color, default '#F5F7FA' */ |
| 169 | + popover?: string; |
| 170 | + /** Selected field from a dropdown background color, default '#D7DBE0' */ |
| 171 | + selected?: string; |
| 172 | + /** Warning text color, default '#F7B955' */ |
| 173 | + warning?: string; |
| 174 | + /** Warning text background color, default '#FFF5E0' */ |
| 175 | + warningBackground?: string; |
| 176 | + }; |
| 177 | + shape?: { |
| 178 | + /** Border radius size in px default 6 */ |
| 179 | + radius?: number; |
| 180 | + }; |
| 181 | + };} |
| 182 | +``` |
| 183 | + |
| 184 | +## Review a CCIP-JS example |
| 185 | + |
| 186 | +This example uses the `ccip-js` package and covers the following steps: |
| 187 | + |
| 188 | +- Initialize CCIP-JS Client for mainnet |
| 189 | +- Approve tokens for transfer |
| 190 | +- Get fee for the transfer |
| 191 | +- Send the transfer through CCIP using one of the following options for fee payment: |
| 192 | + - Using the native token fee |
| 193 | + - Using the provided supported token for fee payment |
| 194 | + |
| 195 | +Review the [reference documentation](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-js#api-reference) for `ccip-js`. |
| 196 | + |
| 197 | +### Review the code |
| 198 | + |
| 199 | +```ts |
| 200 | +import * as CCIP from "@chainlink/ccip-js" |
| 201 | +import { createWalletClient, custom } from "viem" |
| 202 | +import { mainnet } from "viem/chains" |
| 203 | + |
| 204 | +// Initialize CCIP-JS Client for mainnet |
| 205 | +const ccipClient = CCIP.createClient() |
| 206 | +const publicClient = createPublicClient({ |
| 207 | + chain: mainnet, |
| 208 | + transport: http(), |
| 209 | +}) |
| 210 | +const walletClient = createWalletClient({ |
| 211 | + chain: mainnet, |
| 212 | + transport: custom(window.ethereum!), |
| 213 | +}) |
| 214 | + |
| 215 | +// Approve Router to transfer tokens on user's behalf |
| 216 | +const { txHash, txReceipt } = await ccipClient.approveRouter({ |
| 217 | + client: walletClient, |
| 218 | + routerAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 219 | + tokenAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 220 | + amount: 1000000000000000000n, |
| 221 | + waitForReceipt: true, |
| 222 | +}) |
| 223 | + |
| 224 | +console.log(`Transfer approved. Transaction hash: ${txHash}. Transaction receipt: ${txReceipt}`) |
| 225 | + |
| 226 | +// Get fee for the transfer |
| 227 | +const fee = await ccipClient.getFee({ |
| 228 | + client: publicClient, |
| 229 | + routerAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 230 | + tokenAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 231 | + amount: 1000000000000000000n, |
| 232 | + destinationAccount: "0x1234567890abcdef1234567890abcdef12345678", |
| 233 | + destinationChainSelector: "1234", |
| 234 | +}) |
| 235 | + |
| 236 | +console.log(`Fee: ${fee.toLocaleString()}`) |
| 237 | + |
| 238 | +// Variant 1: Transfer via CCIP using native token fee |
| 239 | +const { txHash, messageId } = await client.transferTokens({ |
| 240 | + client: walletClient, |
| 241 | + routerAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 242 | + tokenAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 243 | + amount: 1000000000000000000n, |
| 244 | + destinationAccount: "0x1234567890abcdef1234567890abcdef12345678", |
| 245 | + destinationChainSelector: "1234", |
| 246 | +}) |
| 247 | + |
| 248 | +console.log(`Transfer success. Transaction hash: ${txHash}. Message ID: ${messageId}`) |
| 249 | + |
| 250 | +// Variant 2: Transfer via CCIP using the provided supported token for fee payment |
| 251 | +const { txHash, messageId } = await client.transferTokens({ |
| 252 | + client: walletClient, |
| 253 | + routerAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 254 | + tokenAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 255 | + amount: 1000000000000000000n, |
| 256 | + destinationAccount: "0x1234567890abcdef1234567890abcdef12345678", |
| 257 | + destinationChainSelector: "1234", |
| 258 | + feeTokenAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", |
| 259 | +}) |
| 260 | +``` |
| 261 | + |
| 262 | +## Build packages |
| 263 | + |
| 264 | +Optionally, if you need to modify the packages and use your modified version, follow these instructions to build the packages: |
| 265 | + |
| 266 | +You can use `pnpm build` to build both packages together. If you're building each package individually, make sure to build the `build-ccip-js` package before you build the `ccip-react-components` package. The React components depend on the JS package. |
| 267 | + |
| 268 | +1. Build the `build-ccip-js` package: |
| 269 | + ```sh |
| 270 | + pnpm i -w |
| 271 | + pnpm build-ccip-js |
| 272 | + ``` |
| 273 | +1. Build the `ccip-react-components` package: |
| 274 | + ```sh |
| 275 | + pnpm build-components |
| 276 | + ``` |
| 277 | +1. Update the `ccip-react-components` package to use the local `ccip-js` version by modifying the `packages/ccip-react-components/package.json` file. Replace the `@chainlink/ccip-js` dependency with the workspace reference: |
| 278 | + ``` |
| 279 | + "@chainlink/ccip-js": "workspace:*" |
| 280 | + ``` |
| 281 | +1. Update the `examples/nextjs` app to use both local `ccip-js` and `ccip-react-components` versions by modifying the `examples/nextjs/package.json` file. Replace the `@chainlink/ccip-js` and `@chainlink/ccip-react-components` dependencies with these relative paths: |
| 282 | + ``` |
| 283 | + "@chainlink/ccip-js": "link:../../packages/ccip-js", |
| 284 | + "@chainlink/ccip-react-components": "link:../../packages/ccip-react-components", |
| 285 | + ``` |
0 commit comments