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.
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/l1-launch/CLAUDE.md +91 -0
- package/templates/l1-launch/README.md +45 -0
- package/templates/l1-launch/app/globals.css +95 -0
- package/templates/l1-launch/app/layout.tsx +23 -0
- package/templates/l1-launch/app/page.tsx +5 -0
- package/templates/l1-launch/app/providers.tsx +22 -0
- package/templates/l1-launch/components/demo.tsx +396 -0
- package/templates/l1-launch/contracts/foundry.toml +9 -0
- package/templates/l1-launch/contracts/src/AvaKitToken.sol +53 -0
- package/templates/l1-launch/cursor/rules/avakit.mdc +36 -0
- package/templates/l1-launch/env.example +9 -0
- package/templates/l1-launch/gitignore +15 -0
- package/templates/l1-launch/l1.config.json +10 -0
- package/templates/l1-launch/lib/l1.ts +40 -0
- package/templates/l1-launch/lib/token-artifact.ts +239 -0
- package/templates/l1-launch/llms.txt +31 -0
- package/templates/l1-launch/manifest.json +6 -0
- package/templates/l1-launch/next.config.ts +7 -0
- package/templates/l1-launch/package.json +34 -0
- package/templates/l1-launch/pnpm-workspace.yaml +11 -0
- package/templates/l1-launch/postcss.config.mjs +7 -0
- package/templates/l1-launch/scripts/l1-fuji.sh +100 -0
- package/templates/l1-launch/scripts/l1.sh +108 -0
- package/templates/l1-launch/tsconfig.json +23 -0
- package/templates/token-bridge/CLAUDE.md +70 -0
- package/templates/token-bridge/README.md +39 -0
- package/templates/token-bridge/app/globals.css +95 -0
- package/templates/token-bridge/app/layout.tsx +23 -0
- package/templates/token-bridge/app/page.tsx +5 -0
- package/templates/token-bridge/app/providers.tsx +22 -0
- package/templates/token-bridge/bridge.config.json +6 -0
- package/templates/token-bridge/components/demo.tsx +352 -0
- package/templates/token-bridge/cursor/rules/avakit.mdc +36 -0
- package/templates/token-bridge/env.example +7 -0
- package/templates/token-bridge/gitignore +15 -0
- package/templates/token-bridge/lib/ictt-artifacts.json +1 -0
- package/templates/token-bridge/lib/ictt.ts +70 -0
- package/templates/token-bridge/llms.txt +25 -0
- package/templates/token-bridge/manifest.json +6 -0
- package/templates/token-bridge/next.config.ts +7 -0
- package/templates/token-bridge/package.json +33 -0
- package/templates/token-bridge/pnpm-workspace.yaml +11 -0
- package/templates/token-bridge/postcss.config.mjs +7 -0
- package/templates/token-bridge/scripts/bridge.sh +95 -0
- package/templates/token-bridge/scripts/deploy-bridge.mjs +107 -0
- 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,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,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
|