create-avalanche-app 0.1.4 → 0.1.6

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.
Files changed (49) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/l1-launch/CLAUDE.md +91 -0
  5. package/templates/l1-launch/README.md +45 -0
  6. package/templates/l1-launch/app/globals.css +95 -0
  7. package/templates/l1-launch/app/layout.tsx +23 -0
  8. package/templates/l1-launch/app/page.tsx +5 -0
  9. package/templates/l1-launch/app/providers.tsx +22 -0
  10. package/templates/l1-launch/components/demo.tsx +396 -0
  11. package/templates/l1-launch/contracts/foundry.toml +9 -0
  12. package/templates/l1-launch/contracts/src/AvaKitToken.sol +53 -0
  13. package/templates/l1-launch/cursor/rules/avakit.mdc +36 -0
  14. package/templates/l1-launch/env.example +9 -0
  15. package/templates/l1-launch/gitignore +15 -0
  16. package/templates/l1-launch/l1.config.json +10 -0
  17. package/templates/l1-launch/lib/l1.ts +40 -0
  18. package/templates/l1-launch/lib/token-artifact.ts +239 -0
  19. package/templates/l1-launch/llms.txt +31 -0
  20. package/templates/l1-launch/manifest.json +6 -0
  21. package/templates/l1-launch/next.config.ts +7 -0
  22. package/templates/l1-launch/package.json +34 -0
  23. package/templates/l1-launch/pnpm-workspace.yaml +11 -0
  24. package/templates/l1-launch/postcss.config.mjs +7 -0
  25. package/templates/l1-launch/scripts/l1-fuji.sh +100 -0
  26. package/templates/l1-launch/scripts/l1.sh +108 -0
  27. package/templates/l1-launch/tsconfig.json +23 -0
  28. package/templates/token-bridge/CLAUDE.md +70 -0
  29. package/templates/token-bridge/README.md +39 -0
  30. package/templates/token-bridge/app/globals.css +95 -0
  31. package/templates/token-bridge/app/layout.tsx +23 -0
  32. package/templates/token-bridge/app/page.tsx +5 -0
  33. package/templates/token-bridge/app/providers.tsx +22 -0
  34. package/templates/token-bridge/bridge.config.json +6 -0
  35. package/templates/token-bridge/components/demo.tsx +352 -0
  36. package/templates/token-bridge/cursor/rules/avakit.mdc +36 -0
  37. package/templates/token-bridge/env.example +7 -0
  38. package/templates/token-bridge/gitignore +15 -0
  39. package/templates/token-bridge/lib/ictt-artifacts.json +1 -0
  40. package/templates/token-bridge/lib/ictt.ts +70 -0
  41. package/templates/token-bridge/llms.txt +25 -0
  42. package/templates/token-bridge/manifest.json +6 -0
  43. package/templates/token-bridge/next.config.ts +7 -0
  44. package/templates/token-bridge/package.json +33 -0
  45. package/templates/token-bridge/pnpm-workspace.yaml +11 -0
  46. package/templates/token-bridge/postcss.config.mjs +7 -0
  47. package/templates/token-bridge/scripts/bridge.sh +95 -0
  48. package/templates/token-bridge/scripts/deploy-bridge.mjs +107 -0
  49. package/templates/token-bridge/tsconfig.json +23 -0
@@ -0,0 +1,239 @@
1
+ // Auto-generated from contracts/src/AvaKitToken.sol via `forge build`.
2
+ // Re-generate after editing the contract: cd contracts && forge build, then
3
+ // copy abi + bytecode.object here.
4
+
5
+ import type { Abi, Hex } from "viem";
6
+
7
+ export const abi = [
8
+ {
9
+ "type": "function",
10
+ "name": "allowance",
11
+ "inputs": [
12
+ {
13
+ "name": "",
14
+ "type": "address",
15
+ "internalType": "address"
16
+ },
17
+ {
18
+ "name": "",
19
+ "type": "address",
20
+ "internalType": "address"
21
+ }
22
+ ],
23
+ "outputs": [
24
+ {
25
+ "name": "",
26
+ "type": "uint256",
27
+ "internalType": "uint256"
28
+ }
29
+ ],
30
+ "stateMutability": "view"
31
+ },
32
+ {
33
+ "type": "function",
34
+ "name": "approve",
35
+ "inputs": [
36
+ {
37
+ "name": "spender",
38
+ "type": "address",
39
+ "internalType": "address"
40
+ },
41
+ {
42
+ "name": "value",
43
+ "type": "uint256",
44
+ "internalType": "uint256"
45
+ }
46
+ ],
47
+ "outputs": [
48
+ {
49
+ "name": "",
50
+ "type": "bool",
51
+ "internalType": "bool"
52
+ }
53
+ ],
54
+ "stateMutability": "nonpayable"
55
+ },
56
+ {
57
+ "type": "function",
58
+ "name": "balanceOf",
59
+ "inputs": [
60
+ {
61
+ "name": "",
62
+ "type": "address",
63
+ "internalType": "address"
64
+ }
65
+ ],
66
+ "outputs": [
67
+ {
68
+ "name": "",
69
+ "type": "uint256",
70
+ "internalType": "uint256"
71
+ }
72
+ ],
73
+ "stateMutability": "view"
74
+ },
75
+ {
76
+ "type": "function",
77
+ "name": "decimals",
78
+ "inputs": [],
79
+ "outputs": [
80
+ {
81
+ "name": "",
82
+ "type": "uint8",
83
+ "internalType": "uint8"
84
+ }
85
+ ],
86
+ "stateMutability": "view"
87
+ },
88
+ {
89
+ "type": "function",
90
+ "name": "mint",
91
+ "inputs": [],
92
+ "outputs": [],
93
+ "stateMutability": "nonpayable"
94
+ },
95
+ {
96
+ "type": "function",
97
+ "name": "name",
98
+ "inputs": [],
99
+ "outputs": [
100
+ {
101
+ "name": "",
102
+ "type": "string",
103
+ "internalType": "string"
104
+ }
105
+ ],
106
+ "stateMutability": "view"
107
+ },
108
+ {
109
+ "type": "function",
110
+ "name": "symbol",
111
+ "inputs": [],
112
+ "outputs": [
113
+ {
114
+ "name": "",
115
+ "type": "string",
116
+ "internalType": "string"
117
+ }
118
+ ],
119
+ "stateMutability": "view"
120
+ },
121
+ {
122
+ "type": "function",
123
+ "name": "totalSupply",
124
+ "inputs": [],
125
+ "outputs": [
126
+ {
127
+ "name": "",
128
+ "type": "uint256",
129
+ "internalType": "uint256"
130
+ }
131
+ ],
132
+ "stateMutability": "view"
133
+ },
134
+ {
135
+ "type": "function",
136
+ "name": "transfer",
137
+ "inputs": [
138
+ {
139
+ "name": "to",
140
+ "type": "address",
141
+ "internalType": "address"
142
+ },
143
+ {
144
+ "name": "value",
145
+ "type": "uint256",
146
+ "internalType": "uint256"
147
+ }
148
+ ],
149
+ "outputs": [
150
+ {
151
+ "name": "",
152
+ "type": "bool",
153
+ "internalType": "bool"
154
+ }
155
+ ],
156
+ "stateMutability": "nonpayable"
157
+ },
158
+ {
159
+ "type": "function",
160
+ "name": "transferFrom",
161
+ "inputs": [
162
+ {
163
+ "name": "from",
164
+ "type": "address",
165
+ "internalType": "address"
166
+ },
167
+ {
168
+ "name": "to",
169
+ "type": "address",
170
+ "internalType": "address"
171
+ },
172
+ {
173
+ "name": "value",
174
+ "type": "uint256",
175
+ "internalType": "uint256"
176
+ }
177
+ ],
178
+ "outputs": [
179
+ {
180
+ "name": "",
181
+ "type": "bool",
182
+ "internalType": "bool"
183
+ }
184
+ ],
185
+ "stateMutability": "nonpayable"
186
+ },
187
+ {
188
+ "type": "event",
189
+ "name": "Approval",
190
+ "inputs": [
191
+ {
192
+ "name": "owner",
193
+ "type": "address",
194
+ "indexed": true,
195
+ "internalType": "address"
196
+ },
197
+ {
198
+ "name": "spender",
199
+ "type": "address",
200
+ "indexed": true,
201
+ "internalType": "address"
202
+ },
203
+ {
204
+ "name": "value",
205
+ "type": "uint256",
206
+ "indexed": false,
207
+ "internalType": "uint256"
208
+ }
209
+ ],
210
+ "anonymous": false
211
+ },
212
+ {
213
+ "type": "event",
214
+ "name": "Transfer",
215
+ "inputs": [
216
+ {
217
+ "name": "from",
218
+ "type": "address",
219
+ "indexed": true,
220
+ "internalType": "address"
221
+ },
222
+ {
223
+ "name": "to",
224
+ "type": "address",
225
+ "indexed": true,
226
+ "internalType": "address"
227
+ },
228
+ {
229
+ "name": "value",
230
+ "type": "uint256",
231
+ "indexed": false,
232
+ "internalType": "uint256"
233
+ }
234
+ ],
235
+ "anonymous": false
236
+ }
237
+ ] as const satisfies Abi;
238
+
239
+ export const bytecode = "0x6080604052348015600e575f5ffd5b5061066f8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061009b575f3560e01c8063313ce56711610063578063313ce5671461013657806370a082311461015057806395d89b411461016f578063a9059cbb14610191578063dd62ed3e146101a4575f5ffd5b806306fdde031461009f578063095ea7b3146100e05780631249c58b1461010357806318160ddd1461010d57806323b872dd14610123575b5f5ffd5b6100ca6040518060400160405280600c81526020016b20bb30a5b4ba102a37b5b2b760a11b81525081565b6040516100d791906103f4565b60405180910390f35b6100f36100ee366004610444565b6101ce565b60405190151581526020016100d7565b61010b61023a565b005b6101155f5481565b6040519081526020016100d7565b6100f361013136600461046c565b6102b2565b61013e601281565b60405160ff90911681526020016100d7565b61011561015e3660046104a6565b60016020525f908152604090205481565b6100ca604051806040016040528060038152602001621052d560ea1b81525081565b6100f361019f366004610444565b61037e565b6101156101b23660046104c6565b600260209081525f928352604080842090915290825290205481565b335f8181526002602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906102289086815260200190565b60405180910390a35060015b92915050565b5f6102476012600a6105ee565b6102529060646105fc565b9050805f5f8282546102649190610613565b9091555050335f818152600160209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a350565b6001600160a01b0383165f9081526002602090815260408083203384529091528120805483919083906102e6908490610626565b90915550506001600160a01b0384165f9081526001602052604081208054849290610312908490610626565b90915550506001600160a01b038084165f81815260016020526040908190208054860190555190918616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9061036c9086815260200190565b60405180910390a35060019392505050565b335f9081526001602052604081208054839190839061039e908490610626565b90915550506001600160a01b0383165f81815260016020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906102289086815260200190565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80356001600160a01b038116811461043f575f5ffd5b919050565b5f5f60408385031215610455575f5ffd5b61045e83610429565b946020939093013593505050565b5f5f5f6060848603121561047e575f5ffd5b61048784610429565b925061049560208501610429565b929592945050506040919091013590565b5f602082840312156104b6575f5ffd5b6104bf82610429565b9392505050565b5f5f604083850312156104d7575f5ffd5b6104e083610429565b91506104ee60208401610429565b90509250929050565b634e487b7160e01b5f52601160045260245ffd5b6001815b60018411156105465780850481111561052a5761052a6104f7565b600184161561053857908102905b60019390931c92800261050f565b935093915050565b5f8261055c57506001610234565b8161056857505f610234565b816001811461057e5760028114610588576105a4565b6001915050610234565b60ff841115610599576105996104f7565b50506001821b610234565b5060208310610133831016604e8410600b84101617156105c7575081810a610234565b6105d35f19848461050b565b805f19048211156105e6576105e66104f7565b029392505050565b5f6104bf60ff84168361054e565b8082028115828204841417610234576102346104f7565b80820180821115610234576102346104f7565b81810381811115610234576102346104f756fea26469706673582212203ba8f5901a64d91a7beec3bdc98521ea0a7279a4ebafb699842fb133050e658e64736f6c634300081c0033" as Hex;
@@ -0,0 +1,31 @@
1
+ # __PROJECT_NAME__
2
+
3
+ > Launch your own Avalanche L1 with one command, then explore it in a built-in dashboard (live blocks, transactions, balance, contract deploy). Next.js + @avakit/react + viem + avalanche-cli, no third-party explorer.
4
+
5
+ ## Project map
6
+
7
+ - [scripts/l1.sh](scripts/l1.sh): `pnpm l1` — create + deploy a local Subnet-EVM L1, write l1.config.json.
8
+ - [scripts/l1-fuji.sh](scripts/l1-fuji.sh): `pnpm l1:fuji` — deploy the L1 to the Fuji testnet (advanced).
9
+ - [l1.config.json](l1.config.json): your chain (name, token, evmChainId, rpcUrl, blockchainIdHex, faucetAccount).
10
+ - [lib/l1.ts](lib/l1.ts): config → AvaKit chain (`defineChain`) + `isConfigured`.
11
+ - [components/demo.tsx](components/demo.tsx): the dashboard/explorer — polls RPC for blocks/txs/gas/balance, deploys + mints the demo token.
12
+ - [contracts/src/AvaKitToken.sol](contracts/src/AvaKitToken.sol) + [lib/token-artifact.ts](lib/token-artifact.ts): bundled ERC-20 for browser deploy.
13
+ - [CLAUDE.md](CLAUDE.md): agent guide — the flow, an explainer for every L1 config decision, the EWOQ warning, and the Fuji graduation path.
14
+
15
+ ## Key APIs
16
+
17
+ - Chain: `chain` and `isConfigured` from `@/lib/l1`.
18
+ - Explorer reads (all read-only viem): `getPublicClient(chain)` → `getBlockNumber()`, `getGasPrice()`, `getBlock({ blockNumber, includeTransactions: true })`, `getBalance({ address })`.
19
+ - Deploy: `deployContract({ artifact: { abi, bytecode }, chain, provider, account })` from `@avakit/core`.
20
+ - Write: `getWalletClient(chain, provider).writeContract({ address, abi, functionName: "mint", account })`.
21
+
22
+ ## Config decisions
23
+
24
+ Subnet-EVM VM, EVM chain id (default 9999), native token symbol, `--sovereign=false` for a
25
+ zero-prompt local chain (Fuji path uses a sovereign PoA L1), `--test-defaults` (EWOQ pre-funded).
26
+
27
+ ## External docs
28
+
29
+ - Create/deploy an L1 locally: https://build.avax.network/docs/tooling/avalanche-cli/create-deploy-avalanche-l1s/deploy-locally
30
+ - Deploy on Fuji: https://build.avax.network/docs/tooling/avalanche-cli/create-deploy-avalanche-l1s/deploy-on-fuji-testnet
31
+ - avalanche-cli: https://build.avax.network/docs/tooling/avalanche-cli/get-avalanche-cli
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "l1-launch",
3
+ "title": "Launch your own L1",
4
+ "description": "Spin up your own Avalanche L1 with one command, then explore blocks, send txs, and deploy a contract in a built-in dashboard",
5
+ "contracts": true
6
+ }
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ reactStrictMode: true,
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "next dev",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "typecheck": "tsc --noEmit",
11
+ "l1": "bash scripts/l1.sh",
12
+ "l1:fuji": "bash scripts/l1-fuji.sh"
13
+ },
14
+ "dependencies": {
15
+ "@avakit/core": "__AVAKIT_DEP__",
16
+ "@avakit/react": "__AVAKIT_DEP__",
17
+ "lucide-react": "1.22.0",
18
+ "next": "16.2.9",
19
+ "next-themes": "0.4.6",
20
+ "react": "19.2.7",
21
+ "react-dom": "19.2.7",
22
+ "viem": "2.54.1"
23
+ },
24
+ "devDependencies": {
25
+ "@tailwindcss/postcss": "4.3.2",
26
+ "@types/node": "26.0.1",
27
+ "@types/react": "19.2.17",
28
+ "@types/react-dom": "19.2.3",
29
+ "postcss": "8.5.16",
30
+ "tailwindcss": "4.3.2",
31
+ "tw-animate-css": "1.4.0",
32
+ "typescript": "6.0.3"
33
+ }
34
+ }
@@ -0,0 +1,11 @@
1
+ # pnpm settings for this scaffolded app.
2
+ # allowBuilds: pre-approve native postinstall scripts so `pnpm install` / `pnpm dev`
3
+ # don't fail with ERR_PNPM_IGNORED_BUILDS on pnpm 10+.
4
+ # minimumReleaseAgeExclude: pnpm's supply-chain age gate blocks very fresh releases;
5
+ # exempt AvaKit's own packages so a new @avakit publish never breaks a fresh install
6
+ # (third-party deps keep the protection). Delete this file to opt back into defaults.
7
+ allowBuilds:
8
+ sharp: true
9
+ minimumReleaseAgeExclude:
10
+ - '@avakit/core'
11
+ - '@avakit/react'
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Graduate your L1 to the Fuji TESTNET (advanced).
4
+ #
5
+ # Unlike `pnpm l1` (fully local, instant, free), deploying to Fuji is a real,
6
+ # multi-step operation. This script wraps the avalanche-cli commands and, where
7
+ # the CLI needs interactive input or your own keys, walks you through it. Read
8
+ # the caveats below before running — a Fuji L1 needs an always-on validator and
9
+ # a periodically-topped-up balance, or it stops producing blocks.
10
+ #
11
+ # Configure via env (all optional; must match the chain you created with pnpm l1):
12
+ # L1_NAME=mychain FUJI_KEY=mykey pnpm l1:fuji
13
+
14
+ set -uo pipefail
15
+
16
+ NAME="${L1_NAME:-mychain}"
17
+ KEY="${FUJI_KEY:-}"
18
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
19
+ CONFIG="$ROOT/l1.config.json"
20
+
21
+ say() { printf "\033[1;37m▸\033[0m %s\n" "$1"; }
22
+ warn() { printf "\033[1;33m!\033[0m %s\n" "$1"; }
23
+ die() { printf "\033[1;31m✖\033[0m %s\n" "$1" >&2; exit 1; }
24
+
25
+ command -v avalanche >/dev/null 2>&1 || die "avalanche-cli not found. See CLAUDE.md."
26
+
27
+ cat <<'EOF'
28
+ ────────────────────────────────────────────────────────────────────────
29
+ Deploy your L1 to Fuji — what you need first (one-time):
30
+
31
+ 1. A Fuji key funded with test AVAX:
32
+ avalanche key create mykey # creates a key
33
+ # fund its C-Chain address from the Builder Hub faucet:
34
+ # https://build.avax.network/console/primary-network/faucet
35
+ # then move funds to the P-Chain:
36
+ avalanche key transfer --key mykey --amount 2 --sender-blockchain c --receiver-blockchain p
37
+
38
+ 2. Budget ~1-2 test AVAX PER validator — an L1 validator pays a continuous
39
+ P-Chain fee (~1 AVAX ≈ 1 month). When the balance hits zero the validator
40
+ goes inactive and your L1 STOPS. Top up with:
41
+ avalanche blockchain addValidator / IncreaseL1ValidatorBalance
42
+
43
+ 3. A bootstrap validator node that STAYS RUNNING. Your machine can be it
44
+ (--use-local-machine below), but if this process exits, block production
45
+ stops. For an always-on chain, run a real node instead.
46
+ ────────────────────────────────────────────────────────────────────────
47
+ EOF
48
+
49
+ [ -n "$KEY" ] || die "Set FUJI_KEY to your funded avalanche-cli key name, e.g. FUJI_KEY=mykey pnpm l1:fuji"
50
+
51
+ say "Deploying '$NAME' to Fuji with key '$KEY' (your machine as bootstrap validator)…"
52
+ warn "This is interactive: the CLI will ask you to confirm the CreateSubnet / CreateChain /"
53
+ warn "ConvertSubnetToL1 transactions and to set up the local-machine validator. Follow its prompts."
54
+
55
+ # --use-local-machine turns this machine into the bootstrap validator; the CLI
56
+ # handles subnet creation, chain creation, and conversion to a sovereign L1.
57
+ avalanche blockchain deploy "$NAME" \
58
+ --fuji \
59
+ --key "$KEY" \
60
+ --use-local-machine \
61
+ || die "Fuji deploy did not complete. Re-run after addressing the CLI's output."
62
+
63
+ # Discover the Fuji RPC + hex blockchain ID for the deployed L1.
64
+ say "Reading Fuji chain details…"
65
+ OUT="$(avalanche blockchain describe "$NAME" 2>/dev/null)"
66
+ RPC="$(printf '%s' "$OUT" | grep -oE 'https?://[^ ]*/ext/bc/[A-Za-z0-9]+/rpc' | head -1)"
67
+ BID="$(printf '%s' "$OUT" | grep -iE 'BlockchainID \(HEX\)' | grep -oiE '0x[0-9a-f]{64}' | head -1)"
68
+ CID="$(printf '%s' "$OUT" | grep -iE 'ChainID' | grep -oE '[0-9]+' | head -1)"
69
+
70
+ [ -n "$RPC" ] || die "Could not read the Fuji RPC. Run: avalanche blockchain describe $NAME"
71
+
72
+ TOKEN="$(node -e "process.stdout.write(String(require('$CONFIG').token||'MYL1'))" 2>/dev/null || echo MYL1)"
73
+ CID="${CID:-$(node -e "process.stdout.write(String(require('$CONFIG').evmChainId||9999))" 2>/dev/null || echo 9999)}"
74
+
75
+ cat > "$CONFIG" <<EOF
76
+ {
77
+ "configured": true,
78
+ "network": "fuji",
79
+ "name": "$NAME",
80
+ "token": "$TOKEN",
81
+ "evmChainId": $CID,
82
+ "rpcUrl": "$RPC",
83
+ "blockchainIdHex": "${BID:-}",
84
+ "faucetAccount": { "address": "", "privateKey": "" }
85
+ }
86
+ EOF
87
+
88
+ say "Updated l1.config.json → $RPC (network: fuji)"
89
+ cat <<EOF
90
+
91
+ ✔ Your L1 is on Fuji.
92
+
93
+ • pnpm dev → the built-in explorer now points at your Fuji L1's RPC.
94
+ • Connect your OWN funded wallet (no EWOQ on Fuji).
95
+ • Keep this machine / your validator node running, and keep the validator
96
+ balance topped up, or the L1 stops producing blocks.
97
+
98
+ There is no automatic hosted explorer for a custom Fuji L1 — this app's built-in
99
+ explorer is your window into the chain.
100
+ EOF
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Launch your own local Avalanche L1 — one command.
4
+ #
5
+ # Creates a Subnet-EVM blockchain and deploys it to a local Avalanche network,
6
+ # then writes the discovered RPC URL + blockchain ID into l1.config.json so the
7
+ # app can talk to your chain. Everything runs on your machine — no test AVAX, no
8
+ # faucet, no always-on node. To graduate the same chain to the Fuji testnet, see
9
+ # `pnpm l1:fuji` (scripts/l1-fuji.sh) and CLAUDE.md.
10
+ #
11
+ # Configure via env (all optional):
12
+ # L1_NAME=mychain L1_CHAIN_ID=9999 L1_TOKEN=MYL1 pnpm l1
13
+ #
14
+ # Requires avalanche-cli. It downloads avalanchego + Subnet-EVM on first run.
15
+
16
+ set -uo pipefail
17
+
18
+ NAME="${L1_NAME:-mychain}"
19
+ CHAIN_ID="${L1_CHAIN_ID:-9999}"
20
+ TOKEN="${L1_TOKEN:-MYL1}"
21
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
22
+ CONFIG="$ROOT/l1.config.json"
23
+
24
+ # EWOQ: avalanche-cli's well-known local dev key, pre-funded on every local
25
+ # chain. PUBLIC — for local networks only, never a real network. Import it into
26
+ # your wallet to transact on your L1.
27
+ EWOQ_PK="0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027"
28
+ EWOQ_ADDR="0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"
29
+
30
+ say() { printf "\033[1;37m▸\033[0m %s\n" "$1"; }
31
+ die() { printf "\033[1;31m✖\033[0m %s\n" "$1" >&2; exit 1; }
32
+
33
+ # --- 0. avalanche-cli present? ---------------------------------------------
34
+ if ! command -v avalanche >/dev/null 2>&1; then
35
+ cat >&2 <<'EOF'
36
+ ✖ avalanche-cli not found. Install it:
37
+
38
+ curl -sSfL https://raw.githubusercontent.com/ava-labs/avalanche-cli/main/scripts/install.sh | sh -s -- -b /usr/local/bin
39
+
40
+ # or: brew install ava-labs/tap/avalanche-cli
41
+
42
+ Then re-run: pnpm l1
43
+ EOF
44
+ exit 1
45
+ fi
46
+
47
+ # --- 1. Create the L1 -------------------------------------------------------
48
+ # --sovereign=false keeps this fully non-interactive for local dev (a true
49
+ # sovereign L1 prompts for a validator-manager owner). --test-defaults uses
50
+ # local dev settings (EWOQ pre-funded, fast finality). CLAUDE.md explains each
51
+ # knob (VM, consensus, chain ID, sovereignty) and the Fuji path uses a real
52
+ # sovereign L1.
53
+ say "Creating L1 '$NAME' (Subnet-EVM · chainId $CHAIN_ID · token $TOKEN)…"
54
+ avalanche blockchain create "$NAME" \
55
+ --evm --latest \
56
+ --evm-chain-id "$CHAIN_ID" \
57
+ --evm-token "$TOKEN" \
58
+ --test-defaults \
59
+ --sovereign=false \
60
+ --force </dev/null >/dev/null 2>&1 \
61
+ || die "Failed to create '$NAME'. Try: avalanche blockchain delete $NAME"
62
+
63
+ # --- 2. Deploy it to a local network ----------------------------------------
64
+ say "Deploying '$NAME' locally (first run boots the local network)…"
65
+ avalanche blockchain deploy "$NAME" --local </dev/null || die "Deploy of '$NAME' failed."
66
+
67
+ # --- 3. Discover RPC URL + hex blockchain ID --------------------------------
68
+ # `describe` prints both. We grep by shape (robust across CLI versions).
69
+ say "Reading chain details…"
70
+ OUT="$(avalanche blockchain describe "$NAME" 2>/dev/null)"
71
+ RPC="$(printf '%s' "$OUT" | grep -oE 'http://127\.0\.0\.1:[0-9]+/ext/bc/[A-Za-z0-9]+/rpc' | head -1)"
72
+ BID="$(printf '%s' "$OUT" | grep -iE 'BlockchainID \(HEX\)' | grep -oiE '0x[0-9a-f]{64}' | head -1)"
73
+
74
+ [ -n "$RPC" ] || die "Could not read '$NAME' RPC. Run: avalanche blockchain describe $NAME"
75
+
76
+ # --- 4. Write l1.config.json ------------------------------------------------
77
+ cat > "$CONFIG" <<EOF
78
+ {
79
+ "configured": true,
80
+ "network": "local",
81
+ "name": "$NAME",
82
+ "token": "$TOKEN",
83
+ "evmChainId": $CHAIN_ID,
84
+ "rpcUrl": "$RPC",
85
+ "blockchainIdHex": "${BID:-}",
86
+ "faucetAccount": { "address": "$EWOQ_ADDR", "privateKey": "$EWOQ_PK" }
87
+ }
88
+ EOF
89
+
90
+ say "Wrote l1.config.json → $RPC"
91
+ cat <<EOF
92
+
93
+ ✔ Your L1 is live locally.
94
+
95
+ Next:
96
+ 1. Import the EWOQ dev key into your wallet (Core / MetaMask) — pre-funded on your chain:
97
+ $EWOQ_PK
98
+ (public dev key, local only)
99
+ 2. pnpm dev → http://localhost:3000 (your chain dashboard + explorer)
100
+ 3. Deploy the demo token, watch blocks and transactions land in real time.
101
+
102
+ Manage the network:
103
+ avalanche network stop # pause (keeps state)
104
+ avalanche network clean # wipe (new blockchain ID — re-run pnpm l1)
105
+
106
+ Graduate to the Fuji testnet (advanced, needs test AVAX + an always-on node):
107
+ pnpm l1:fuji
108
+ EOF
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": {
18
+ "@/*": ["./*"]
19
+ }
20
+ },
21
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "exclude": ["node_modules"]
23
+ }
@@ -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