create-kozalak-l1 0.1.0

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 (61) hide show
  1. package/README.md +87 -0
  2. package/dist/deploy.js +63 -0
  3. package/dist/forge.js +27 -0
  4. package/dist/index.js +176 -0
  5. package/dist/prompts.js +23 -0
  6. package/dist/scaffold.js +33 -0
  7. package/dist/templates.js +108 -0
  8. package/package.json +29 -0
  9. package/templates/erc20-gas/.env.example +19 -0
  10. package/templates/erc20-gas/.gitmodules +6 -0
  11. package/templates/erc20-gas/README.md +48 -0
  12. package/templates/erc20-gas/foundry.toml +35 -0
  13. package/templates/erc20-gas/gitignore +5 -0
  14. package/templates/erc20-gas/remappings.txt +2 -0
  15. package/templates/erc20-gas/script/DeployERC20Gas.s.sol +131 -0
  16. package/templates/erc20-gas/src/KozaGasToken.sol +105 -0
  17. package/templates/erc20-gas/test/DeployERC20Gas.t.sol +60 -0
  18. package/templates/erc20-gas/test/ERC20Gas.invariants.t.sol +144 -0
  19. package/templates/erc20-gas/test/ERC20Gas.t.sol +354 -0
  20. package/templates/erc721-collection/.env.example +37 -0
  21. package/templates/erc721-collection/.gitmodules +6 -0
  22. package/templates/erc721-collection/README.md +48 -0
  23. package/templates/erc721-collection/foundry.toml +35 -0
  24. package/templates/erc721-collection/gitignore +5 -0
  25. package/templates/erc721-collection/remappings.txt +2 -0
  26. package/templates/erc721-collection/script/DeployERC721Collection.s.sol +151 -0
  27. package/templates/erc721-collection/src/KozaCollection.sol +281 -0
  28. package/templates/erc721-collection/test/DeployERC721Collection.t.sol +76 -0
  29. package/templates/erc721-collection/test/ERC721Collection.invariants.t.sol +175 -0
  30. package/templates/erc721-collection/test/ERC721Collection.t.sol +501 -0
  31. package/templates/ictt-bridge/.env.example +19 -0
  32. package/templates/ictt-bridge/.gitmodules +9 -0
  33. package/templates/ictt-bridge/README.md +49 -0
  34. package/templates/ictt-bridge/foundry.toml +41 -0
  35. package/templates/ictt-bridge/gitignore +5 -0
  36. package/templates/ictt-bridge/remappings.txt +8 -0
  37. package/templates/ictt-bridge/script/DeployTokenHome.s.sol +139 -0
  38. package/templates/ictt-bridge/src/KozaTokenHome.sol +57 -0
  39. package/templates/ictt-bridge/src/KozaTokenRemote.sol +65 -0
  40. package/templates/ictt-bridge/test/ICTTBridge.t.sol +157 -0
  41. package/templates/soulbound-credential/.env.example +19 -0
  42. package/templates/soulbound-credential/.gitmodules +6 -0
  43. package/templates/soulbound-credential/README.md +48 -0
  44. package/templates/soulbound-credential/foundry.toml +35 -0
  45. package/templates/soulbound-credential/gitignore +5 -0
  46. package/templates/soulbound-credential/remappings.txt +2 -0
  47. package/templates/soulbound-credential/script/DeployCredential.s.sol +126 -0
  48. package/templates/soulbound-credential/src/KozaCredential.sol +201 -0
  49. package/templates/soulbound-credential/test/DeployCredential.t.sol +46 -0
  50. package/templates/soulbound-credential/test/Soulbound.invariants.t.sol +133 -0
  51. package/templates/soulbound-credential/test/Soulbound.t.sol +319 -0
  52. package/templates/treasury-multisig/.env.example +19 -0
  53. package/templates/treasury-multisig/.gitmodules +6 -0
  54. package/templates/treasury-multisig/README.md +48 -0
  55. package/templates/treasury-multisig/foundry.toml +35 -0
  56. package/templates/treasury-multisig/gitignore +5 -0
  57. package/templates/treasury-multisig/remappings.txt +2 -0
  58. package/templates/treasury-multisig/script/DeployTreasury.s.sol +128 -0
  59. package/templates/treasury-multisig/src/KozaTreasury.sol +55 -0
  60. package/templates/treasury-multisig/test/DeployTreasury.t.sol +50 -0
  61. package/templates/treasury-multisig/test/Treasury.t.sol +154 -0
@@ -0,0 +1,354 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.34;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {KozaGasToken} from "../src/KozaGasToken.sol";
6
+ import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
7
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
8
+ import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
9
+
10
+ /**
11
+ * @title ERC20GasTest
12
+ * @notice Unit + fuzz tests for KozaGasToken (Phase 1, Template 1).
13
+ * @dev Foundry test suite covering constructor, mint, burn, ERC-20 transfer, permit, ownership.
14
+ * Invariant tests are in ERC20Gas.invariants.t.sol.
15
+ */
16
+ contract ERC20GasTest is Test {
17
+ /*//////////////////////////////////////////////////////////////
18
+ STATE
19
+ //////////////////////////////////////////////////////////////*/
20
+
21
+ KozaGasToken internal token;
22
+
23
+ address internal owner;
24
+ uint256 internal ownerKey;
25
+ address internal alice;
26
+ uint256 internal aliceKey;
27
+ address internal bob;
28
+ address internal charlie;
29
+
30
+ string internal constant NAME = "Koza Gas Token";
31
+ string internal constant SYMBOL = "KGAS";
32
+ uint256 internal constant CAP = 1_000_000 ether;
33
+ uint256 internal constant INITIAL_MINT = 100_000 ether;
34
+
35
+ /*//////////////////////////////////////////////////////////////
36
+ SETUP
37
+ //////////////////////////////////////////////////////////////*/
38
+
39
+ function setUp() public {
40
+ (owner, ownerKey) = makeAddrAndKey("owner");
41
+ (alice, aliceKey) = makeAddrAndKey("alice");
42
+ bob = makeAddr("bob");
43
+ charlie = makeAddr("charlie");
44
+
45
+ token = new KozaGasToken(NAME, SYMBOL, CAP, INITIAL_MINT, owner);
46
+ }
47
+
48
+ /*//////////////////////////////////////////////////////////////
49
+ CONSTRUCTOR
50
+ //////////////////////////////////////////////////////////////*/
51
+
52
+ function test_Constructor_SetsMetadata() public view {
53
+ assertEq(token.name(), NAME);
54
+ assertEq(token.symbol(), SYMBOL);
55
+ assertEq(token.decimals(), 18);
56
+ assertEq(token.cap(), CAP);
57
+ assertEq(token.totalSupply(), INITIAL_MINT);
58
+ assertEq(token.balanceOf(owner), INITIAL_MINT);
59
+ assertEq(token.owner(), owner);
60
+ assertEq(token.pendingOwner(), address(0));
61
+ }
62
+
63
+ function test_Constructor_DeploysWithoutInitialMint() public {
64
+ KozaGasToken t = new KozaGasToken(NAME, SYMBOL, CAP, 0, owner);
65
+ assertEq(t.totalSupply(), 0);
66
+ assertEq(t.balanceOf(owner), 0);
67
+ }
68
+
69
+ function test_RevertWhen_ConstructorOwnerIsZero() public {
70
+ // Ownable parent rejects zero address before our body executes
71
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0)));
72
+ new KozaGasToken(NAME, SYMBOL, CAP, INITIAL_MINT, address(0));
73
+ }
74
+
75
+ function test_RevertWhen_ConstructorCapIsZero() public {
76
+ // ERC20Capped parent rejects zero cap before our body executes
77
+ vm.expectRevert(abi.encodeWithSelector(ERC20Capped.ERC20InvalidCap.selector, 0));
78
+ new KozaGasToken(NAME, SYMBOL, 0, 0, owner);
79
+ }
80
+
81
+ function test_RevertWhen_InitialMintExceedsCap() public {
82
+ uint256 tooMuch = CAP + 1;
83
+ vm.expectRevert(abi.encodeWithSelector(KozaGasToken.InitialMintExceedsCap.selector, CAP, tooMuch));
84
+ new KozaGasToken(NAME, SYMBOL, CAP, tooMuch, owner);
85
+ }
86
+
87
+ /*//////////////////////////////////////////////////////////////
88
+ MINT
89
+ //////////////////////////////////////////////////////////////*/
90
+
91
+ function test_Mint_OwnerCanMint() public {
92
+ uint256 amount = 50_000 ether;
93
+ vm.prank(owner);
94
+ token.mint(alice, amount);
95
+
96
+ assertEq(token.balanceOf(alice), amount);
97
+ assertEq(token.totalSupply(), INITIAL_MINT + amount);
98
+ }
99
+
100
+ function test_RevertWhen_NonOwnerMints() public {
101
+ vm.prank(alice);
102
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice));
103
+ token.mint(alice, 1 ether);
104
+ }
105
+
106
+ function test_RevertWhen_MintToZeroAddress() public {
107
+ // ERC20 _mint rejects zero address with ERC20InvalidReceiver
108
+ vm.prank(owner);
109
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidReceiver.selector, address(0)));
110
+ token.mint(address(0), 1 ether);
111
+ }
112
+
113
+ function test_RevertWhen_MintZeroAmount() public {
114
+ vm.prank(owner);
115
+ vm.expectRevert(KozaGasToken.ZeroAmount.selector);
116
+ token.mint(alice, 0);
117
+ }
118
+
119
+ function test_RevertWhen_MintExceedsCap() public {
120
+ uint256 remaining = CAP - INITIAL_MINT;
121
+ uint256 tooMuch = remaining + 1;
122
+
123
+ vm.prank(owner);
124
+ vm.expectRevert(abi.encodeWithSelector(ERC20Capped.ERC20ExceededCap.selector, INITIAL_MINT + tooMuch, CAP));
125
+ token.mint(alice, tooMuch);
126
+ }
127
+
128
+ function test_Mint_AtExactCapBoundary() public {
129
+ uint256 remaining = CAP - INITIAL_MINT;
130
+ vm.prank(owner);
131
+ token.mint(alice, remaining);
132
+
133
+ assertEq(token.totalSupply(), CAP);
134
+ assertEq(token.balanceOf(alice), remaining);
135
+ }
136
+
137
+ /*//////////////////////////////////////////////////////////////
138
+ BURN
139
+ //////////////////////////////////////////////////////////////*/
140
+
141
+ function test_Burn_HolderCanBurnOwnTokens() public {
142
+ uint256 burnAmount = 10_000 ether;
143
+
144
+ vm.prank(owner);
145
+ token.burn(burnAmount);
146
+
147
+ assertEq(token.balanceOf(owner), INITIAL_MINT - burnAmount);
148
+ assertEq(token.totalSupply(), INITIAL_MINT - burnAmount);
149
+ }
150
+
151
+ function test_RevertWhen_BurnZeroAmount() public {
152
+ vm.prank(owner);
153
+ vm.expectRevert(KozaGasToken.ZeroAmount.selector);
154
+ token.burn(0);
155
+ }
156
+
157
+ function test_RevertWhen_BurnExceedsBalance() public {
158
+ vm.prank(alice);
159
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 0, 1 ether));
160
+ token.burn(1 ether);
161
+ }
162
+
163
+ function test_Burn_FreesCapHeadroom() public {
164
+ // owner burns half, then mints back same amount → still under cap
165
+ uint256 half = INITIAL_MINT / 2;
166
+
167
+ vm.prank(owner);
168
+ token.burn(half);
169
+
170
+ vm.prank(owner);
171
+ token.mint(alice, half);
172
+
173
+ assertEq(token.totalSupply(), INITIAL_MINT);
174
+ }
175
+
176
+ /*//////////////////////////////////////////////////////////////
177
+ ERC-20 STANDARD
178
+ //////////////////////////////////////////////////////////////*/
179
+
180
+ function test_Transfer_Standard() public {
181
+ vm.prank(owner);
182
+ bool ok = token.transfer(alice, 1000 ether);
183
+
184
+ assertTrue(ok);
185
+ assertEq(token.balanceOf(alice), 1000 ether);
186
+ assertEq(token.balanceOf(owner), INITIAL_MINT - 1000 ether);
187
+ }
188
+
189
+ function test_Approve_AndTransferFrom() public {
190
+ vm.prank(owner);
191
+ token.approve(alice, 500 ether);
192
+
193
+ assertEq(token.allowance(owner, alice), 500 ether);
194
+
195
+ vm.prank(alice);
196
+ bool ok = token.transferFrom(owner, bob, 500 ether);
197
+
198
+ assertTrue(ok);
199
+ assertEq(token.balanceOf(bob), 500 ether);
200
+ assertEq(token.allowance(owner, alice), 0);
201
+ }
202
+
203
+ /*//////////////////////////////////////////////////////////////
204
+ ERC-2612 PERMIT
205
+ //////////////////////////////////////////////////////////////*/
206
+
207
+ function test_Permit_ValidSignature() public {
208
+ uint256 value = 100 ether;
209
+ uint256 deadline = block.timestamp + 1 hours;
210
+
211
+ // Fund alice first
212
+ vm.prank(owner);
213
+ token.transfer(alice, 100 ether);
214
+
215
+ bytes32 digest = _permitDigest(alice, bob, value, token.nonces(alice), deadline);
216
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(aliceKey, digest);
217
+
218
+ token.permit(alice, bob, value, deadline, v, r, s);
219
+
220
+ assertEq(token.allowance(alice, bob), value);
221
+ assertEq(token.nonces(alice), 1);
222
+ }
223
+
224
+ function test_RevertWhen_PermitExpired() public {
225
+ // Move forward so deadline=1 (a stale timestamp) is in the past relative to block.timestamp
226
+ vm.warp(2 days);
227
+ uint256 deadline = 1;
228
+
229
+ bytes32 digest = _permitDigest(alice, bob, 100 ether, 0, deadline);
230
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(aliceKey, digest);
231
+
232
+ vm.expectRevert();
233
+ token.permit(alice, bob, 100 ether, deadline, v, r, s);
234
+ }
235
+
236
+ function test_RevertWhen_PermitReplayed() public {
237
+ uint256 value = 100 ether;
238
+ uint256 deadline = block.timestamp + 1 hours;
239
+
240
+ bytes32 digest = _permitDigest(alice, bob, value, token.nonces(alice), deadline);
241
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(aliceKey, digest);
242
+
243
+ token.permit(alice, bob, value, deadline, v, r, s);
244
+
245
+ // Replay should fail (nonce was consumed)
246
+ vm.expectRevert();
247
+ token.permit(alice, bob, value, deadline, v, r, s);
248
+ }
249
+
250
+ /*//////////////////////////////////////////////////////////////
251
+ OWNABLE2STEP
252
+ //////////////////////////////////////////////////////////////*/
253
+
254
+ function test_TransferOwnership_TwoStep() public {
255
+ // Step 1: owner initiates transfer
256
+ vm.prank(owner);
257
+ token.transferOwnership(alice);
258
+
259
+ assertEq(token.owner(), owner, "owner unchanged before accept");
260
+ assertEq(token.pendingOwner(), alice, "alice is pending");
261
+
262
+ // Step 2: alice accepts
263
+ vm.prank(alice);
264
+ token.acceptOwnership();
265
+
266
+ assertEq(token.owner(), alice, "alice is now owner");
267
+ assertEq(token.pendingOwner(), address(0), "pending cleared");
268
+ }
269
+
270
+ function test_RevertWhen_NonPendingAcceptsOwnership() public {
271
+ vm.prank(owner);
272
+ token.transferOwnership(alice);
273
+
274
+ vm.prank(bob);
275
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, bob));
276
+ token.acceptOwnership();
277
+ }
278
+
279
+ function test_TransferOwnership_OldOwnerCanCancel() public {
280
+ vm.prank(owner);
281
+ token.transferOwnership(alice);
282
+
283
+ // owner overrides with another transfer (pending update)
284
+ vm.prank(owner);
285
+ token.transferOwnership(charlie);
286
+
287
+ assertEq(token.pendingOwner(), charlie);
288
+
289
+ // alice can no longer accept
290
+ vm.prank(alice);
291
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice));
292
+ token.acceptOwnership();
293
+ }
294
+
295
+ /*//////////////////////////////////////////////////////////////
296
+ FUZZ
297
+ //////////////////////////////////////////////////////////////*/
298
+
299
+ function testFuzz_Transfer(uint256 amount) public {
300
+ amount = bound(amount, 1, INITIAL_MINT);
301
+
302
+ vm.prank(owner);
303
+ bool ok = token.transfer(alice, amount);
304
+ assertTrue(ok);
305
+
306
+ assertEq(token.balanceOf(alice), amount);
307
+ assertEq(token.balanceOf(owner), INITIAL_MINT - amount);
308
+ }
309
+
310
+ function testFuzz_MintRespectsCap(uint256 amount) public {
311
+ // Bound to avoid uint256 overflow on INITIAL_MINT + amount
312
+ amount = bound(amount, 1, type(uint128).max);
313
+
314
+ uint256 remaining = CAP - INITIAL_MINT;
315
+
316
+ vm.prank(owner);
317
+ if (amount > remaining) {
318
+ vm.expectRevert(abi.encodeWithSelector(ERC20Capped.ERC20ExceededCap.selector, INITIAL_MINT + amount, CAP));
319
+ }
320
+ token.mint(alice, amount);
321
+
322
+ assertLe(token.totalSupply(), CAP);
323
+ }
324
+
325
+ function testFuzz_BurnDoesNotUnderflow(uint256 amount) public {
326
+ amount = bound(amount, 1, INITIAL_MINT);
327
+
328
+ vm.prank(owner);
329
+ token.burn(amount);
330
+
331
+ assertEq(token.balanceOf(owner), INITIAL_MINT - amount);
332
+ }
333
+
334
+ /*//////////////////////////////////////////////////////////////
335
+ HELPERS
336
+ //////////////////////////////////////////////////////////////*/
337
+
338
+ function _permitDigest(
339
+ address ownerAddr,
340
+ address spender,
341
+ uint256 value,
342
+ uint256 nonce,
343
+ uint256 deadline
344
+ )
345
+ internal
346
+ view
347
+ returns (bytes32)
348
+ {
349
+ bytes32 PERMIT_TYPEHASH =
350
+ keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
351
+ bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, ownerAddr, spender, value, nonce, deadline));
352
+ return keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash));
353
+ }
354
+ }
@@ -0,0 +1,37 @@
1
+ # ============================================
2
+ # Bu dosyayı kopyalayıp `.env` olarak adlandırın. `.env` ASLA commit edilmez.
3
+ # ============================================
4
+
5
+ # ---- Deploy Wallet (Fuji testnet) ----
6
+ # UYARI: Mainnet private key'i ASLA buraya koyma. Sadece testnet için.
7
+ # Production'da Foundry Cast Wallet (encrypted keystore) veya hardware wallet kullan.
8
+ PRIVATE_KEY=
9
+
10
+ # Deployer adres (sanity check, opsiyonel)
11
+ DEPLOYER_ADDRESS=
12
+
13
+ # ---- Block Explorer (Routescan) ----
14
+ # routescan.io'dan ücretsiz `rs_` prefix'li API key al. `--verify` için gerekir.
15
+ SNOWTRACE_API_KEY=
16
+
17
+ # ---- ICM / Teleporter ----
18
+ # Avalanche Teleporter messenger — tüm L1'lerde deterministic adres.
19
+ TELEPORTER_MESSENGER_ADDRESS=0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf
20
+
21
+ # ---- ERC-721 Collection parametreleri ----
22
+ # Koleksiyon adı (opsiyonel)
23
+ NFT_NAME=
24
+ # Koleksiyon sembolü (opsiyonel)
25
+ NFT_SYMBOL=
26
+ # Base URI (ipfs://.../ ile bitmeli) (opsiyonel)
27
+ NFT_BASE_URI=
28
+ # Maksimum arz (opsiyonel)
29
+ NFT_MAX_SUPPLY=
30
+ # Mint fiyatı (wei) (opsiyonel)
31
+ NFT_MINT_PRICE=
32
+ # Royalty (basis points, 500 = %5) (opsiyonel)
33
+ NFT_ROYALTY_BPS=
34
+ # Royalty alıcı adresi (opsiyonel)
35
+ NFT_ROYALTY_RECEIVER=
36
+ # Sahip adresi (boş → deployer) (opsiyonel)
37
+ NFT_OWNER=
@@ -0,0 +1,6 @@
1
+ [submodule "lib/forge-std"]
2
+ path = lib/forge-std
3
+ url = https://github.com/foundry-rs/forge-std
4
+ [submodule "lib/openzeppelin-contracts"]
5
+ path = lib/openzeppelin-contracts
6
+ url = https://github.com/OpenZeppelin/openzeppelin-contracts
@@ -0,0 +1,48 @@
1
+ # ERC-721 Collection
2
+
3
+ Merkle allowlist + faz bazlı mint, royalty destekli NFT koleksiyonu (KozaCollection).
4
+
5
+ Bu, kozalak-l1 deposundan üretilmiş **standalone** bir Foundry projesidir;
6
+ kendi başına derlenir ve test edilir.
7
+
8
+ ## Kurulum
9
+
10
+ ```bash
11
+ forge install foundry-rs/forge-std@8987040ede9553cea20c95ad40d0455930f9c8e0 OpenZeppelin/openzeppelin-contracts@e4f70216d759d8e6a64144a9e1f7bbeed78e7079
12
+ forge build
13
+ forge test
14
+ ```
15
+
16
+ > `forge install ...@<commit>` bağımlılıkları repo ile birebir aynı commit'lere
17
+ > pinler (aşağıdaki tabloya bakın). `.gitmodules` ve `remappings.txt`
18
+ > bu pinlerle uyumludur.
19
+
20
+ ## Deploy (Fuji testnet)
21
+
22
+ ```bash
23
+ cp .env.example .env # PRIVATE_KEY + SNOWTRACE_API_KEY doldur
24
+ forge script script/DeployERC721Collection.s.sol \
25
+ --rpc-url fuji \
26
+ --broadcast \
27
+ --verify
28
+ ```
29
+
30
+ > Production: `PRIVATE_KEY` yalnızca testnet olmalı; sahiplik/yönetici
31
+ > adreslerini bir multisig'e (Safe) yönlendir, EOA bırakma.
32
+
33
+ ## Yeniden adlandırma
34
+
35
+ Contract'ı kendi adınla yeniden adlandırmak istersen `src/`, `test/` ve
36
+ `script/` altındaki dosyalarda contract/dosya adını birlikte güncelle
37
+ (import'lar tek-seviye relative olduğu için tutarlı kalmalı).
38
+
39
+ ## Bağımlılık pinleri
40
+
41
+ | Bağımlılık | Tag | Commit (pin) |
42
+ | --- | --- | --- |
43
+ | `forge-std` | v1.16.0 | `8987040ede9553cea20c95ad40d0455930f9c8e0` |
44
+ | `openzeppelin-contracts` | v5.3.0 | `e4f70216d759d8e6a64144a9e1f7bbeed78e7079` |
45
+
46
+ ---
47
+
48
+ _Bu proje `create-kozalak-l1` tarafından üretildi. Kaynak: kozalak-l1 mono-repo._
@@ -0,0 +1,35 @@
1
+ [profile.default]
2
+ src = "src"
3
+ out = "out"
4
+ libs = ["lib"]
5
+ test = "test"
6
+ script = "script"
7
+ # Bu şablon solc 0.8.34 pragma'sı kullanır. auto_detect_solc dosya
8
+ # pragma'sına göre doğru derleyiciyi otomatik indirir/seçer.
9
+ auto_detect_solc = true
10
+ optimizer = true
11
+ optimizer_runs = 200
12
+ via_ir = true
13
+ bytecode_hash = "none"
14
+ cbor_metadata = false
15
+ remappings = [
16
+ "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
17
+ "forge-std/=lib/forge-std/src/"
18
+ ]
19
+
20
+ [fmt]
21
+ line_length = 120
22
+ tab_width = 4
23
+ bracket_spacing = false
24
+ int_types = "long"
25
+ multiline_func_header = "all"
26
+ quote_style = "double"
27
+ number_underscore = "thousands"
28
+
29
+ [rpc_endpoints]
30
+ fuji = "https://api.avax-test.network/ext/bc/C/rpc"
31
+ avalanche = "https://api.avax.network/ext/bc/C/rpc"
32
+
33
+ [etherscan]
34
+ fuji = { key = "${SNOWTRACE_API_KEY}", url = "https://api.routescan.io/v2/network/testnet/evm/43113/etherscan", chain = 43113 }
35
+ avalanche = { key = "${SNOWTRACE_API_KEY}", url = "https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan", chain = 43114 }
@@ -0,0 +1,5 @@
1
+ .env
2
+ out/
3
+ cache/
4
+ broadcast/
5
+ lib/
@@ -0,0 +1,2 @@
1
+ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
2
+ forge-std/=lib/forge-std/src/
@@ -0,0 +1,151 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.34;
3
+
4
+ import {Script} from "forge-std/Script.sol";
5
+ import {console2} from "forge-std/console2.sol";
6
+ import {KozaCollection} from "../src/KozaCollection.sol";
7
+
8
+ /**
9
+ * @title DeployERC721Collection
10
+ * @notice Foundry deployment script for KozaCollection (Phase 1, Template 2).
11
+ *
12
+ * Usage (Fuji testnet, with .env populated):
13
+ *
14
+ * forge script script/deploy/DeployERC721Collection.s.sol \
15
+ * --rpc-url fuji \
16
+ * --broadcast \
17
+ * --verify
18
+ *
19
+ * Required env (loaded by `forge` from .env):
20
+ * - PRIVATE_KEY — deployer private key (testnet only!)
21
+ *
22
+ * Optional env (defaults below if unset):
23
+ * - NFT_NAME ("Koza Genesis")
24
+ * - NFT_SYMBOL ("KOZA")
25
+ * - NFT_BASE_URI ("ipfs://CHANGE_ME/") — sonu `/` ile bitir
26
+ * - NFT_MAX_SUPPLY (5000)
27
+ * - NFT_MINT_PRICE (0.05 ether)
28
+ * - NFT_ROYALTY_RECEIVER (broadcaster)
29
+ * - NFT_ROYALTY_BPS (500 = %5)
30
+ * - NFT_OWNER (broadcaster) — production: multisig
31
+ *
32
+ * Production checklist:
33
+ * - PRIVATE_KEY testnet-only veya cast-wallet/keystore ile değiştirilmeli
34
+ * - NFT_OWNER + NFT_ROYALTY_RECEIVER birer Safe (Gnosis) multisig olmalı
35
+ * - NFT_BASE_URI gerçek IPFS CID veya HTTPS gateway içermeli (sonu `/` ile)
36
+ * - Snowtrace/Routescan API key `.env`'de SNOWTRACE_API_KEY olarak set'li olmalı
37
+ */
38
+ contract DeployERC721Collection is Script {
39
+ /*//////////////////////////////////////////////////////////////
40
+ DEFAULTS
41
+ //////////////////////////////////////////////////////////////*/
42
+
43
+ string internal constant DEFAULT_NAME = "Koza Genesis";
44
+ string internal constant DEFAULT_SYMBOL = "KOZA";
45
+ string internal constant DEFAULT_BASE_URI = "ipfs://CHANGE_ME/";
46
+ uint256 internal constant DEFAULT_MAX_SUPPLY = 5000;
47
+ uint256 internal constant DEFAULT_MINT_PRICE = 0.05 ether;
48
+ uint96 internal constant DEFAULT_ROYALTY_BPS = 500; // %5
49
+
50
+ /*//////////////////////////////////////////////////////////////
51
+ RUN
52
+ //////////////////////////////////////////////////////////////*/
53
+
54
+ /// @notice Entry point invoked by `forge script`. Reads parameters from env.
55
+ function run() external returns (KozaCollection nft, address deployer) {
56
+ string memory name = vm.envOr("NFT_NAME", DEFAULT_NAME);
57
+ string memory symbol = vm.envOr("NFT_SYMBOL", DEFAULT_SYMBOL);
58
+ string memory baseURI = vm.envOr("NFT_BASE_URI", DEFAULT_BASE_URI);
59
+ uint256 maxSupply = vm.envOr("NFT_MAX_SUPPLY", DEFAULT_MAX_SUPPLY);
60
+ uint256 mintPrice = vm.envOr("NFT_MINT_PRICE", DEFAULT_MINT_PRICE);
61
+ uint256 royaltyBpsRaw = vm.envOr("NFT_ROYALTY_BPS", uint256(DEFAULT_ROYALTY_BPS));
62
+ uint96 royaltyBps = uint96(royaltyBpsRaw);
63
+
64
+ address broadcaster = _resolveBroadcaster();
65
+ address royaltyReceiver = vm.envOr("NFT_ROYALTY_RECEIVER", broadcaster);
66
+ address owner = vm.envOr("NFT_OWNER", broadcaster);
67
+
68
+ return deploy(name, symbol, baseURI, maxSupply, mintPrice, royaltyReceiver, royaltyBps, owner, broadcaster);
69
+ }
70
+
71
+ /// @notice Test-friendly entry point. Takes explicit parameters instead of env.
72
+ /// @dev Prefer this from Foundry tests so env state does not leak between cases.
73
+ function deploy(
74
+ string memory name,
75
+ string memory symbol,
76
+ string memory baseURI,
77
+ uint256 maxSupply,
78
+ uint256 mintPrice,
79
+ address royaltyReceiver,
80
+ uint96 royaltyBps,
81
+ address owner,
82
+ address broadcaster
83
+ )
84
+ public
85
+ returns (KozaCollection nft, address deployer)
86
+ {
87
+ _logPreDeploy(name, symbol, baseURI, maxSupply, mintPrice, royaltyReceiver, royaltyBps, owner, broadcaster);
88
+
89
+ vm.startBroadcast();
90
+ nft = new KozaCollection(name, symbol, baseURI, maxSupply, mintPrice, royaltyReceiver, royaltyBps, owner);
91
+ vm.stopBroadcast();
92
+
93
+ deployer = broadcaster;
94
+
95
+ _logPostDeploy(nft, owner);
96
+ }
97
+
98
+ /*//////////////////////////////////////////////////////////////
99
+ INTERNALS
100
+ //////////////////////////////////////////////////////////////*/
101
+
102
+ /// @dev Order: explicit DEPLOYER_ADDRESS > derived from PRIVATE_KEY > tx.origin.
103
+ function _resolveBroadcaster() internal view returns (address) {
104
+ address explicitDeployer = vm.envOr("DEPLOYER_ADDRESS", address(0));
105
+ if (explicitDeployer != address(0)) return explicitDeployer;
106
+
107
+ uint256 pk = vm.envOr("PRIVATE_KEY", uint256(0));
108
+ if (pk != 0) return vm.addr(pk);
109
+
110
+ return tx.origin;
111
+ }
112
+
113
+ function _logPreDeploy(
114
+ string memory name,
115
+ string memory symbol,
116
+ string memory baseURI,
117
+ uint256 maxSupply,
118
+ uint256 mintPrice,
119
+ address royaltyReceiver,
120
+ uint96 royaltyBps,
121
+ address owner,
122
+ address broadcaster
123
+ )
124
+ internal
125
+ pure
126
+ {
127
+ console2.log("=== Deploying KozaCollection ===");
128
+ console2.log(" Broadcaster: ", broadcaster);
129
+ console2.log(" Owner: ", owner);
130
+ console2.log(" Name: ", name);
131
+ console2.log(" Symbol: ", symbol);
132
+ console2.log(" Base URI: ", baseURI);
133
+ console2.log(" Max supply: ", maxSupply);
134
+ console2.log(" Mint price (wei):", mintPrice);
135
+ console2.log(" Royalty BPS: ", royaltyBps);
136
+ console2.log(" Royalty receiver:", royaltyReceiver);
137
+ }
138
+
139
+ function _logPostDeploy(KozaCollection nft, address owner_) internal view {
140
+ console2.log("=== Deployed ===");
141
+ console2.log(" Address: ", address(nft));
142
+ console2.log(" Owner: ", nft.owner());
143
+ console2.log(" Owner (input): ", owner_);
144
+ console2.log(" Phase: ", uint8(nft.phase()));
145
+ console2.log("");
146
+ console2.log("Next steps:");
147
+ console2.log(" 1) cast send <addr> 'setMerkleRoot(bytes32)' <root> --rpc-url fuji ...");
148
+ console2.log(" 2) cast send <addr> 'setPhase(uint8)' 1 # Allowlist");
149
+ console2.log(" 3) cast send <addr> 'setPhase(uint8)' 2 # Public");
150
+ }
151
+ }