Skip to content
Open
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
24 changes: 24 additions & 0 deletions cctp-non-usdc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
40 changes: 40 additions & 0 deletions cctp-non-usdc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Non-USDC Crosschain Transfer Sample

This sample application shows how to transfer non-USDC assets with CCTP, using EURC, cirBTC, or a registered custom token. The recommended path is `@circle-fin/bridge-kit` in `src/main.ts`. If Bridge Kit is not an option, swap to `src/direct.ts` for the same UI against a direct CCTP flow: approve, Iris quote, `crossChainTransfer`, and destination forwarding.

Developer documentation at: https://developers.circle.com/cctp/expanded-assets

## Run

```bash
bun install
bun run dev
```

Connect an injected EVM testnet wallet and choose a token. **EURC** and **cirBTC** load source chains from the Iris registry for that token; destinations are the other chains from that same result (one registry read per token). **Custom token ID** waits for a valid bytes32 ID, then loads sources the same way. Source and destination are always different chains. Leave **Recipient** blank to mint to the connected wallet, or enter another EVM address.

The app submits transactions only after the user presses **Transfer** and approves the wallet prompts. Use a testnet wallet and test tokens while exploring the example.

## Structure

```text
src/
main.ts # Bridge Kit entrypoint
direct.ts # Direct CCTPx entrypoint (no Bridge Kit)
chains.ts # Hardcoded CCTPx testnet catalog (direct path)
ui.ts # Shared form / wallet / Iris registry helper
style.css
vite.config.ts # Local testnet IRIS proxy
```

`main.ts` takes its chain list from the CCTPx provider. `direct.ts` uses the hardcoded catalog in `chains.ts` (domain + CCTS + `viem/chains`). Both rely on `ui.ts` for Iris token→chain filtering and destination derivation.

Swap implementations in `index.html`:

```html
<script type="module" src="/src/main.ts"></script>
<!-- or -->
<script type="module" src="/src/direct.ts"></script>
```

The testnet Iris proxy is for local development. A deployed app needs an equivalent same-origin `/iris/testnet` proxy.
85 changes: 85 additions & 0 deletions cctp-non-usdc/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Sample app for non-USDC CCTP transfers with Bridge Kit or direct CCTP flow." />
<title>Non-USDC Crosschain Transfer Sample</title>
</head>
<body>
<header>
<h1>Non-USDC Crosschain Transfer</h1>
</header>
<main>
<p>
Transfer non-USDC assets with CCTP, using EURC, cirBTC, or a custom
token. Developer documentation at:
<a href="https://developers.circle.com/cctp/expanded-assets" target="_blank" rel="noopener noreferrer"
>CCTP for non-USDC assets</a>.
</p>

<button id="connect-wallet" type="button">Connect wallet</button>

<form id="transfer-form">
<div class="row">
<label class="transfer__token-kind">
<span>Token</span>
<select id="token-kind">
<option value="EURC">EURC</option>
<option value="cirBTC">cirBTC</option>
<option value="custom">Custom token ID</option>
</select>
</label>

<label class="transfer__token-id">
<span>Token ID</span>
<input id="token-id" type="text" placeholder="Alias token ID" spellcheck="false" readonly />
</label>

<label class="transfer__amount">
<span>Amount</span>
<input
id="amount"
type="text"
inputmode="decimal"
placeholder="0.00"
required
/>
</label>
</div>

<div class="row">
<label class="transfer__source-chain">
<span>Source chain</span>
<select id="source-chain" required>
<option value="">Choose a source</option>
</select>
</label>

<label class="transfer__destination-chain">
<span>Destination chain</span>
<select id="destination-chain" required>
<option value="">Choose a destination</option>
</select>
</label>

<label class="transfer__recipient">
<span>Recipient</span>
<input
id="recipient"
type="text"
placeholder="0x..."
/>
</label>
</div>

<button id="transfer-button" type="submit" disabled>Transfer</button>
</form>

<pre id="transfer-status" aria-live="polite"></pre>
</main>
<!-- Bridge Kit: /src/main.ts — CCTP flow: /src/direct.ts -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions cctp-non-usdc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "cctp-non-usdc",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@circle-fin/adapter-viem-v2": "^1.20.1",
"@circle-fin/bridge-kit": "^1.17.1",
"viem": "^2.57.17"
},
"devDependencies": {
"typescript": "~7.0.2",
"vite": "^8.3.7"
}
}
1 change: 1 addition & 0 deletions cctp-non-usdc/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions cctp-non-usdc/src/chains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Chain } from "viem";
import {
arbitrumSepolia,
arcTestnet,
avalancheFuji,
baseSepolia,
hyperliquidEvmTestnet,
inkSepolia,
optimismSepolia,
polygonAmoy,
sepolia,
worldchainSepolia,
} from "viem/chains";

/** Sandbox CrossChainTokenService — same CREATE3 address on every CCTPx testnet. */
export const TESTNET_CCTS =
"0x63753E722bd2C2A5DF6EE19C5106662208B81077" as const;

/**
* Hardcoded CCTPx testnet catalog for the direct (non–Bridge Kit) path.
* Token configuration still comes from Iris — this is chain + CCTS only.
*/
export interface CCTPXChain {
name: string;
chainId: number;
cctp: { domain: number };
cctpx: { serviceAddress: typeof TESTNET_CCTS };
viemChain: Chain;
}

function cctpxChain(
name: string,
domain: number,
viemChain: Chain,
): CCTPXChain {
return {
name,
chainId: viemChain.id,
cctp: { domain },
cctpx: { serviceAddress: TESTNET_CCTS },
viemChain,
};
}

/**
* CCTPx-enabled EVM testnets — keep in sync with Bridge Kit's
* CCTPXBridgingProvider testnet catalog (`serviceAddress` present).
*/
export const CCTPX_TESTNET_CHAINS: readonly CCTPXChain[] = [
cctpxChain("Ethereum Sepolia", 0, sepolia),
cctpxChain("Avalanche Fuji", 1, avalancheFuji),
cctpxChain("OP Sepolia", 2, optimismSepolia),
cctpxChain("Arbitrum Sepolia", 3, arbitrumSepolia),
cctpxChain("Base Sepolia", 6, baseSepolia),
cctpxChain("Polygon Amoy", 7, polygonAmoy),
cctpxChain("World Chain Sepolia", 14, worldchainSepolia),
cctpxChain("HyperEVM Testnet", 19, hyperliquidEvmTestnet),
cctpxChain("Ink Sepolia", 21, inkSepolia),
cctpxChain("Arc Testnet", 26, arcTestnet),
];
Loading
Loading