create-izi-noir 0.1.10 → 0.2.1
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 +1245 -235
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import path2 from "path";
|
|
8
8
|
import { execSync } from "child_process";
|
|
9
|
-
import
|
|
9
|
+
import pc4 from "picocolors";
|
|
10
10
|
|
|
11
11
|
// src/prompts/project.ts
|
|
12
12
|
import prompts from "prompts";
|
|
@@ -153,41 +153,187 @@ function createSpinner(message) {
|
|
|
153
153
|
return new Spinner(message);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// src/utils/progress.ts
|
|
157
|
+
import pc3 from "picocolors";
|
|
158
|
+
var THINKING_PHRASES = [
|
|
159
|
+
"Initializing ZK environment",
|
|
160
|
+
"Configuring proof system",
|
|
161
|
+
"Setting up circuit compiler",
|
|
162
|
+
"Preparing cryptographic primitives",
|
|
163
|
+
"Generating project scaffold",
|
|
164
|
+
"Configuring Noir integration",
|
|
165
|
+
"Setting up React components",
|
|
166
|
+
"Preparing WASM bindings"
|
|
167
|
+
];
|
|
168
|
+
var FILE_ICONS = {
|
|
169
|
+
".json": "\u{1F4E6}",
|
|
170
|
+
".ts": "\u{1F4DC}",
|
|
171
|
+
".tsx": "\u269B\uFE0F",
|
|
172
|
+
".css": "\u{1F3A8}",
|
|
173
|
+
".html": "\u{1F310}",
|
|
174
|
+
".md": "\u{1F4DD}",
|
|
175
|
+
".svg": "\u{1F5BC}\uFE0F",
|
|
176
|
+
default: "\u{1F4C4}"
|
|
177
|
+
};
|
|
178
|
+
function getFileIcon(filename) {
|
|
179
|
+
const ext = filename.slice(filename.lastIndexOf("."));
|
|
180
|
+
return FILE_ICONS[ext] || FILE_ICONS.default;
|
|
181
|
+
}
|
|
182
|
+
function sleep(ms) {
|
|
183
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
184
|
+
}
|
|
185
|
+
var ProgressReporter = class {
|
|
186
|
+
currentLine = "";
|
|
187
|
+
thinkingIndex = 0;
|
|
188
|
+
thinkingInterval = null;
|
|
189
|
+
async startThinking() {
|
|
190
|
+
process.stdout.write("\x1B[?25l");
|
|
191
|
+
this.showThinking();
|
|
192
|
+
}
|
|
193
|
+
showThinking() {
|
|
194
|
+
const frames2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
195
|
+
let frameIndex = 0;
|
|
196
|
+
let dotCount = 0;
|
|
197
|
+
this.thinkingInterval = setInterval(() => {
|
|
198
|
+
const frame = pc3.cyan(frames2[frameIndex]);
|
|
199
|
+
const phrase = THINKING_PHRASES[this.thinkingIndex % THINKING_PHRASES.length];
|
|
200
|
+
const dots = ".".repeat(dotCount % 4);
|
|
201
|
+
process.stdout.write(`\r${frame} ${pc3.dim(phrase)}${dots} `);
|
|
202
|
+
frameIndex = (frameIndex + 1) % frames2.length;
|
|
203
|
+
dotCount++;
|
|
204
|
+
if (dotCount % 12 === 0) {
|
|
205
|
+
this.thinkingIndex++;
|
|
206
|
+
}
|
|
207
|
+
}, 80);
|
|
208
|
+
}
|
|
209
|
+
stopThinking() {
|
|
210
|
+
if (this.thinkingInterval) {
|
|
211
|
+
clearInterval(this.thinkingInterval);
|
|
212
|
+
this.thinkingInterval = null;
|
|
213
|
+
}
|
|
214
|
+
process.stdout.write("\r\x1B[K");
|
|
215
|
+
}
|
|
216
|
+
async reportFile(filename, isLast = false) {
|
|
217
|
+
const icon = getFileIcon(filename);
|
|
218
|
+
const line = ` ${icon} ${pc3.dim("created")} ${pc3.white(filename)}`;
|
|
219
|
+
process.stdout.write(" " + icon + " " + pc3.dim("created") + " ");
|
|
220
|
+
for (const char of filename) {
|
|
221
|
+
process.stdout.write(pc3.white(char));
|
|
222
|
+
await sleep(8 + Math.random() * 12);
|
|
223
|
+
}
|
|
224
|
+
process.stdout.write("\n");
|
|
225
|
+
this.currentLine = line;
|
|
226
|
+
}
|
|
227
|
+
async reportDirectory(dirname) {
|
|
228
|
+
process.stdout.write(` \u{1F4C1} ${pc3.dim("mkdir")} ${pc3.blue(dirname)}/
|
|
229
|
+
`);
|
|
230
|
+
await sleep(30);
|
|
231
|
+
}
|
|
232
|
+
showSuccess(message) {
|
|
233
|
+
process.stdout.write("\x1B[?25h");
|
|
234
|
+
console.log();
|
|
235
|
+
console.log(pc3.green("\u2713") + " " + message);
|
|
236
|
+
}
|
|
237
|
+
showError(message) {
|
|
238
|
+
process.stdout.write("\x1B[?25h");
|
|
239
|
+
console.log();
|
|
240
|
+
console.log(pc3.red("\u2717") + " " + message);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
function createProgressReporter() {
|
|
244
|
+
return new ProgressReporter();
|
|
245
|
+
}
|
|
246
|
+
var InstallProgress = class {
|
|
247
|
+
interval = null;
|
|
248
|
+
progress = 0;
|
|
249
|
+
packages = [
|
|
250
|
+
"react",
|
|
251
|
+
"react-dom",
|
|
252
|
+
"vite",
|
|
253
|
+
"@izi-noir/sdk",
|
|
254
|
+
"@noir-lang/acvm_js",
|
|
255
|
+
"@noir-lang/noirc_abi",
|
|
256
|
+
"prism-react-renderer",
|
|
257
|
+
"typescript"
|
|
258
|
+
];
|
|
259
|
+
currentPackage = 0;
|
|
260
|
+
start() {
|
|
261
|
+
process.stdout.write("\x1B[?25l");
|
|
262
|
+
const barWidth = 30;
|
|
263
|
+
const frames2 = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
264
|
+
let frameIndex = 0;
|
|
265
|
+
this.interval = setInterval(() => {
|
|
266
|
+
const frame = pc3.cyan(frames2[frameIndex]);
|
|
267
|
+
const filled = Math.floor(this.progress / 100 * barWidth);
|
|
268
|
+
const empty = barWidth - filled;
|
|
269
|
+
const bar = pc3.green("\u2588".repeat(filled)) + pc3.dim("\u2591".repeat(empty));
|
|
270
|
+
const pkg = this.packages[this.currentPackage % this.packages.length];
|
|
271
|
+
process.stdout.write(
|
|
272
|
+
`\r${frame} Installing dependencies ${bar} ${pc3.dim(pkg)} `
|
|
273
|
+
);
|
|
274
|
+
frameIndex = (frameIndex + 1) % frames2.length;
|
|
275
|
+
if (this.progress < 95) {
|
|
276
|
+
this.progress += Math.random() * 3;
|
|
277
|
+
if (this.progress > (this.currentPackage + 1) * 12) {
|
|
278
|
+
this.currentPackage++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}, 100);
|
|
282
|
+
}
|
|
283
|
+
stop(success = true) {
|
|
284
|
+
if (this.interval) {
|
|
285
|
+
clearInterval(this.interval);
|
|
286
|
+
this.interval = null;
|
|
287
|
+
}
|
|
288
|
+
process.stdout.write("\x1B[?25h");
|
|
289
|
+
process.stdout.write("\r\x1B[K");
|
|
290
|
+
const icon = success ? pc3.green("\u2713") : pc3.red("\u2717");
|
|
291
|
+
const message = success ? "Dependencies installed" : "Failed to install dependencies";
|
|
292
|
+
console.log(`${icon} ${message}`);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
function createInstallProgress() {
|
|
296
|
+
return new InstallProgress();
|
|
297
|
+
}
|
|
298
|
+
|
|
156
299
|
// src/generators/package-json.ts
|
|
157
300
|
function generatePackageJson(options) {
|
|
301
|
+
const isSolana = options.provider === "arkworks";
|
|
302
|
+
const dependencies = {
|
|
303
|
+
"@izi-noir/sdk": "^0.1.4",
|
|
304
|
+
"@noir-lang/acvm_js": "1.0.0-beta.13-1d260df.nightly",
|
|
305
|
+
"@noir-lang/noirc_abi": "1.0.0-beta.13-1d260df.nightly",
|
|
306
|
+
"react": "^18.3.1",
|
|
307
|
+
"react-dom": "^18.3.1",
|
|
308
|
+
"prism-react-renderer": "^2.4.1"
|
|
309
|
+
};
|
|
310
|
+
if (isSolana) {
|
|
311
|
+
dependencies["@solana/wallet-adapter-react"] = "^0.15.0";
|
|
312
|
+
dependencies["@solana/wallet-adapter-react-ui"] = "^0.9.0";
|
|
313
|
+
dependencies["@solana/wallet-adapter-wallets"] = "^0.19.0";
|
|
314
|
+
dependencies["@solana/web3.js"] = "^1.95.0";
|
|
315
|
+
}
|
|
158
316
|
const pkg = {
|
|
159
317
|
name: options.projectName,
|
|
160
318
|
version: "0.1.0",
|
|
319
|
+
private: true,
|
|
161
320
|
description: "ZK circuits built with IZI-NOIR",
|
|
162
321
|
type: "module",
|
|
163
|
-
main: "./dist/index.js",
|
|
164
|
-
types: "./dist/index.d.ts",
|
|
165
|
-
exports: {
|
|
166
|
-
".": {
|
|
167
|
-
types: "./dist/index.d.ts",
|
|
168
|
-
import: "./dist/index.js"
|
|
169
|
-
},
|
|
170
|
-
"./circuits": {
|
|
171
|
-
types: "./circuits/index.d.ts",
|
|
172
|
-
import: "./circuits/index.js"
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
files: ["dist", "circuits", "generated"],
|
|
176
322
|
scripts: {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
},
|
|
182
|
-
dependencies: {
|
|
183
|
-
"@izi-noir/sdk": "^0.1.4"
|
|
323
|
+
dev: "vite",
|
|
324
|
+
build: "vite build",
|
|
325
|
+
preview: "vite preview",
|
|
326
|
+
typecheck: "tsc --noEmit"
|
|
184
327
|
},
|
|
328
|
+
dependencies,
|
|
185
329
|
devDependencies: {
|
|
186
|
-
"@types/
|
|
187
|
-
|
|
188
|
-
|
|
330
|
+
"@types/react": "^18.3.0",
|
|
331
|
+
"@types/react-dom": "^18.3.0",
|
|
332
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
333
|
+
"typescript": "^5.4.0",
|
|
334
|
+
"vite": "^5.4.0"
|
|
189
335
|
},
|
|
190
|
-
keywords: ["zk", "noir", "zero-knowledge", "privacy", "solana"],
|
|
336
|
+
keywords: ["zk", "noir", "zero-knowledge", "privacy", isSolana ? "solana" : "evm"],
|
|
191
337
|
license: "MIT"
|
|
192
338
|
};
|
|
193
339
|
return JSON.stringify(pkg, null, 2);
|
|
@@ -197,50 +343,35 @@ function generatePackageJson(options) {
|
|
|
197
343
|
function generateTsconfig() {
|
|
198
344
|
const config = {
|
|
199
345
|
compilerOptions: {
|
|
200
|
-
target: "
|
|
346
|
+
target: "ES2020",
|
|
347
|
+
useDefineForClassFields: true,
|
|
348
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
201
349
|
module: "ESNext",
|
|
350
|
+
skipLibCheck: true,
|
|
351
|
+
// Bundler mode
|
|
202
352
|
moduleResolution: "bundler",
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
353
|
+
allowImportingTsExtensions: true,
|
|
354
|
+
isolatedModules: true,
|
|
355
|
+
moduleDetection: "force",
|
|
356
|
+
noEmit: true,
|
|
357
|
+
jsx: "react-jsx",
|
|
358
|
+
// Linting
|
|
206
359
|
strict: true,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
360
|
+
noUnusedLocals: true,
|
|
361
|
+
noUnusedParameters: true,
|
|
362
|
+
noFallthroughCasesInSwitch: true,
|
|
363
|
+
// Paths
|
|
364
|
+
baseUrl: ".",
|
|
365
|
+
paths: {
|
|
366
|
+
"@/*": ["./src/*"]
|
|
367
|
+
}
|
|
214
368
|
},
|
|
215
|
-
include: ["
|
|
216
|
-
|
|
369
|
+
include: ["src", "circuits"],
|
|
370
|
+
references: [{ path: "./tsconfig.node.json" }]
|
|
217
371
|
};
|
|
218
372
|
return JSON.stringify(config, null, 2);
|
|
219
373
|
}
|
|
220
374
|
|
|
221
|
-
// src/generators/config.ts
|
|
222
|
-
function generateConfig(options) {
|
|
223
|
-
return `import { defineConfig } from '@izi-noir/sdk';
|
|
224
|
-
|
|
225
|
-
export default defineConfig({
|
|
226
|
-
// Directory containing your circuit files
|
|
227
|
-
circuitsDir: './circuits',
|
|
228
|
-
|
|
229
|
-
// Output directory for compiled circuits
|
|
230
|
-
outDir: './generated',
|
|
231
|
-
|
|
232
|
-
// Proving provider to use
|
|
233
|
-
provider: '${options.provider}',
|
|
234
|
-
|
|
235
|
-
// Enable watch mode optimizations
|
|
236
|
-
watch: {
|
|
237
|
-
// Debounce file changes (ms)
|
|
238
|
-
debounce: 100,
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
`;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
375
|
// src/generators/circuits.ts
|
|
245
376
|
function generateBalanceProof() {
|
|
246
377
|
return `/**
|
|
@@ -328,133 +459,49 @@ declare function assert(condition: boolean): void;
|
|
|
328
459
|
`;
|
|
329
460
|
}
|
|
330
461
|
|
|
331
|
-
// src/generators/scripts.ts
|
|
332
|
-
function generateTestScript(options) {
|
|
333
|
-
const imports = getImports(options.template);
|
|
334
|
-
const tests = getTests(options.template);
|
|
335
|
-
return `/**
|
|
336
|
-
* Test script for ZK proofs
|
|
337
|
-
*
|
|
338
|
-
* Run with: npm test
|
|
339
|
-
*/
|
|
340
|
-
import { IziNoir, Provider, AcornParser, generateNoir, type CircuitFunction } from '@izi-noir/sdk';
|
|
341
|
-
${imports}
|
|
342
|
-
|
|
343
|
-
// Helper to convert JS circuit function to Noir code
|
|
344
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
345
|
-
function toNoir(circuitFn: any): string {
|
|
346
|
-
const parser = new AcornParser();
|
|
347
|
-
const parsed = parser.parse(circuitFn as CircuitFunction, [], []);
|
|
348
|
-
return generateNoir(parsed);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
async function main() {
|
|
352
|
-
console.log('Initializing IZI-NOIR...');
|
|
353
|
-
const izi = await IziNoir.init({
|
|
354
|
-
provider: Provider.${capitalizeFirst(options.provider)},
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
${tests}
|
|
358
|
-
|
|
359
|
-
console.log('\\n\u2713 All proofs verified successfully!');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
main().catch((error) => {
|
|
363
|
-
console.error('Error:', error);
|
|
364
|
-
process.exit(1);
|
|
365
|
-
});
|
|
366
|
-
`;
|
|
367
|
-
}
|
|
368
|
-
function getImports(template) {
|
|
369
|
-
switch (template) {
|
|
370
|
-
case "minimal":
|
|
371
|
-
return `import { myCircuit } from '../circuits/index.js';`;
|
|
372
|
-
case "balance-proof":
|
|
373
|
-
return `import { balanceProof } from '../circuits/index.js';`;
|
|
374
|
-
default:
|
|
375
|
-
return `import { balanceProof, ageProof } from '../circuits/index.js';`;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
function getTests(template) {
|
|
379
|
-
switch (template) {
|
|
380
|
-
case "minimal":
|
|
381
|
-
return ` // Test: myCircuit
|
|
382
|
-
console.log('\\nTesting myCircuit...');
|
|
383
|
-
const noirCode1 = toNoir(myCircuit);
|
|
384
|
-
const result1 = await izi.createProof(noirCode1, {
|
|
385
|
-
publicInput: '42', // public: expected value
|
|
386
|
-
privateInput: '42', // private: actual value
|
|
387
|
-
});
|
|
388
|
-
console.log(' Proof verified:', result1.verified);`;
|
|
389
|
-
case "balance-proof":
|
|
390
|
-
return ` // Test: Balance Proof
|
|
391
|
-
console.log('\\nTesting balanceProof...');
|
|
392
|
-
const noirCode1 = toNoir(balanceProof);
|
|
393
|
-
const result1 = await izi.createProof(noirCode1, {
|
|
394
|
-
threshold: '100', // public: minimum required
|
|
395
|
-
balance: '1500', // private: actual balance
|
|
396
|
-
});
|
|
397
|
-
console.log(' Proof verified:', result1.verified);
|
|
398
|
-
console.log(' The prover has >= 100 balance (actual: hidden)');`;
|
|
399
|
-
default:
|
|
400
|
-
return ` // Test 1: Balance Proof
|
|
401
|
-
console.log('\\nTesting balanceProof...');
|
|
402
|
-
const noirCode1 = toNoir(balanceProof);
|
|
403
|
-
const result1 = await izi.createProof(noirCode1, {
|
|
404
|
-
threshold: '100', // public: minimum required
|
|
405
|
-
balance: '1500', // private: actual balance
|
|
406
|
-
});
|
|
407
|
-
console.log(' Proof verified:', result1.verified);
|
|
408
|
-
console.log(' The prover has >= 100 balance (actual: hidden)');
|
|
409
|
-
|
|
410
|
-
// Test 2: Age Proof
|
|
411
|
-
console.log('\\nTesting ageProof...');
|
|
412
|
-
const noirCode2 = toNoir(ageProof);
|
|
413
|
-
const result2 = await izi.createProof(noirCode2, {
|
|
414
|
-
currentYear: '2024', // public: current year
|
|
415
|
-
minAge: '18', // public: minimum age
|
|
416
|
-
birthYear: '1990', // private: birth year
|
|
417
|
-
});
|
|
418
|
-
console.log(' Proof verified:', result2.verified);
|
|
419
|
-
console.log(' The prover is >= 18 years old (birth year: hidden)');`;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
function capitalizeFirst(str) {
|
|
423
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
462
|
// src/generators/readme.ts
|
|
427
463
|
function generateReadme(options) {
|
|
464
|
+
const isSolana = options.provider === "arkworks";
|
|
465
|
+
const networkInfo = isSolana ? "- Deploy VK to Solana devnet\n- Verify proofs on-chain" : "- Local proof verification";
|
|
428
466
|
return `# ${options.projectName}
|
|
429
467
|
|
|
430
|
-
ZK
|
|
468
|
+
ZK proof demo built with [IZI-NOIR](https://github.com/izi-noir/izi-noir) and React.
|
|
431
469
|
|
|
432
470
|
## Getting Started
|
|
433
471
|
|
|
434
472
|
\`\`\`bash
|
|
435
|
-
#
|
|
436
|
-
npm
|
|
437
|
-
|
|
438
|
-
# Run tests
|
|
439
|
-
npm test
|
|
473
|
+
# Install dependencies
|
|
474
|
+
npm install
|
|
440
475
|
|
|
441
|
-
#
|
|
476
|
+
# Start development server
|
|
442
477
|
npm run dev
|
|
478
|
+
|
|
479
|
+
# Build for production
|
|
480
|
+
npm run build
|
|
443
481
|
\`\`\`
|
|
444
482
|
|
|
483
|
+
Then open http://localhost:5173 in your browser.
|
|
484
|
+
|
|
485
|
+
## Features
|
|
486
|
+
|
|
487
|
+
- Interactive circuit selection
|
|
488
|
+
- Real-time proof generation
|
|
489
|
+
- Syntax-highlighted code display
|
|
490
|
+
${networkInfo}
|
|
491
|
+
|
|
445
492
|
## Project Structure
|
|
446
493
|
|
|
447
494
|
\`\`\`
|
|
448
495
|
${options.projectName}/
|
|
449
|
-
\u251C\u2500\u2500
|
|
450
|
-
\u2502 \u251C\u2500\u2500
|
|
451
|
-
\u2502 \
|
|
452
|
-
\u251C\u2500\u2500
|
|
453
|
-
\u2502 \
|
|
454
|
-
\
|
|
455
|
-
\u251C\u2500\u2500
|
|
456
|
-
\u2502 \u2514\u2500\u2500
|
|
457
|
-
\u251C\u2500\u2500
|
|
496
|
+
\u251C\u2500\u2500 src/
|
|
497
|
+
\u2502 \u251C\u2500\u2500 App.tsx # Main demo component
|
|
498
|
+
\u2502 \u251C\u2500\u2500 main.tsx # React entry point
|
|
499
|
+
\u2502 \u251C\u2500\u2500 components/ # Reusable components
|
|
500
|
+
\u2502 \u2514\u2500\u2500 lib/ # Utility functions
|
|
501
|
+
\u251C\u2500\u2500 circuits/ # Your ZK circuit definitions
|
|
502
|
+
\u2502 \u251C\u2500\u2500 *.ts # Circuits as JS functions with assert()
|
|
503
|
+
\u2502 \u2514\u2500\u2500 index.ts # Re-exports
|
|
504
|
+
\u251C\u2500\u2500 vite.config.ts # Vite configuration
|
|
458
505
|
\u2514\u2500\u2500 package.json
|
|
459
506
|
\`\`\`
|
|
460
507
|
|
|
@@ -472,35 +519,16 @@ export function myCircuit(
|
|
|
472
519
|
}
|
|
473
520
|
\`\`\`
|
|
474
521
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
import { IziNoir, Provider } from '@izi-noir/sdk';
|
|
479
|
-
import { myCircuit } from '${options.projectName}/circuits';
|
|
480
|
-
|
|
481
|
-
const izi = await IziNoir.init({ provider: Provider.Arkworks });
|
|
482
|
-
|
|
483
|
-
const proof = await izi.createProof(
|
|
484
|
-
myCircuit,
|
|
485
|
-
[100], // public inputs
|
|
486
|
-
[1500] // private inputs (hidden)
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
console.log(proof.verified); // true
|
|
490
|
-
\`\`\`
|
|
491
|
-
|
|
492
|
-
## Publishing
|
|
493
|
-
|
|
494
|
-
\`\`\`bash
|
|
495
|
-
npm publish
|
|
496
|
-
\`\`\`
|
|
497
|
-
|
|
498
|
-
Your circuits can then be installed as a dependency in other projects.
|
|
522
|
+
After adding a new circuit:
|
|
523
|
+
1. Export it from \`circuits/index.ts\`
|
|
524
|
+
2. Add it to the CIRCUITS array in \`src/App.tsx\`
|
|
499
525
|
|
|
500
526
|
## Learn More
|
|
501
527
|
|
|
502
528
|
- [IZI-NOIR Documentation](https://github.com/izi-noir/izi-noir)
|
|
503
529
|
- [Noir Language](https://noir-lang.org)
|
|
530
|
+
- [Vite](https://vitejs.dev)
|
|
531
|
+
- [React](https://react.dev)
|
|
504
532
|
`;
|
|
505
533
|
}
|
|
506
534
|
|
|
@@ -537,6 +565,973 @@ coverage/
|
|
|
537
565
|
`;
|
|
538
566
|
}
|
|
539
567
|
|
|
568
|
+
// src/generators/vite.ts
|
|
569
|
+
function generateViteConfig() {
|
|
570
|
+
return `import { defineConfig, type PluginOption } from "vite";
|
|
571
|
+
import react from "@vitejs/plugin-react";
|
|
572
|
+
import path from "path";
|
|
573
|
+
|
|
574
|
+
// Plugin to set correct MIME type for WASM files in dev server
|
|
575
|
+
function wasmMimePlugin(): PluginOption {
|
|
576
|
+
return {
|
|
577
|
+
name: "wasm-mime-type",
|
|
578
|
+
configureServer(server) {
|
|
579
|
+
server.middlewares.use((req, res, next) => {
|
|
580
|
+
if (req.url?.endsWith(".wasm")) {
|
|
581
|
+
res.setHeader("Content-Type", "application/wasm");
|
|
582
|
+
}
|
|
583
|
+
next();
|
|
584
|
+
});
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export default defineConfig({
|
|
590
|
+
plugins: [react(), wasmMimePlugin()],
|
|
591
|
+
resolve: {
|
|
592
|
+
alias: {
|
|
593
|
+
"@": path.resolve(__dirname, "./src"),
|
|
594
|
+
buffer: "buffer/",
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
define: {
|
|
598
|
+
"global": "globalThis",
|
|
599
|
+
},
|
|
600
|
+
optimizeDeps: {
|
|
601
|
+
exclude: ["@noir-lang/noir_wasm", "@aztec/bb.js", "@izi-noir/sdk"],
|
|
602
|
+
include: ["buffer"],
|
|
603
|
+
},
|
|
604
|
+
build: {
|
|
605
|
+
rollupOptions: {
|
|
606
|
+
external: [
|
|
607
|
+
/arkworks_groth16_wasm/,
|
|
608
|
+
],
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
`;
|
|
613
|
+
}
|
|
614
|
+
function generateTsconfigNode() {
|
|
615
|
+
return `{
|
|
616
|
+
"compilerOptions": {
|
|
617
|
+
"composite": true,
|
|
618
|
+
"skipLibCheck": true,
|
|
619
|
+
"module": "ESNext",
|
|
620
|
+
"moduleResolution": "bundler",
|
|
621
|
+
"allowSyntheticDefaultImports": true,
|
|
622
|
+
"strict": true
|
|
623
|
+
},
|
|
624
|
+
"include": ["vite.config.ts"]
|
|
625
|
+
}
|
|
626
|
+
`;
|
|
627
|
+
}
|
|
628
|
+
function generateIndexHtml(options) {
|
|
629
|
+
return `<!doctype html>
|
|
630
|
+
<html lang="en">
|
|
631
|
+
<head>
|
|
632
|
+
<meta charset="UTF-8" />
|
|
633
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
634
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
635
|
+
<title>${options.projectName} - ZK Proof Demo</title>
|
|
636
|
+
</head>
|
|
637
|
+
<body>
|
|
638
|
+
<div id="root"></div>
|
|
639
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
640
|
+
</body>
|
|
641
|
+
</html>
|
|
642
|
+
`;
|
|
643
|
+
}
|
|
644
|
+
function generateMainTsx(options) {
|
|
645
|
+
const isSolana = options.provider === "arkworks";
|
|
646
|
+
if (isSolana) {
|
|
647
|
+
return `import { StrictMode } from 'react';
|
|
648
|
+
import { createRoot } from 'react-dom/client';
|
|
649
|
+
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
|
|
650
|
+
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
|
|
651
|
+
import { clusterApiUrl } from '@solana/web3.js';
|
|
652
|
+
import App from './App';
|
|
653
|
+
import './index.css';
|
|
654
|
+
import '@solana/wallet-adapter-react-ui/styles.css';
|
|
655
|
+
|
|
656
|
+
const endpoint = clusterApiUrl('devnet');
|
|
657
|
+
|
|
658
|
+
createRoot(document.getElementById('root')!).render(
|
|
659
|
+
<StrictMode>
|
|
660
|
+
<ConnectionProvider endpoint={endpoint}>
|
|
661
|
+
<WalletProvider wallets={[]} autoConnect>
|
|
662
|
+
<WalletModalProvider>
|
|
663
|
+
<App />
|
|
664
|
+
</WalletModalProvider>
|
|
665
|
+
</WalletProvider>
|
|
666
|
+
</ConnectionProvider>
|
|
667
|
+
</StrictMode>,
|
|
668
|
+
);
|
|
669
|
+
`;
|
|
670
|
+
}
|
|
671
|
+
return `import { StrictMode } from 'react';
|
|
672
|
+
import { createRoot } from 'react-dom/client';
|
|
673
|
+
import App from './App';
|
|
674
|
+
import './index.css';
|
|
675
|
+
|
|
676
|
+
createRoot(document.getElementById('root')!).render(
|
|
677
|
+
<StrictMode>
|
|
678
|
+
<App />
|
|
679
|
+
</StrictMode>,
|
|
680
|
+
);
|
|
681
|
+
`;
|
|
682
|
+
}
|
|
683
|
+
function generateAppTsx(options) {
|
|
684
|
+
const isSolana = options.provider === "arkworks";
|
|
685
|
+
const circuitImports = getCircuitImports(options.template);
|
|
686
|
+
const circuitOptions = getCircuitOptions(options.template);
|
|
687
|
+
const solanaImports = isSolana ? `
|
|
688
|
+
import { useWallet } from '@solana/wallet-adapter-react';
|
|
689
|
+
import { useWalletModal } from '@solana/wallet-adapter-react-ui';
|
|
690
|
+
import { Chain, Network } from '@izi-noir/sdk';` : "";
|
|
691
|
+
const solanaHooks = isSolana ? `
|
|
692
|
+
const { publicKey, connected, sendTransaction } = useWallet();
|
|
693
|
+
const { setVisible } = useWalletModal();` : "";
|
|
694
|
+
const solanaProviderConfig = isSolana ? `
|
|
695
|
+
chain: Chain.Solana,
|
|
696
|
+
network: Network.Devnet,` : "";
|
|
697
|
+
const solanaState = isSolana ? `
|
|
698
|
+
// Deploy state
|
|
699
|
+
const [isDeploying, setIsDeploying] = useState(false);
|
|
700
|
+
const [vkAccount, setVkAccount] = useState<string | null>(null);
|
|
701
|
+
const [deployError, setDeployError] = useState<string | null>(null);
|
|
702
|
+
|
|
703
|
+
// Verify state
|
|
704
|
+
const [isVerifying, setIsVerifying] = useState(false);
|
|
705
|
+
const [verified, setVerified] = useState<boolean | null>(null);
|
|
706
|
+
const [verifyError, setVerifyError] = useState<string | null>(null);` : "";
|
|
707
|
+
const solanaHandlers = isSolana ? `
|
|
708
|
+
// Deploy VK to Solana
|
|
709
|
+
const handleDeploy = async () => {
|
|
710
|
+
if (!iziInstance || !connected || !publicKey || !sendTransaction) return;
|
|
711
|
+
|
|
712
|
+
setIsDeploying(true);
|
|
713
|
+
setDeployError(null);
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
const result = await iziInstance.deploy({ publicKey, sendTransaction });
|
|
717
|
+
setVkAccount(result.vkAccount);
|
|
718
|
+
} catch (error) {
|
|
719
|
+
console.error('Deploy error:', error);
|
|
720
|
+
setDeployError((error as Error).message);
|
|
721
|
+
} finally {
|
|
722
|
+
setIsDeploying(false);
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// Verify proof on-chain
|
|
727
|
+
const handleVerify = async () => {
|
|
728
|
+
if (!iziInstance || !vkAccount || !publicKey || !sendTransaction) return;
|
|
729
|
+
|
|
730
|
+
setIsVerifying(true);
|
|
731
|
+
setVerifyError(null);
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
const result = await iziInstance.verifyOnChain({ publicKey, sendTransaction }, vkAccount);
|
|
735
|
+
setVerified(result.verified);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.error('Verify error:', error);
|
|
738
|
+
setVerifyError((error as Error).message);
|
|
739
|
+
} finally {
|
|
740
|
+
setIsVerifying(false);
|
|
741
|
+
}
|
|
742
|
+
};` : "";
|
|
743
|
+
const solanaDeploySection = isSolana ? `
|
|
744
|
+
{/* Deploy & Verify Section */}
|
|
745
|
+
{proof && (
|
|
746
|
+
<div className="section">
|
|
747
|
+
<h2>Deploy & Verify on Solana</h2>
|
|
748
|
+
|
|
749
|
+
{!connected ? (
|
|
750
|
+
<button onClick={() => setVisible(true)} className="btn btn-secondary">
|
|
751
|
+
Connect Wallet
|
|
752
|
+
</button>
|
|
753
|
+
) : (
|
|
754
|
+
<div className="deploy-verify-row">
|
|
755
|
+
<div className="deploy-box">
|
|
756
|
+
<button
|
|
757
|
+
onClick={handleDeploy}
|
|
758
|
+
disabled={isDeploying || !!vkAccount}
|
|
759
|
+
className="btn btn-primary"
|
|
760
|
+
>
|
|
761
|
+
{isDeploying ? 'Deploying...' : vkAccount ? 'Deployed' : 'Deploy VK'}
|
|
762
|
+
</button>
|
|
763
|
+
{deployError && <p className="error">{deployError}</p>}
|
|
764
|
+
{vkAccount && <p className="success">VK: {vkAccount.slice(0, 8)}...</p>}
|
|
765
|
+
</div>
|
|
766
|
+
|
|
767
|
+
<div className="verify-box">
|
|
768
|
+
<button
|
|
769
|
+
onClick={handleVerify}
|
|
770
|
+
disabled={!vkAccount || isVerifying}
|
|
771
|
+
className="btn btn-primary"
|
|
772
|
+
>
|
|
773
|
+
{isVerifying ? 'Verifying...' : verified ? 'Verified!' : 'Verify On-Chain'}
|
|
774
|
+
</button>
|
|
775
|
+
{verifyError && <p className="error">{verifyError}</p>}
|
|
776
|
+
{verified && <p className="success">Proof verified on Solana!</p>}
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
)}
|
|
780
|
+
</div>
|
|
781
|
+
)}` : "";
|
|
782
|
+
return `import { useState, useEffect, useCallback } from 'react';
|
|
783
|
+
import initNoirC from '@noir-lang/noirc_abi';
|
|
784
|
+
import initACVM from '@noir-lang/acvm_js';
|
|
785
|
+
import acvm from '@noir-lang/acvm_js/web/acvm_js_bg.wasm?url';
|
|
786
|
+
import noirc from '@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm?url';
|
|
787
|
+
import {
|
|
788
|
+
IziNoir,
|
|
789
|
+
Provider,
|
|
790
|
+
markWasmInitialized,
|
|
791
|
+
AcornParser,
|
|
792
|
+
generateNoir,
|
|
793
|
+
} from '@izi-noir/sdk';${solanaImports}
|
|
794
|
+
import { CodeBlock } from './components/CodeBlock';
|
|
795
|
+
${circuitImports}
|
|
796
|
+
import './App.css';
|
|
797
|
+
|
|
798
|
+
// Circuit definition type
|
|
799
|
+
interface CircuitDef {
|
|
800
|
+
name: string;
|
|
801
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
802
|
+
fn: any;
|
|
803
|
+
publicInputKeys: string[];
|
|
804
|
+
privateInputKeys: string[];
|
|
805
|
+
defaultInputs: Record<string, string>;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Circuit options
|
|
809
|
+
const CIRCUITS: CircuitDef[] = ${circuitOptions};
|
|
810
|
+
|
|
811
|
+
// WASM initialization
|
|
812
|
+
let wasmInitialized = false;
|
|
813
|
+
async function initBrowserWasm() {
|
|
814
|
+
if (wasmInitialized) return;
|
|
815
|
+
await Promise.all([
|
|
816
|
+
initACVM({ module_or_path: acvm }),
|
|
817
|
+
initNoirC({ module_or_path: noirc }),
|
|
818
|
+
]);
|
|
819
|
+
markWasmInitialized();
|
|
820
|
+
wasmInitialized = true;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function App() {
|
|
824
|
+
// Circuit state
|
|
825
|
+
const [selectedCircuit, setSelectedCircuit] = useState(CIRCUITS[0].name);
|
|
826
|
+
const [circuitCode, setCircuitCode] = useState(CIRCUITS[0].fn.toString());
|
|
827
|
+
const [noirCode, setNoirCode] = useState<string | null>(null);
|
|
828
|
+
const [transpileError, setTranspileError] = useState<string | null>(null);
|
|
829
|
+
|
|
830
|
+
// Input state
|
|
831
|
+
const [inputs, setInputs] = useState<Record<string, string>>(CIRCUITS[0].defaultInputs);
|
|
832
|
+
|
|
833
|
+
// Proof state
|
|
834
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
835
|
+
const [proof, setProof] = useState<Uint8Array | null>(null);
|
|
836
|
+
const [proofTime, setProofTime] = useState<number | null>(null);
|
|
837
|
+
const [proofError, setProofError] = useState<string | null>(null);
|
|
838
|
+
const [localVerified, setLocalVerified] = useState<boolean | null>(null);
|
|
839
|
+
|
|
840
|
+
// IziNoir instance
|
|
841
|
+
const [iziInstance, setIziInstance] = useState<IziNoir | null>(null);
|
|
842
|
+
${solanaHooks}
|
|
843
|
+
${solanaState}
|
|
844
|
+
|
|
845
|
+
// Update circuit when selection changes
|
|
846
|
+
useEffect(() => {
|
|
847
|
+
const circuit = CIRCUITS.find(c => c.name === selectedCircuit);
|
|
848
|
+
if (circuit) {
|
|
849
|
+
setCircuitCode(circuit.fn.toString());
|
|
850
|
+
setInputs(circuit.defaultInputs);
|
|
851
|
+
// Reset proof state
|
|
852
|
+
setProof(null);
|
|
853
|
+
setProofTime(null);
|
|
854
|
+
setLocalVerified(null);
|
|
855
|
+
setNoirCode(null);
|
|
856
|
+
}
|
|
857
|
+
}, [selectedCircuit]);
|
|
858
|
+
|
|
859
|
+
// Transpile circuit to Noir
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
const transpileCode = () => {
|
|
862
|
+
try {
|
|
863
|
+
setTranspileError(null);
|
|
864
|
+
const circuit = CIRCUITS.find(c => c.name === selectedCircuit);
|
|
865
|
+
if (!circuit) return;
|
|
866
|
+
|
|
867
|
+
const parser = new AcornParser();
|
|
868
|
+
const publicInputs = Object.entries(inputs)
|
|
869
|
+
.filter(([key]) => circuit.publicInputKeys.includes(key))
|
|
870
|
+
.map(([, val]) => Number(val));
|
|
871
|
+
const privateInputs = Object.entries(inputs)
|
|
872
|
+
.filter(([key]) => circuit.privateInputKeys.includes(key))
|
|
873
|
+
.map(([, val]) => Number(val));
|
|
874
|
+
|
|
875
|
+
const parsedCircuit = parser.parse(circuit.fn, publicInputs, privateInputs);
|
|
876
|
+
const result = generateNoir(parsedCircuit);
|
|
877
|
+
setNoirCode(result);
|
|
878
|
+
} catch (error) {
|
|
879
|
+
setTranspileError((error as Error).message);
|
|
880
|
+
setNoirCode(null);
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
const debounce = setTimeout(transpileCode, 300);
|
|
885
|
+
return () => clearTimeout(debounce);
|
|
886
|
+
}, [selectedCircuit, inputs]);
|
|
887
|
+
|
|
888
|
+
// Generate proof
|
|
889
|
+
const handleGenerateProof = useCallback(async () => {
|
|
890
|
+
if (!noirCode) return;
|
|
891
|
+
|
|
892
|
+
setIsGenerating(true);
|
|
893
|
+
setProofError(null);
|
|
894
|
+
setProof(null);
|
|
895
|
+
setProofTime(null);
|
|
896
|
+
setLocalVerified(null);
|
|
897
|
+
|
|
898
|
+
try {
|
|
899
|
+
await initBrowserWasm();
|
|
900
|
+
|
|
901
|
+
const startTime = performance.now();
|
|
902
|
+
|
|
903
|
+
const izi = await IziNoir.init({
|
|
904
|
+
provider: Provider.${capitalizeFirst(options.provider)},${solanaProviderConfig}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
await izi.compile(noirCode);
|
|
908
|
+
|
|
909
|
+
const proofResult = await izi.prove(inputs);
|
|
910
|
+
setIziInstance(izi);
|
|
911
|
+
|
|
912
|
+
// Get proof bytes
|
|
913
|
+
const proofBytes = 'bytes' in proofResult.proof
|
|
914
|
+
? proofResult.proof.bytes
|
|
915
|
+
: proofResult.proof;
|
|
916
|
+
const publicInputsHex = 'hex' in proofResult.publicInputs
|
|
917
|
+
? proofResult.publicInputs.hex
|
|
918
|
+
: proofResult.publicInputs;
|
|
919
|
+
|
|
920
|
+
// Local verification
|
|
921
|
+
const verified = await izi.verify(proofBytes, publicInputsHex);
|
|
922
|
+
setLocalVerified(verified);
|
|
923
|
+
|
|
924
|
+
const endTime = performance.now();
|
|
925
|
+
setProof(proofBytes);
|
|
926
|
+
setProofTime(Math.round(endTime - startTime));
|
|
927
|
+
} catch (error) {
|
|
928
|
+
console.error('Proof generation error:', error);
|
|
929
|
+
setProofError((error as Error).message);
|
|
930
|
+
} finally {
|
|
931
|
+
setIsGenerating(false);
|
|
932
|
+
}
|
|
933
|
+
}, [noirCode, inputs]);
|
|
934
|
+
${solanaHandlers}
|
|
935
|
+
|
|
936
|
+
return (
|
|
937
|
+
<div className="app">
|
|
938
|
+
<header>
|
|
939
|
+
<h1>${options.projectName}</h1>
|
|
940
|
+
<p>Zero-Knowledge Proof Demo</p>
|
|
941
|
+
</header>
|
|
942
|
+
|
|
943
|
+
<main>
|
|
944
|
+
{/* Circuit Selection */}
|
|
945
|
+
<div className="section">
|
|
946
|
+
<h2>1. Select Circuit</h2>
|
|
947
|
+
<select
|
|
948
|
+
value={selectedCircuit}
|
|
949
|
+
onChange={(e) => setSelectedCircuit(e.target.value)}
|
|
950
|
+
className="select"
|
|
951
|
+
>
|
|
952
|
+
{CIRCUITS.map((c) => (
|
|
953
|
+
<option key={c.name} value={c.name}>
|
|
954
|
+
{c.name}
|
|
955
|
+
</option>
|
|
956
|
+
))}
|
|
957
|
+
</select>
|
|
958
|
+
</div>
|
|
959
|
+
|
|
960
|
+
{/* Circuit Code */}
|
|
961
|
+
<div className="section">
|
|
962
|
+
<h2>2. Circuit Code</h2>
|
|
963
|
+
<CodeBlock code={circuitCode} language="typescript" />
|
|
964
|
+
|
|
965
|
+
{noirCode && (
|
|
966
|
+
<details className="noir-details">
|
|
967
|
+
<summary>View Generated Noir</summary>
|
|
968
|
+
<CodeBlock code={noirCode} language="rust" />
|
|
969
|
+
</details>
|
|
970
|
+
)}
|
|
971
|
+
|
|
972
|
+
{transpileError && (
|
|
973
|
+
<p className="error">{transpileError}</p>
|
|
974
|
+
)}
|
|
975
|
+
</div>
|
|
976
|
+
|
|
977
|
+
{/* Inputs */}
|
|
978
|
+
<div className="section">
|
|
979
|
+
<h2>3. Inputs</h2>
|
|
980
|
+
<div className="inputs-grid">
|
|
981
|
+
{Object.entries(inputs).map(([key, value]) => {
|
|
982
|
+
const circuit = CIRCUITS.find(c => c.name === selectedCircuit);
|
|
983
|
+
const isPublic = circuit?.publicInputKeys.includes(key);
|
|
984
|
+
return (
|
|
985
|
+
<div key={key} className="input-group">
|
|
986
|
+
<label>
|
|
987
|
+
<span className={\`input-badge \${isPublic ? 'public' : 'private'}\`}>
|
|
988
|
+
{isPublic ? 'public' : 'private'}
|
|
989
|
+
</span>
|
|
990
|
+
{key}
|
|
991
|
+
</label>
|
|
992
|
+
<input
|
|
993
|
+
type="number"
|
|
994
|
+
value={value}
|
|
995
|
+
onChange={(e) => setInputs({ ...inputs, [key]: e.target.value })}
|
|
996
|
+
/>
|
|
997
|
+
</div>
|
|
998
|
+
);
|
|
999
|
+
})}
|
|
1000
|
+
</div>
|
|
1001
|
+
</div>
|
|
1002
|
+
|
|
1003
|
+
{/* Generate Proof */}
|
|
1004
|
+
<div className="section">
|
|
1005
|
+
<h2>4. Generate Proof</h2>
|
|
1006
|
+
<button
|
|
1007
|
+
onClick={handleGenerateProof}
|
|
1008
|
+
disabled={isGenerating || !noirCode || !!transpileError}
|
|
1009
|
+
className="btn btn-primary"
|
|
1010
|
+
>
|
|
1011
|
+
{isGenerating ? 'Generating...' : 'Generate Proof'}
|
|
1012
|
+
</button>
|
|
1013
|
+
|
|
1014
|
+
{proofError && <p className="error">{proofError}</p>}
|
|
1015
|
+
|
|
1016
|
+
{proof && (
|
|
1017
|
+
<div className="results">
|
|
1018
|
+
<div className="result-card">
|
|
1019
|
+
<span className="result-value">{proof.length} bytes</span>
|
|
1020
|
+
<span className="result-label">Proof Size</span>
|
|
1021
|
+
</div>
|
|
1022
|
+
<div className="result-card">
|
|
1023
|
+
<span className="result-value">{proofTime} ms</span>
|
|
1024
|
+
<span className="result-label">Generation Time</span>
|
|
1025
|
+
</div>
|
|
1026
|
+
<div className="result-card">
|
|
1027
|
+
<span className={\`result-value \${localVerified ? 'success' : 'error'}\`}>
|
|
1028
|
+
{localVerified ? 'Yes' : 'No'}
|
|
1029
|
+
</span>
|
|
1030
|
+
<span className="result-label">Locally Verified</span>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
)}
|
|
1034
|
+
</div>
|
|
1035
|
+
${solanaDeploySection}
|
|
1036
|
+
</main>
|
|
1037
|
+
|
|
1038
|
+
<footer>
|
|
1039
|
+
<p>Built with <a href="https://github.com/izi-noir/izi-noir" target="_blank">IZI-NOIR</a></p>
|
|
1040
|
+
</footer>
|
|
1041
|
+
</div>
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
export default App;
|
|
1046
|
+
`;
|
|
1047
|
+
}
|
|
1048
|
+
function getCircuitImports(template) {
|
|
1049
|
+
switch (template) {
|
|
1050
|
+
case "minimal":
|
|
1051
|
+
return `import { myCircuit } from '../circuits';`;
|
|
1052
|
+
case "balance-proof":
|
|
1053
|
+
return `import { balanceProof } from '../circuits';`;
|
|
1054
|
+
default:
|
|
1055
|
+
return `import { balanceProof, ageProof } from '../circuits';`;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
function getCircuitOptions(template) {
|
|
1059
|
+
switch (template) {
|
|
1060
|
+
case "minimal":
|
|
1061
|
+
return `[
|
|
1062
|
+
{
|
|
1063
|
+
name: 'myCircuit',
|
|
1064
|
+
fn: myCircuit,
|
|
1065
|
+
publicInputKeys: ['publicInput'],
|
|
1066
|
+
privateInputKeys: ['privateInput'],
|
|
1067
|
+
defaultInputs: { publicInput: '42', privateInput: '42' },
|
|
1068
|
+
},
|
|
1069
|
+
]`;
|
|
1070
|
+
case "balance-proof":
|
|
1071
|
+
return `[
|
|
1072
|
+
{
|
|
1073
|
+
name: 'balanceProof',
|
|
1074
|
+
fn: balanceProof,
|
|
1075
|
+
publicInputKeys: ['threshold'],
|
|
1076
|
+
privateInputKeys: ['balance'],
|
|
1077
|
+
defaultInputs: { threshold: '100', balance: '1500' },
|
|
1078
|
+
},
|
|
1079
|
+
]`;
|
|
1080
|
+
default:
|
|
1081
|
+
return `[
|
|
1082
|
+
{
|
|
1083
|
+
name: 'balanceProof',
|
|
1084
|
+
fn: balanceProof,
|
|
1085
|
+
publicInputKeys: ['threshold'],
|
|
1086
|
+
privateInputKeys: ['balance'],
|
|
1087
|
+
defaultInputs: { threshold: '100', balance: '1500' },
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
name: 'ageProof',
|
|
1091
|
+
fn: ageProof,
|
|
1092
|
+
publicInputKeys: ['currentYear', 'minAge'],
|
|
1093
|
+
privateInputKeys: ['birthYear'],
|
|
1094
|
+
defaultInputs: { currentYear: '2024', minAge: '18', birthYear: '1990' },
|
|
1095
|
+
},
|
|
1096
|
+
]`;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function capitalizeFirst(str) {
|
|
1100
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1101
|
+
}
|
|
1102
|
+
function generateAppCss() {
|
|
1103
|
+
return `* {
|
|
1104
|
+
box-sizing: border-box;
|
|
1105
|
+
margin: 0;
|
|
1106
|
+
padding: 0;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
:root {
|
|
1110
|
+
--purple: #9945FF;
|
|
1111
|
+
--green: #14F195;
|
|
1112
|
+
--bg: #0a0a0a;
|
|
1113
|
+
--bg-elevated: #111111;
|
|
1114
|
+
--border: #222222;
|
|
1115
|
+
--text: #ffffff;
|
|
1116
|
+
--text-muted: #888888;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
body {
|
|
1120
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1121
|
+
background: var(--bg);
|
|
1122
|
+
color: var(--text);
|
|
1123
|
+
line-height: 1.6;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.app {
|
|
1127
|
+
min-height: 100vh;
|
|
1128
|
+
display: flex;
|
|
1129
|
+
flex-direction: column;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
header {
|
|
1133
|
+
padding: 2rem;
|
|
1134
|
+
text-align: center;
|
|
1135
|
+
border-bottom: 1px solid var(--border);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
header h1 {
|
|
1139
|
+
font-size: 2rem;
|
|
1140
|
+
background: linear-gradient(90deg, var(--purple), var(--green));
|
|
1141
|
+
-webkit-background-clip: text;
|
|
1142
|
+
-webkit-text-fill-color: transparent;
|
|
1143
|
+
background-clip: text;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
header p {
|
|
1147
|
+
color: var(--text-muted);
|
|
1148
|
+
margin-top: 0.5rem;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
main {
|
|
1152
|
+
flex: 1;
|
|
1153
|
+
max-width: 800px;
|
|
1154
|
+
margin: 0 auto;
|
|
1155
|
+
padding: 2rem;
|
|
1156
|
+
width: 100%;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.section {
|
|
1160
|
+
margin-bottom: 2rem;
|
|
1161
|
+
padding: 1.5rem;
|
|
1162
|
+
background: var(--bg-elevated);
|
|
1163
|
+
border: 1px solid var(--border);
|
|
1164
|
+
border-radius: 12px;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.section h2 {
|
|
1168
|
+
font-size: 1.25rem;
|
|
1169
|
+
margin-bottom: 1rem;
|
|
1170
|
+
color: var(--text);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.select {
|
|
1174
|
+
width: 100%;
|
|
1175
|
+
padding: 0.75rem;
|
|
1176
|
+
background: var(--bg);
|
|
1177
|
+
border: 1px solid var(--border);
|
|
1178
|
+
border-radius: 8px;
|
|
1179
|
+
color: var(--text);
|
|
1180
|
+
font-size: 1rem;
|
|
1181
|
+
cursor: pointer;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.select:focus {
|
|
1185
|
+
outline: none;
|
|
1186
|
+
border-color: var(--purple);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.inputs-grid {
|
|
1190
|
+
display: grid;
|
|
1191
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
1192
|
+
gap: 1rem;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.input-group {
|
|
1196
|
+
display: flex;
|
|
1197
|
+
flex-direction: column;
|
|
1198
|
+
gap: 0.5rem;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.input-group label {
|
|
1202
|
+
display: flex;
|
|
1203
|
+
align-items: center;
|
|
1204
|
+
gap: 0.5rem;
|
|
1205
|
+
font-size: 0.875rem;
|
|
1206
|
+
color: var(--text-muted);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.input-badge {
|
|
1210
|
+
font-size: 0.625rem;
|
|
1211
|
+
padding: 0.125rem 0.375rem;
|
|
1212
|
+
border-radius: 4px;
|
|
1213
|
+
text-transform: uppercase;
|
|
1214
|
+
font-weight: 600;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.input-badge.public {
|
|
1218
|
+
background: var(--green);
|
|
1219
|
+
color: #000;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
.input-badge.private {
|
|
1223
|
+
background: var(--purple);
|
|
1224
|
+
color: #fff;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
.input-group input {
|
|
1228
|
+
padding: 0.75rem;
|
|
1229
|
+
background: var(--bg);
|
|
1230
|
+
border: 1px solid var(--border);
|
|
1231
|
+
border-radius: 8px;
|
|
1232
|
+
color: var(--text);
|
|
1233
|
+
font-size: 1rem;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
.input-group input:focus {
|
|
1237
|
+
outline: none;
|
|
1238
|
+
border-color: var(--purple);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
.btn {
|
|
1242
|
+
padding: 0.75rem 1.5rem;
|
|
1243
|
+
font-size: 1rem;
|
|
1244
|
+
font-weight: 600;
|
|
1245
|
+
border: none;
|
|
1246
|
+
border-radius: 8px;
|
|
1247
|
+
cursor: pointer;
|
|
1248
|
+
transition: all 0.2s;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.btn:disabled {
|
|
1252
|
+
opacity: 0.5;
|
|
1253
|
+
cursor: not-allowed;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.btn-primary {
|
|
1257
|
+
background: linear-gradient(90deg, var(--purple), var(--green));
|
|
1258
|
+
color: white;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.btn-primary:hover:not(:disabled) {
|
|
1262
|
+
transform: translateY(-2px);
|
|
1263
|
+
box-shadow: 0 4px 20px rgba(153, 69, 255, 0.3);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.btn-secondary {
|
|
1267
|
+
background: var(--bg);
|
|
1268
|
+
border: 1px solid var(--border);
|
|
1269
|
+
color: var(--text);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.btn-secondary:hover:not(:disabled) {
|
|
1273
|
+
border-color: var(--purple);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
.results {
|
|
1277
|
+
display: grid;
|
|
1278
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1279
|
+
gap: 1rem;
|
|
1280
|
+
margin-top: 1rem;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.result-card {
|
|
1284
|
+
padding: 1rem;
|
|
1285
|
+
background: var(--bg);
|
|
1286
|
+
border: 1px solid var(--border);
|
|
1287
|
+
border-radius: 8px;
|
|
1288
|
+
text-align: center;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.result-value {
|
|
1292
|
+
font-size: 1.25rem;
|
|
1293
|
+
font-weight: 600;
|
|
1294
|
+
display: block;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
.result-value.success {
|
|
1298
|
+
color: var(--green);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.result-value.error {
|
|
1302
|
+
color: #ff4444;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.result-label {
|
|
1306
|
+
font-size: 0.75rem;
|
|
1307
|
+
color: var(--text-muted);
|
|
1308
|
+
text-transform: uppercase;
|
|
1309
|
+
margin-top: 0.25rem;
|
|
1310
|
+
display: block;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
.error {
|
|
1314
|
+
color: #ff4444;
|
|
1315
|
+
font-size: 0.875rem;
|
|
1316
|
+
margin-top: 0.5rem;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
.success {
|
|
1320
|
+
color: var(--green);
|
|
1321
|
+
font-size: 0.875rem;
|
|
1322
|
+
margin-top: 0.5rem;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
.noir-details {
|
|
1326
|
+
margin-top: 1rem;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
.noir-details summary {
|
|
1330
|
+
cursor: pointer;
|
|
1331
|
+
color: var(--text-muted);
|
|
1332
|
+
font-size: 0.875rem;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
.noir-details summary:hover {
|
|
1336
|
+
color: var(--text);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.deploy-verify-row {
|
|
1340
|
+
display: grid;
|
|
1341
|
+
grid-template-columns: 1fr 1fr;
|
|
1342
|
+
gap: 1rem;
|
|
1343
|
+
margin-top: 1rem;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.deploy-box,
|
|
1347
|
+
.verify-box {
|
|
1348
|
+
padding: 1rem;
|
|
1349
|
+
background: var(--bg);
|
|
1350
|
+
border: 1px solid var(--border);
|
|
1351
|
+
border-radius: 8px;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
footer {
|
|
1355
|
+
padding: 1.5rem;
|
|
1356
|
+
text-align: center;
|
|
1357
|
+
border-top: 1px solid var(--border);
|
|
1358
|
+
color: var(--text-muted);
|
|
1359
|
+
font-size: 0.875rem;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
footer a {
|
|
1363
|
+
color: var(--purple);
|
|
1364
|
+
text-decoration: none;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
footer a:hover {
|
|
1368
|
+
text-decoration: underline;
|
|
1369
|
+
}
|
|
1370
|
+
`;
|
|
1371
|
+
}
|
|
1372
|
+
function generateIndexCss() {
|
|
1373
|
+
return `body {
|
|
1374
|
+
margin: 0;
|
|
1375
|
+
-webkit-font-smoothing: antialiased;
|
|
1376
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1377
|
+
}
|
|
1378
|
+
`;
|
|
1379
|
+
}
|
|
1380
|
+
function generateViteEnvDts() {
|
|
1381
|
+
return `/// <reference types="vite/client" />
|
|
1382
|
+
`;
|
|
1383
|
+
}
|
|
1384
|
+
function generateViteSvg() {
|
|
1385
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFBD4F"></stop><stop offset="100%" stop-color="#FF980E"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
1386
|
+
`;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// src/generators/components.ts
|
|
1390
|
+
function generateCodeBlock() {
|
|
1391
|
+
return `import { Highlight, themes } from 'prism-react-renderer';
|
|
1392
|
+
|
|
1393
|
+
interface CodeBlockProps {
|
|
1394
|
+
code: string;
|
|
1395
|
+
language?: 'typescript' | 'javascript' | 'rust';
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
export function CodeBlock({ code, language = 'typescript' }: CodeBlockProps) {
|
|
1399
|
+
return (
|
|
1400
|
+
<Highlight theme={themes.nightOwl} code={code.trim()} language={language}>
|
|
1401
|
+
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
|
1402
|
+
<pre
|
|
1403
|
+
className={className}
|
|
1404
|
+
style={{
|
|
1405
|
+
...style,
|
|
1406
|
+
padding: '1rem',
|
|
1407
|
+
borderRadius: '8px',
|
|
1408
|
+
overflow: 'auto',
|
|
1409
|
+
fontSize: '0.875rem',
|
|
1410
|
+
lineHeight: '1.5',
|
|
1411
|
+
}}
|
|
1412
|
+
>
|
|
1413
|
+
{tokens.map((line, i) => (
|
|
1414
|
+
<div key={i} {...getLineProps({ line })}>
|
|
1415
|
+
<span style={{ color: '#666', marginRight: '1rem', userSelect: 'none' }}>
|
|
1416
|
+
{String(i + 1).padStart(2, ' ')}
|
|
1417
|
+
</span>
|
|
1418
|
+
{line.map((token, key) => (
|
|
1419
|
+
<span key={key} {...getTokenProps({ token })} />
|
|
1420
|
+
))}
|
|
1421
|
+
</div>
|
|
1422
|
+
))}
|
|
1423
|
+
</pre>
|
|
1424
|
+
)}
|
|
1425
|
+
</Highlight>
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
`;
|
|
1429
|
+
}
|
|
1430
|
+
function generateEditableCodeBlock() {
|
|
1431
|
+
return `import { useRef, useEffect, useState } from 'react';
|
|
1432
|
+
import { Highlight, themes } from 'prism-react-renderer';
|
|
1433
|
+
|
|
1434
|
+
interface EditableCodeBlockProps {
|
|
1435
|
+
code: string;
|
|
1436
|
+
onChange: (code: string) => void;
|
|
1437
|
+
language?: 'typescript' | 'javascript';
|
|
1438
|
+
rows?: number;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
export function EditableCodeBlock({
|
|
1442
|
+
code,
|
|
1443
|
+
onChange,
|
|
1444
|
+
language = 'typescript',
|
|
1445
|
+
rows = 8,
|
|
1446
|
+
}: EditableCodeBlockProps) {
|
|
1447
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
1448
|
+
const preRef = useRef<HTMLPreElement>(null);
|
|
1449
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
1450
|
+
|
|
1451
|
+
useEffect(() => {
|
|
1452
|
+
if (preRef.current) {
|
|
1453
|
+
preRef.current.scrollTop = scrollTop;
|
|
1454
|
+
}
|
|
1455
|
+
}, [scrollTop]);
|
|
1456
|
+
|
|
1457
|
+
const handleScroll = () => {
|
|
1458
|
+
if (textareaRef.current) {
|
|
1459
|
+
setScrollTop(textareaRef.current.scrollTop);
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
const lineHeight = 1.5;
|
|
1464
|
+
const padding = 16;
|
|
1465
|
+
const minHeight = rows * 14 * lineHeight + padding * 2;
|
|
1466
|
+
|
|
1467
|
+
return (
|
|
1468
|
+
<div style={{ position: 'relative', minHeight }}>
|
|
1469
|
+
<Highlight theme={themes.nightOwl} code={code} language={language}>
|
|
1470
|
+
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
|
1471
|
+
<pre
|
|
1472
|
+
ref={preRef}
|
|
1473
|
+
className={className}
|
|
1474
|
+
style={{
|
|
1475
|
+
...style,
|
|
1476
|
+
position: 'absolute',
|
|
1477
|
+
top: 0,
|
|
1478
|
+
left: 0,
|
|
1479
|
+
right: 0,
|
|
1480
|
+
bottom: 0,
|
|
1481
|
+
padding: '1rem',
|
|
1482
|
+
borderRadius: '8px',
|
|
1483
|
+
overflow: 'auto',
|
|
1484
|
+
fontSize: '0.875rem',
|
|
1485
|
+
lineHeight: '1.5',
|
|
1486
|
+
margin: 0,
|
|
1487
|
+
pointerEvents: 'none',
|
|
1488
|
+
}}
|
|
1489
|
+
>
|
|
1490
|
+
{tokens.map((line, i) => (
|
|
1491
|
+
<div key={i} {...getLineProps({ line })}>
|
|
1492
|
+
{line.map((token, key) => (
|
|
1493
|
+
<span key={key} {...getTokenProps({ token })} />
|
|
1494
|
+
))}
|
|
1495
|
+
</div>
|
|
1496
|
+
))}
|
|
1497
|
+
</pre>
|
|
1498
|
+
)}
|
|
1499
|
+
</Highlight>
|
|
1500
|
+
|
|
1501
|
+
<textarea
|
|
1502
|
+
ref={textareaRef}
|
|
1503
|
+
value={code}
|
|
1504
|
+
onChange={(e) => onChange(e.target.value)}
|
|
1505
|
+
onScroll={handleScroll}
|
|
1506
|
+
spellCheck={false}
|
|
1507
|
+
style={{
|
|
1508
|
+
position: 'absolute',
|
|
1509
|
+
top: 0,
|
|
1510
|
+
left: 0,
|
|
1511
|
+
right: 0,
|
|
1512
|
+
bottom: 0,
|
|
1513
|
+
width: '100%',
|
|
1514
|
+
height: '100%',
|
|
1515
|
+
minHeight,
|
|
1516
|
+
padding: '1rem',
|
|
1517
|
+
background: 'transparent',
|
|
1518
|
+
border: 'none',
|
|
1519
|
+
outline: 'none',
|
|
1520
|
+
resize: 'none',
|
|
1521
|
+
fontFamily: 'monospace',
|
|
1522
|
+
fontSize: '0.875rem',
|
|
1523
|
+
lineHeight: '1.5',
|
|
1524
|
+
color: 'transparent',
|
|
1525
|
+
caretColor: 'white',
|
|
1526
|
+
WebkitTextFillColor: 'transparent',
|
|
1527
|
+
}}
|
|
1528
|
+
/>
|
|
1529
|
+
</div>
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
`;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
540
1535
|
// src/commands/init.ts
|
|
541
1536
|
async function initCommand(projectName, options) {
|
|
542
1537
|
let projectOptions;
|
|
@@ -558,28 +1553,31 @@ async function initCommand(projectName, options) {
|
|
|
558
1553
|
});
|
|
559
1554
|
}
|
|
560
1555
|
if (!projectOptions) {
|
|
561
|
-
console.log(
|
|
1556
|
+
console.log(pc4.yellow("\nOperation cancelled."));
|
|
562
1557
|
process.exit(0);
|
|
563
1558
|
}
|
|
564
1559
|
const projectDir = path2.resolve(process.cwd(), projectOptions.projectName);
|
|
565
1560
|
if (await directoryExists(projectDir)) {
|
|
566
1561
|
if (!await isDirectoryEmpty(projectDir)) {
|
|
567
1562
|
console.log(
|
|
568
|
-
|
|
1563
|
+
pc4.red(`
|
|
569
1564
|
Error: Directory "${projectOptions.projectName}" already exists and is not empty.`)
|
|
570
1565
|
);
|
|
571
1566
|
process.exit(1);
|
|
572
1567
|
}
|
|
573
1568
|
}
|
|
574
1569
|
console.log();
|
|
575
|
-
const
|
|
576
|
-
spinner.start();
|
|
1570
|
+
const progress = createProgressReporter();
|
|
577
1571
|
try {
|
|
578
|
-
await
|
|
579
|
-
|
|
1572
|
+
await progress.startThinking();
|
|
1573
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
1574
|
+
progress.stopThinking();
|
|
1575
|
+
console.log(pc4.bold("\n Scaffolding your ZK project...\n"));
|
|
1576
|
+
await createProjectStructure(projectDir, projectOptions, progress);
|
|
1577
|
+
progress.showSuccess("Project structure created");
|
|
580
1578
|
} catch (error) {
|
|
581
|
-
|
|
582
|
-
console.error(
|
|
1579
|
+
progress.showError("Failed to create project structure");
|
|
1580
|
+
console.error(pc4.red("\n"), error);
|
|
583
1581
|
process.exit(1);
|
|
584
1582
|
}
|
|
585
1583
|
if (!projectOptions.skipGit) {
|
|
@@ -590,33 +1588,48 @@ Error: Directory "${projectOptions.projectName}" already exists and is not empty
|
|
|
590
1588
|
gitSpinner.stop(true);
|
|
591
1589
|
} catch {
|
|
592
1590
|
gitSpinner.stop(false);
|
|
593
|
-
console.log(
|
|
1591
|
+
console.log(pc4.yellow(" Warning: Failed to initialize git repository"));
|
|
594
1592
|
}
|
|
595
1593
|
}
|
|
596
1594
|
if (!projectOptions.skipInstall) {
|
|
597
|
-
const
|
|
598
|
-
|
|
1595
|
+
const installProgress = createInstallProgress();
|
|
1596
|
+
installProgress.start();
|
|
599
1597
|
try {
|
|
600
1598
|
execSync("npm install", { cwd: projectDir, stdio: "ignore" });
|
|
601
|
-
|
|
1599
|
+
installProgress.stop(true);
|
|
602
1600
|
} catch {
|
|
603
|
-
|
|
604
|
-
console.log(
|
|
1601
|
+
installProgress.stop(false);
|
|
1602
|
+
console.log(pc4.yellow(' Run "npm install" manually.'));
|
|
605
1603
|
}
|
|
606
1604
|
}
|
|
607
1605
|
printSuccessMessage(projectOptions);
|
|
608
1606
|
}
|
|
609
|
-
async function createProjectStructure(projectDir, options) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
1607
|
+
async function createProjectStructure(projectDir, options, progress) {
|
|
1608
|
+
const dirs = ["circuits", "src", "src/components", "src/lib", "public"];
|
|
1609
|
+
for (const dir of dirs) {
|
|
1610
|
+
await ensureDir(path2.join(projectDir, dir));
|
|
1611
|
+
await progress.reportDirectory(dir);
|
|
1612
|
+
}
|
|
613
1613
|
const files = [
|
|
1614
|
+
// Root config files
|
|
614
1615
|
["package.json", generatePackageJson(options)],
|
|
615
1616
|
["tsconfig.json", generateTsconfig()],
|
|
616
|
-
["
|
|
1617
|
+
["tsconfig.node.json", generateTsconfigNode()],
|
|
1618
|
+
["vite.config.ts", generateViteConfig()],
|
|
1619
|
+
["index.html", generateIndexHtml(options)],
|
|
617
1620
|
["README.md", generateReadme(options)],
|
|
618
1621
|
[".gitignore", generateGitignore()],
|
|
619
|
-
|
|
1622
|
+
// Source files
|
|
1623
|
+
["src/main.tsx", generateMainTsx(options)],
|
|
1624
|
+
["src/App.tsx", generateAppTsx(options)],
|
|
1625
|
+
["src/App.css", generateAppCss()],
|
|
1626
|
+
["src/index.css", generateIndexCss()],
|
|
1627
|
+
["src/vite-env.d.ts", generateViteEnvDts()],
|
|
1628
|
+
// Components
|
|
1629
|
+
["src/components/CodeBlock.tsx", generateCodeBlock()],
|
|
1630
|
+
["src/components/EditableCodeBlock.tsx", generateEditableCodeBlock()],
|
|
1631
|
+
// Public assets
|
|
1632
|
+
["public/vite.svg", generateViteSvg()]
|
|
620
1633
|
];
|
|
621
1634
|
switch (options.template) {
|
|
622
1635
|
case "minimal":
|
|
@@ -632,37 +1645,34 @@ async function createProjectStructure(projectDir, options) {
|
|
|
632
1645
|
}
|
|
633
1646
|
files.push(["circuits/index.ts", generateCircuitsIndex(options.template)]);
|
|
634
1647
|
files.push(["circuits/types.d.ts", generateCircuitTypes()]);
|
|
635
|
-
files.
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
files.map(
|
|
641
|
-
([relativePath, content]) => writeFile(path2.join(projectDir, relativePath), content)
|
|
642
|
-
)
|
|
643
|
-
);
|
|
1648
|
+
for (let i = 0; i < files.length; i++) {
|
|
1649
|
+
const [relativePath, content] = files[i];
|
|
1650
|
+
await writeFile(path2.join(projectDir, relativePath), content);
|
|
1651
|
+
await progress.reportFile(relativePath, i === files.length - 1);
|
|
1652
|
+
}
|
|
644
1653
|
}
|
|
645
1654
|
function printSuccessMessage(options) {
|
|
646
1655
|
console.log();
|
|
647
|
-
console.log(
|
|
1656
|
+
console.log(pc4.green("\u2713") + " Project created successfully!");
|
|
648
1657
|
console.log();
|
|
649
1658
|
console.log("Next steps:");
|
|
650
1659
|
console.log();
|
|
651
|
-
console.log(
|
|
1660
|
+
console.log(pc4.cyan(` cd ${options.projectName}`));
|
|
652
1661
|
if (options.skipInstall) {
|
|
653
|
-
console.log(
|
|
1662
|
+
console.log(pc4.cyan(" npm install"));
|
|
654
1663
|
}
|
|
655
|
-
console.log(
|
|
656
|
-
console.log(
|
|
1664
|
+
console.log(pc4.cyan(" npm run dev"));
|
|
1665
|
+
console.log();
|
|
1666
|
+
console.log("Then open " + pc4.blue("http://localhost:5173") + " in your browser.");
|
|
657
1667
|
console.log();
|
|
658
|
-
console.log("To
|
|
1668
|
+
console.log("To add circuits:");
|
|
659
1669
|
console.log();
|
|
660
|
-
console.log(
|
|
661
|
-
console.log(
|
|
662
|
-
console.log(
|
|
1670
|
+
console.log(pc4.dim(" 1. Create a new circuit in circuits/*.ts"));
|
|
1671
|
+
console.log(pc4.dim(" 2. Export it from circuits/index.ts"));
|
|
1672
|
+
console.log(pc4.dim(" 3. Add it to CIRCUITS array in src/App.tsx"));
|
|
663
1673
|
console.log();
|
|
664
1674
|
console.log(
|
|
665
|
-
|
|
1675
|
+
pc4.dim("Learn more: ") + pc4.blue("https://github.com/izi-noir/izi-noir")
|
|
666
1676
|
);
|
|
667
1677
|
console.log();
|
|
668
1678
|
}
|