From 9f4423611031dda8e4788f761edf623afc900e33 Mon Sep 17 00:00:00 2001 From: Lola Aimar Date: Thu, 18 Jun 2026 17:27:12 -0300 Subject: [PATCH 1/3] add script wizard to cli --- package.json | 2 + pnpm-lock.yaml | 311 +++++++++++++++++++++++++++++++++++++++++++ src/cli.ts | 8 ++ src/script-wizard.ts | 104 +++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 src/script-wizard.ts diff --git a/package.json b/package.json index fe0920c..0b451e1 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "generate-code": "tsx src/cli.ts generate-code", "make-commitment": "tsx src/cli.ts make-commitment", + "script-wizard": "tsx src/cli.ts script-wizard", "build": "tsc && turbo run build", "compact": "compact compile generated/Warden.compact ./generated/managed/warden", "clean": "pnpm --filter @e2e/* run clean && rm -rf dist generated tsconfig.tsbuildinfo .turbo", @@ -36,6 +37,7 @@ "vitest": "^4.1.6" }, "dependencies": { + "@inquirer/prompts": "^8.5.2", "@midnight-ntwrk/compact-runtime": "0.16.0", "commander": "^14.0.3", "zod": "^4.3.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6f175a..3e69d26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: .: dependencies: + '@inquirer/prompts': + specifier: ^8.5.2 + version: 8.5.2(@types/node@25.6.0) '@midnight-ntwrk/compact-runtime': specifier: 0.16.0 version: 0.16.0 @@ -437,6 +440,140 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.1': + resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.2.2': + resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.1.1': + resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@3.0.3': + resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/input@5.1.2': + resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.1.1': + resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.1.1': + resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.5.2': + resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.3.1': + resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.2.1': + resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.1': + resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -1121,10 +1258,17 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + classic-level@3.0.0: resolution: {integrity: sha512-yGy8j8LjPbN0Bh3+ygmyYvrmskVita92pD/zCoalfcC9XxZj6iDtZTAnz+ot7GG8p9KLTG+MZ84tSA4AhkgVZQ==} engines: {node: '>=18'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -1249,6 +1393,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1335,6 +1488,10 @@ packages: resolution: {integrity: sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1515,6 +1672,10 @@ packages: multipasta@0.2.7: resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1662,6 +1823,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scale-ts@1.6.1: resolution: {integrity: sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==} @@ -1681,6 +1845,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + smoldot@2.0.26: resolution: {integrity: sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig==} @@ -2078,6 +2246,125 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@2.0.7': {} + + '@inquirer/checkbox@5.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/confirm@6.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/core@11.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/editor@5.2.2(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/external-editor': 3.0.3(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/expand@5.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/external-editor@3.0.3(@types/node@25.6.0)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/figures@2.0.7': {} + + '@inquirer/input@5.1.2(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/number@4.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/password@5.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/prompts@8.5.2(@types/node@25.6.0)': + dependencies: + '@inquirer/checkbox': 5.2.1(@types/node@25.6.0) + '@inquirer/confirm': 6.1.1(@types/node@25.6.0) + '@inquirer/editor': 5.2.2(@types/node@25.6.0) + '@inquirer/expand': 5.1.1(@types/node@25.6.0) + '@inquirer/input': 5.1.2(@types/node@25.6.0) + '@inquirer/number': 4.1.1(@types/node@25.6.0) + '@inquirer/password': 5.1.1(@types/node@25.6.0) + '@inquirer/rawlist': 5.3.1(@types/node@25.6.0) + '@inquirer/search': 4.2.1(@types/node@25.6.0) + '@inquirer/select': 5.2.1(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/rawlist@5.3.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/search@4.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/select@5.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/type@4.0.7(@types/node@25.6.0)': + optionalDependencies: + '@types/node': 25.6.0 + '@jridgewell/sourcemap-codec@1.5.5': {} '@midnight-ntwrk/compact-js@2.5.1': @@ -3113,6 +3400,8 @@ snapshots: chai@6.2.2: {} + chardet@2.1.1: {} + classic-level@3.0.0: dependencies: abstract-level: 3.1.1 @@ -3120,6 +3409,8 @@ snapshots: napi-macros: 2.2.2 node-gyp-build: 4.8.4 + cli-width@4.1.0: {} + commander@14.0.3: {} convert-source-map@2.0.0: {} @@ -3274,6 +3565,16 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3337,6 +3638,10 @@ snapshots: graphql@16.14.1: {} + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -3478,6 +3783,8 @@ snapshots: multipasta@0.2.7: {} + mute-stream@3.0.0: {} + nanoid@3.3.12: {} napi-macros@2.2.2: {} @@ -3624,6 +3931,8 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + scale-ts@1.6.1: optional: true @@ -3637,6 +3946,8 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + smoldot@2.0.26: dependencies: ws: 8.21.0 diff --git a/src/cli.ts b/src/cli.ts index 02f78d2..d244906 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from 'url'; import { NativeScriptSchema } from './schema.js'; import { generateCompact } from './generator/index.js'; import { generateSecretPair, generateCommitment } from './pair.js'; +import { scriptWizard } from './script-wizard.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -80,4 +81,11 @@ program } }); +program + .command('script-wizard') + .description('Interactively build a native script schema JSON file') + .action(async () => { + await scriptWizard(); + }); + program.parse(); diff --git a/src/script-wizard.ts b/src/script-wizard.ts new file mode 100644 index 0000000..bba29ed --- /dev/null +++ b/src/script-wizard.ts @@ -0,0 +1,104 @@ +import { select, input, number, confirm } from '@inquirer/prompts'; +import { writeFileSync } from 'fs'; + +type ScriptNode = Record; + +const SCRIPT_TYPES = [ + { + name: 'cmt', + value: 'cmt', + description: 'Commitment verification clause (signature-equivalent)', + }, + { + name: 'after', + value: 'after', + description: 'Transaction must be at or after the specified block', + }, + { + name: 'before', + value: 'before', + description: 'Transaction must be before the specified block', + }, + { + name: 'any', + value: 'any', + description: 'At least one sub-script must be satisfied', + }, + { + name: 'all', + value: 'all', + description: 'All sub-scripts must be satisfied', + }, + { + name: 'atLeast', + value: 'atLeast', + description: 'At least N of the sub-scripts must be satisfied', + }, +] as const; + +async function promptForScript(): Promise { + const type = await select<'cmt' | 'after' | 'before' | 'any' | 'all' | 'atLeast'>({ + message: 'Select script type:', + choices: SCRIPT_TYPES, + }); + + switch (type) { + case 'cmt': { + const hash = await input({ + message: 'Enter commitment hash (64 hex characters):', + validate: (v: string) => + /^[0-9a-f]{64}$/i.test(v) || 'Must be 64 hex characters', + }); + return { type: 'cmt', hash: hash.toLowerCase() }; + } + case 'after': { + const block = await number({ + message: 'Enter block number:', + required: true, + }); + return { type: 'after', block }; + } + case 'before': { + const block = await number({ + message: 'Enter block number:', + required: true, + }); + return { type: 'before', block }; + } + case 'any': + case 'all': { + const scripts = await collectScripts(); + return { type, scripts }; + } + case 'atLeast': { + const required = await number({ + message: 'Minimum number of scripts that must be satisfied:', + required: true, + }); + const scripts = await collectScripts(); + return { type: 'atLeast', required, scripts }; + } + } +} + +async function collectScripts(): Promise { + const scripts: ScriptNode[] = []; + let addMore = true; + while (addMore) { + scripts.push(await promptForScript()); + if (scripts.length >= 1) { + addMore = await confirm({ message: 'Add another sub-script?', default: true }); + } + } + return scripts; +} + +export async function scriptWizard(): Promise { + console.log( + 'This wizard helps you build a native script schema JSON file. Native scripts define spending conditions for UTXOs using commitment verification, time locks, and composite (any/all/atLeast) clauses.\n' + ); + const schema = await promptForScript(); + const outputPath = await input({ message: 'Output file path:', required: true }); + writeFileSync(outputPath, JSON.stringify(schema, null, 2) + '\n', 'utf-8'); + console.log(`Written to: ${outputPath}`); +} From 89e969d347bd04c6fa623a025deeef562e94cf93 Mon Sep 17 00:00:00 2001 From: Lola Aimar Date: Thu, 18 Jun 2026 17:51:49 -0300 Subject: [PATCH 2/3] add script-wizard to readme and update log --- README.md | 37 +++++++++++++++++++++++++------------ src/script-wizard.ts | 27 ++++++++++++++++----------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4b60692..3932b8d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,31 @@ pnpm install ## Usage +### Commitment generator + +```bash +pnpm make-commitment -s -o +``` + +Generates a SecretPair comprised of a `secret` and a `randomness`, and the corresponding `commitment` product of these two. +Optional parameters are: + +- -s, --seed 64-character hex seed for the secret +- -o, --output Path to write the result as JSON + +### Script wizard + +```bash +pnpm script-wizard +``` + +Launches an interactive wizard that builds a native script schema JSON file +that can be used as input to the `generate-code` command. +Walks through script node types — commitment (`cmt`), time locks (`after`/`before`), +and composites (`any`/`all`/`atLeast`) — and writes the result to a JSON file +(defaults to `script.json`). +Ensure all commitments required have been gathered prior to running this command. + ### Compact code generator ```bash @@ -41,18 +66,6 @@ Optional parameters are: - `-o, --output ` Directory to write the generated Compact code (default: `generated/`) - `-t, --test` Include import/export boilerplate for unit testing -### Commitment generator - -```bash -pnpm make-commitment -s -o -``` - -Generates a SecretPair comprised of a `secret` and a `randomness`, and the corresponding `commitment` product of these two. -Optional parameters are: - -- -s, --seed 64-character hex seed for the secret -- -o, --output Path to write the result as JSON - ### Compile ```bash diff --git a/src/script-wizard.ts b/src/script-wizard.ts index bba29ed..658219c 100644 --- a/src/script-wizard.ts +++ b/src/script-wizard.ts @@ -1,4 +1,4 @@ -import { select, input, number, confirm } from '@inquirer/prompts'; +import { select, input, number } from '@inquirer/prompts'; import { writeFileSync } from 'fs'; type ScriptNode = Record; @@ -34,15 +34,22 @@ const SCRIPT_TYPES = [ value: 'atLeast', description: 'At least N of the sub-scripts must be satisfied', }, + { + name: 'done', + value: 'done', + description: 'Finish building the script', + }, ] as const; -async function promptForScript(): Promise { - const type = await select<'cmt' | 'after' | 'before' | 'any' | 'all' | 'atLeast'>({ +async function promptForScript(): Promise { + const type = await select<'cmt' | 'after' | 'before' | 'any' | 'all' | 'atLeast' | 'done'>({ message: 'Select script type:', choices: SCRIPT_TYPES, }); switch (type) { + case 'done': + return null; case 'cmt': { const hash = await input({ message: 'Enter commitment hash (64 hex characters):', @@ -83,22 +90,20 @@ async function promptForScript(): Promise { async function collectScripts(): Promise { const scripts: ScriptNode[] = []; - let addMore = true; - while (addMore) { - scripts.push(await promptForScript()); - if (scripts.length >= 1) { - addMore = await confirm({ message: 'Add another sub-script?', default: true }); - } + while (true) { + const node = await promptForScript(); + if (node === null) break; + scripts.push(node); } return scripts; } export async function scriptWizard(): Promise { console.log( - 'This wizard helps you build a native script schema JSON file. Native scripts define spending conditions for UTXOs using commitment verification, time locks, and composite (any/all/atLeast) clauses.\n' + 'This wizard helps you interactively build a script JSON file. The output can be used as input to `pnpm generate-code -i ` to generate a Compact contract that checks commitments and/or time-lock conditions to authorize an operation.\n' ); const schema = await promptForScript(); - const outputPath = await input({ message: 'Output file path:', required: true }); + const outputPath = await input({ message: 'Output file path:', default: 'script.json' }); writeFileSync(outputPath, JSON.stringify(schema, null, 2) + '\n', 'utf-8'); console.log(`Written to: ${outputPath}`); } From bd23c846abf789e96e97af33545ce2ef02c95de7 Mon Sep 17 00:00:00 2001 From: Lola Aimar Date: Fri, 19 Jun 2026 16:26:49 -0300 Subject: [PATCH 3/3] add better logs to commands and readme --- README.md | 122 +++++++++++++++++++++++++++++++++++++++++-- src/cli.ts | 17 ++++-- src/script-wizard.ts | 11 ++-- 3 files changed, 138 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3932b8d..ade94d8 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,28 @@ # Compact native scripts -CLI-based application to generate compact code that checks commitments based on Cardano native scripts. Accepts JSON inputs structured around native script patterns, supporting nested conditions (`any`, `all`, `atLeast`). +CLI-based application to generate compact code that checks commitments and time-lock conditions based on multisignature scripts. Accepts JSON inputs structured around Cardano native script patterns, supporting nested conditions (`any`, `all`, `atLeast`). -The idea is that the CLI generates a Compact contract module that can be used to grant access to circuits based on a set of commitments. The contract has two exported circuits: `commit` and `verify`: +The idea is that the CLI generates a Compact contract module that can be used to grant access to circuits based on a set of commitments. The contract has three exported circuits: `init`, `commit` and `verify`: +- `init` is used to initalize the contract's state - `commit` is used by a user when it wants to add its commitment to the set to authorize a certain circuit running. - `verify` is used within a circuit to ensure that it will be run if and only if the set of commitments present satisfies the predefined assertions. The predefined assertions are constructed based on Cardano native scripts. +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Setup](#setup) +- [Documentation](#documentation) +- [Authorization workflow](#authorization-workflow) +- [Commands](#commands) + - [Commitment generator](#commitment-generator) + - [Script wizard](#script-wizard) + - [Compact code generator](#compact-code-generator) + - [Compile](#compile) + - [Test](#test) +- [E2E example](#e2e-example) + ## Prerequisites - Compact Devtools 0.4.0 (check with `compact --version`) @@ -22,7 +37,99 @@ Install dependencies pnpm install ``` -## Usage +## Documentation + +See [docs/schema.md](docs/schema.md) for the complete schema design documentation, and [docs/design.md](docs/design.md) for a description of the Compact code that the CLI generates and usage instructions. + +## Authorization workflow + +This tool involves two distinct roles: + +### Participant + +Each person who needs to authorize operations runs `make-commitment` to generate their **secret pair** (`secret` + `randomness`) and the corresponding **commitment** which will be present in the Compact code. + +```bash +pnpm make-commitment -o my-pair.json +``` + +This produces a file like: + +```json +{ + "secret": "ed192a825b79d3602cd82cdf0c15ad7accfdc06f32bc0062f89a6c09edaf964b", + "randomness": "1a0a174c772ee1556bf1b980475eaffe2b4bb8112e82f5348e612b93804405da", + "commitment": "21f314e3679165f234fe8a986f60d2a0308e4b846415fe68f4bf9d3d194ea74c" +} +``` + +> **Keep your `secret` and `randomness` private** — they prove your identity at circuit execution time. +> **Share only the `commitment` hex** with the script author so it can be included in the authorization policy. + +### Script author + +The person in charge of building the authorization policy collects the commitments from all participants and runs `generate-code` to produce the Compact contract. + +``` +Commitments from participants Native script definition + │ │ + ▼ ▼ + ┌──────────────────────────────────────────┐ + │ pnpm generate-code │ + │ -i script.json │ + └──────────────────────────────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ Warden.compact │ + │ (authorization │ + │ contract) │ + └──────────────────┘ +``` + +**Step 1 — Collect commitments** + +Ask each participant to run `make-commitment` and share the `commitment` hex with you. + +**Step 2 — Build the native script** + +Write a JSON file describing the authorization conditions. You can either: + +- Use the interactive wizard: + ```bash + pnpm script-wizard + ``` +- Write the JSON manually (see [the schema docs](docs/schema.md) and the [examples directory](examples/inputs/)). + +The script references each commitment by its hex hash: + +```json +{ + "type": "all", + "scripts": [ + { "type": "cmt", "hash": "21f314e3679165f234fe8a986f60d2a0308e4b846415fe68f4bf9d3d194ea74c" }, + { "type": "cmt", "hash": "9517dcb986da18099688e88b6fe1fce568fd0eeb7ddbba86a8263dc489f85783" } + ] +} +``` + +**Step 3 — Generate the contract** + +```bash +pnpm generate-code -i script.json -o generated/ +``` + +This creates `generated/Warden.compact` — a Compact module with the authorization logic baked in. + +**Step 4 — Import into your project** + +```compact +import "generated/Warden"; +``` + +Use `verify()` as a guard before any protected operation. + +## Commands ### Commitment generator @@ -88,6 +195,11 @@ To use another example, the command must be run like: TEST_INPUT=examples/inputs/ pnpm test ``` -## Documentation +## E2E example + +A complete [end-to-end example](e2e/) demonstrating Warden in action — a token +supply contract that uses Warden to gate mint and burn operations by an +authorization policy (any, all, atLeast, time locks, or combinations). -See [docs/schema.md](docs/schema.md) for the complete schema design documentation, and [docs/design.md](docs/design.md) for a description of the Compact code that the CLI generates. +The flow: **deploy** → participants **commit** → **mint/burn** guarded by +`verify()`. See the [e2e README](e2e/README.md) for setup and usage. diff --git a/src/cli.ts b/src/cli.ts index d244906..15ba919 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,11 +9,13 @@ import { scriptWizard } from './script-wizard.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -program.description('CLI-based tool to generate Compact code and commitments.'); +program.description('CLI-based tool to generate Compact code, commitments and JSON inputs.'); program .command('generate-code') - .description('Generate Compact code from a JSON schema') + .description( + 'Generate Compact module that represents an authorization policy, from a JSON schema' + ) .requiredOption('-i, --input ', 'Path to input JSON file') .option( '-o, --output ', @@ -41,7 +43,8 @@ program const outputPath = path.join(outputDir, 'Warden.compact'); writeFileSync(outputPath, compactCode, 'utf-8'); - console.log(`✅ Valid script. Output written to: ${outputPath}`); + console.log(`✅ Authorization contract generated at ${outputPath}`); + console.log(` Import this module into your Compact program to enforce the policy.`); }); program @@ -73,11 +76,17 @@ program ), 'utf-8' ); - console.log(`Written to: ${options.output}`); + console.log(`✅ Secret pair and commitment written to ${options.output}`); + console.log(` Share this commitment with the script author: ${commitmentHex}`); + console.log(` ⚠️ Keep the secret and randomness private — they prove your identity.`); + console.log(` They'll include this commitment in the authorization policy.`); } else { console.log(`Secret (hex): ${secretHex}`); console.log(`Randomness (hex): ${randomnessHex}`); console.log(`Commitment (hex): ${commitmentHex}`); + console.log(`--- Share the "commitment" hex with the script author ---`); + console.log(`⚠️ Keep the secret and randomness private — they prove your identity.`); + console.log(`They'll include this commitment in the authorization policy.`); } }); diff --git a/src/script-wizard.ts b/src/script-wizard.ts index 658219c..d4101e0 100644 --- a/src/script-wizard.ts +++ b/src/script-wizard.ts @@ -53,8 +53,7 @@ async function promptForScript(): Promise { case 'cmt': { const hash = await input({ message: 'Enter commitment hash (64 hex characters):', - validate: (v: string) => - /^[0-9a-f]{64}$/i.test(v) || 'Must be 64 hex characters', + validate: (v: string) => /^[0-9a-f]{64}$/i.test(v) || 'Must be 64 hex characters', }); return { type: 'cmt', hash: hash.toLowerCase() }; } @@ -102,8 +101,14 @@ export async function scriptWizard(): Promise { console.log( 'This wizard helps you interactively build a script JSON file. The output can be used as input to `pnpm generate-code -i ` to generate a Compact contract that checks commitments and/or time-lock conditions to authorize an operation.\n' ); + console.log( + 'Before running this command, ensure each participant has generated their commitment via `pnpm make-commitment` and shared the commitment hex with you.\n' + ); const schema = await promptForScript(); const outputPath = await input({ message: 'Output file path:', default: 'script.json' }); writeFileSync(outputPath, JSON.stringify(schema, null, 2) + '\n', 'utf-8'); - console.log(`Written to: ${outputPath}`); + console.log(`✅ Script written to: ${outputPath}`); + console.log( + `You can now run pnpm generate-code -i ${outputPath} to generate the Compact contract.` + ); }