create-izi-noir 0.1.10 → 0.2.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 (2) hide show
  1. package/dist/index.js +1073 -209
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -155,39 +155,42 @@ function createSpinner(message) {
155
155
 
156
156
  // src/generators/package-json.ts
157
157
  function generatePackageJson(options) {
158
+ const isSolana = options.provider === "arkworks";
159
+ const dependencies = {
160
+ "@izi-noir/sdk": "^0.1.4",
161
+ "@noir-lang/acvm_js": "1.0.0-beta.13-1d260df.nightly",
162
+ "@noir-lang/noirc_abi": "1.0.0-beta.13-1d260df.nightly",
163
+ "react": "^18.3.1",
164
+ "react-dom": "^18.3.1",
165
+ "prism-react-renderer": "^2.4.1"
166
+ };
167
+ if (isSolana) {
168
+ dependencies["@solana/wallet-adapter-react"] = "^0.15.0";
169
+ dependencies["@solana/wallet-adapter-react-ui"] = "^0.9.0";
170
+ dependencies["@solana/wallet-adapter-wallets"] = "^0.19.0";
171
+ dependencies["@solana/web3.js"] = "^1.95.0";
172
+ }
158
173
  const pkg = {
159
174
  name: options.projectName,
160
175
  version: "0.1.0",
176
+ private: true,
161
177
  description: "ZK circuits built with IZI-NOIR",
162
178
  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
179
  scripts: {
177
- build: "tsc",
178
- test: "tsx scripts/test-proof.ts",
179
- "test:watch": "tsx watch scripts/test-proof.ts",
180
- prepublishOnly: "npm run build"
181
- },
182
- dependencies: {
183
- "@izi-noir/sdk": "^0.1.4"
180
+ dev: "vite",
181
+ build: "vite build",
182
+ preview: "vite preview",
183
+ typecheck: "tsc --noEmit"
184
184
  },
185
+ dependencies,
185
186
  devDependencies: {
186
- "@types/node": "^22.0.0",
187
- tsx: "^4.0.0",
188
- typescript: "^5.4.0"
187
+ "@types/react": "^18.3.0",
188
+ "@types/react-dom": "^18.3.0",
189
+ "@vitejs/plugin-react": "^4.3.0",
190
+ "typescript": "^5.4.0",
191
+ "vite": "^5.4.0"
189
192
  },
190
- keywords: ["zk", "noir", "zero-knowledge", "privacy", "solana"],
193
+ keywords: ["zk", "noir", "zero-knowledge", "privacy", isSolana ? "solana" : "evm"],
191
194
  license: "MIT"
192
195
  };
193
196
  return JSON.stringify(pkg, null, 2);
@@ -197,50 +200,35 @@ function generatePackageJson(options) {
197
200
  function generateTsconfig() {
198
201
  const config = {
199
202
  compilerOptions: {
200
- target: "ES2022",
203
+ target: "ES2020",
204
+ useDefineForClassFields: true,
205
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
201
206
  module: "ESNext",
207
+ skipLibCheck: true,
208
+ // Bundler mode
202
209
  moduleResolution: "bundler",
203
- lib: ["ES2022"],
204
- outDir: "./dist",
205
- rootDir: ".",
210
+ allowImportingTsExtensions: true,
211
+ isolatedModules: true,
212
+ moduleDetection: "force",
213
+ noEmit: true,
214
+ jsx: "react-jsx",
215
+ // Linting
206
216
  strict: true,
207
- esModuleInterop: true,
208
- skipLibCheck: true,
209
- forceConsistentCasingInFileNames: true,
210
- declaration: true,
211
- declarationMap: true,
212
- sourceMap: true,
213
- resolveJsonModule: true
217
+ noUnusedLocals: true,
218
+ noUnusedParameters: true,
219
+ noFallthroughCasesInSwitch: true,
220
+ // Paths
221
+ baseUrl: ".",
222
+ paths: {
223
+ "@/*": ["./src/*"]
224
+ }
214
225
  },
215
- include: ["circuits/**/*", "generated/**/*", "scripts/**/*"],
216
- exclude: ["node_modules", "dist"]
226
+ include: ["src", "circuits"],
227
+ references: [{ path: "./tsconfig.node.json" }]
217
228
  };
218
229
  return JSON.stringify(config, null, 2);
219
230
  }
220
231
 
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
232
  // src/generators/circuits.ts
245
233
  function generateBalanceProof() {
246
234
  return `/**
@@ -328,133 +316,49 @@ declare function assert(condition: boolean): void;
328
316
  `;
329
317
  }
330
318
 
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
319
  // src/generators/readme.ts
427
320
  function generateReadme(options) {
321
+ const isSolana = options.provider === "arkworks";
322
+ const networkInfo = isSolana ? "- Deploy VK to Solana devnet\n- Verify proofs on-chain" : "- Local proof verification";
428
323
  return `# ${options.projectName}
429
324
 
430
- ZK circuits built with [IZI-NOIR](https://github.com/izi-noir/izi-noir).
325
+ ZK proof demo built with [IZI-NOIR](https://github.com/izi-noir/izi-noir) and React.
431
326
 
432
327
  ## Getting Started
433
328
 
434
329
  \`\`\`bash
435
- # Build circuits
436
- npm run build
437
-
438
- # Run tests
439
- npm test
330
+ # Install dependencies
331
+ npm install
440
332
 
441
- # Watch mode (rebuild on changes)
333
+ # Start development server
442
334
  npm run dev
335
+
336
+ # Build for production
337
+ npm run build
443
338
  \`\`\`
444
339
 
340
+ Then open http://localhost:5173 in your browser.
341
+
342
+ ## Features
343
+
344
+ - Interactive circuit selection
345
+ - Real-time proof generation
346
+ - Syntax-highlighted code display
347
+ ${networkInfo}
348
+
445
349
  ## Project Structure
446
350
 
447
351
  \`\`\`
448
352
  ${options.projectName}/
449
- \u251C\u2500\u2500 circuits/ # Your ZK circuit definitions
450
- \u2502 \u251C\u2500\u2500 *.ts # Circuits as JS functions with assert()
451
- \u2502 \u2514\u2500\u2500 index.ts # Re-exports
452
- \u251C\u2500\u2500 generated/ # Compiled circuits (auto-generated)
453
- \u2502 \u251C\u2500\u2500 *.json # Compiled circuit artifacts
454
- \u2502 \u2514\u2500\u2500 index.ts # Typed re-exports
455
- \u251C\u2500\u2500 scripts/
456
- \u2502 \u2514\u2500\u2500 test-proof.ts # Local test script
457
- \u251C\u2500\u2500 izi-noir.config.ts # Build configuration
353
+ \u251C\u2500\u2500 src/
354
+ \u2502 \u251C\u2500\u2500 App.tsx # Main demo component
355
+ \u2502 \u251C\u2500\u2500 main.tsx # React entry point
356
+ \u2502 \u251C\u2500\u2500 components/ # Reusable components
357
+ \u2502 \u2514\u2500\u2500 lib/ # Utility functions
358
+ \u251C\u2500\u2500 circuits/ # Your ZK circuit definitions
359
+ \u2502 \u251C\u2500\u2500 *.ts # Circuits as JS functions with assert()
360
+ \u2502 \u2514\u2500\u2500 index.ts # Re-exports
361
+ \u251C\u2500\u2500 vite.config.ts # Vite configuration
458
362
  \u2514\u2500\u2500 package.json
459
363
  \`\`\`
460
364
 
@@ -472,35 +376,16 @@ export function myCircuit(
472
376
  }
473
377
  \`\`\`
474
378
 
475
- ## Usage in Frontend
476
-
477
- \`\`\`typescript
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.
379
+ After adding a new circuit:
380
+ 1. Export it from \`circuits/index.ts\`
381
+ 2. Add it to the CIRCUITS array in \`src/App.tsx\`
499
382
 
500
383
  ## Learn More
501
384
 
502
385
  - [IZI-NOIR Documentation](https://github.com/izi-noir/izi-noir)
503
386
  - [Noir Language](https://noir-lang.org)
387
+ - [Vite](https://vitejs.dev)
388
+ - [React](https://react.dev)
504
389
  `;
505
390
  }
506
391
 
@@ -537,6 +422,973 @@ coverage/
537
422
  `;
538
423
  }
539
424
 
425
+ // src/generators/vite.ts
426
+ function generateViteConfig() {
427
+ return `import { defineConfig, type PluginOption } from "vite";
428
+ import react from "@vitejs/plugin-react";
429
+ import path from "path";
430
+
431
+ // Plugin to set correct MIME type for WASM files in dev server
432
+ function wasmMimePlugin(): PluginOption {
433
+ return {
434
+ name: "wasm-mime-type",
435
+ configureServer(server) {
436
+ server.middlewares.use((req, res, next) => {
437
+ if (req.url?.endsWith(".wasm")) {
438
+ res.setHeader("Content-Type", "application/wasm");
439
+ }
440
+ next();
441
+ });
442
+ },
443
+ };
444
+ }
445
+
446
+ export default defineConfig({
447
+ plugins: [react(), wasmMimePlugin()],
448
+ resolve: {
449
+ alias: {
450
+ "@": path.resolve(__dirname, "./src"),
451
+ buffer: "buffer/",
452
+ },
453
+ },
454
+ define: {
455
+ "global": "globalThis",
456
+ },
457
+ optimizeDeps: {
458
+ exclude: ["@noir-lang/noir_wasm", "@aztec/bb.js", "@izi-noir/sdk"],
459
+ include: ["buffer"],
460
+ },
461
+ build: {
462
+ rollupOptions: {
463
+ external: [
464
+ /arkworks_groth16_wasm/,
465
+ ],
466
+ },
467
+ },
468
+ });
469
+ `;
470
+ }
471
+ function generateTsconfigNode() {
472
+ return `{
473
+ "compilerOptions": {
474
+ "composite": true,
475
+ "skipLibCheck": true,
476
+ "module": "ESNext",
477
+ "moduleResolution": "bundler",
478
+ "allowSyntheticDefaultImports": true,
479
+ "strict": true
480
+ },
481
+ "include": ["vite.config.ts"]
482
+ }
483
+ `;
484
+ }
485
+ function generateIndexHtml(options) {
486
+ return `<!doctype html>
487
+ <html lang="en">
488
+ <head>
489
+ <meta charset="UTF-8" />
490
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
491
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
492
+ <title>${options.projectName} - ZK Proof Demo</title>
493
+ </head>
494
+ <body>
495
+ <div id="root"></div>
496
+ <script type="module" src="/src/main.tsx"></script>
497
+ </body>
498
+ </html>
499
+ `;
500
+ }
501
+ function generateMainTsx(options) {
502
+ const isSolana = options.provider === "arkworks";
503
+ if (isSolana) {
504
+ return `import { StrictMode } from 'react';
505
+ import { createRoot } from 'react-dom/client';
506
+ import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
507
+ import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
508
+ import { clusterApiUrl } from '@solana/web3.js';
509
+ import App from './App';
510
+ import './index.css';
511
+ import '@solana/wallet-adapter-react-ui/styles.css';
512
+
513
+ const endpoint = clusterApiUrl('devnet');
514
+
515
+ createRoot(document.getElementById('root')!).render(
516
+ <StrictMode>
517
+ <ConnectionProvider endpoint={endpoint}>
518
+ <WalletProvider wallets={[]} autoConnect>
519
+ <WalletModalProvider>
520
+ <App />
521
+ </WalletModalProvider>
522
+ </WalletProvider>
523
+ </ConnectionProvider>
524
+ </StrictMode>,
525
+ );
526
+ `;
527
+ }
528
+ return `import { StrictMode } from 'react';
529
+ import { createRoot } from 'react-dom/client';
530
+ import App from './App';
531
+ import './index.css';
532
+
533
+ createRoot(document.getElementById('root')!).render(
534
+ <StrictMode>
535
+ <App />
536
+ </StrictMode>,
537
+ );
538
+ `;
539
+ }
540
+ function generateAppTsx(options) {
541
+ const isSolana = options.provider === "arkworks";
542
+ const circuitImports = getCircuitImports(options.template);
543
+ const circuitOptions = getCircuitOptions(options.template);
544
+ const solanaImports = isSolana ? `
545
+ import { useWallet } from '@solana/wallet-adapter-react';
546
+ import { useWalletModal } from '@solana/wallet-adapter-react-ui';
547
+ import { Chain, Network } from '@izi-noir/sdk';` : "";
548
+ const solanaHooks = isSolana ? `
549
+ const { publicKey, connected, sendTransaction } = useWallet();
550
+ const { setVisible } = useWalletModal();` : "";
551
+ const solanaProviderConfig = isSolana ? `
552
+ chain: Chain.Solana,
553
+ network: Network.Devnet,` : "";
554
+ const solanaState = isSolana ? `
555
+ // Deploy state
556
+ const [isDeploying, setIsDeploying] = useState(false);
557
+ const [vkAccount, setVkAccount] = useState<string | null>(null);
558
+ const [deployError, setDeployError] = useState<string | null>(null);
559
+
560
+ // Verify state
561
+ const [isVerifying, setIsVerifying] = useState(false);
562
+ const [verified, setVerified] = useState<boolean | null>(null);
563
+ const [verifyError, setVerifyError] = useState<string | null>(null);` : "";
564
+ const solanaHandlers = isSolana ? `
565
+ // Deploy VK to Solana
566
+ const handleDeploy = async () => {
567
+ if (!iziInstance || !connected || !publicKey || !sendTransaction) return;
568
+
569
+ setIsDeploying(true);
570
+ setDeployError(null);
571
+
572
+ try {
573
+ const result = await iziInstance.deploy({ publicKey, sendTransaction });
574
+ setVkAccount(result.vkAccount);
575
+ } catch (error) {
576
+ console.error('Deploy error:', error);
577
+ setDeployError((error as Error).message);
578
+ } finally {
579
+ setIsDeploying(false);
580
+ }
581
+ };
582
+
583
+ // Verify proof on-chain
584
+ const handleVerify = async () => {
585
+ if (!iziInstance || !vkAccount || !publicKey || !sendTransaction) return;
586
+
587
+ setIsVerifying(true);
588
+ setVerifyError(null);
589
+
590
+ try {
591
+ const result = await iziInstance.verifyOnChain({ publicKey, sendTransaction }, vkAccount);
592
+ setVerified(result.verified);
593
+ } catch (error) {
594
+ console.error('Verify error:', error);
595
+ setVerifyError((error as Error).message);
596
+ } finally {
597
+ setIsVerifying(false);
598
+ }
599
+ };` : "";
600
+ const solanaDeploySection = isSolana ? `
601
+ {/* Deploy & Verify Section */}
602
+ {proof && (
603
+ <div className="section">
604
+ <h2>Deploy & Verify on Solana</h2>
605
+
606
+ {!connected ? (
607
+ <button onClick={() => setVisible(true)} className="btn btn-secondary">
608
+ Connect Wallet
609
+ </button>
610
+ ) : (
611
+ <div className="deploy-verify-row">
612
+ <div className="deploy-box">
613
+ <button
614
+ onClick={handleDeploy}
615
+ disabled={isDeploying || !!vkAccount}
616
+ className="btn btn-primary"
617
+ >
618
+ {isDeploying ? 'Deploying...' : vkAccount ? 'Deployed' : 'Deploy VK'}
619
+ </button>
620
+ {deployError && <p className="error">{deployError}</p>}
621
+ {vkAccount && <p className="success">VK: {vkAccount.slice(0, 8)}...</p>}
622
+ </div>
623
+
624
+ <div className="verify-box">
625
+ <button
626
+ onClick={handleVerify}
627
+ disabled={!vkAccount || isVerifying}
628
+ className="btn btn-primary"
629
+ >
630
+ {isVerifying ? 'Verifying...' : verified ? 'Verified!' : 'Verify On-Chain'}
631
+ </button>
632
+ {verifyError && <p className="error">{verifyError}</p>}
633
+ {verified && <p className="success">Proof verified on Solana!</p>}
634
+ </div>
635
+ </div>
636
+ )}
637
+ </div>
638
+ )}` : "";
639
+ return `import { useState, useEffect, useCallback } from 'react';
640
+ import initNoirC from '@noir-lang/noirc_abi';
641
+ import initACVM from '@noir-lang/acvm_js';
642
+ import acvm from '@noir-lang/acvm_js/web/acvm_js_bg.wasm?url';
643
+ import noirc from '@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm?url';
644
+ import {
645
+ IziNoir,
646
+ Provider,
647
+ markWasmInitialized,
648
+ AcornParser,
649
+ generateNoir,
650
+ } from '@izi-noir/sdk';${solanaImports}
651
+ import { CodeBlock } from './components/CodeBlock';
652
+ ${circuitImports}
653
+ import './App.css';
654
+
655
+ // Circuit definition type
656
+ interface CircuitDef {
657
+ name: string;
658
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
659
+ fn: any;
660
+ publicInputKeys: string[];
661
+ privateInputKeys: string[];
662
+ defaultInputs: Record<string, string>;
663
+ }
664
+
665
+ // Circuit options
666
+ const CIRCUITS: CircuitDef[] = ${circuitOptions};
667
+
668
+ // WASM initialization
669
+ let wasmInitialized = false;
670
+ async function initBrowserWasm() {
671
+ if (wasmInitialized) return;
672
+ await Promise.all([
673
+ initACVM({ module_or_path: acvm }),
674
+ initNoirC({ module_or_path: noirc }),
675
+ ]);
676
+ markWasmInitialized();
677
+ wasmInitialized = true;
678
+ }
679
+
680
+ function App() {
681
+ // Circuit state
682
+ const [selectedCircuit, setSelectedCircuit] = useState(CIRCUITS[0].name);
683
+ const [circuitCode, setCircuitCode] = useState(CIRCUITS[0].fn.toString());
684
+ const [noirCode, setNoirCode] = useState<string | null>(null);
685
+ const [transpileError, setTranspileError] = useState<string | null>(null);
686
+
687
+ // Input state
688
+ const [inputs, setInputs] = useState<Record<string, string>>(CIRCUITS[0].defaultInputs);
689
+
690
+ // Proof state
691
+ const [isGenerating, setIsGenerating] = useState(false);
692
+ const [proof, setProof] = useState<Uint8Array | null>(null);
693
+ const [proofTime, setProofTime] = useState<number | null>(null);
694
+ const [proofError, setProofError] = useState<string | null>(null);
695
+ const [localVerified, setLocalVerified] = useState<boolean | null>(null);
696
+
697
+ // IziNoir instance
698
+ const [iziInstance, setIziInstance] = useState<IziNoir | null>(null);
699
+ ${solanaHooks}
700
+ ${solanaState}
701
+
702
+ // Update circuit when selection changes
703
+ useEffect(() => {
704
+ const circuit = CIRCUITS.find(c => c.name === selectedCircuit);
705
+ if (circuit) {
706
+ setCircuitCode(circuit.fn.toString());
707
+ setInputs(circuit.defaultInputs);
708
+ // Reset proof state
709
+ setProof(null);
710
+ setProofTime(null);
711
+ setLocalVerified(null);
712
+ setNoirCode(null);
713
+ }
714
+ }, [selectedCircuit]);
715
+
716
+ // Transpile circuit to Noir
717
+ useEffect(() => {
718
+ const transpileCode = () => {
719
+ try {
720
+ setTranspileError(null);
721
+ const circuit = CIRCUITS.find(c => c.name === selectedCircuit);
722
+ if (!circuit) return;
723
+
724
+ const parser = new AcornParser();
725
+ const publicInputs = Object.entries(inputs)
726
+ .filter(([key]) => circuit.publicInputKeys.includes(key))
727
+ .map(([, val]) => Number(val));
728
+ const privateInputs = Object.entries(inputs)
729
+ .filter(([key]) => circuit.privateInputKeys.includes(key))
730
+ .map(([, val]) => Number(val));
731
+
732
+ const parsedCircuit = parser.parse(circuit.fn, publicInputs, privateInputs);
733
+ const result = generateNoir(parsedCircuit);
734
+ setNoirCode(result);
735
+ } catch (error) {
736
+ setTranspileError((error as Error).message);
737
+ setNoirCode(null);
738
+ }
739
+ };
740
+
741
+ const debounce = setTimeout(transpileCode, 300);
742
+ return () => clearTimeout(debounce);
743
+ }, [selectedCircuit, inputs]);
744
+
745
+ // Generate proof
746
+ const handleGenerateProof = useCallback(async () => {
747
+ if (!noirCode) return;
748
+
749
+ setIsGenerating(true);
750
+ setProofError(null);
751
+ setProof(null);
752
+ setProofTime(null);
753
+ setLocalVerified(null);
754
+
755
+ try {
756
+ await initBrowserWasm();
757
+
758
+ const startTime = performance.now();
759
+
760
+ const izi = await IziNoir.init({
761
+ provider: Provider.${capitalizeFirst(options.provider)},${solanaProviderConfig}
762
+ });
763
+
764
+ await izi.compile(noirCode);
765
+
766
+ const proofResult = await izi.prove(inputs);
767
+ setIziInstance(izi);
768
+
769
+ // Get proof bytes
770
+ const proofBytes = 'bytes' in proofResult.proof
771
+ ? proofResult.proof.bytes
772
+ : proofResult.proof;
773
+ const publicInputsHex = 'hex' in proofResult.publicInputs
774
+ ? proofResult.publicInputs.hex
775
+ : proofResult.publicInputs;
776
+
777
+ // Local verification
778
+ const verified = await izi.verify(proofBytes, publicInputsHex);
779
+ setLocalVerified(verified);
780
+
781
+ const endTime = performance.now();
782
+ setProof(proofBytes);
783
+ setProofTime(Math.round(endTime - startTime));
784
+ } catch (error) {
785
+ console.error('Proof generation error:', error);
786
+ setProofError((error as Error).message);
787
+ } finally {
788
+ setIsGenerating(false);
789
+ }
790
+ }, [noirCode, inputs]);
791
+ ${solanaHandlers}
792
+
793
+ return (
794
+ <div className="app">
795
+ <header>
796
+ <h1>${options.projectName}</h1>
797
+ <p>Zero-Knowledge Proof Demo</p>
798
+ </header>
799
+
800
+ <main>
801
+ {/* Circuit Selection */}
802
+ <div className="section">
803
+ <h2>1. Select Circuit</h2>
804
+ <select
805
+ value={selectedCircuit}
806
+ onChange={(e) => setSelectedCircuit(e.target.value)}
807
+ className="select"
808
+ >
809
+ {CIRCUITS.map((c) => (
810
+ <option key={c.name} value={c.name}>
811
+ {c.name}
812
+ </option>
813
+ ))}
814
+ </select>
815
+ </div>
816
+
817
+ {/* Circuit Code */}
818
+ <div className="section">
819
+ <h2>2. Circuit Code</h2>
820
+ <CodeBlock code={circuitCode} language="typescript" />
821
+
822
+ {noirCode && (
823
+ <details className="noir-details">
824
+ <summary>View Generated Noir</summary>
825
+ <CodeBlock code={noirCode} language="rust" />
826
+ </details>
827
+ )}
828
+
829
+ {transpileError && (
830
+ <p className="error">{transpileError}</p>
831
+ )}
832
+ </div>
833
+
834
+ {/* Inputs */}
835
+ <div className="section">
836
+ <h2>3. Inputs</h2>
837
+ <div className="inputs-grid">
838
+ {Object.entries(inputs).map(([key, value]) => {
839
+ const circuit = CIRCUITS.find(c => c.name === selectedCircuit);
840
+ const isPublic = circuit?.publicInputKeys.includes(key);
841
+ return (
842
+ <div key={key} className="input-group">
843
+ <label>
844
+ <span className={\`input-badge \${isPublic ? 'public' : 'private'}\`}>
845
+ {isPublic ? 'public' : 'private'}
846
+ </span>
847
+ {key}
848
+ </label>
849
+ <input
850
+ type="number"
851
+ value={value}
852
+ onChange={(e) => setInputs({ ...inputs, [key]: e.target.value })}
853
+ />
854
+ </div>
855
+ );
856
+ })}
857
+ </div>
858
+ </div>
859
+
860
+ {/* Generate Proof */}
861
+ <div className="section">
862
+ <h2>4. Generate Proof</h2>
863
+ <button
864
+ onClick={handleGenerateProof}
865
+ disabled={isGenerating || !noirCode || !!transpileError}
866
+ className="btn btn-primary"
867
+ >
868
+ {isGenerating ? 'Generating...' : 'Generate Proof'}
869
+ </button>
870
+
871
+ {proofError && <p className="error">{proofError}</p>}
872
+
873
+ {proof && (
874
+ <div className="results">
875
+ <div className="result-card">
876
+ <span className="result-value">{proof.length} bytes</span>
877
+ <span className="result-label">Proof Size</span>
878
+ </div>
879
+ <div className="result-card">
880
+ <span className="result-value">{proofTime} ms</span>
881
+ <span className="result-label">Generation Time</span>
882
+ </div>
883
+ <div className="result-card">
884
+ <span className={\`result-value \${localVerified ? 'success' : 'error'}\`}>
885
+ {localVerified ? 'Yes' : 'No'}
886
+ </span>
887
+ <span className="result-label">Locally Verified</span>
888
+ </div>
889
+ </div>
890
+ )}
891
+ </div>
892
+ ${solanaDeploySection}
893
+ </main>
894
+
895
+ <footer>
896
+ <p>Built with <a href="https://github.com/izi-noir/izi-noir" target="_blank">IZI-NOIR</a></p>
897
+ </footer>
898
+ </div>
899
+ );
900
+ }
901
+
902
+ export default App;
903
+ `;
904
+ }
905
+ function getCircuitImports(template) {
906
+ switch (template) {
907
+ case "minimal":
908
+ return `import { myCircuit } from '../circuits';`;
909
+ case "balance-proof":
910
+ return `import { balanceProof } from '../circuits';`;
911
+ default:
912
+ return `import { balanceProof, ageProof } from '../circuits';`;
913
+ }
914
+ }
915
+ function getCircuitOptions(template) {
916
+ switch (template) {
917
+ case "minimal":
918
+ return `[
919
+ {
920
+ name: 'myCircuit',
921
+ fn: myCircuit,
922
+ publicInputKeys: ['publicInput'],
923
+ privateInputKeys: ['privateInput'],
924
+ defaultInputs: { publicInput: '42', privateInput: '42' },
925
+ },
926
+ ]`;
927
+ case "balance-proof":
928
+ return `[
929
+ {
930
+ name: 'balanceProof',
931
+ fn: balanceProof,
932
+ publicInputKeys: ['threshold'],
933
+ privateInputKeys: ['balance'],
934
+ defaultInputs: { threshold: '100', balance: '1500' },
935
+ },
936
+ ]`;
937
+ default:
938
+ return `[
939
+ {
940
+ name: 'balanceProof',
941
+ fn: balanceProof,
942
+ publicInputKeys: ['threshold'],
943
+ privateInputKeys: ['balance'],
944
+ defaultInputs: { threshold: '100', balance: '1500' },
945
+ },
946
+ {
947
+ name: 'ageProof',
948
+ fn: ageProof,
949
+ publicInputKeys: ['currentYear', 'minAge'],
950
+ privateInputKeys: ['birthYear'],
951
+ defaultInputs: { currentYear: '2024', minAge: '18', birthYear: '1990' },
952
+ },
953
+ ]`;
954
+ }
955
+ }
956
+ function capitalizeFirst(str) {
957
+ return str.charAt(0).toUpperCase() + str.slice(1);
958
+ }
959
+ function generateAppCss() {
960
+ return `* {
961
+ box-sizing: border-box;
962
+ margin: 0;
963
+ padding: 0;
964
+ }
965
+
966
+ :root {
967
+ --purple: #9945FF;
968
+ --green: #14F195;
969
+ --bg: #0a0a0a;
970
+ --bg-elevated: #111111;
971
+ --border: #222222;
972
+ --text: #ffffff;
973
+ --text-muted: #888888;
974
+ }
975
+
976
+ body {
977
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
978
+ background: var(--bg);
979
+ color: var(--text);
980
+ line-height: 1.6;
981
+ }
982
+
983
+ .app {
984
+ min-height: 100vh;
985
+ display: flex;
986
+ flex-direction: column;
987
+ }
988
+
989
+ header {
990
+ padding: 2rem;
991
+ text-align: center;
992
+ border-bottom: 1px solid var(--border);
993
+ }
994
+
995
+ header h1 {
996
+ font-size: 2rem;
997
+ background: linear-gradient(90deg, var(--purple), var(--green));
998
+ -webkit-background-clip: text;
999
+ -webkit-text-fill-color: transparent;
1000
+ background-clip: text;
1001
+ }
1002
+
1003
+ header p {
1004
+ color: var(--text-muted);
1005
+ margin-top: 0.5rem;
1006
+ }
1007
+
1008
+ main {
1009
+ flex: 1;
1010
+ max-width: 800px;
1011
+ margin: 0 auto;
1012
+ padding: 2rem;
1013
+ width: 100%;
1014
+ }
1015
+
1016
+ .section {
1017
+ margin-bottom: 2rem;
1018
+ padding: 1.5rem;
1019
+ background: var(--bg-elevated);
1020
+ border: 1px solid var(--border);
1021
+ border-radius: 12px;
1022
+ }
1023
+
1024
+ .section h2 {
1025
+ font-size: 1.25rem;
1026
+ margin-bottom: 1rem;
1027
+ color: var(--text);
1028
+ }
1029
+
1030
+ .select {
1031
+ width: 100%;
1032
+ padding: 0.75rem;
1033
+ background: var(--bg);
1034
+ border: 1px solid var(--border);
1035
+ border-radius: 8px;
1036
+ color: var(--text);
1037
+ font-size: 1rem;
1038
+ cursor: pointer;
1039
+ }
1040
+
1041
+ .select:focus {
1042
+ outline: none;
1043
+ border-color: var(--purple);
1044
+ }
1045
+
1046
+ .inputs-grid {
1047
+ display: grid;
1048
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1049
+ gap: 1rem;
1050
+ }
1051
+
1052
+ .input-group {
1053
+ display: flex;
1054
+ flex-direction: column;
1055
+ gap: 0.5rem;
1056
+ }
1057
+
1058
+ .input-group label {
1059
+ display: flex;
1060
+ align-items: center;
1061
+ gap: 0.5rem;
1062
+ font-size: 0.875rem;
1063
+ color: var(--text-muted);
1064
+ }
1065
+
1066
+ .input-badge {
1067
+ font-size: 0.625rem;
1068
+ padding: 0.125rem 0.375rem;
1069
+ border-radius: 4px;
1070
+ text-transform: uppercase;
1071
+ font-weight: 600;
1072
+ }
1073
+
1074
+ .input-badge.public {
1075
+ background: var(--green);
1076
+ color: #000;
1077
+ }
1078
+
1079
+ .input-badge.private {
1080
+ background: var(--purple);
1081
+ color: #fff;
1082
+ }
1083
+
1084
+ .input-group input {
1085
+ padding: 0.75rem;
1086
+ background: var(--bg);
1087
+ border: 1px solid var(--border);
1088
+ border-radius: 8px;
1089
+ color: var(--text);
1090
+ font-size: 1rem;
1091
+ }
1092
+
1093
+ .input-group input:focus {
1094
+ outline: none;
1095
+ border-color: var(--purple);
1096
+ }
1097
+
1098
+ .btn {
1099
+ padding: 0.75rem 1.5rem;
1100
+ font-size: 1rem;
1101
+ font-weight: 600;
1102
+ border: none;
1103
+ border-radius: 8px;
1104
+ cursor: pointer;
1105
+ transition: all 0.2s;
1106
+ }
1107
+
1108
+ .btn:disabled {
1109
+ opacity: 0.5;
1110
+ cursor: not-allowed;
1111
+ }
1112
+
1113
+ .btn-primary {
1114
+ background: linear-gradient(90deg, var(--purple), var(--green));
1115
+ color: white;
1116
+ }
1117
+
1118
+ .btn-primary:hover:not(:disabled) {
1119
+ transform: translateY(-2px);
1120
+ box-shadow: 0 4px 20px rgba(153, 69, 255, 0.3);
1121
+ }
1122
+
1123
+ .btn-secondary {
1124
+ background: var(--bg);
1125
+ border: 1px solid var(--border);
1126
+ color: var(--text);
1127
+ }
1128
+
1129
+ .btn-secondary:hover:not(:disabled) {
1130
+ border-color: var(--purple);
1131
+ }
1132
+
1133
+ .results {
1134
+ display: grid;
1135
+ grid-template-columns: repeat(3, 1fr);
1136
+ gap: 1rem;
1137
+ margin-top: 1rem;
1138
+ }
1139
+
1140
+ .result-card {
1141
+ padding: 1rem;
1142
+ background: var(--bg);
1143
+ border: 1px solid var(--border);
1144
+ border-radius: 8px;
1145
+ text-align: center;
1146
+ }
1147
+
1148
+ .result-value {
1149
+ font-size: 1.25rem;
1150
+ font-weight: 600;
1151
+ display: block;
1152
+ }
1153
+
1154
+ .result-value.success {
1155
+ color: var(--green);
1156
+ }
1157
+
1158
+ .result-value.error {
1159
+ color: #ff4444;
1160
+ }
1161
+
1162
+ .result-label {
1163
+ font-size: 0.75rem;
1164
+ color: var(--text-muted);
1165
+ text-transform: uppercase;
1166
+ margin-top: 0.25rem;
1167
+ display: block;
1168
+ }
1169
+
1170
+ .error {
1171
+ color: #ff4444;
1172
+ font-size: 0.875rem;
1173
+ margin-top: 0.5rem;
1174
+ }
1175
+
1176
+ .success {
1177
+ color: var(--green);
1178
+ font-size: 0.875rem;
1179
+ margin-top: 0.5rem;
1180
+ }
1181
+
1182
+ .noir-details {
1183
+ margin-top: 1rem;
1184
+ }
1185
+
1186
+ .noir-details summary {
1187
+ cursor: pointer;
1188
+ color: var(--text-muted);
1189
+ font-size: 0.875rem;
1190
+ }
1191
+
1192
+ .noir-details summary:hover {
1193
+ color: var(--text);
1194
+ }
1195
+
1196
+ .deploy-verify-row {
1197
+ display: grid;
1198
+ grid-template-columns: 1fr 1fr;
1199
+ gap: 1rem;
1200
+ margin-top: 1rem;
1201
+ }
1202
+
1203
+ .deploy-box,
1204
+ .verify-box {
1205
+ padding: 1rem;
1206
+ background: var(--bg);
1207
+ border: 1px solid var(--border);
1208
+ border-radius: 8px;
1209
+ }
1210
+
1211
+ footer {
1212
+ padding: 1.5rem;
1213
+ text-align: center;
1214
+ border-top: 1px solid var(--border);
1215
+ color: var(--text-muted);
1216
+ font-size: 0.875rem;
1217
+ }
1218
+
1219
+ footer a {
1220
+ color: var(--purple);
1221
+ text-decoration: none;
1222
+ }
1223
+
1224
+ footer a:hover {
1225
+ text-decoration: underline;
1226
+ }
1227
+ `;
1228
+ }
1229
+ function generateIndexCss() {
1230
+ return `body {
1231
+ margin: 0;
1232
+ -webkit-font-smoothing: antialiased;
1233
+ -moz-osx-font-smoothing: grayscale;
1234
+ }
1235
+ `;
1236
+ }
1237
+ function generateViteEnvDts() {
1238
+ return `/// <reference types="vite/client" />
1239
+ `;
1240
+ }
1241
+ function generateViteSvg() {
1242
+ 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>
1243
+ `;
1244
+ }
1245
+
1246
+ // src/generators/components.ts
1247
+ function generateCodeBlock() {
1248
+ return `import { Highlight, themes } from 'prism-react-renderer';
1249
+
1250
+ interface CodeBlockProps {
1251
+ code: string;
1252
+ language?: 'typescript' | 'javascript' | 'rust';
1253
+ }
1254
+
1255
+ export function CodeBlock({ code, language = 'typescript' }: CodeBlockProps) {
1256
+ return (
1257
+ <Highlight theme={themes.nightOwl} code={code.trim()} language={language}>
1258
+ {({ className, style, tokens, getLineProps, getTokenProps }) => (
1259
+ <pre
1260
+ className={className}
1261
+ style={{
1262
+ ...style,
1263
+ padding: '1rem',
1264
+ borderRadius: '8px',
1265
+ overflow: 'auto',
1266
+ fontSize: '0.875rem',
1267
+ lineHeight: '1.5',
1268
+ }}
1269
+ >
1270
+ {tokens.map((line, i) => (
1271
+ <div key={i} {...getLineProps({ line })}>
1272
+ <span style={{ color: '#666', marginRight: '1rem', userSelect: 'none' }}>
1273
+ {String(i + 1).padStart(2, ' ')}
1274
+ </span>
1275
+ {line.map((token, key) => (
1276
+ <span key={key} {...getTokenProps({ token })} />
1277
+ ))}
1278
+ </div>
1279
+ ))}
1280
+ </pre>
1281
+ )}
1282
+ </Highlight>
1283
+ );
1284
+ }
1285
+ `;
1286
+ }
1287
+ function generateEditableCodeBlock() {
1288
+ return `import { useRef, useEffect, useState } from 'react';
1289
+ import { Highlight, themes } from 'prism-react-renderer';
1290
+
1291
+ interface EditableCodeBlockProps {
1292
+ code: string;
1293
+ onChange: (code: string) => void;
1294
+ language?: 'typescript' | 'javascript';
1295
+ rows?: number;
1296
+ }
1297
+
1298
+ export function EditableCodeBlock({
1299
+ code,
1300
+ onChange,
1301
+ language = 'typescript',
1302
+ rows = 8,
1303
+ }: EditableCodeBlockProps) {
1304
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
1305
+ const preRef = useRef<HTMLPreElement>(null);
1306
+ const [scrollTop, setScrollTop] = useState(0);
1307
+
1308
+ useEffect(() => {
1309
+ if (preRef.current) {
1310
+ preRef.current.scrollTop = scrollTop;
1311
+ }
1312
+ }, [scrollTop]);
1313
+
1314
+ const handleScroll = () => {
1315
+ if (textareaRef.current) {
1316
+ setScrollTop(textareaRef.current.scrollTop);
1317
+ }
1318
+ };
1319
+
1320
+ const lineHeight = 1.5;
1321
+ const padding = 16;
1322
+ const minHeight = rows * 14 * lineHeight + padding * 2;
1323
+
1324
+ return (
1325
+ <div style={{ position: 'relative', minHeight }}>
1326
+ <Highlight theme={themes.nightOwl} code={code} language={language}>
1327
+ {({ className, style, tokens, getLineProps, getTokenProps }) => (
1328
+ <pre
1329
+ ref={preRef}
1330
+ className={className}
1331
+ style={{
1332
+ ...style,
1333
+ position: 'absolute',
1334
+ top: 0,
1335
+ left: 0,
1336
+ right: 0,
1337
+ bottom: 0,
1338
+ padding: '1rem',
1339
+ borderRadius: '8px',
1340
+ overflow: 'auto',
1341
+ fontSize: '0.875rem',
1342
+ lineHeight: '1.5',
1343
+ margin: 0,
1344
+ pointerEvents: 'none',
1345
+ }}
1346
+ >
1347
+ {tokens.map((line, i) => (
1348
+ <div key={i} {...getLineProps({ line })}>
1349
+ {line.map((token, key) => (
1350
+ <span key={key} {...getTokenProps({ token })} />
1351
+ ))}
1352
+ </div>
1353
+ ))}
1354
+ </pre>
1355
+ )}
1356
+ </Highlight>
1357
+
1358
+ <textarea
1359
+ ref={textareaRef}
1360
+ value={code}
1361
+ onChange={(e) => onChange(e.target.value)}
1362
+ onScroll={handleScroll}
1363
+ spellCheck={false}
1364
+ style={{
1365
+ position: 'absolute',
1366
+ top: 0,
1367
+ left: 0,
1368
+ right: 0,
1369
+ bottom: 0,
1370
+ width: '100%',
1371
+ height: '100%',
1372
+ minHeight,
1373
+ padding: '1rem',
1374
+ background: 'transparent',
1375
+ border: 'none',
1376
+ outline: 'none',
1377
+ resize: 'none',
1378
+ fontFamily: 'monospace',
1379
+ fontSize: '0.875rem',
1380
+ lineHeight: '1.5',
1381
+ color: 'transparent',
1382
+ caretColor: 'white',
1383
+ WebkitTextFillColor: 'transparent',
1384
+ }}
1385
+ />
1386
+ </div>
1387
+ );
1388
+ }
1389
+ `;
1390
+ }
1391
+
540
1392
  // src/commands/init.ts
541
1393
  async function initCommand(projectName, options) {
542
1394
  let projectOptions;
@@ -608,15 +1460,30 @@ Error: Directory "${projectOptions.projectName}" already exists and is not empty
608
1460
  }
609
1461
  async function createProjectStructure(projectDir, options) {
610
1462
  await ensureDir(path2.join(projectDir, "circuits"));
611
- await ensureDir(path2.join(projectDir, "generated"));
612
- await ensureDir(path2.join(projectDir, "scripts"));
1463
+ await ensureDir(path2.join(projectDir, "src"));
1464
+ await ensureDir(path2.join(projectDir, "src", "components"));
1465
+ await ensureDir(path2.join(projectDir, "src", "lib"));
1466
+ await ensureDir(path2.join(projectDir, "public"));
613
1467
  const files = [
1468
+ // Root config files
614
1469
  ["package.json", generatePackageJson(options)],
615
1470
  ["tsconfig.json", generateTsconfig()],
616
- ["izi-noir.config.ts", generateConfig(options)],
1471
+ ["tsconfig.node.json", generateTsconfigNode()],
1472
+ ["vite.config.ts", generateViteConfig()],
1473
+ ["index.html", generateIndexHtml(options)],
617
1474
  ["README.md", generateReadme(options)],
618
1475
  [".gitignore", generateGitignore()],
619
- ["scripts/test-proof.ts", generateTestScript(options)]
1476
+ // Source files
1477
+ ["src/main.tsx", generateMainTsx(options)],
1478
+ ["src/App.tsx", generateAppTsx(options)],
1479
+ ["src/App.css", generateAppCss()],
1480
+ ["src/index.css", generateIndexCss()],
1481
+ ["src/vite-env.d.ts", generateViteEnvDts()],
1482
+ // Components
1483
+ ["src/components/CodeBlock.tsx", generateCodeBlock()],
1484
+ ["src/components/EditableCodeBlock.tsx", generateEditableCodeBlock()],
1485
+ // Public assets
1486
+ ["public/vite.svg", generateViteSvg()]
620
1487
  ];
621
1488
  switch (options.template) {
622
1489
  case "minimal":
@@ -632,10 +1499,6 @@ async function createProjectStructure(projectDir, options) {
632
1499
  }
633
1500
  files.push(["circuits/index.ts", generateCircuitsIndex(options.template)]);
634
1501
  files.push(["circuits/types.d.ts", generateCircuitTypes()]);
635
- files.push([
636
- "generated/.gitkeep",
637
- "# This directory contains compiled circuit artifacts\n# Generated by: npm run build\n"
638
- ]);
639
1502
  await Promise.all(
640
1503
  files.map(
641
1504
  ([relativePath, content]) => writeFile(path2.join(projectDir, relativePath), content)
@@ -652,14 +1515,15 @@ function printSuccessMessage(options) {
652
1515
  if (options.skipInstall) {
653
1516
  console.log(pc3.cyan(" npm install"));
654
1517
  }
655
- console.log(pc3.cyan(" npm run build"));
656
- console.log(pc3.cyan(" npm test"));
1518
+ console.log(pc3.cyan(" npm run dev"));
1519
+ console.log();
1520
+ console.log("Then open " + pc3.blue("http://localhost:5173") + " in your browser.");
657
1521
  console.log();
658
- console.log("To start developing:");
1522
+ console.log("To add circuits:");
659
1523
  console.log();
660
- console.log(pc3.dim(" 1. Edit circuits in circuits/*.ts"));
661
- console.log(pc3.dim(' 2. Run "npm run dev" for watch mode'));
662
- console.log(pc3.dim(' 3. Test with "npm test"'));
1524
+ console.log(pc3.dim(" 1. Create a new circuit in circuits/*.ts"));
1525
+ console.log(pc3.dim(" 2. Export it from circuits/index.ts"));
1526
+ console.log(pc3.dim(" 3. Add it to CIRCUITS array in src/App.tsx"));
663
1527
  console.log();
664
1528
  console.log(
665
1529
  pc3.dim("Learn more: ") + pc3.blue("https://github.com/izi-noir/izi-noir")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-izi-noir",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "description": "CLI to scaffold IZI-NOIR ZK projects",
5
5
  "type": "module",
6
6
  "bin": {