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
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # create-kozalak-l1
2
+
3
+ Avalanche L1 için **denetime hazır (audit-grade) Foundry projelerini** tek
4
+ komutla oluşturan interaktif CLI. Bir şablon seçersin, CLI kendi başına
5
+ derlenip test edilen standalone bir Foundry projesi çıkarır; istersen
6
+ bağımlılıkları kurar ve kontratı Fuji testnet'e deploy eder.
7
+
8
+ ## Kullanım
9
+
10
+ ```bash
11
+ npx create-kozalak-l1
12
+ ```
13
+
14
+ CLI seni adım adım yönlendirir:
15
+
16
+ 1. **Proje adı** — oluşturulacak klasör (argüman olarak da verebilirsin:
17
+ `npx create-kozalak-l1 benim-projem`).
18
+ 2. **Şablon seçimi** — aşağıdaki 5 şablondan biri.
19
+ 3. **Bağımlılıklar** — `forge install` ile pinli commit'lerden kurulum
20
+ (isteğe bağlı).
21
+ 4. **Deploy** — deploy edilebilir şablonlarda Fuji testnet'e tek-komut deploy
22
+ (isteğe bağlı; private key **yalnızca testnet cüzdanı** olmalı, disk'e
23
+ yazılmaz).
24
+
25
+ ### Şablonlar
26
+
27
+ | Şablon | Ne işe yarar |
28
+ | --- | --- |
29
+ | `erc20-gas` | Mintable, capped arz, sahip-kontrollü ERC-20 gaz token'ı |
30
+ | `erc721-collection` | Merkle allowlist + faz bazlı mint, royalty destekli NFT koleksiyonu |
31
+ | `soulbound-credential` | Devredilemez, role-bazlı sertifika NFT'si |
32
+ | `treasury-multisig` | TimelockController tabanlı gecikmeli-yürütme hazine kontratı |
33
+ | `ictt-bridge` | Avalanche ICTT ile C-Chain ↔ L1 token köprüsü (scaffold-only) |
34
+
35
+ > `ictt-bridge` iki-zincirli, çok-adımlı bir kurulum gerektirdiği için otomatik
36
+ > deploy edilmez; CLI seni adım adım rehbere (`docs/tr/03-templateler/ictt-bridge.md`)
37
+ > yönlendirir.
38
+
39
+ ### Oluşturduktan sonra
40
+
41
+ ```bash
42
+ cd benim-projem
43
+ forge install # bağımlılıkları CLI'da kurmadıysan
44
+ forge test
45
+ ```
46
+
47
+ Deploy için proje README'sindeki adımları izle (`.env` doldur → `forge script`).
48
+
49
+ ## Geliştirme
50
+
51
+ Bu paket kozalak-l1 mono-repo'sunun bir parçasıdır.
52
+
53
+ ```bash
54
+ cd cli
55
+ npm install
56
+ npm run build # TypeScript → dist/
57
+ npm run build:templates # cli/templates/ paketlerini repo kaynaklarından yeniden üret
58
+ npm test # vitest (registry / scaffold / forge parse testleri)
59
+ ```
60
+
61
+ `build:templates`, repo'nun denetlenmiş kaynaklarından (`src/templates/`,
62
+ `test/templates/`, `script/deploy/`, `foundry.lock`) her şablon için standalone
63
+ bir Foundry projesi assemble eder ve `cli/templates/<id>/` altına yazar. Repo
64
+ kaynakları değişince bu komut yeniden çalıştırılır ve paketlenmiş şablonlar
65
+ senkronlanır.
66
+
67
+ ### Non-interactive mod (CI / E2E)
68
+
69
+ Otomasyon için prompt'lar atlanabilir:
70
+
71
+ ```bash
72
+ node dist/index.js koza-e2e --template erc20-gas --yes-install --no-deploy
73
+ ```
74
+
75
+ - `--template <id>` — şablon seçer ve non-interactive modu açar.
76
+ - `--yes-install` / `--no-install` — bağımlılık kurulumunu aç/kapat.
77
+ - `--no-deploy` — deploy'u atla (non-interactive modda deploy zaten yapılmaz,
78
+ çünkü private key interaktif alınır).
79
+
80
+ ## Yayınlama
81
+
82
+ Paket henüz npm'e yayınlanmadı. `npm publish` **yakında**; o zamana kadar
83
+ mono-repo içinden `node dist/index.js` veya `npm run dev` ile çalıştırılır.
84
+
85
+ ## Lisans
86
+
87
+ MIT
package/dist/deploy.js ADDED
@@ -0,0 +1,63 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
+ import { runForge, parseDeployedAddress } from './forge.js';
4
+ const TELEPORTER_MESSENGER_ADDRESS = '0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf';
5
+ const ROUTESCAN_TESTNET_VERIFIER = 'https://api.routescan.io/v2/network/testnet/evm/43113/etherscan';
6
+ const FUJI_CHAIN_ID = '43113';
7
+ /**
8
+ * Fuji deploy için forge `script` komutunun argümanlarını üretir.
9
+ *
10
+ * `deploy()`'dan ayrıştırılmıştır ki test edilebilsin — forge no-arg
11
+ * `vm.startBroadcast()` kullanıyor, yani `--private-key` argv'de geçilmezse
12
+ * forge `PRIVATE_KEY` env var'ından OTOMATİK imzalamaz ve "No associated
13
+ * wallet" hatasıyla fail eder.
14
+ */
15
+ export function buildDeployArgs(input) {
16
+ const { template, privateKey, snowtraceKey } = input;
17
+ const scriptRelPath = join('script', basename(template.deployScript));
18
+ const args = ['script', scriptRelPath, '--rpc-url', 'fuji', '--broadcast', '--private-key', privateKey];
19
+ if (snowtraceKey) {
20
+ args.push('--verify', '--verifier-url', ROUTESCAN_TESTNET_VERIFIER, '--etherscan-api-key', snowtraceKey);
21
+ }
22
+ return args;
23
+ }
24
+ /**
25
+ * Scaffold edilmiş bir projeyi Fuji testnet'e deploy eder.
26
+ *
27
+ * Sadece `template.deployable === true` olan şablonlar için çalışır (ICTT gibi
28
+ * çok-adımlı şablonlar scaffold-only'dir, burada deploy edilmez).
29
+ *
30
+ * `privateKey` disk'e yazılmaz; yalnızca forge alt-süreci için argv + `process.env`
31
+ * üzerinden geçirilir.
32
+ */
33
+ export async function deploy(input) {
34
+ const { template, cwd, privateKey, snowtraceKey, envParams } = input;
35
+ if (!template.deployable) {
36
+ throw new Error(`Şablon "${template.id}" deploy edilemez (deployable: false). Rehber: ${template.guideDoc}`);
37
+ }
38
+ // Registry'deki deployScript repo-relative bir yol (script/deploy/X.s.sol).
39
+ // Scaffold edilmiş projede yapı flatten edilmiştir (build-templates.ts): script/X.s.sol.
40
+ const scriptFile = basename(template.deployScript);
41
+ const env = {
42
+ ...process.env,
43
+ PRIVATE_KEY: privateKey,
44
+ TELEPORTER_MESSENGER_ADDRESS,
45
+ ...envParams,
46
+ };
47
+ if (snowtraceKey)
48
+ env.SNOWTRACE_API_KEY = snowtraceKey;
49
+ const args = buildDeployArgs({ template, privateKey, snowtraceKey });
50
+ const result = runForge(args, cwd, env);
51
+ if (result.code !== 0) {
52
+ // forge compile hatası / revert reason genelde stderr'e yazılır; ikisini de göster.
53
+ const detail = [result.stdout, result.stderr].filter((s) => s.trim()).join('\n');
54
+ throw new Error(`forge script başarısız oldu (exit ${result.code}):\n${detail}`);
55
+ }
56
+ const broadcastPath = join(cwd, 'broadcast', scriptFile, FUJI_CHAIN_ID, 'run-latest.json');
57
+ if (!existsSync(broadcastPath)) {
58
+ return { address: null, explorerUrl: null };
59
+ }
60
+ const address = parseDeployedAddress(readFileSync(broadcastPath, 'utf8'));
61
+ const explorerUrl = address ? `https://testnet.snowtrace.io/address/${address}` : null;
62
+ return { address, explorerUrl };
63
+ }
package/dist/forge.js ADDED
@@ -0,0 +1,27 @@
1
+ import { execSync, spawnSync } from 'node:child_process';
2
+ /** `forge --version` çalışıyor mu (Foundry kurulu mu) kontrol eder. */
3
+ export function forgeAvailable() {
4
+ try {
5
+ execSync('forge --version', { stdio: 'ignore' });
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ /** `forge` komutunu verilen çalışma dizininde + ortam değişkenleriyle çalıştırır. */
13
+ export function runForge(args, cwd, env) {
14
+ const result = spawnSync('forge', args, { cwd, env, encoding: 'utf8' });
15
+ return { code: result.status ?? 1, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
16
+ }
17
+ /** Foundry `broadcast/.../run-latest.json` içeriğinden ilk dolu contractAddress'i çıkarır. */
18
+ export function parseDeployedAddress(broadcastJson) {
19
+ try {
20
+ const parsed = JSON.parse(broadcastJson);
21
+ const tx = parsed.transactions?.find((t) => t.contractAddress);
22
+ return tx?.contractAddress ?? null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
package/dist/index.js ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-kozalak-l1 — interaktif scaffold + Fuji deploy akışı.
4
+ *
5
+ * Varsayılan mod: @clack ile adım adım sorular. Non-interactive mod
6
+ * (`--template <id>` verilince) prompt'ları atlar; E2E ve CI içindir.
7
+ *
8
+ * Örnek (non-interactive):
9
+ * create-kozalak-l1 koza-e2e --template erc20-gas --yes-install --no-deploy
10
+ */
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join, resolve } from "node:path";
13
+ import { intro, outro, cancel, select, text, password, confirm, isCancel, note, log, } from "./prompts.js";
14
+ import { TEMPLATES, getTemplate } from "./templates.js";
15
+ import { scaffold } from "./scaffold.js";
16
+ import { deploy } from "./deploy.js";
17
+ import { forgeAvailable, runForge } from "./forge.js";
18
+ function parseArgs(argv) {
19
+ let projectName;
20
+ let templateId;
21
+ let install = false;
22
+ for (let i = 0; i < argv.length; i++) {
23
+ const a = argv[i];
24
+ if (a === "--template")
25
+ templateId = argv[++i];
26
+ else if (a.startsWith("--template="))
27
+ templateId = a.slice("--template=".length);
28
+ else if (a === "--yes-install")
29
+ install = true;
30
+ else if (a === "--no-install")
31
+ install = false;
32
+ else if (a === "--no-deploy") {
33
+ /* non-interactive modda deploy zaten yapılmaz; flag CI okunabilirliği için kabul edilir */
34
+ }
35
+ else if (!a.startsWith("-") && projectName === undefined)
36
+ projectName = a;
37
+ }
38
+ return { projectName, templateId, install, nonInteractive: templateId !== undefined };
39
+ }
40
+ /** Bir prompt sonucu iptal edildiyse temiz çıkış yapar; değilse değeri döner. */
41
+ function unwrap(value) {
42
+ if (isCancel(value))
43
+ cancel("İşlem iptal edildi.");
44
+ return value;
45
+ }
46
+ /**
47
+ * Scaffold edilmiş projenin README'sinden pinli `forge install ...` komutunu
48
+ * çıkarır (build-templates.ts her şablona bu satırı yazar). Bulunamazsa null.
49
+ */
50
+ function forgeInstallArgs(cwd) {
51
+ const readmePath = join(cwd, "README.md");
52
+ if (!existsSync(readmePath))
53
+ return null;
54
+ const line = readFileSync(readmePath, "utf8")
55
+ .split(/\r?\n/)
56
+ .find((l) => l.trim().startsWith("forge install "));
57
+ if (!line)
58
+ return null;
59
+ return line.trim().split(/\s+/).slice(1); // ['install', 'org/repo@rev', ...]
60
+ }
61
+ async function runInstall(targetDir) {
62
+ if (!forgeAvailable()) {
63
+ log.warn("forge bulunamadı — bağımlılık kurulumu atlandı. Foundry kurup projede `forge install` çalıştırın.");
64
+ return;
65
+ }
66
+ const args = forgeInstallArgs(targetDir);
67
+ if (!args) {
68
+ log.warn("forge install komutu README'de bulunamadı; bağımlılıkları manuel kurun.");
69
+ return;
70
+ }
71
+ log.step("Bağımlılıklar kuruluyor (forge install)... bu biraz sürebilir.");
72
+ const res = runForge(args, targetDir, process.env);
73
+ if (res.code === 0) {
74
+ log.success("Bağımlılıklar kuruldu.");
75
+ }
76
+ else {
77
+ log.warn(`forge install başarısız (exit ${res.code}). Projede manuel çalıştırın:\n${res.stderr || res.stdout}`);
78
+ }
79
+ }
80
+ /** Deploy adımı: private key + snowtrace + envParams sorup deploy() çağırır. */
81
+ async function runDeploy(template, targetDir) {
82
+ if (!forgeAvailable()) {
83
+ log.warn("forge bulunamadı — deploy atlandı. Foundry kurulumundan sonra tekrar deneyin.");
84
+ return;
85
+ }
86
+ const privateKey = unwrap(await password({
87
+ message: "Deploy private key (⚠️ SADECE testnet cüzdanı — mainnet anahtarı GİRMEYİN)",
88
+ }));
89
+ const snowRaw = unwrap(await text({
90
+ message: "Snowtrace/Routescan API key (opsiyonel — kontrat doğrulama için, boş geçebilirsiniz)",
91
+ placeholder: "rs_...",
92
+ }));
93
+ const snowtraceKey = snowRaw.trim() || undefined;
94
+ const envParams = {};
95
+ for (const p of template.envParams) {
96
+ const label = `${p.prompt}${p.optional ? " (opsiyonel)" : ""}`;
97
+ const raw = unwrap(p.secret ? await password({ message: label }) : await text({ message: label, placeholder: "" }));
98
+ const value = raw.trim();
99
+ if (value)
100
+ envParams[p.key] = value;
101
+ }
102
+ log.step("Fuji testnet'e deploy ediliyor...");
103
+ try {
104
+ const { address, explorerUrl } = await deploy({ template, cwd: targetDir, privateKey, snowtraceKey, envParams });
105
+ if (address) {
106
+ note(`Kontrat adresi:\n${address}\n\nExplorer:\n${explorerUrl}`, "Deploy başarılı");
107
+ }
108
+ else {
109
+ log.warn("Deploy tamamlandı ama kontrat adresi broadcast dosyasından okunamadı.");
110
+ }
111
+ }
112
+ catch (e) {
113
+ log.error(e.message);
114
+ }
115
+ }
116
+ async function main() {
117
+ const cli = parseArgs(process.argv.slice(2));
118
+ intro();
119
+ // 1) Proje adı
120
+ let projectName = cli.projectName;
121
+ if (!projectName) {
122
+ if (cli.nonInteractive)
123
+ cancel("Non-interactive modda proje adı zorunludur: create-kozalak-l1 <ad> --template <id>");
124
+ projectName = unwrap(await text({
125
+ message: "Proje adı (klasör olarak oluşturulur)",
126
+ placeholder: "benim-l1-projem",
127
+ validate: (v) => (v && v.trim() ? undefined : "Proje adı boş olamaz."),
128
+ })).trim();
129
+ }
130
+ // 2) Şablon
131
+ let template;
132
+ if (cli.nonInteractive) {
133
+ template = getTemplate(cli.templateId);
134
+ if (!template) {
135
+ cancel(`Geçersiz şablon: "${cli.templateId}". Geçerli: ${TEMPLATES.map((t) => t.id).join(", ")}`);
136
+ }
137
+ }
138
+ else {
139
+ const id = unwrap(await select({
140
+ message: "Bir şablon seçin",
141
+ options: TEMPLATES.map((t) => ({ value: t.id, label: t.label, hint: t.description })),
142
+ }));
143
+ template = getTemplate(id);
144
+ }
145
+ const tmpl = template;
146
+ // 3) Bağımlılık kurulumu?
147
+ const wantInstall = cli.nonInteractive
148
+ ? cli.install
149
+ : unwrap(await confirm({ message: "Bağımlılıklar şimdi kurulsun mu? (forge install)" }));
150
+ // 4) Scaffold
151
+ const targetDir = resolve(process.cwd(), projectName);
152
+ log.step(`"${projectName}" oluşturuluyor (${tmpl.label})...`);
153
+ try {
154
+ await scaffold({ template: tmpl, targetDir, projectName });
155
+ }
156
+ catch (e) {
157
+ cancel(e.message);
158
+ }
159
+ log.success("Proje dosyaları oluşturuldu.");
160
+ // 5) forge install (best-effort)
161
+ if (wantInstall)
162
+ await runInstall(targetDir);
163
+ // 6) Deploy (yalnızca interaktif mod + deployable şablon)
164
+ if (!tmpl.deployable) {
165
+ note(`Bu şablon (ICTT köprüsü) iki-zincirli, çok-adımlı bir kurulum gerektirir; otomatik tek-komut deploy yoktur.\n\nAdım adım rehber:\n${tmpl.guideDoc}`, "Sonraki adım: ICTT kurulum rehberi");
166
+ }
167
+ else if (!cli.nonInteractive) {
168
+ const wantDeploy = unwrap(await confirm({ message: "Şimdi Fuji testnet'e deploy edelim mi?" }));
169
+ if (wantDeploy)
170
+ await runDeploy(tmpl, targetDir);
171
+ }
172
+ outro(`Hazır! Sonraki adımlar:\n cd ${projectName}\n forge test`);
173
+ }
174
+ main().catch((e) => {
175
+ cancel(e.message);
176
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @clack/prompts ince sarmalayıcısı.
3
+ *
4
+ * intro/outro/cancel burada sabitlenir (banner metni, exit kodu); diğer
5
+ * prompt fonksiyonları (select/text/password/confirm/isCancel) doğrudan
6
+ * @clack/prompts'tan re-export edilir. Sonraki task'lar bu modülden import
7
+ * eder, doğrudan @clack/prompts'a bağımlı olmaz.
8
+ */
9
+ import { intro as clackIntro, outro as clackOutro, cancel as clackCancel, } from "@clack/prompts";
10
+ /** CLI banner'ını yazdırır. */
11
+ export const intro = () => {
12
+ clackIntro("create-kozalak-l1");
13
+ };
14
+ /** Kapanış mesajını yazdırır. */
15
+ export const outro = (msg) => {
16
+ clackOutro(msg);
17
+ };
18
+ /** İptal mesajını yazdırır ve süreci hata koduyla sonlandırır. */
19
+ export function cancel(msg) {
20
+ clackCancel(msg);
21
+ process.exit(1);
22
+ }
23
+ export { select, text, password, confirm, isCancel, note, log } from "@clack/prompts";
@@ -0,0 +1,33 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { cp, mkdir, rename } from 'node:fs/promises';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { join } from 'node:path';
5
+ import { execFile } from 'node:child_process';
6
+ import { promisify } from 'node:util';
7
+ const execFileAsync = promisify(execFile);
8
+ /** Bundle edilmiş templates/ dizininin paket-relative yolu (dist'ten paket köküne çözülür). */
9
+ export function templatesRoot() {
10
+ return fileURLToPath(new URL('../templates', import.meta.url));
11
+ }
12
+ export async function scaffold(opts) {
13
+ const { template, targetDir } = opts;
14
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
15
+ throw new Error(`Hedef dizin boş değil: ${targetDir}`);
16
+ }
17
+ await mkdir(targetDir, { recursive: true });
18
+ const source = join(templatesRoot(), template.id);
19
+ await cp(source, targetDir, { recursive: true });
20
+ // npm `.gitignore` dosyalarını pakete koymaz (gitignore-fallback), bu yüzden
21
+ // template'lerde noktasız "gitignore" olarak taşınır. Kullanıcı gerçek bir
22
+ // `.gitignore` alsın diye burada rename ediyoruz.
23
+ const gitignoreSrc = join(targetDir, 'gitignore');
24
+ if (existsSync(gitignoreSrc)) {
25
+ await rename(gitignoreSrc, join(targetDir, '.gitignore'));
26
+ }
27
+ try {
28
+ await execFileAsync('git', ['init'], { cwd: targetDir });
29
+ }
30
+ catch {
31
+ console.warn('git init başarısız oldu, scaffold dosyaları yine de oluşturuldu (git yüklü değil mi?).');
32
+ }
33
+ }
@@ -0,0 +1,108 @@
1
+ const OZ_REMAPPINGS = ['@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', 'forge-std/=lib/forge-std/src/'];
2
+ const OZ_SUBMODULES = ['forge-std', 'openzeppelin-contracts'];
3
+ const ICTT_REMAPPINGS = [
4
+ ...OZ_REMAPPINGS,
5
+ 'icm-contracts/=lib/icm-contracts/',
6
+ '@subnet-evm/=lib/icm-contracts/contracts/subnet-evm/',
7
+ '@teleporter/=lib/icm-contracts/contracts/teleporter/',
8
+ '@utilities/=lib/icm-contracts/contracts/utilities/',
9
+ '@mocks/=lib/icm-contracts/contracts/mocks/',
10
+ '@ictt/=lib/icm-contracts/contracts/ictt/',
11
+ ];
12
+ const ICTT_SUBMODULES = ['forge-std', 'openzeppelin-contracts', 'icm-contracts'];
13
+ export const TEMPLATES = [
14
+ {
15
+ id: 'erc20-gas',
16
+ label: 'ERC-20 Gas Token',
17
+ description: 'Mintable, capped arz, sahip-kontrollü ERC-20 (KozaGasToken).',
18
+ srcFiles: ['src/templates/erc20-gas/KozaGasToken.sol'],
19
+ testFiles: [
20
+ 'test/templates/ERC20Gas.t.sol',
21
+ 'test/templates/ERC20Gas.invariants.t.sol',
22
+ 'test/templates/DeployERC20Gas.t.sol',
23
+ ],
24
+ deployScript: 'script/deploy/DeployERC20Gas.s.sol',
25
+ guideDoc: 'docs/tr/03-templateler/erc20-gas.md',
26
+ remappings: OZ_REMAPPINGS,
27
+ submodules: OZ_SUBMODULES,
28
+ solc: '0.8.34',
29
+ envParams: [],
30
+ deployable: true,
31
+ },
32
+ {
33
+ id: 'erc721-collection',
34
+ label: 'ERC-721 Collection',
35
+ description: 'Merkle allowlist + faz bazlı mint, royalty destekli NFT koleksiyonu (KozaCollection).',
36
+ srcFiles: ['src/templates/erc721-collection/KozaCollection.sol'],
37
+ testFiles: [
38
+ 'test/templates/ERC721Collection.t.sol',
39
+ 'test/templates/ERC721Collection.invariants.t.sol',
40
+ 'test/templates/DeployERC721Collection.t.sol',
41
+ ],
42
+ deployScript: 'script/deploy/DeployERC721Collection.s.sol',
43
+ guideDoc: 'docs/tr/03-templateler/erc721-collection.md',
44
+ remappings: OZ_REMAPPINGS,
45
+ submodules: OZ_SUBMODULES,
46
+ solc: '0.8.34',
47
+ envParams: [
48
+ { key: 'NFT_NAME', prompt: 'Koleksiyon adı', optional: true },
49
+ { key: 'NFT_SYMBOL', prompt: 'Koleksiyon sembolü', optional: true },
50
+ { key: 'NFT_BASE_URI', prompt: 'Base URI (ipfs://.../ ile bitmeli)', optional: true },
51
+ { key: 'NFT_MAX_SUPPLY', prompt: 'Maksimum arz', optional: true },
52
+ { key: 'NFT_MINT_PRICE', prompt: 'Mint fiyatı (wei)', optional: true },
53
+ { key: 'NFT_ROYALTY_BPS', prompt: 'Royalty (basis points, 500 = %5)', optional: true },
54
+ { key: 'NFT_ROYALTY_RECEIVER', prompt: 'Royalty alıcı adresi', optional: true },
55
+ { key: 'NFT_OWNER', prompt: 'Sahip adresi (boş → deployer)', optional: true },
56
+ ],
57
+ deployable: true,
58
+ },
59
+ {
60
+ id: 'soulbound-credential',
61
+ label: 'Soulbound Credential',
62
+ description: 'Devredilemez (soulbound), role-bazlı sertifika NFT\'si (KozaCredential).',
63
+ srcFiles: ['src/templates/soulbound-credential/KozaCredential.sol'],
64
+ testFiles: [
65
+ 'test/templates/Soulbound.t.sol',
66
+ 'test/templates/Soulbound.invariants.t.sol',
67
+ 'test/templates/DeployCredential.t.sol',
68
+ ],
69
+ deployScript: 'script/deploy/DeployCredential.s.sol',
70
+ guideDoc: 'docs/tr/03-templateler/soulbound-credential.md',
71
+ remappings: OZ_REMAPPINGS,
72
+ submodules: OZ_SUBMODULES,
73
+ solc: '0.8.34',
74
+ envParams: [],
75
+ deployable: true,
76
+ },
77
+ {
78
+ id: 'treasury-multisig',
79
+ label: 'Treasury Multisig (Timelock)',
80
+ description: 'OpenZeppelin TimelockController tabanlı, gecikmeli-yürütme hazine kontratı (KozaTreasury).',
81
+ srcFiles: ['src/templates/treasury-multisig/KozaTreasury.sol'],
82
+ testFiles: ['test/templates/Treasury.t.sol', 'test/templates/DeployTreasury.t.sol'],
83
+ deployScript: 'script/deploy/DeployTreasury.s.sol',
84
+ guideDoc: 'docs/tr/03-templateler/treasury-multisig.md',
85
+ remappings: OZ_REMAPPINGS,
86
+ submodules: OZ_SUBMODULES,
87
+ solc: '0.8.34',
88
+ envParams: [],
89
+ deployable: true,
90
+ },
91
+ {
92
+ id: 'ictt-bridge',
93
+ label: 'ICTT Bridge (Home + Remote)',
94
+ description: 'Avalanche Interchain Token Transfer ile C-Chain ↔ L1 token köprüsü (KozaTokenHome + KozaTokenRemote).',
95
+ srcFiles: ['src/templates/ictt-bridge/KozaTokenHome.sol', 'src/templates/ictt-bridge/KozaTokenRemote.sol'],
96
+ testFiles: ['test/templates/ICTTBridge.t.sol'],
97
+ deployScript: 'script/deploy/DeployTokenHome.s.sol',
98
+ guideDoc: 'docs/tr/03-templateler/ictt-bridge.md',
99
+ remappings: ICTT_REMAPPINGS,
100
+ submodules: ICTT_SUBMODULES,
101
+ solc: '0.8.25',
102
+ envParams: [],
103
+ deployable: false,
104
+ },
105
+ ];
106
+ export function getTemplate(id) {
107
+ return TEMPLATES.find((t) => t.id === id);
108
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "create-kozalak-l1",
3
+ "version": "0.1.0",
4
+ "description": "Avalanche L1 Foundry projesi scaffold + Fuji deploy eden interaktif CLI",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-kozalak-l1": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "build:templates": "tsx scripts/build-templates.ts",
16
+ "test": "vitest run",
17
+ "dev": "tsx src/index.ts"
18
+ },
19
+ "dependencies": {
20
+ "@clack/prompts": "^1.6.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^26.0.1",
24
+ "tsx": "^4.22.4",
25
+ "typescript": "^6.0.3",
26
+ "vitest": "^4.1.9"
27
+ },
28
+ "license": "MIT"
29
+ }
@@ -0,0 +1,19 @@
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
@@ -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-20 Gas Token
2
+
3
+ Mintable, capped arz, sahip-kontrollü ERC-20 (KozaGasToken).
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/DeployERC20Gas.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/