create-avalanche-app 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { existsSync, readdirSync } from "fs";
10
10
  import path from "path";
11
11
  import * as p from "@clack/prompts";
12
12
  import pc from "picocolors";
13
- var VERSION = "0.1.5";
13
+ var VERSION = "0.1.7";
14
14
  var AVAKIT_DEP_VERSION = "0.1.2";
15
15
  function parseArgs(argv) {
16
16
  const opts = { yes: false, local: false, install: true };
@@ -72,7 +72,7 @@ function printHelp() {
72
72
  "",
73
73
  "Options:",
74
74
  " -t, --template <id> minimal | nft-mint | token-gated-app | erc20-token |",
75
- " icm-messenger | eerc-token | l1-launch",
75
+ " icm-messenger | eerc-token | l1-launch | token-bridge",
76
76
  " -w, --wallet <id> web3auth | injected (default: web3auth)",
77
77
  " -c, --chain <id> fuji | c-chain (default: fuji)",
78
78
  " --pm <manager> pnpm | npm | yarn | bun",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { existsSync, readdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { type ChainId, listTemplates, scaffoldApp, type WalletId } from \"./api.js\";\n\nconst VERSION = \"0.1.5\";\n\n// The @avakit/* dependency version stamped into scaffolded apps' package.json\n// (as `^AVAKIT_DEP_VERSION`). Kept separate from the CLI's own VERSION: it must\n// resolve every published @avakit package, so it tracks the LOWEST current\n// @avakit version (core 0.1.2 · react 0.1.3 → ^0.1.2 satisfies both). Bump only\n// when the minimum @avakit version a fresh app needs goes up.\nconst AVAKIT_DEP_VERSION = \"0.1.2\";\n\ntype PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\n\ninterface Options {\n projectName?: string;\n template?: string;\n wallet?: WalletId;\n chain?: ChainId;\n pm?: PackageManager;\n yes: boolean;\n local: boolean;\n install: boolean;\n}\n\nfunction parseArgs(argv: string[]): Options {\n const opts: Options = { yes: false, local: false, install: true };\n const rest = argv.slice(2);\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n const next = () => rest[++i];\n switch (arg) {\n case \"--yes\":\n case \"-y\":\n opts.yes = true;\n break;\n case \"--local\":\n opts.local = true;\n break;\n case \"--no-install\":\n opts.install = false;\n break;\n case \"--template\":\n case \"-t\":\n opts.template = next();\n break;\n case \"--wallet\":\n case \"-w\":\n opts.wallet = next() as WalletId;\n break;\n case \"--chain\":\n case \"-c\":\n opts.chain = next() as ChainId;\n break;\n case \"--pm\":\n opts.pm = next() as PackageManager;\n break;\n case \"--version\":\n case \"-v\":\n process.stdout.write(`${VERSION}\\n`);\n process.exit(0);\n break;\n case \"--help\":\n case \"-h\":\n printHelp();\n process.exit(0);\n break;\n default:\n if (arg && !arg.startsWith(\"-\") && !opts.projectName) {\n opts.projectName = arg;\n }\n }\n }\n return opts;\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n \"create-avalanche-app — scaffold a batteries-included Avalanche dapp\",\n \"\",\n \"Usage: npm create avalanche-app@latest [name] [options]\",\n \"\",\n \"Options:\",\n \" -t, --template <id> minimal | nft-mint | token-gated-app | erc20-token |\",\n \" icm-messenger | eerc-token | l1-launch\",\n \" -w, --wallet <id> web3auth | injected (default: web3auth)\",\n \" -c, --chain <id> fuji | c-chain (default: fuji)\",\n \" --pm <manager> pnpm | npm | yarn | bun\",\n \" -y, --yes skip prompts (non-interactive)\",\n \" --no-install do not install dependencies\",\n \" --local link @avakit/* via workspace (repo dev only)\",\n \" -v, --version print version\",\n \" -h, --help print this help\",\n \"\",\n ].join(\"\\n\"),\n );\n}\n\nfunction isValidName(name: string): boolean {\n return /^[a-z0-9][a-z0-9._-]*$/.test(name);\n}\n\nasync function resolveOptions(\n opts: Options,\n): Promise<Required<Omit<Options, \"yes\" | \"local\" | \"install\">>> {\n const templates = listTemplates();\n const templateIds = templates.map((t) => t.id);\n\n if (opts.yes) {\n const projectName = opts.projectName ?? \"my-avax-app\";\n return {\n projectName,\n template: opts.template && templateIds.includes(opts.template) ? opts.template : \"minimal\",\n wallet: opts.wallet ?? \"web3auth\",\n chain: opts.chain ?? \"fuji\",\n pm: opts.pm ?? \"pnpm\",\n };\n }\n\n p.intro(pc.bgCyan(pc.black(\" create-avalanche-app \")));\n\n const projectName =\n opts.projectName ??\n (await p.text({\n message: \"Project name?\",\n placeholder: \"my-avax-app\",\n defaultValue: \"my-avax-app\",\n validate: (v) => (!v || isValidName(v) ? undefined : \"Use lowercase letters, digits, - . _\"),\n }));\n if (p.isCancel(projectName)) cancel();\n\n const template =\n opts.template ??\n (await p.select({\n message: \"Template?\",\n options: templates.map((t) => ({ value: t.id, label: t.title, hint: t.description })),\n initialValue: \"minimal\",\n }));\n if (p.isCancel(template)) cancel();\n\n const wallet =\n opts.wallet ??\n (await p.select({\n message: \"Wallet provider?\",\n options: [\n { value: \"web3auth\", label: \"Social login (Web3Auth)\", hint: \"free, recommended\" },\n { value: \"injected\", label: \"Browser wallet (Core / MetaMask)\" },\n ],\n initialValue: \"web3auth\",\n }));\n if (p.isCancel(wallet)) cancel();\n\n const chain =\n opts.chain ??\n (await p.select({\n message: \"Target chain?\",\n options: [\n { value: \"fuji\", label: \"Avalanche Fuji (testnet)\", hint: \"recommended\" },\n { value: \"c-chain\", label: \"Avalanche C-Chain (mainnet)\" },\n ],\n initialValue: \"fuji\",\n }));\n if (p.isCancel(chain)) cancel();\n\n const pm =\n opts.pm ??\n (await p.select({\n message: \"Package manager?\",\n options: ([\"pnpm\", \"npm\", \"yarn\", \"bun\"] as const).map((m) => ({ value: m, label: m })),\n initialValue: \"pnpm\",\n }));\n if (p.isCancel(pm)) cancel();\n\n return {\n projectName: projectName as string,\n template: template as string,\n wallet: wallet as WalletId,\n chain: chain as ChainId,\n pm: pm as PackageManager,\n };\n}\n\nfunction cancel(): never {\n p.cancel(\"Cancelled.\");\n process.exit(0);\n}\n\nasync function main(): Promise<void> {\n const opts = parseArgs(process.argv);\n const resolved = await resolveOptions(opts);\n\n const targetDir = path.resolve(process.cwd(), resolved.projectName);\n if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {\n process.stderr.write(\n pc.red(`\\nDirectory \"${resolved.projectName}\" already exists and is not empty.\\n`),\n );\n process.exit(1);\n }\n\n const spin = opts.yes ? null : p.spinner();\n spin?.start(\"Scaffolding project\");\n const { files } = await scaffoldApp({\n projectName: resolved.projectName,\n targetDir,\n template: resolved.template,\n wallet: resolved.wallet,\n chain: resolved.chain,\n local: opts.local,\n avakitVersion: AVAKIT_DEP_VERSION,\n });\n spin?.stop(`Created ${files.length} files`);\n\n if (opts.install) {\n const installSpin = opts.yes ? null : p.spinner();\n installSpin?.start(`Installing dependencies with ${resolved.pm}`);\n const result = spawnSync(resolved.pm, [\"install\"], {\n cwd: targetDir,\n stdio: opts.yes ? \"inherit\" : \"ignore\",\n });\n if (result.status === 0) {\n installSpin?.stop(\"Dependencies installed\");\n } else {\n installSpin?.stop(pc.yellow(\"Install skipped/failed — run it manually\"));\n }\n }\n\n const next = [\n `cd ${resolved.projectName}`,\n ...(opts.install ? [] : [`${resolved.pm} install`]),\n ...(resolved.wallet === \"web3auth\"\n ? [\"cp .env.example .env.local # add your Web3Auth client ID\"]\n : []),\n `${resolved.pm} run dev`,\n ];\n\n if (opts.yes) {\n process.stdout.write(`\\nDone. Next steps:\\n ${next.join(\"\\n \")}\\n`);\n } else {\n p.note(next.join(\"\\n\"), \"Next steps\");\n p.outro(pc.green(\"Your Avalanche dapp is ready.\"));\n }\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(\n `\\n${pc.red(\"Error:\")} ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exit(1);\n});\n"],"mappings":"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","names":["projectName","cancel"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { existsSync, readdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { type ChainId, listTemplates, scaffoldApp, type WalletId } from \"./api.js\";\n\nconst VERSION = \"0.1.7\";\n\n// The @avakit/* dependency version stamped into scaffolded apps' package.json\n// (as `^AVAKIT_DEP_VERSION`). Kept separate from the CLI's own VERSION: it must\n// resolve every published @avakit package, so it tracks the LOWEST current\n// @avakit version (core 0.1.2 · react 0.1.3 → ^0.1.2 satisfies both). Bump only\n// when the minimum @avakit version a fresh app needs goes up.\nconst AVAKIT_DEP_VERSION = \"0.1.2\";\n\ntype PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\n\ninterface Options {\n projectName?: string;\n template?: string;\n wallet?: WalletId;\n chain?: ChainId;\n pm?: PackageManager;\n yes: boolean;\n local: boolean;\n install: boolean;\n}\n\nfunction parseArgs(argv: string[]): Options {\n const opts: Options = { yes: false, local: false, install: true };\n const rest = argv.slice(2);\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n const next = () => rest[++i];\n switch (arg) {\n case \"--yes\":\n case \"-y\":\n opts.yes = true;\n break;\n case \"--local\":\n opts.local = true;\n break;\n case \"--no-install\":\n opts.install = false;\n break;\n case \"--template\":\n case \"-t\":\n opts.template = next();\n break;\n case \"--wallet\":\n case \"-w\":\n opts.wallet = next() as WalletId;\n break;\n case \"--chain\":\n case \"-c\":\n opts.chain = next() as ChainId;\n break;\n case \"--pm\":\n opts.pm = next() as PackageManager;\n break;\n case \"--version\":\n case \"-v\":\n process.stdout.write(`${VERSION}\\n`);\n process.exit(0);\n break;\n case \"--help\":\n case \"-h\":\n printHelp();\n process.exit(0);\n break;\n default:\n if (arg && !arg.startsWith(\"-\") && !opts.projectName) {\n opts.projectName = arg;\n }\n }\n }\n return opts;\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n \"create-avalanche-app — scaffold a batteries-included Avalanche dapp\",\n \"\",\n \"Usage: npm create avalanche-app@latest [name] [options]\",\n \"\",\n \"Options:\",\n \" -t, --template <id> minimal | nft-mint | token-gated-app | erc20-token |\",\n \" icm-messenger | eerc-token | l1-launch | token-bridge\",\n \" -w, --wallet <id> web3auth | injected (default: web3auth)\",\n \" -c, --chain <id> fuji | c-chain (default: fuji)\",\n \" --pm <manager> pnpm | npm | yarn | bun\",\n \" -y, --yes skip prompts (non-interactive)\",\n \" --no-install do not install dependencies\",\n \" --local link @avakit/* via workspace (repo dev only)\",\n \" -v, --version print version\",\n \" -h, --help print this help\",\n \"\",\n ].join(\"\\n\"),\n );\n}\n\nfunction isValidName(name: string): boolean {\n return /^[a-z0-9][a-z0-9._-]*$/.test(name);\n}\n\nasync function resolveOptions(\n opts: Options,\n): Promise<Required<Omit<Options, \"yes\" | \"local\" | \"install\">>> {\n const templates = listTemplates();\n const templateIds = templates.map((t) => t.id);\n\n if (opts.yes) {\n const projectName = opts.projectName ?? \"my-avax-app\";\n return {\n projectName,\n template: opts.template && templateIds.includes(opts.template) ? opts.template : \"minimal\",\n wallet: opts.wallet ?? \"web3auth\",\n chain: opts.chain ?? \"fuji\",\n pm: opts.pm ?? \"pnpm\",\n };\n }\n\n p.intro(pc.bgCyan(pc.black(\" create-avalanche-app \")));\n\n const projectName =\n opts.projectName ??\n (await p.text({\n message: \"Project name?\",\n placeholder: \"my-avax-app\",\n defaultValue: \"my-avax-app\",\n validate: (v) => (!v || isValidName(v) ? undefined : \"Use lowercase letters, digits, - . _\"),\n }));\n if (p.isCancel(projectName)) cancel();\n\n const template =\n opts.template ??\n (await p.select({\n message: \"Template?\",\n options: templates.map((t) => ({ value: t.id, label: t.title, hint: t.description })),\n initialValue: \"minimal\",\n }));\n if (p.isCancel(template)) cancel();\n\n const wallet =\n opts.wallet ??\n (await p.select({\n message: \"Wallet provider?\",\n options: [\n { value: \"web3auth\", label: \"Social login (Web3Auth)\", hint: \"free, recommended\" },\n { value: \"injected\", label: \"Browser wallet (Core / MetaMask)\" },\n ],\n initialValue: \"web3auth\",\n }));\n if (p.isCancel(wallet)) cancel();\n\n const chain =\n opts.chain ??\n (await p.select({\n message: \"Target chain?\",\n options: [\n { value: \"fuji\", label: \"Avalanche Fuji (testnet)\", hint: \"recommended\" },\n { value: \"c-chain\", label: \"Avalanche C-Chain (mainnet)\" },\n ],\n initialValue: \"fuji\",\n }));\n if (p.isCancel(chain)) cancel();\n\n const pm =\n opts.pm ??\n (await p.select({\n message: \"Package manager?\",\n options: ([\"pnpm\", \"npm\", \"yarn\", \"bun\"] as const).map((m) => ({ value: m, label: m })),\n initialValue: \"pnpm\",\n }));\n if (p.isCancel(pm)) cancel();\n\n return {\n projectName: projectName as string,\n template: template as string,\n wallet: wallet as WalletId,\n chain: chain as ChainId,\n pm: pm as PackageManager,\n };\n}\n\nfunction cancel(): never {\n p.cancel(\"Cancelled.\");\n process.exit(0);\n}\n\nasync function main(): Promise<void> {\n const opts = parseArgs(process.argv);\n const resolved = await resolveOptions(opts);\n\n const targetDir = path.resolve(process.cwd(), resolved.projectName);\n if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {\n process.stderr.write(\n pc.red(`\\nDirectory \"${resolved.projectName}\" already exists and is not empty.\\n`),\n );\n process.exit(1);\n }\n\n const spin = opts.yes ? null : p.spinner();\n spin?.start(\"Scaffolding project\");\n const { files } = await scaffoldApp({\n projectName: resolved.projectName,\n targetDir,\n template: resolved.template,\n wallet: resolved.wallet,\n chain: resolved.chain,\n local: opts.local,\n avakitVersion: AVAKIT_DEP_VERSION,\n });\n spin?.stop(`Created ${files.length} files`);\n\n if (opts.install) {\n const installSpin = opts.yes ? null : p.spinner();\n installSpin?.start(`Installing dependencies with ${resolved.pm}`);\n const result = spawnSync(resolved.pm, [\"install\"], {\n cwd: targetDir,\n stdio: opts.yes ? \"inherit\" : \"ignore\",\n });\n if (result.status === 0) {\n installSpin?.stop(\"Dependencies installed\");\n } else {\n installSpin?.stop(pc.yellow(\"Install skipped/failed — run it manually\"));\n }\n }\n\n const next = [\n `cd ${resolved.projectName}`,\n ...(opts.install ? [] : [`${resolved.pm} install`]),\n ...(resolved.wallet === \"web3auth\"\n ? [\"cp .env.example .env.local # add your Web3Auth client ID\"]\n : []),\n `${resolved.pm} run dev`,\n ];\n\n if (opts.yes) {\n process.stdout.write(`\\nDone. Next steps:\\n ${next.join(\"\\n \")}\\n`);\n } else {\n p.note(next.join(\"\\n\"), \"Next steps\");\n p.outro(pc.green(\"Your Avalanche dapp is ready.\"));\n }\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(\n `\\n${pc.red(\"Error:\")} ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exit(1);\n});\n"],"mappings":"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","names":["projectName","cancel"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-avalanche-app",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Scaffold a batteries-included Avalanche dapp: social-login onboarding, deploy-ready, AI-native.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -70,6 +70,44 @@ cross-chain transfer to the P-Chain), (2) a **bootstrap validator** — your mac
70
70
  It is a multi-step, multi-minute process, not one command. There is no automatic hosted explorer for
71
71
  a custom Fuji L1 — this app's built-in explorer keeps working by pointing at your Fuji L1's RPC.
72
72
 
73
+ ## AI guide: state-aware Fuji walkthrough
74
+
75
+ When the user asks for help getting their L1 onto Fuji, first figure out WHERE they are, then give
76
+ the ONE next command. Detect state like this:
77
+
78
+ 1. **Do they have a funded key?** Run `avalanche key list --keys <name>` (or create one:
79
+ `avalanche key create <name>`). Check the key's **C-Chain** balance — if it's 0, the next step is
80
+ the faucet: send them to `https://build.avax.network/console/primary-network/faucet` for their key's
81
+ C-Chain address, and wait until the balance is non-zero. Budget ~1–2 test AVAX (validators pay a
82
+ continuous P-Chain fee, ~1 AVAX ≈ 1 month).
83
+ 2. **Is the balance on the P-Chain?** The L1 deploy spends from the **P-Chain**, but the faucet funds
84
+ the **C-Chain**. If P-Chain balance is 0, next command:
85
+ `avalanche key transfer --key <name> --fuji --c-chain-sender --p-chain-receiver --amount <x>`
86
+ (the C/P selectors are the boolean flags `--c-chain-sender` / `--p-chain-receiver`, NOT
87
+ `--sender-blockchain c`).
88
+ 3. **Is the blockchain config created?** If `~/.avalanche-cli/subnets/<name>` doesn't exist, create it:
89
+ `avalanche blockchain create <name> --evm --latest --evm-chain-id <id> --evm-token <TOK>
90
+ --proof-of-authority --validator-manager-owner <yourAddr> --proxy-contract-owner <yourAddr>
91
+ --production-defaults --force`.
92
+ 4. **Deploy to Fuji.** `avalanche blockchain deploy <name> --fuji --key <name> --use-local-machine
93
+ --num-bootstrap-validators 1 --balance 0.1 --vmc-L1`. Notes for driving this non-interactively:
94
+ `--vmc-L1` pre-answers the "deploy Validator Manager into an external blockchain?" prompt; the final
95
+ "fund the relayer?" prompt is optional and can be skipped (the L1 still deploys — the relayer only
96
+ matters for cross-chain messaging). If it complains a **local deploy already exists**, the machine
97
+ has stale state — `avalanche network clean` (or pick a fresh `<name>`) and retry. The node
98
+ bootstraps to Fuji first (downloads a multi-hundred-MB archive, a few minutes) — that wait is
99
+ normal, not a hang.
100
+ 5. **Verify it's live.** The deploy prints an `RPC Endpoint`. Point this app at it (set
101
+ `l1.config.json` → `network: "fuji"`, `rpcUrl`, `blockchainIdHex`). Confirm: `cast chain-id
102
+ --rpc-url <rpc>` returns your chain id, `cast block-number` advances, and the blockchain is listed
103
+ on Fuji's P-Chain via `platform.getBlockchains` on `https://api.avax-test.network/ext/bc/P`.
104
+ 6. **Remind them of the ongoing cost:** the validator node must stay running, and its balance drains —
105
+ top it up (`avalanche blockchain addValidator` / increase-balance) or the L1 goes inactive. For an
106
+ always-on chain, run the node on a server, not a laptop.
107
+
108
+ Also available with the visual version of this flow: **AvaKit Studio** (`npx @avakit/studio`) has a
109
+ "Launch on Fuji" wizard that runs these exact steps with a balance poller and a live progress log.
110
+
73
111
  ## Editing the demo contract
74
112
 
75
113
  After changing `contracts/src/AvaKitToken.sol`: `cd contracts && forge build`, then copy
@@ -8,7 +8,7 @@ import {
8
8
  useAvaAccount,
9
9
  useAvaKit,
10
10
  } from "@avakit/react";
11
- import { Blocks, Check, Copy, Moon, Rocket, Sun } from "lucide-react";
11
+ import { Blocks, Check, Copy, ExternalLink, Moon, Rocket, Sun, Wallet } from "lucide-react";
12
12
  import { useTheme } from "next-themes";
13
13
  import { useCallback, useEffect, useState } from "react";
14
14
  import { type Address, type Hash, formatEther, formatUnits } from "viem";
@@ -274,11 +274,109 @@ function Dashboard() {
274
274
  </div>
275
275
  </section>
276
276
 
277
+ <NextSteps hasToken={Boolean(token)} />
278
+
277
279
  {error ? <p className="text-muted-foreground text-sm break-all">{error}</p> : null}
278
280
  </>
279
281
  );
280
282
  }
281
283
 
284
+ // Guides the user through what to do once their L1 is live: add it to a wallet,
285
+ // get gas, deploy something, and (on Fuji) the realities of keeping it running.
286
+ function NextSteps({ hasToken }: { hasToken: boolean }) {
287
+ const { provider } = useAvaKit();
288
+ const [added, setAdded] = useState(false);
289
+ const isFuji = l1.network === "fuji";
290
+
291
+ const addToWallet = useCallback(async () => {
292
+ if (!provider) return;
293
+ try {
294
+ await provider.request({
295
+ method: "wallet_addEthereumChain",
296
+ params: [
297
+ {
298
+ chainId: `0x${chain.id.toString(16)}`,
299
+ chainName: chain.name,
300
+ rpcUrls: [chain.rpcUrl],
301
+ nativeCurrency: { name: l1.token, symbol: l1.token, decimals: 18 },
302
+ },
303
+ ],
304
+ });
305
+ setAdded(true);
306
+ setTimeout(() => setAdded(false), 2000);
307
+ } catch {
308
+ // user rejected or wallet unavailable
309
+ }
310
+ }, [provider]);
311
+
312
+ return (
313
+ <section className="flex flex-col gap-3 rounded-xl border p-5">
314
+ <p className="text-muted-foreground text-xs uppercase tracking-wide">Next steps</p>
315
+ <div className="flex flex-col gap-2">
316
+ <StepRow n={1} done={added} label="Add this L1 to your wallet">
317
+ <Button size="sm" variant="outline" onClick={addToWallet}>
318
+ <Wallet className="size-4" />
319
+ {added ? "Added" : "Add network"}
320
+ </Button>
321
+ </StepRow>
322
+
323
+ <StepRow n={2} label={isFuji ? "Get gas — fund your wallet with test AVAX" : "Get gas — import the EWOQ dev key"}>
324
+ {isFuji ? (
325
+ <a
326
+ href="https://build.avax.network/console/primary-network/faucet"
327
+ target="_blank"
328
+ rel="noreferrer"
329
+ className="text-muted-foreground inline-flex items-center gap-1 text-xs underline underline-offset-4"
330
+ >
331
+ Fuji faucet <ExternalLink className="size-3" />
332
+ </a>
333
+ ) : (
334
+ <span className="text-muted-foreground text-xs">
335
+ EWOQ key is pre-funded (printed by <span className="font-mono">pnpm l1</span>).
336
+ </span>
337
+ )}
338
+ </StepRow>
339
+
340
+ <StepRow n={3} done={hasToken} label="Deploy your first contract (the demo token above)" />
341
+ </div>
342
+
343
+ {isFuji ? (
344
+ <p className="text-muted-foreground border-t pt-3 text-xs">
345
+ ⚠︎ Your L1 only produces blocks while its validator node stays running, and the
346
+ validator's balance drains over time (~1 AVAX ≈ 1 month) — top it up, or the chain goes
347
+ inactive. For an always-on L1, run the validator on a server rather than your laptop.
348
+ </p>
349
+ ) : null}
350
+ </section>
351
+ );
352
+ }
353
+
354
+ function StepRow({
355
+ n,
356
+ label,
357
+ done,
358
+ children,
359
+ }: {
360
+ n: number;
361
+ label: string;
362
+ done?: boolean;
363
+ children?: React.ReactNode;
364
+ }) {
365
+ return (
366
+ <div className="flex items-center justify-between gap-3">
367
+ <span className="flex items-center gap-2 text-sm">
368
+ <span
369
+ className={`flex size-5 items-center justify-center rounded-full border text-xs ${done ? "bg-foreground text-background" : "text-muted-foreground"}`}
370
+ >
371
+ {done ? <Check className="size-3" /> : n}
372
+ </span>
373
+ {label}
374
+ </span>
375
+ {children}
376
+ </div>
377
+ );
378
+ }
379
+
282
380
  function ago(timestamp: bigint): string {
283
381
  const secs = Math.max(0, Math.floor(Date.now() / 1000) - Number(timestamp));
284
382
  if (secs < 60) return `${secs}s ago`;
@@ -0,0 +1,70 @@
1
+ # __PROJECT_NAME__ — cross-chain token bridge with ICTT (scaffolded with AvaKit)
2
+
3
+ Operational guide for AI agents (Claude Code / Cursor) working in this project.
4
+
5
+ ## What this is
6
+
7
+ A dapp that bridges an ERC-20 between two Avalanche L1s using **Interchain Token Transfer (ICTT)**.
8
+ It runs against a **local devnet** of two L1s that `scripts/bridge.sh` (`pnpm bridge`) spins up — with
9
+ Interchain Messaging, a relayer, and a full ICTT bridge (a demo token + Home + Remote) deployed and
10
+ registered automatically.
11
+
12
+ ## Stack
13
+
14
+ Next.js 16 (App Router) · React 19 · `@avakit/react` · `@avakit/core` · viem · shadcn/ui · avalanche-cli (local devnet) · ava-labs/icm-contracts (ICTT)
15
+
16
+ ## How ICTT works here
17
+
18
+ - **ERC20TokenHome** (on chain1) holds the real ERC-20. When you bridge, it **locks** your tokens and
19
+ sends an ICM message to the remote.
20
+ - **ERC20TokenRemote** (on chain2) **is itself an ERC-20** (the "bridged" token, symbol `TOK1.b`). On
21
+ arrival it **mints** to the recipient. Bridging back **burns** the remote token and **unlocks** the
22
+ original on the home chain.
23
+ - A **TeleporterRegistry** on each chain points the ICTT contracts at the ICM messenger predeploy
24
+ (`0x253b…5fcf`). The relayer (started by `pnpm bridge`) carries the messages.
25
+
26
+ ## Architecture
27
+
28
+ - `scripts/bridge.sh` (`pnpm bridge`) — creates + deploys two L1s, then runs `deploy-bridge.mjs`.
29
+ - `scripts/deploy-bridge.mjs` — deploys the demo ERC-20 + TeleporterRegistry + Home on chain1, a
30
+ TeleporterRegistry + Remote on chain2, and calls `registerWithHome`. Uses viem + the embedded
31
+ artifacts + the public EWOQ key. Writes all addresses to `bridge.config.json`.
32
+ - `lib/ictt-artifacts.json` — ABIs + bytecode for TeleporterRegistry, ERC20TokenHome,
33
+ ERC20TokenRemote, and the demo ERC-20, compiled from `ava-labs/icm-contracts` with the optimizer.
34
+ - `lib/ictt.ts` — turns `bridge.config.json` into AvaKit chains + the addresses/ABIs the app uses.
35
+ - `components/demo.tsx` — the bridge UI: balances on both chains, mint, and bridge in either direction.
36
+
37
+ ## The bridge flow (in the UI)
38
+
39
+ 1. `home → remote`: `approve(home, amount)` on the demo token, then `home.send(SendTokensInput, amount)`.
40
+ The home locks the token; the relayer delivers; the remote mints `TOK1.b` to you on chain2.
41
+ 2. `remote → home`: `remote.send(SendTokensInput, amount)` (no approval — the remote burns its own
42
+ ERC-20); the relayer delivers; the home unlocks your original token on chain1.
43
+
44
+ `SendTokensInput` = `{ destinationBlockchainID (bytes32), destinationTokenTransferrerAddress,
45
+ recipient, primaryFeeTokenAddress, primaryFee, secondaryFee, requiredGasLimit, multiHopFallback }`.
46
+ On the local devnet, fees are 0, `primaryFeeTokenAddress` is the zero address, and
47
+ `requiredGasLimit` is 250000. `destinationBlockchainID` is the bytes32 (Avalanche) blockchain ID in
48
+ hex — NOT the EVM chainId — via `blockchainIdOf(chain)` in `lib/ictt.ts`.
49
+
50
+ ## Editing / regenerating the contracts
51
+
52
+ The bundled artifacts are compiled from `ava-labs/icm-contracts` (`contracts/ictt`) with solc + the
53
+ optimizer (runs 200) — the optimizer keeps ERC20TokenHome/Remote under the 24 KB EVM code-size limit.
54
+ To regenerate, compile those contracts with any optimizing toolchain (Hardhat or solc standard-JSON)
55
+ and replace the `abi`/`bytecode` in `lib/ictt-artifacts.json`. (Note: `avalanche interchain
56
+ tokenTransferrer deploy` can also deploy an ICTT bridge, but it requires a specific pinned Foundry
57
+ build to compile under the size limit — this template ships pre-compiled bytecode to avoid that.)
58
+
59
+ ## Rules
60
+
61
+ - shadcn/ui only; `@avakit/react` components are shadcn-styled. Black & white for now; dark/light via next-themes; both must work.
62
+ - Animations: Framer Motion or GSAP only.
63
+ - Amounts are 18-decimal — always convert with `parseEther` / `formatEther`.
64
+ - Never hardcode a real private key. The EWOQ key is a PUBLIC dev key — local devnet only.
65
+
66
+ ## Commands
67
+
68
+ - `pnpm bridge` — spin up the 2-L1 devnet + deploy the ICTT bridge (writes `bridge.config.json`)
69
+ - `pnpm dev` — dev server (http://localhost:3000)
70
+ - `avalanche network stop | clean` — pause | wipe the local devnet
@@ -0,0 +1,39 @@
1
+ # __PROJECT_NAME__
2
+
3
+ Bridge an ERC-20 between two Avalanche L1s with **Interchain Token Transfer (ICTT)** — over a one-command local devnet. Scaffolded with [AvaKit](https://github.com/mericcintosun/AvaKit).
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ # 1. spin up two L1s + a relayer + the full ICTT bridge (needs avalanche-cli)
9
+ pnpm bridge
10
+ # → creates chain1 + chain2, deploys a demo ERC-20 + Home + Remote, registers them,
11
+ # and writes bridge.config.json
12
+
13
+ # 2. run the app
14
+ pnpm dev # http://localhost:3000
15
+ ```
16
+
17
+ Then: import the printed EWOQ dev key into your wallet (pre-funded on both chains, local-only) →
18
+ **Mint** the demo token on chain1 → **Bridge to chain2** → watch the bridged token (`TOK1.b`) arrive.
19
+ Swap direction to bridge it back.
20
+
21
+ ## How it works
22
+
23
+ - **Home** (chain1) locks your ERC-20 and sends an Interchain message.
24
+ - **Remote** (chain2) is itself an ERC-20 — it mints a bridged version on arrival, and burns it when
25
+ you bridge back (which unlocks the original on chain1).
26
+ - A relayer (started by `pnpm bridge`) carries the messages; a TeleporterRegistry on each chain wires
27
+ the ICTT contracts to the ICM messenger.
28
+
29
+ The bridge contracts are compiled from [ava-labs/icm-contracts](https://github.com/ava-labs/icm-contracts)
30
+ and bundled as bytecode, so `pnpm bridge` deploys them with no Solidity toolchain on your machine.
31
+
32
+ ## Stack
33
+
34
+ Next.js 16 · `@avakit/react` · `@avakit/core` · viem · shadcn/ui · avalanche-cli · ICTT
35
+
36
+ ## AI-native
37
+
38
+ Ships with `CLAUDE.md`, `llms.txt`, and `.cursor/rules` so Claude Code / Cursor understand the ICTT
39
+ bridge flow (Home/Remote, lock/mint/burn/unlock, `SendTokensInput`) and the conventions.
@@ -0,0 +1,95 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ /* Scan @avakit/react's shadcn-style classes from node_modules. */
5
+ @source "../node_modules/@avakit/react/dist";
6
+
7
+ @custom-variant dark (&:is(.dark *));
8
+
9
+ /*
10
+ * Black & white only (oklch chroma 0 = pure grayscale). Dark/light wired from
11
+ * day one via next-themes. Add brand colors later by editing these tokens —
12
+ * components never hardcode colors.
13
+ */
14
+ :root {
15
+ --radius: 0.625rem;
16
+ --background: oklch(1 0 0);
17
+ --foreground: oklch(0.145 0 0);
18
+ --card: oklch(1 0 0);
19
+ --card-foreground: oklch(0.145 0 0);
20
+ --popover: oklch(1 0 0);
21
+ --popover-foreground: oklch(0.145 0 0);
22
+ --primary: oklch(0.205 0 0);
23
+ --primary-foreground: oklch(0.985 0 0);
24
+ --secondary: oklch(0.97 0 0);
25
+ --secondary-foreground: oklch(0.205 0 0);
26
+ --muted: oklch(0.97 0 0);
27
+ --muted-foreground: oklch(0.556 0 0);
28
+ --accent: oklch(0.97 0 0);
29
+ --accent-foreground: oklch(0.205 0 0);
30
+ --destructive: oklch(0.3 0 0);
31
+ --destructive-foreground: oklch(0.985 0 0);
32
+ --border: oklch(0.922 0 0);
33
+ --input: oklch(0.922 0 0);
34
+ --ring: oklch(0.708 0 0);
35
+ }
36
+
37
+ .dark {
38
+ --background: oklch(0.145 0 0);
39
+ --foreground: oklch(0.985 0 0);
40
+ --card: oklch(0.205 0 0);
41
+ --card-foreground: oklch(0.985 0 0);
42
+ --popover: oklch(0.205 0 0);
43
+ --popover-foreground: oklch(0.985 0 0);
44
+ --primary: oklch(0.985 0 0);
45
+ --primary-foreground: oklch(0.205 0 0);
46
+ --secondary: oklch(0.269 0 0);
47
+ --secondary-foreground: oklch(0.985 0 0);
48
+ --muted: oklch(0.269 0 0);
49
+ --muted-foreground: oklch(0.708 0 0);
50
+ --accent: oklch(0.269 0 0);
51
+ --accent-foreground: oklch(0.985 0 0);
52
+ --destructive: oklch(0.7 0 0);
53
+ --destructive-foreground: oklch(0.205 0 0);
54
+ --border: oklch(1 0 0 / 10%);
55
+ --input: oklch(1 0 0 / 15%);
56
+ --ring: oklch(0.556 0 0);
57
+ }
58
+
59
+ @theme inline {
60
+ --color-background: var(--background);
61
+ --color-foreground: var(--foreground);
62
+ --color-card: var(--card);
63
+ --color-card-foreground: var(--card-foreground);
64
+ --color-popover: var(--popover);
65
+ --color-popover-foreground: var(--popover-foreground);
66
+ --color-primary: var(--primary);
67
+ --color-primary-foreground: var(--primary-foreground);
68
+ --color-secondary: var(--secondary);
69
+ --color-secondary-foreground: var(--secondary-foreground);
70
+ --color-muted: var(--muted);
71
+ --color-muted-foreground: var(--muted-foreground);
72
+ --color-accent: var(--accent);
73
+ --color-accent-foreground: var(--accent-foreground);
74
+ --color-destructive: var(--destructive);
75
+ --color-destructive-foreground: var(--destructive-foreground);
76
+ --color-border: var(--border);
77
+ --color-input: var(--input);
78
+ --color-ring: var(--ring);
79
+ --radius-sm: calc(var(--radius) - 4px);
80
+ --radius-md: calc(var(--radius) - 2px);
81
+ --radius-lg: var(--radius);
82
+ --radius-xl: calc(var(--radius) + 4px);
83
+ --font-sans: var(--font-geist-sans);
84
+ --font-mono: var(--font-geist-mono);
85
+ }
86
+
87
+ @layer base {
88
+ * {
89
+ border-color: var(--border);
90
+ }
91
+ body {
92
+ background-color: var(--background);
93
+ color: var(--foreground);
94
+ }
95
+ }
@@ -0,0 +1,23 @@
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import type { ReactNode } from "react";
4
+ import { Providers } from "./providers";
5
+ import "./globals.css";
6
+
7
+ const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
8
+ const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
9
+
10
+ export const metadata: Metadata = {
11
+ title: "__PROJECT_NAME__",
12
+ description: "Bridge an ERC-20 across two Avalanche L1s — scaffolded with AvaKit.",
13
+ };
14
+
15
+ export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
16
+ return (
17
+ <html lang="en" suppressHydrationWarning>
18
+ <body className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}>
19
+ <Providers>{children}</Providers>
20
+ </body>
21
+ </html>
22
+ );
23
+ }
@@ -0,0 +1,5 @@
1
+ import { Demo } from "@/components/demo";
2
+
3
+ export default function Home() {
4
+ return <Demo />;
5
+ }
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ import { injectedAdapter, type WalletAdapter } from "@avakit/core";
4
+ import { AvaKitProvider } from "@avakit/react";
5
+ import { ThemeProvider } from "next-themes";
6
+ import { type ReactNode, useMemo } from "react";
7
+ import { homeChain, remoteChain } from "@/lib/ictt";
8
+
9
+ export function Providers({ children }: { children: ReactNode }) {
10
+ // Local devnet: connect a browser wallet (Core / MetaMask) with the imported
11
+ // EWOQ dev key. Both local L1s are registered so the app can switch between
12
+ // them to bridge and read balances on each.
13
+ const adapters = useMemo<WalletAdapter[]>(() => [injectedAdapter()], []);
14
+
15
+ return (
16
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
17
+ <AvaKitProvider chains={[homeChain, remoteChain]} adapters={adapters}>
18
+ {children}
19
+ </AvaKitProvider>
20
+ </ThemeProvider>
21
+ );
22
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "configured": false,
3
+ "chain1": { "name": "chain1", "token": "TOK1", "evmChainId": 1001, "rpcUrl": "", "blockchainIdHex": "" },
4
+ "chain2": { "name": "chain2", "token": "TOK2", "evmChainId": 1002, "rpcUrl": "", "blockchainIdHex": "" },
5
+ "bridge": null
6
+ }