From 1e5f7d3ec828b8bf37eec6de707917957c054c3d Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Mon, 18 May 2026 15:06:07 +0200 Subject: [PATCH 01/12] feat: add compact-deploy package and wire it into compact-cli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds @openzeppelin/compact-deploy — a Forge-style deployer library for Midnight Compact contracts — and exposes its CLI through the existing @openzeppelin/compact-cli package. Library (packages/deploy/): - runPipeline orchestrator (config → wallet → faucet → providers → submit → persist), decomposed into per-step helpers - Deployments class — head + history JSON ledger with read methods (getHead, getHistory, listContracts) and atomic rotation on write - Keystore class — Web3 Secret Storage v3-compatible with a "midnight-1" version marker; scrypt + AES-128-CTR + SHA-256 MAC - ProofServer class — lifecycle wrapper over the five-step precedence chain (CLI > TOML URL > "auto" container > PROOF_SERVER_PORT > default) - Typed error hierarchy with stable exit codes (DeployError + subclasses) - src/ layout: loaders/, config/, wallet/, providers/ + top-level errors.ts / pipeline.ts / deployments.ts / index.ts CLI (packages/cli/): - runDeploy.ts: thin shell over the deploy library (chalk + ora + pino UX, --json mode, exit-code-mapped errors); ~250 LOC, zero business logic - compact-deploy bin entry added alongside compact-builder + compact-compiler - engines.node bumped 20 → 22 to match the deploy library Integration tests (tests/integrations/): - vitest config + wallet pool harness + Counter fixture - specs cover deploy, dry-run, history rotation, errors, wallet pool Root: - @openzeppelin/compact-deploy workspace devDep - test:integration / env:up / env:down scripts - resolutions pin for @midnight-ntwrk/ledger-v8 8.0.3 --- package.json | 8 + packages/cli/package.json | 18 +- packages/cli/src/logger.ts | 68 + packages/cli/src/prompt.ts | 56 + packages/cli/src/runDeploy.ts | 251 ++ packages/deploy/README.md | 129 + packages/deploy/package.json | 67 + packages/deploy/src/config/load.test.ts | 79 + packages/deploy/src/config/load.ts | 86 + packages/deploy/src/config/schema.ts | 113 + packages/deploy/src/deployments.test.ts | 73 + packages/deploy/src/deployments.ts | 124 + packages/deploy/src/errors.ts | 83 + packages/deploy/src/index.ts | 45 + packages/deploy/src/loaders/args.test.ts | 50 + packages/deploy/src/loaders/args.ts | 99 + packages/deploy/src/loaders/artifact.ts | 181 + .../deploy/src/loaders/init-state.test.ts | 33 + packages/deploy/src/loaders/init-state.ts | 78 + .../deploy/src/loaders/signing-key.test.ts | 34 + packages/deploy/src/loaders/signing-key.ts | 34 + packages/deploy/src/pipeline.ts | 510 +++ packages/deploy/src/providers/build.ts | 64 + packages/deploy/src/providers/network.ts | 56 + .../providers/private-state-password.test.ts | 37 + .../src/providers/private-state-password.ts | 35 + packages/deploy/src/providers/proof-server.ts | 94 + packages/deploy/src/wallet/build-deployer.ts | 70 + packages/deploy/src/wallet/keystore.test.ts | 35 + packages/deploy/src/wallet/keystore.ts | 227 + packages/deploy/src/wallet/local-seeds.ts | 29 + packages/deploy/src/wallet/normalize.test.ts | 35 + packages/deploy/src/wallet/normalize.ts | 35 + packages/deploy/src/wallet/resolve.ts | 92 + packages/deploy/tsconfig.json | 16 + tests/integrations/.gitignore | 3 + tests/integrations/Makefile | 31 + tests/integrations/README.md | 54 + tests/integrations/_harness/deployer.ts | 36 + tests/integrations/_harness/logger.ts | 15 + tests/integrations/_harness/network.ts | 42 + tests/integrations/_harness/paths.ts | 32 + tests/integrations/_harness/teardown.ts | 12 + tests/integrations/_harness/walletPool.ts | 97 + tests/integrations/compact.toml | 20 + tests/integrations/fixtures/Counter.compact | 13 + .../fixtures/signingkeys/Counter.signingkey | 1 + tests/integrations/local-env.yml | 61 + tests/integrations/specs/deploy.spec.ts | 54 + tests/integrations/specs/dryRun.spec.ts | 42 + tests/integrations/specs/errors.spec.ts | 47 + .../specs/historyRotation.spec.ts | 55 + tests/integrations/specs/walletPool.spec.ts | 59 + tests/integrations/vitest.config.ts | 18 + yarn.lock | 3935 ++++++++++++++++- 55 files changed, 7442 insertions(+), 229 deletions(-) create mode 100644 packages/cli/src/logger.ts create mode 100644 packages/cli/src/prompt.ts create mode 100644 packages/cli/src/runDeploy.ts create mode 100644 packages/deploy/README.md create mode 100644 packages/deploy/package.json create mode 100644 packages/deploy/src/config/load.test.ts create mode 100644 packages/deploy/src/config/load.ts create mode 100644 packages/deploy/src/config/schema.ts create mode 100644 packages/deploy/src/deployments.test.ts create mode 100644 packages/deploy/src/deployments.ts create mode 100644 packages/deploy/src/errors.ts create mode 100644 packages/deploy/src/index.ts create mode 100644 packages/deploy/src/loaders/args.test.ts create mode 100644 packages/deploy/src/loaders/args.ts create mode 100644 packages/deploy/src/loaders/artifact.ts create mode 100644 packages/deploy/src/loaders/init-state.test.ts create mode 100644 packages/deploy/src/loaders/init-state.ts create mode 100644 packages/deploy/src/loaders/signing-key.test.ts create mode 100644 packages/deploy/src/loaders/signing-key.ts create mode 100644 packages/deploy/src/pipeline.ts create mode 100644 packages/deploy/src/providers/build.ts create mode 100644 packages/deploy/src/providers/network.ts create mode 100644 packages/deploy/src/providers/private-state-password.test.ts create mode 100644 packages/deploy/src/providers/private-state-password.ts create mode 100644 packages/deploy/src/providers/proof-server.ts create mode 100644 packages/deploy/src/wallet/build-deployer.ts create mode 100644 packages/deploy/src/wallet/keystore.test.ts create mode 100644 packages/deploy/src/wallet/keystore.ts create mode 100644 packages/deploy/src/wallet/local-seeds.ts create mode 100644 packages/deploy/src/wallet/normalize.test.ts create mode 100644 packages/deploy/src/wallet/normalize.ts create mode 100644 packages/deploy/src/wallet/resolve.ts create mode 100644 packages/deploy/tsconfig.json create mode 100644 tests/integrations/.gitignore create mode 100644 tests/integrations/Makefile create mode 100644 tests/integrations/README.md create mode 100644 tests/integrations/_harness/deployer.ts create mode 100644 tests/integrations/_harness/logger.ts create mode 100644 tests/integrations/_harness/network.ts create mode 100644 tests/integrations/_harness/paths.ts create mode 100644 tests/integrations/_harness/teardown.ts create mode 100644 tests/integrations/_harness/walletPool.ts create mode 100644 tests/integrations/compact.toml create mode 100644 tests/integrations/fixtures/Counter.compact create mode 100644 tests/integrations/fixtures/signingkeys/Counter.signingkey create mode 100644 tests/integrations/local-env.yml create mode 100644 tests/integrations/specs/deploy.spec.ts create mode 100644 tests/integrations/specs/dryRun.spec.ts create mode 100644 tests/integrations/specs/errors.spec.ts create mode 100644 tests/integrations/specs/historyRotation.spec.ts create mode 100644 tests/integrations/specs/walletPool.spec.ts create mode 100644 tests/integrations/vitest.config.ts diff --git a/package.json b/package.json index a9bb156..f69fce4 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "scripts": { "build": "turbo run build --log-prefix=none", "test": "turbo run test --log-prefix=none", + "test:integration": "yarn vitest run --config tests/integrations/vitest.config.ts", + "env:up": "make -C tests/integrations env-up", + "env:down": "make -C tests/integrations env-down", "lint": "biome check .", "lint:fix": "biome check . --write", "lint:ci": "biome ci . --no-errors-on-unmatched", @@ -17,10 +20,15 @@ }, "devDependencies": { "@biomejs/biome": "2.3.8", + "@openzeppelin/compact-deploy": "workspace:^", "@types/node": "24.10.1", + "pino": "^9.7.0", "ts-node": "^10.9.2", "turbo": "^2.6.1", "typescript": "^5.9.3", "vitest": "^4.0.15" + }, + "resolutions": { + "@midnight-ntwrk/ledger-v8": "8.0.3" } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 11c8d91..29ceca3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,12 +1,13 @@ { "name": "@openzeppelin/compact-cli", - "description": "CLI for compiling and building Compact smart contracts", + "description": "CLI for compiling, building, and deploying Compact smart contracts", "version": "0.0.1", "keywords": [ "compact", "cli", "compiler", "builder", + "deployer", "testing" ], "author": "OpenZeppelin Community ", @@ -14,7 +15,8 @@ "type": "module", "exports": { "./run-builder": "./dist/runBuilder.js", - "./run-compiler": "./dist/runCompiler.js" + "./run-compiler": "./dist/runCompiler.js", + "./run-deploy": "./dist/runDeploy.js" }, "files": [ "dist", @@ -22,11 +24,12 @@ "LICENSE" ], "engines": { - "node": ">=20" + "node": ">=22" }, "bin": { "compact-builder": "dist/runBuilder.js", - "compact-compiler": "dist/runCompiler.js" + "compact-compiler": "dist/runCompiler.js", + "compact-deploy": "dist/runDeploy.js" }, "scripts": { "build": "tsc -p .", @@ -37,12 +40,17 @@ "devDependencies": { "@tsconfig/node24": "^24.0.3", "@types/node": "24.10.1", + "@types/ws": "^8.5.10", "typescript": "^5.9.3", "vitest": "^4.0.15" }, "dependencies": { "@openzeppelin/compact-builder": "workspace:^", + "@openzeppelin/compact-deploy": "workspace:^", "chalk": "^5.6.2", - "ora": "^9.0.0" + "ora": "^9.0.0", + "pino": "^9.7.0", + "pino-pretty": "^13.0.0", + "ws": "^8.16.0" } } diff --git a/packages/cli/src/logger.ts b/packages/cli/src/logger.ts new file mode 100644 index 0000000..179e2b9 --- /dev/null +++ b/packages/cli/src/logger.ts @@ -0,0 +1,68 @@ +import { mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import pino, { type Logger } from 'pino'; + +/** + * Pino logger factory tuned for the CLI's three modes. + * + * --json : raw JSON to stdout, no transports (CI-friendly). + * default : pretty-printed `info+` to stdout via `pino-pretty`. + * --verbose (no json): same pretty stdout AND `debug+` mirrored to a + * timestamped file under `.compact/logs/` so the + * transcript survives ephemeral spinner overwrites. + */ +export interface CreateLoggerOptions { + verbose: boolean; + json: boolean; + logDir?: string; +} + +export function createLogger(opts: CreateLoggerOptions): Logger { + if (opts.json) { + return pino({ level: opts.verbose ? 'debug' : 'info' }); + } + + if (opts.verbose) { + const dir = opts.logDir ?? join(process.cwd(), '.compact', 'logs'); + mkdirSync(dir, { recursive: true }); + const file = join( + dir, + `${new Date().toISOString().replace(/[:.]/g, '-')}.log`, + ); + return pino( + { level: 'debug' }, + pino.transport({ + targets: [ + { + target: 'pino/file', + options: { destination: file }, + level: 'debug', + }, + { + target: 'pino-pretty', + options: { + destination: 1, + colorize: true, + translateTime: 'HH:MM:ss', + ignore: 'pid,hostname', + }, + level: 'info', + }, + ], + }), + ); + } + + return pino( + { level: 'info' }, + pino.transport({ + target: 'pino-pretty', + options: { + destination: 1, + colorize: true, + translateTime: 'HH:MM:ss', + ignore: 'pid,hostname', + }, + }), + ); +} diff --git a/packages/cli/src/prompt.ts b/packages/cli/src/prompt.ts new file mode 100644 index 0000000..39e26ab --- /dev/null +++ b/packages/cli/src/prompt.ts @@ -0,0 +1,56 @@ +import { stdin, stdout } from 'node:process'; + +/** + * Prompt the user for a keystore passphrase on stdout and read it from stdin + * with terminal echo suppressed. + * + * Uses raw mode + manual byte handling so we can swallow each character as + * it arrives (no glyphs, no asterisks) and handle Ctrl-C / Backspace + * correctly. Falls back to plain line-read when stdin is not a TTY (piped + * input in CI). + */ +export async function promptPassphrase(label: string): Promise { + stdout.write(`Passphrase for ${label}: `); + return readMaskedLine(); +} + +function readMaskedLine(): Promise { + return new Promise((resolveFn, rejectFn) => { + let buffer = ''; + const isTTY = stdin.isTTY === true; + + const cleanup = () => { + if (isTTY) stdin.setRawMode(false); + stdin.pause(); + stdin.removeListener('data', onData); + stdout.write('\n'); + }; + + const onData = (chunk: Buffer) => { + const s = chunk.toString('utf8'); + for (const ch of s) { + const code = ch.charCodeAt(0); + if (code === 0x03) { + cleanup(); + rejectFn(new Error('Aborted')); + return; + } + if (code === 0x0d || code === 0x0a) { + cleanup(); + resolveFn(buffer); + return; + } + if (code === 0x7f || code === 0x08) { + buffer = buffer.slice(0, -1); + continue; + } + buffer += ch; + } + }; + + if (isTTY) stdin.setRawMode(true); + stdin.resume(); + stdin.setEncoding('utf8'); + stdin.on('data', onData); + }); +} diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts new file mode 100644 index 0000000..f18d9a0 --- /dev/null +++ b/packages/cli/src/runDeploy.ts @@ -0,0 +1,251 @@ +#!/usr/bin/env node +/** + * `compact-deploy` — opinionated CLI shell over the deploy pipeline. + * + * Responsibilities limited to: + * - argv parsing (handwritten, no external CLI lib — keeps cold-start fast) + * - constructing the logger / spinner / passphrase prompt + * - delegating to `runPipeline` and rendering its result + * - mapping exceptions to typed exit codes via {@link DeployError.exitCode} + * + * All deploy logic lives in `pipeline.ts` and its dependencies; this file + * should never grow business logic. + * + * The `globalThis.WebSocket = ws` shim is required because midnight-js's + * indexer client uses the browser WebSocket interface and Node only + * provides it natively from v22. + */ +// biome-ignore-all lint/suspicious/noConsole: CLI writes user-facing diagnostics to stdout/stderr +import chalk from 'chalk'; +import ora from 'ora'; +import { WebSocket } from 'ws'; +import { deploy, DeployError } from '@openzeppelin/compact-deploy'; +import { createLogger } from './logger.ts'; +import { promptPassphrase } from './prompt.ts'; + +(globalThis as { WebSocket?: unknown }).WebSocket = WebSocket; + +interface ParsedArgs { + contract?: string; + network?: string; + configPath?: string; + seedFile?: string; + proofServer?: string; + skipFaucet: boolean; + dryRun: boolean; + json: boolean; + verbose: boolean; + help: boolean; + version: boolean; + positional: string[]; +} + +function parseArgs(argv: string[]): ParsedArgs { + const out: ParsedArgs = { + skipFaucet: false, + dryRun: false, + json: false, + verbose: false, + help: false, + version: false, + positional: [], + }; + for (let i = 0; i < argv.length; i++) { + const arg = argv[i] as string; + switch (arg) { + case '-h': + case '--help': + out.help = true; + break; + case '--version': + out.version = true; + break; + case '-v': + case '--verbose': + out.verbose = true; + break; + case '--json': + out.json = true; + break; + case '--dry-run': + out.dryRun = true; + break; + case '--skip-faucet': + out.skipFaucet = true; + break; + case '--network': + out.network = expectValue(argv, ++i, '--network'); + break; + case '--config': + out.configPath = expectValue(argv, ++i, '--config'); + break; + case '--seed-file': + out.seedFile = expectValue(argv, ++i, '--seed-file'); + break; + case '--proof-server': + out.proofServer = expectValue(argv, ++i, '--proof-server'); + break; + default: + if (arg.startsWith('--')) throw new Error(`Unknown flag: ${arg}`); + out.positional.push(arg); + } + } + out.contract = out.positional[0]; + return out; +} + +function expectValue(argv: string[], i: number, flag: string): string { + const v = argv[i]; + if (v === undefined || v.startsWith('-')) { + throw new Error(`${flag} requires a value`); + } + return v; +} + +async function main(): Promise { + let args: ParsedArgs; + try { + args = parseArgs(process.argv.slice(2)); + } catch (e) { + console.error(chalk.red(`[DEPLOY] ${(e as Error).message}`)); + showUsage(); + process.exit(2); + return; + } + + if (args.help) { + showUsage(); + return; + } + if (args.version) { + console.log(packageVersion()); + return; + } + + if (!args.contract) { + console.error( + chalk.red('[DEPLOY] Missing required positional argument.'), + ); + showUsage(); + process.exit(2); + return; + } + + const logger = createLogger({ verbose: args.verbose, json: args.json }); + const spinner = args.json + ? undefined + : ora( + chalk.blue( + `[DEPLOY] ${args.dryRun ? 'Dry-running' : 'Deploying'} ${args.contract}…`, + ), + ).start(); + + try { + const result = await deploy({ + contract: args.contract, + network: args.network, + configPath: args.configPath, + seedFile: args.seedFile, + proofServer: args.proofServer, + skipFaucet: args.skipFaucet, + dryRun: args.dryRun, + logger, + promptPassphrase: async (path) => { + if (spinner) spinner.stop(); + const pp = await promptPassphrase(path); + if (spinner) spinner.start(); + return pp; + }, + }); + + if (args.json) { + process.stdout.write(`${JSON.stringify(result)}\n`); + return; + } + if (result.dryRun) { + spinner?.succeed( + chalk.green( + `[DEPLOY] Dry-run for ${result.contractName} on ${result.network} OK`, + ), + ); + return; + } + spinner?.succeed( + chalk.green( + `[DEPLOY] ${result.contractName} deployed on ${result.network}: ${result.address}`, + ), + ); + console.log(chalk.gray(` txId: ${result.txId}`)); + console.log(chalk.gray(` txHash: ${result.txHash}`)); + console.log(chalk.gray(` blockHeight: ${result.blockHeight}`)); + console.log(chalk.gray(` saved to: ${result.deploymentsFile}`)); + } catch (e) { + const code = e instanceof DeployError ? e.exitCode : 1; + const name = e instanceof Error ? e.name : 'Error'; + const message = e instanceof Error ? e.message : String(e); + if (args.json) { + process.stdout.write( + `${JSON.stringify({ error: name, message, exitCode: code })}\n`, + ); + } else { + spinner?.fail(chalk.red(`[DEPLOY] ${name}: ${message}`)); + if (args.verbose && e instanceof Error && e.stack) { + console.error(chalk.gray(e.stack)); + } + } + process.exit(code); + } +} + +function showUsage(): void { + console.log(chalk.yellow('\nUsage: compact-deploy [options]')); + console.log(chalk.yellow('\nOptions:')); + console.log( + chalk.yellow( + ' --network Target network (or set [profile].default_network)', + ), + ); + console.log( + chalk.yellow( + ' --config Path to compact.toml (default: walk up from CWD)', + ), + ); + console.log( + chalk.yellow( + ' --seed-file Seed override (raw hex or BIP39 mnemonic, one line)', + ), + ); + console.log( + chalk.yellow(' --proof-server Override [networks.X].proof_server'), + ); + console.log( + chalk.yellow(' --skip-faucet Skip faucet even if faucet=true'), + ); + console.log( + chalk.yellow(' --dry-run Load+validate, do NOT submit a tx'), + ); + console.log( + chalk.yellow(' --json Single JSON object on stdout'), + ); + console.log( + chalk.yellow(' -v, --verbose Pino debug logs to .compact/logs/'), + ); + console.log(chalk.yellow(' -h, --help Show this help')); + console.log(chalk.yellow(' --version Print package version')); + console.log(chalk.yellow('\nExamples:')); + console.log(chalk.yellow(' compact-deploy Token --network local')); + console.log( + chalk.yellow( + ' MN_DEPLOYER_SEED=$(cat seed.hex) compact-deploy Vault --network testnet', + ), + ); + console.log( + chalk.yellow(' compact-deploy Token --network preprod --dry-run --json'), + ); +} + +function packageVersion(): string { + return process.env.npm_package_version ?? 'dev'; +} + +main(); diff --git a/packages/deploy/README.md b/packages/deploy/README.md new file mode 100644 index 0000000..2d89f8a --- /dev/null +++ b/packages/deploy/README.md @@ -0,0 +1,129 @@ +# @openzeppelin/compact-deploy + +Forge-style deployer CLI for Midnight Compact contracts. + +```bash +compact-deploy Token --network local +``` + +## Quick start + +1. Compile your contract with `compact-compiler` so artifacts land under `src/artifacts//`. +2. Drop a `compact.toml` at your repo root (see [Sample config](#sample-config)). +3. Generate a signing key per contract: `head -c 32 /dev/urandom | xxd -p -c 32 > deploy/Token.signingkey`. +4. Run: + ```bash + compact-deploy Token --network local + ``` + +The deploy result lands in `deployments/compact/.json`. + +## CLI + +``` +compact-deploy + --network required unless [profile].default_network is set + --config default: walk up from CWD for compact.toml + --seed-file seed override (raw hex or BIP39 mnemonic, one line) + --proof-server override [networks.X].proof_server + --skip-faucet don't call the faucet even if faucet=true + --dry-run load, validate, build providers, log plan, DO NOT submit + --json single JSON object on stdout (machine-readable) + -v, --verbose pino debug logs to .compact/logs/.log + -h, --help --version +``` + +Exit codes: `0` ok · `2` config error · `3` wallet error · `4` provider unreachable · `5` deploy tx failed · `1` unexpected. + +## Wallet seed resolution + +Precedence, first non-null wins: + +1. `--seed-file ` +2. `MN_DEPLOYER_SEED` env var (hex or BIP39 mnemonic) +3. `[wallet].keystore` (encrypted JSON, passphrase prompted) +4. `--network local` only: built-in prefunded standalone seed at `[networks.local].wallet.index` (0..3) + +## Sample config + +```toml +[profile] +default_network = "local" +artifacts_dir = "src/artifacts" +deployments_dir = "deployments/compact" + +# ---------- Networks ---------- +[networks.local] +network_id = "undeployed" +indexer = "http://127.0.0.1:8088/api/v3/graphql" +indexer_ws = "ws://127.0.0.1:8088/api/v3/graphql/ws" +node = "http://127.0.0.1:9944" +node_ws = "ws://127.0.0.1:9944" +proof_server = "http://127.0.0.1:6300" +wallet = { source = "local", index = 0 } +faucet = false + +[networks.testnet] +network_id = "test" +indexer = "https://indexer.testnet-02.midnight.network/api/v1/graphql" +indexer_ws = "wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws" +node = "https://rpc.testnet-02.midnight.network" +node_ws = "wss://rpc.testnet-02.midnight.network" +proof_server = "auto" +faucet = true +faucet_url = "https://faucet.testnet-02.midnight.network" + +[networks.preprod] +network_id = "preprod" +indexer = "https://indexer.preprod.midnight.network/api/v3/graphql" +indexer_ws = "wss://indexer.preprod.midnight.network/api/v3/graphql/ws" +node = "https://rpc.preprod.midnight.network" +node_ws = "wss://rpc.preprod.midnight.network" +proof_server = "auto" +faucet = true + +[networks.mainnet] +network_id = "mainnet" +indexer = "https://indexer.mainnet.midnight.network/api/v3/graphql" +indexer_ws = "wss://indexer.mainnet.midnight.network/api/v3/graphql/ws" +node = "https://rpc.mainnet.midnight.network" +node_ws = "wss://rpc.mainnet.midnight.network" +proof_server = "auto" +faucet = false + +# ---------- Wallet (non-local) ---------- +[wallet] +keystore = "./deployer.keystore.json" + +# ---------- Contracts ---------- +[contracts.Token] +artifact = "src/artifacts/Token/Token" +private_state_id = "tokenPrivateState" +init_private_state = { file = "./deploy/Token.private-state.json" } +args = ["MyToken", "MTK", 18] +signing_key_file = "./deploy/Token.signingkey" + +[contracts.Vault] +artifact = "src/artifacts/Vault/Vault" +args = [] +signing_key_file = "./deploy/Vault.signingkey" +``` + +`proof_server`: a URL pins the server; `"auto"` spawns a `testcontainers`-managed proof-server container for the duration of the deploy; omitting it falls back to the env var `PROOF_SERVER_PORT` then to `http://127.0.0.1:6300`. + +## Keystore format + +`compact-deploy` reads/writes a JSON keystore with the Ethereum V3 shape (scrypt + AES-128-CTR) but with `version: "midnight-1"` so other tooling does not silently mis-read it as an Ethereum key. The encrypted secret is a 32-byte Midnight wallet seed (hex). + +## Programmatic API + +```ts +import { deploy } from "@openzeppelin/compact-deploy"; + +const result = await deploy({ + contract: "Token", + network: "local", + configPath: "./compact.toml", +}); +console.log(result.address); +``` diff --git a/packages/deploy/package.json b/packages/deploy/package.json new file mode 100644 index 0000000..f46415b --- /dev/null +++ b/packages/deploy/package.json @@ -0,0 +1,67 @@ +{ + "name": "@openzeppelin/compact-deploy", + "description": "Forge-style deployer library for Midnight Compact contracts", + "version": "0.0.1", + "keywords": [ + "compact", + "midnight", + "deploy" + ], + "author": "OpenZeppelin Community ", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "engines": { + "node": ">=22" + }, + "scripts": { + "build": "tsc -p .", + "types": "tsc -p tsconfig.json --noEmit", + "test": "yarn vitest run", + "clean": "git clean -fXd" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.3", + "@types/node": "24.10.1", + "typescript": "^5.9.3", + "vitest": "^4.0.15" + }, + "dependencies": { + "@midnight-ntwrk/compact-js": "2.5.0", + "@midnight-ntwrk/compact-runtime": "0.16.0", + "@midnight-ntwrk/ledger-v8": "8.0.3", + "@midnight-ntwrk/midnight-js-contracts": "4.0.2", + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.2", + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.2", + "@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.2", + "@midnight-ntwrk/midnight-js-network-id": "4.0.2", + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.2", + "@midnight-ntwrk/midnight-js-types": "4.0.2", + "@midnight-ntwrk/midnight-js-utils": "4.0.2", + "@midnight-ntwrk/testkit-js": "4.0.2", + "@midnight-ntwrk/wallet-sdk-address-format": "3.1.0", + "@midnight-ntwrk/wallet-sdk-facade": "3.0.0", + "@midnight-ntwrk/wallet-sdk-hd": "3.0.1", + "@midnight-ntwrk/wallet-sdk-shielded": "2.1.0", + "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "2.1.0", + "@scure/bip39": "^1.2.1", + "axios": "^1.12.0", + "pino": "^9.7.0", + "rxjs": "^7.8.1", + "smol-toml": "^1.3.4", + "testcontainers": "^10.28.0", + "zod": "^3.23.8" + } +} diff --git a/packages/deploy/src/config/load.test.ts b/packages/deploy/src/config/load.test.ts new file mode 100644 index 0000000..e3856cf --- /dev/null +++ b/packages/deploy/src/config/load.test.ts @@ -0,0 +1,79 @@ +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { ConfigError } from '../errors.ts'; +import { loadConfig } from './load.ts'; + +const MIN_VALID = ` +[profile] +default_network = "local" + +[networks.local] +network_id = "undeployed" +indexer = "http://127.0.0.1:8088/api/v3/graphql" +indexer_ws = "ws://127.0.0.1:8088/api/v3/graphql/ws" +node = "http://127.0.0.1:9944" +node_ws = "ws://127.0.0.1:9944" +proof_server = "http://127.0.0.1:6300" + +[contracts.Token] +artifact = "src/artifacts/Token/Token" +signing_key_file = "./deploy/Token.signingkey" +`; + +function tmpRepo(toml: string): string { + const dir = mkdtempSync(join(tmpdir(), 'compact-deploy-test-')); + writeFileSync(join(dir, 'compact.toml'), toml); + return dir; +} + +describe('loadConfig', () => { + it('parses a minimal valid config', async () => { + const dir = tmpRepo(MIN_VALID); + const { config, rootDir } = await loadConfig(undefined, dir); + expect(rootDir).toBe(dir); + expect(config.profile.default_network).toBe('local'); + expect(config.networks.local.network_id).toBe('undeployed'); + expect(config.contracts.Token.artifact).toBe('src/artifacts/Token/Token'); + }); + + it('rejects a config whose default_network does not exist', async () => { + const dir = tmpRepo(`${MIN_VALID}\n[profile]\ndefault_network = "ghost"\n`); + await expect(loadConfig(undefined, dir)).rejects.toThrow(ConfigError); + }); + + it('rejects a contract missing signing_key_file', async () => { + const dir = tmpRepo(` +[networks.local] +network_id = "undeployed" +indexer = "http://x" +indexer_ws = "ws://x" +node = "http://x" +node_ws = "ws://x" +proof_server = "http://x" + +[contracts.Token] +artifact = "x" +`); + await expect(loadConfig(undefined, dir)).rejects.toThrow(ConfigError); + }); + + it('rejects when init_private_state is set but private_state_id is not', async () => { + const dir = tmpRepo(` +[networks.local] +network_id = "undeployed" +indexer = "http://127.0.0.1:8088/api/v3/graphql" +indexer_ws = "ws://127.0.0.1:8088/api/v3/graphql/ws" +node = "http://127.0.0.1:9944" +node_ws = "ws://127.0.0.1:9944" +proof_server = "http://127.0.0.1:6300" + +[contracts.Token] +artifact = "x" +signing_key_file = "x.sk" +init_private_state = { file = "x.json" } +`); + await expect(loadConfig(undefined, dir)).rejects.toThrow(ConfigError); + }); +}); diff --git a/packages/deploy/src/config/load.ts b/packages/deploy/src/config/load.ts new file mode 100644 index 0000000..4e78cf0 --- /dev/null +++ b/packages/deploy/src/config/load.ts @@ -0,0 +1,86 @@ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { dirname, isAbsolute, resolve } from 'node:path'; +import { parse as parseToml } from 'smol-toml'; +import { ConfigError } from '../errors.ts'; +import { type CompactConfig, configSchema } from './schema.ts'; + +/** + * Find, parse, and validate `compact.toml` against {@link configSchema}. + * + * When `explicitPath` is omitted the loader walks the directory tree + * upward from `cwd` (Foundry-style) and the *first* `compact.toml` found + * becomes the project root. `rootDir` in the returned bundle is always the + * config file's directory — every other module resolves relative paths + * against it, so the same TOML works whether invoked from a subdir or root. + */ +export interface LoadedConfig { + config: CompactConfig; + configPath: string; + rootDir: string; +} + +export async function loadConfig( + explicitPath: string | undefined, + cwd = process.cwd(), +): Promise { + const configPath = explicitPath + ? resolveExplicit(explicitPath, cwd) + : findUpward(cwd); + if (!configPath) { + throw new ConfigError( + `compact.toml not found (searched upward from ${cwd}). Pass --config or create one at the repo root.`, + ); + } + + let raw: string; + try { + raw = await readFile(configPath, 'utf8'); + } catch (e) { + throw new ConfigError( + `Failed to read ${configPath}: ${(e as Error).message}`, + ); + } + + let parsed: unknown; + try { + parsed = parseToml(raw); + } catch (e) { + throw new ConfigError( + `Invalid TOML in ${configPath}: ${(e as Error).message}`, + ); + } + + const result = configSchema.safeParse(parsed); + if (!result.success) { + const issues = result.error.issues + .map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`) + .join('\n'); + throw new ConfigError(`compact.toml validation failed:\n${issues}`); + } + + return { + config: result.data, + configPath, + rootDir: dirname(configPath), + }; +} + +function resolveExplicit(p: string, cwd: string): string { + const abs = isAbsolute(p) ? p : resolve(cwd, p); + if (!existsSync(abs)) { + throw new ConfigError(`--config path does not exist: ${abs}`); + } + return abs; +} + +function findUpward(start: string): string | undefined { + let dir = resolve(start); + while (true) { + const candidate = resolve(dir, 'compact.toml'); + if (existsSync(candidate)) return candidate; + const parent = dirname(dir); + if (parent === dir) return undefined; + dir = parent; + } +} diff --git a/packages/deploy/src/config/schema.ts b/packages/deploy/src/config/schema.ts new file mode 100644 index 0000000..f02ce6f --- /dev/null +++ b/packages/deploy/src/config/schema.ts @@ -0,0 +1,113 @@ +/** + * Zod schema for `compact.toml` — the single source of truth for config shape. + * + * Top-level sections: + * [profile] — defaults (artifacts dir, deployments dir, default_network). + * [networks.X] — one block per target (URLs, optional faucet, optional + * prefunded local-wallet pointer). + * [wallet] — optional keystore path (passphrase-prompted at runtime). + * [contracts.X] — per-contract: artifact ref, args, witnesses, init + * private state, signing-key path (REQUIRED). + * + * Two `.refine()` cross-field rules: + * 1. `profile.default_network` must reference a defined `[networks.X]`. + * 2. `private_state_id` and `init_private_state` must both be set or both + * omitted (a contract either has private state or it doesn't). + */ + +import { z } from 'zod'; + +const url = z.string().url(); + +const profileSchema = z + .object({ + default_network: z.string().optional(), + artifacts_dir: z.string().default('src/artifacts'), + deployments_dir: z.string().default('deployments/compact'), + }) + .default({}); + +const localWalletSchema = z.object({ + source: z.literal('local'), + index: z.number().int().min(0).max(3).default(0), +}); + +const networkSchema = z.object({ + network_id: z.string().min(1), + indexer: url, + indexer_ws: url, + node: url, + node_ws: url, + proof_server: z.union([url, z.literal('auto')]).optional(), + wallet: localWalletSchema.optional(), + faucet: z.boolean().default(false), + faucet_url: url.optional(), +}); + +const walletSchema = z + .object({ + keystore: z.string().optional(), + }) + .optional(); + +const fileRefSchema = z.object({ file: z.string().min(1) }); +const moduleRefSchema = z.object({ + module: z.string().min(1), + export: z.string().default('default'), +}); +const fileOrModuleRefSchema = z.union([fileRefSchema, moduleRefSchema]); + +const argsSchema = z.union([z.array(z.unknown()), fileOrModuleRefSchema]); + +const contractSchema = z + .object({ + artifact: z.string().min(1), + private_state_id: z.string().optional(), + init_private_state: fileOrModuleRefSchema.optional(), + private_state_store_name: z.string().optional(), + args: argsSchema.optional(), + witnesses: fileOrModuleRefSchema.optional(), + signing_key_file: z.string().min(1), + }) + .refine( + (c) => + (c.private_state_id === undefined) === + (c.init_private_state === undefined), + { + message: + 'private_state_id and init_private_state must be set together (or both omitted)', + }, + ); + +export const configSchema = z + .object({ + profile: profileSchema, + networks: z.record(z.string(), networkSchema), + wallet: walletSchema, + contracts: z.record(z.string(), contractSchema), + }) + .refine( + (c) => + c.profile.default_network === undefined || + Object.hasOwn(c.networks, c.profile.default_network), + { + message: + 'profile.default_network must reference a defined [networks.X] block', + path: ['profile', 'default_network'], + }, + ); + +export type CompactConfig = z.infer; +export type NetworkConfig = z.infer; +export type ContractConfig = z.infer; +export type FileRef = z.infer; +export type ModuleRef = z.infer; +export type FileOrModuleRef = z.infer; + +export function isFileRef(v: unknown): v is FileRef { + return typeof v === 'object' && v !== null && 'file' in v; +} + +export function isModuleRef(v: unknown): v is ModuleRef { + return typeof v === 'object' && v !== null && 'module' in v; +} diff --git a/packages/deploy/src/deployments.test.ts b/packages/deploy/src/deployments.test.ts new file mode 100644 index 0000000..3798a96 --- /dev/null +++ b/packages/deploy/src/deployments.test.ts @@ -0,0 +1,73 @@ +import { mkdtempSync, readFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { Deployments, type DeploymentRecord } from './deployments.ts'; + +function rec(address: string): DeploymentRecord { + return { + address, + txHash: '0xhash', + txId: '0xtx', + blockHeight: 42, + signingKey: 'aa'.repeat(32), + deployer: '0xdep', + artifact: 'src/artifacts/Token/Token', + timestamp: new Date('2026-05-15T00:00:00Z').toISOString(), + }; +} + +function make(root: string): Deployments { + return new Deployments({ + rootDir: root, + deploymentsDir: 'deployments/compact', + network: 'local', + }); +} + +describe('Deployments', () => { + it('writes a fresh deployments/.json', async () => { + const root = mkdtempSync(join(tmpdir(), 'persist-test-')); + const { head } = await make(root).record('Token', rec('0xaddr1')); + const parsed = JSON.parse(readFileSync(head, 'utf8')); + expect(parsed.Token.address).toBe('0xaddr1'); + }); + + it('rotates the previous head into history on overwrite', async () => { + const root = mkdtempSync(join(tmpdir(), 'persist-test-')); + const d = make(root); + await d.record('Token', rec('0xfirst')); + const { head, history } = await d.record('Token', rec('0xsecond')); + + const headJson = JSON.parse(readFileSync(head, 'utf8')); + const historyJson = JSON.parse(readFileSync(history, 'utf8')); + + expect(headJson.Token.address).toBe('0xsecond'); + expect(historyJson.Token).toHaveLength(1); + expect(historyJson.Token[0].address).toBe('0xfirst'); + }); + + it('preserves other contracts when one is updated', async () => { + const root = mkdtempSync(join(tmpdir(), 'persist-test-')); + const d = make(root); + await d.record('Token', rec('0xT1')); + const { head } = await d.record('Vault', rec('0xV1')); + const headJson = JSON.parse(readFileSync(head, 'utf8')); + expect(headJson.Token.address).toBe('0xT1'); + expect(headJson.Vault.address).toBe('0xV1'); + }); + + it('getHead/getHistory/listContracts read what record wrote', async () => { + const root = mkdtempSync(join(tmpdir(), 'persist-test-')); + const d = make(root); + await d.record('Token', rec('0xT1')); + await d.record('Token', rec('0xT2')); + await d.record('Vault', rec('0xV1')); + + expect((await d.getHead('Token'))?.address).toBe('0xT2'); + expect(await d.getHead('Missing')).toBeUndefined(); + expect((await d.getHistory('Token')).map((r) => r.address)).toEqual(['0xT1']); + expect(await d.getHistory('Vault')).toEqual([]); + expect(await d.listContracts()).toEqual(['Token', 'Vault']); + }); +}); diff --git a/packages/deploy/src/deployments.ts b/packages/deploy/src/deployments.ts new file mode 100644 index 0000000..71c8202 --- /dev/null +++ b/packages/deploy/src/deployments.ts @@ -0,0 +1,124 @@ +import { existsSync } from 'node:fs'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { dirname, isAbsolute, resolve } from 'node:path'; + +/** + * Two-file deployment ledger per network. + * + * /.json — latest record per contract (head map) + * /.history.json — superseded records (per-contract history list) + * + * On every deploy the previous head is moved into the history list and the + * new record becomes head. Consumers (CLIs, scripts) typically read just + * the head file; the history list is for audit and rollback. + */ + +/** A single confirmed deploy. Persisted under the contract name in the head map. */ +export interface DeploymentRecord { + address: string; + txHash: string; + txId: string; + blockHeight: number; + signingKey: string; + deployer: string; + artifact: string; + timestamp: string; +} + +/** Head map: contract name → latest deploy. */ +export type DeploymentsFile = Record; + +/** History map: contract name → past deploys (newest first). */ +export type DeploymentsHistory = Record; + +export interface DeploymentsOptions { + rootDir: string; + deploymentsDir: string; + network: string; +} + +/** + * Read/write the per-network deployment ledger. + * + * One instance owns one `` pair; create a fresh ledger for each + * network you touch. Reads are cheap (one JSON load each), writes are + * atomic (head rotation + new head in one logical operation, head file + * written last so a crash mid-rotate leaves the previous head intact). + */ +export class Deployments { + readonly #headPath: string; + readonly #historyPath: string; + + constructor(opts: DeploymentsOptions) { + const dir = isAbsolute(opts.deploymentsDir) + ? opts.deploymentsDir + : resolve(opts.rootDir, opts.deploymentsDir); + this.#headPath = resolve(dir, `${opts.network}.json`); + this.#historyPath = resolve(dir, `${opts.network}.history.json`); + } + + /** Absolute on-disk paths for the two ledger files. */ + get paths(): { head: string; history: string } { + return { head: this.#headPath, history: this.#historyPath }; + } + + /** + * Rotate any prior head record for `contractName` into history, then write + * `record` as the new head. Returns both absolute paths. + */ + async record( + contractName: string, + record: DeploymentRecord, + ): Promise<{ head: string; history: string }> { + await mkdir(dirname(this.#headPath), { recursive: true }); + + const head = await this.#readHead(); + const previous = head[contractName]; + if (previous) { + const history = await this.#readHistory(); + const bucket = history[contractName] ?? []; + bucket.unshift(previous); + history[contractName] = bucket; + await writeJson(this.#historyPath, history); + } + + head[contractName] = record; + await writeJson(this.#headPath, head); + + return { head: this.#headPath, history: this.#historyPath }; + } + + /** Latest deploy for `contractName`, or `undefined` if none. */ + async getHead(contractName: string): Promise { + return (await this.#readHead())[contractName]; + } + + /** Per-contract history (newest first); empty array if none. */ + async getHistory(contractName: string): Promise { + return (await this.#readHistory())[contractName] ?? []; + } + + /** Names of every contract with a current head record on this network. */ + async listContracts(): Promise { + return Object.keys(await this.#readHead()).sort(); + } + + #readHead(): Promise { + return readJson(this.#headPath, {}); + } + + #readHistory(): Promise { + return readJson(this.#historyPath, {}); + } +} + +async function readJson(path: string, fallback: T): Promise { + if (!existsSync(path)) return fallback; + const raw = await readFile(path, 'utf8'); + if (!raw.trim()) return fallback; + return JSON.parse(raw) as T; +} + +async function writeJson(path: string, value: unknown): Promise { + await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); +} diff --git a/packages/deploy/src/errors.ts b/packages/deploy/src/errors.ts new file mode 100644 index 0000000..ccb162b --- /dev/null +++ b/packages/deploy/src/errors.ts @@ -0,0 +1,83 @@ +/** + * Typed error hierarchy with stable process exit codes. + * + * Each subclass pins a distinct `exitCode` so CI / scripts can branch on the + * failure mode without parsing messages: 1 generic, 2 config, 3 wallet, + * 4 network, 5 deploy-tx. The `bin/compact-deploy` shell reads `exitCode` + * directly on catch. + */ + +/** Base class for every deploy-pipeline failure. Default exit code is `1`. */ +export class DeployError extends Error { + readonly exitCode: number; + constructor(message: string, exitCode = 1, options?: ErrorOptions) { + super(message, options); + this.name = 'DeployError'; + this.exitCode = exitCode; + } +} + +/** Config / TOML / schema problems. Exit code `2`. */ +export class ConfigError extends DeployError { + constructor(message: string, options?: ErrorOptions) { + super(message, 2, options); + this.name = 'ConfigError'; + } +} + +/** Seed resolution, keystore decryption, or wallet construction failures. Exit code `3`. */ +export class WalletError extends DeployError { + constructor(message: string, options?: ErrorOptions) { + super(message, 3, options); + this.name = 'WalletError'; + } +} + +/** Proof server didn't respond. Exit code `4`. */ +export class ProofServerUnreachableError extends DeployError { + constructor(url: string, options?: ErrorOptions) { + super(`Proof server unreachable at ${url}`, 4, options); + this.name = 'ProofServerUnreachableError'; + } +} + +/** Indexer GraphQL endpoint didn't respond. Exit code `4`. */ +export class IndexerUnreachableError extends DeployError { + constructor(url: string, options?: ErrorOptions) { + super(`Indexer unreachable at ${url}`, 4, options); + this.name = 'IndexerUnreachableError'; + } +} + +/** Deployer wallet has zero balance and no faucet was hit (or faucet failed). Exit code `3`. */ +export class UnfundedWalletError extends DeployError { + constructor( + address: string, + faucetUrl: string | undefined, + options?: ErrorOptions, + ) { + const hint = faucetUrl ? ` (faucet: ${faucetUrl})` : ''; + super(`Wallet ${address} has zero balance${hint}`, 3, options); + this.name = 'UnfundedWalletError'; + } +} + +/** Compiled artifact directory or required subfiles missing. Exit code `2`. */ +export class ArtifactNotFoundError extends DeployError { + constructor(path: string, options?: ErrorOptions) { + super( + `Compiled artifact not found at ${path}. Run \`compact-compiler\` to produce it.`, + 2, + options, + ); + this.name = 'ArtifactNotFoundError'; + } +} + +/** On-chain submission rejected the tx (proof invalid, fee too low, etc). Exit code `5`. */ +export class DeployTxFailedError extends DeployError { + constructor(message: string, options?: ErrorOptions) { + super(message, 5, options); + this.name = 'DeployTxFailedError'; + } +} diff --git a/packages/deploy/src/index.ts b/packages/deploy/src/index.ts new file mode 100644 index 0000000..505ec3f --- /dev/null +++ b/packages/deploy/src/index.ts @@ -0,0 +1,45 @@ +/** + * Programmatic API surface for `@openzeppelin/compact-deploy`. + * + * Consumers that need to embed the deploy pipeline (CI runners, custom CLIs, + * test harnesses) should import from this barrel. The `compact-deploy` binary + * in `bin/` re-uses the same exports — it is just an opinionated shell. + */ +// biome-ignore-all lint/performance/noBarrelFile: this file is the programmatic API surface for consumers of @openzeppelin/compact-deploy +export { loadConfig } from './config/load.ts'; +export type { + CompactConfig, + ContractConfig, + NetworkConfig, +} from './config/schema.ts'; +export { Deployments } from './deployments.ts'; +export type { + DeploymentRecord, + DeploymentsFile, + DeploymentsHistory, +} from './deployments.ts'; +export type { + PipelineOptions as DeployOptions, + PipelineResult as DeployResult, +} from './pipeline.ts'; +export { runPipeline as deploy } from './pipeline.ts'; +export { + ArtifactNotFoundError, + ConfigError, + DeployError, + DeployTxFailedError, + IndexerUnreachableError, + ProofServerUnreachableError, + UnfundedWalletError, + WalletError, +} from './errors.ts'; +export { Keystore } from './wallet/keystore.ts'; +export type { MidnightKeystore } from './wallet/keystore.ts'; +export { ProofServer } from './providers/proof-server.ts'; +export { + LOCAL_PREFUNDED_SEEDS, + localPrefundedSeed, +} from './wallet/local-seeds.ts'; +export { buildDeployerWallet } from './wallet/build-deployer.ts'; +export { classifySeed } from './wallet/normalize.ts'; +export type { WalletSeed } from './wallet/normalize.ts'; diff --git a/packages/deploy/src/loaders/args.test.ts b/packages/deploy/src/loaders/args.test.ts new file mode 100644 index 0000000..4f15106 --- /dev/null +++ b/packages/deploy/src/loaders/args.test.ts @@ -0,0 +1,50 @@ +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import type { ContractConfig } from '../config/schema.ts'; +import { ConfigError } from '../errors.ts'; +import { loadConstructorArgs } from './args.ts'; + +const baseContract = (extra: Partial = {}): ContractConfig => + ({ + artifact: 'x', + signing_key_file: 'x.sk', + ...extra, + }) as ContractConfig; + +describe('loadConstructorArgs', () => { + it('returns [] when args is unset', async () => { + const args = await loadConstructorArgs(baseContract(), '/tmp'); + expect(args).toEqual([]); + }); + + it('passes inline arrays through', async () => { + const args = await loadConstructorArgs( + baseContract({ args: ['MyToken', 'MTK', 18] }), + '/tmp', + ); + expect(args).toEqual(['MyToken', 'MTK', 18]); + }); + + it('reads a JSON file ref and revives bigints', async () => { + const dir = mkdtempSync(join(tmpdir(), 'args-test-')); + writeFileSync(join(dir, 'a.json'), '["x", "100n"]'); + const args = await loadConstructorArgs( + baseContract({ args: { file: 'a.json' } }), + dir, + ); + expect(args).toEqual(['x', 100n]); + }); + + it('parses a --args override JSON string', async () => { + const args = await loadConstructorArgs(baseContract(), '/tmp', '[1,2,3]'); + expect(args).toEqual([1, 2, 3]); + }); + + it('rejects a non-array --args override', async () => { + await expect( + loadConstructorArgs(baseContract(), '/tmp', '{"x":1}'), + ).rejects.toThrow(ConfigError); + }); +}); diff --git a/packages/deploy/src/loaders/args.ts b/packages/deploy/src/loaders/args.ts new file mode 100644 index 0000000..52885f2 --- /dev/null +++ b/packages/deploy/src/loaders/args.ts @@ -0,0 +1,99 @@ +import { readFile } from 'node:fs/promises'; +import { isAbsolute, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { + type ContractConfig, + isFileRef, + isModuleRef, +} from '../config/schema.ts'; +import { ConfigError } from '../errors.ts'; + +/** + * Resolve a contract's constructor args from CLI / TOML / file / module. + * + * Precedence (highest first): + * 1. `override` (CLI `--args '[…]'`, parsed as JSON). + * 2. Inline `args = [...]` array in TOML. + * 3. `args = { file = "…" }` → JSON file (bigints encoded as `"123n"`). + * 4. `args = { module = "…", export = "…" }` → ES module export (value + * or zero-arg function returning an array). + * + * Returns `[]` when no source supplies args. + */ +export async function loadConstructorArgs( + contract: ContractConfig, + rootDir: string, + override?: string, +): Promise { + if (override !== undefined) { + return parseJsonArray(override, '--args'); + } + const raw = contract.args; + if (raw === undefined) return []; + + if (Array.isArray(raw)) return raw; + + if (isFileRef(raw)) { + const path = abs(rootDir, raw.file); + const text = await safeRead(path, 'args file'); + return parseJsonArray(text, path); + } + + if (isModuleRef(raw)) { + const path = abs(rootDir, raw.module); + let mod: Record; + try { + mod = await import(pathToFileURL(path).href); + } catch (e) { + throw new ConfigError( + `args: failed to import ${path}: ${(e as Error).message}`, + ); + } + const exported = mod[raw.export]; + const resolved = + typeof exported === 'function' + ? await (exported as () => unknown)() + : exported; + if (!Array.isArray(resolved)) { + throw new ConfigError( + `args: module ${path} export "${raw.export}" must be an array`, + ); + } + return resolved; + } + + throw new ConfigError( + 'args must be an inline array, { file }, or { module, export }', + ); +} + +function abs(rootDir: string, p: string): string { + return isAbsolute(p) ? p : resolve(rootDir, p); +} + +function parseJsonArray(text: string, label: string): unknown[] { + let parsed: unknown; + try { + parsed = JSON.parse(text, (_k, v) => + typeof v === 'string' && /^-?\d+n$/.test(v) ? BigInt(v.slice(0, -1)) : v, + ); + } catch (e) { + throw new ConfigError( + `args: invalid JSON at ${label}: ${(e as Error).message}`, + ); + } + if (!Array.isArray(parsed)) { + throw new ConfigError(`args at ${label} must be a JSON array`); + } + return parsed; +} + +async function safeRead(path: string, label: string): Promise { + try { + return await readFile(path, 'utf8'); + } catch (e) { + throw new ConfigError( + `Failed to read ${label} (${path}): ${(e as Error).message}`, + ); + } +} diff --git a/packages/deploy/src/loaders/artifact.ts b/packages/deploy/src/loaders/artifact.ts new file mode 100644 index 0000000..b6fa29b --- /dev/null +++ b/packages/deploy/src/loaders/artifact.ts @@ -0,0 +1,181 @@ +import { existsSync, readdirSync } from 'node:fs'; +import { isAbsolute, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { + CompiledContract, + type Contract, +} from '@midnight-ntwrk/compact-js'; +import type { Types } from 'effect'; +import { + isFileRef, + isModuleRef, + type FileOrModuleRef, +} from '../config/schema.ts'; +import { ArtifactNotFoundError, ConfigError } from '../errors.ts'; + +/** + * Locate a compactc artifact bundle on disk and wrap it for the deploy + * pipeline. + * + * The bundle layout (produced by `compactc` / `compact-builder`) is: + * /contract/index.{cjs,js} — Contract class (marshaling shim) + * /keys/.{prover,verifier} + * /zkir/.bzkir + * Witnesses are NOT in the bundle; they are caller-supplied via a TS module + * referenced from `[contracts.X].witnesses` in `compact.toml`. + */ + +type AnyContract = Contract.Any; +type AnyWitnesses = Contract.Witnesses; +type AnyCompiledContract = CompiledContract.CompiledContract; + +/** Output of {@link loadArtifact}; consumed by {@link buildProviders} and the pipeline. */ +export interface LoadedArtifact { + compiledContract: AnyCompiledContract; + zkConfigPath: string; + artifactPath: string; + circuitNames: string[]; +} + +export interface LoadArtifactOptions { + rootDir: string; + artifactsDir: string; + artifact: string; + contractName: string; + witnesses?: FileOrModuleRef; +} + +/** + * Resolve, validate, and import a compactc artifact bundle. + * + * Throws {@link ArtifactNotFoundError} when the directory, `contract/index` + * entry, or `keys/`/`zkir/` subdirs are missing. The returned `circuitNames` + * is a sorted list scraped from `.bzkir` files — useful for diagnostics and + * for the JSON CLI output. + */ +export async function loadArtifact({ + rootDir, + artifactsDir, + artifact, + contractName, + witnesses, +}: LoadArtifactOptions): Promise { + const artifactPath = resolveUnderRoot(rootDir, artifact, artifactsDir); + + if (!existsSync(artifactPath)) { + throw new ArtifactNotFoundError(artifactPath); + } + + const contractDir = resolve(artifactPath, 'contract'); + const entry = findEntry(contractDir, artifactPath); + if (!entry) { + throw new ArtifactNotFoundError( + `${artifactPath} (no contract/index.{cjs,js} or index.{cjs,js} found)`, + ); + } + + const keysDir = resolve(artifactPath, 'keys'); + const zkirDir = resolve(artifactPath, 'zkir'); + if (!existsSync(keysDir) || !existsSync(zkirDir)) { + throw new ArtifactNotFoundError( + `${artifactPath} (missing keys/ or zkir/ subdirectory)`, + ); + } + + const circuitNames = collectCircuitNames(zkirDir); + + const Ctor = await importContractCtor(entry); + const witnessImpls = witnesses ? await importWitnesses(witnesses, rootDir) : undefined; + + const compiledContract = buildCompiledContract({ + contractName, + Ctor, + witnessImpls, + contractDir, + }); + + return { compiledContract, zkConfigPath: artifactPath, artifactPath, circuitNames }; +} + +async function importContractCtor(entry: string): Promise> { + const mod = (await import(pathToFileURL(entry).href)) as ArtifactModule; + const Ctor = mod.Contract ?? mod.default?.Contract; + if (!Ctor) { + throw new ConfigError( + `Artifact at ${entry} does not export a \`Contract\` class (got keys: ${Object.keys(mod).join(', ')})`, + ); + } + return Ctor; +} + +async function importWitnesses( + ref: FileOrModuleRef, + rootDir: string, +): Promise { + if (isFileRef(ref)) { + throw new ConfigError( + 'witnesses must be a { module, export } reference; JSON file refs are not supported (witnesses are functions)', + ); + } + if (!isModuleRef(ref)) { + throw new ConfigError('witnesses must be { module, export }'); + } + const path = isAbsolute(ref.module) ? ref.module : resolve(rootDir, ref.module); + let mod: Record; + try { + mod = await import(pathToFileURL(path).href); + } catch (e) { + throw new ConfigError(`witnesses: failed to import ${path}: ${(e as Error).message}`); + } + const exported = mod[ref.export]; + const resolved = + typeof exported === 'function' ? await (exported as () => unknown)() : exported; + if (typeof resolved !== 'object' || resolved === null) { + throw new ConfigError( + `witnesses: module ${path} export "${ref.export}" must resolve to an object`, + ); + } + return resolved as AnyWitnesses; +} + +function buildCompiledContract(input: { + contractName: string; + Ctor: Types.Ctor; + witnessImpls: AnyWitnesses | undefined; + contractDir: string; +}): AnyCompiledContract { + const base = CompiledContract.make(input.contractName, input.Ctor); + const withWit = input.witnessImpls + ? CompiledContract.withWitnesses(base, input.witnessImpls) + : CompiledContract.withVacantWitnesses(base); + return CompiledContract.withCompiledFileAssets(withWit, input.contractDir); +} + +interface ArtifactModule { + Contract?: Types.Ctor; + default?: { Contract?: Types.Ctor }; +} + +function resolveUnderRoot(rootDir: string, artifact: string, artifactsDir: string): string { + if (isAbsolute(artifact)) return artifact; + const direct = resolve(rootDir, artifact); + if (existsSync(direct)) return direct; + return resolve(rootDir, artifactsDir, artifact); +} + +function findEntry(contractDir: string, artifactDir: string): string | undefined { + const candidates = [ + resolve(contractDir, 'index.cjs'), + resolve(contractDir, 'index.js'), + resolve(artifactDir, 'index.cjs'), + resolve(artifactDir, 'index.js'), + ]; + return candidates.find(existsSync); +} + +function collectCircuitNames(zkirDir: string): string[] { + return readdirSync(zkirDir) + .filter((f) => f.endsWith('.bzkir')) + .map((f) => f.slice(0, -'.bzkir'.length)) + .sort(); +} diff --git a/packages/deploy/src/loaders/init-state.test.ts b/packages/deploy/src/loaders/init-state.test.ts new file mode 100644 index 0000000..905cd3e --- /dev/null +++ b/packages/deploy/src/loaders/init-state.test.ts @@ -0,0 +1,33 @@ +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { ConfigError } from '../errors.ts'; +import { loadInitialPrivateState } from './init-state.ts'; + +describe('loadInitialPrivateState', () => { + it('returns undefined when ref is absent', async () => { + expect(await loadInitialPrivateState(undefined, '/tmp')).toBeUndefined(); + }); + + it('parses a { file } JSON ref with bigint revival', async () => { + const dir = mkdtempSync(join(tmpdir(), 'initstate-test-')); + writeFileSync(join(dir, 's.json'), '{"counter":"100n","name":"x"}'); + const state = await loadInitialPrivateState({ file: 's.json' }, dir); + expect(state).toEqual({ counter: 100n, name: 'x' }); + }); + + it('throws ConfigError for missing files', async () => { + await expect( + loadInitialPrivateState({ file: 'does-not-exist.json' }, '/tmp'), + ).rejects.toThrow(ConfigError); + }); + + it('throws ConfigError for invalid JSON', async () => { + const dir = mkdtempSync(join(tmpdir(), 'initstate-test-')); + writeFileSync(join(dir, 'bad.json'), 'not json'); + await expect( + loadInitialPrivateState({ file: 'bad.json' }, dir), + ).rejects.toThrow(ConfigError); + }); +}); diff --git a/packages/deploy/src/loaders/init-state.ts b/packages/deploy/src/loaders/init-state.ts new file mode 100644 index 0000000..62225a0 --- /dev/null +++ b/packages/deploy/src/loaders/init-state.ts @@ -0,0 +1,78 @@ +import { readFile } from 'node:fs/promises'; +import { isAbsolute, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { + type FileOrModuleRef, + isFileRef, + isModuleRef, +} from '../config/schema.ts'; +import { ConfigError } from '../errors.ts'; + +/** + * Load the initial private state passed to a contract's constructor. + * + * Source is either `{ file }` (JSON, with `"123n"` strings revived as + * bigints) or `{ module, export }` (TS/JS module, value or zero-arg function). + * Returns `undefined` when the config omits `init_private_state`. + */ +export async function loadInitialPrivateState( + ref: FileOrModuleRef | undefined, + rootDir: string, +): Promise { + if (!ref) return undefined; + + if (isFileRef(ref)) { + const path = abs(rootDir, ref.file); + let raw: string; + try { + raw = await readFile(path, 'utf8'); + } catch (e) { + throw new ConfigError( + `init_private_state: failed to read ${path}: ${(e as Error).message}`, + ); + } + try { + return JSON.parse(raw, bigintReviver); + } catch (e) { + throw new ConfigError( + `init_private_state: invalid JSON at ${path}: ${(e as Error).message}`, + ); + } + } + + if (isModuleRef(ref)) { + const path = abs(rootDir, ref.module); + let mod: Record; + try { + mod = await import(pathToFileURL(path).href); + } catch (e) { + throw new ConfigError( + `init_private_state: failed to import ${path}: ${(e as Error).message}`, + ); + } + const exported = mod[ref.export]; + if (exported === undefined) { + throw new ConfigError( + `init_private_state: module ${path} has no export "${ref.export}"`, + ); + } + return typeof exported === 'function' + ? await (exported as () => unknown)() + : exported; + } + + throw new ConfigError( + 'init_private_state must be { file } or { module, export }', + ); +} + +function abs(rootDir: string, p: string): string { + return isAbsolute(p) ? p : resolve(rootDir, p); +} + +function bigintReviver(_key: string, value: unknown): unknown { + if (typeof value === 'string' && /^-?\d+n$/.test(value)) { + return BigInt(value.slice(0, -1)); + } + return value; +} diff --git a/packages/deploy/src/loaders/signing-key.test.ts b/packages/deploy/src/loaders/signing-key.test.ts new file mode 100644 index 0000000..8193b80 --- /dev/null +++ b/packages/deploy/src/loaders/signing-key.test.ts @@ -0,0 +1,34 @@ +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { ConfigError } from '../errors.ts'; +import { loadSigningKey } from './signing-key.ts'; + +const VALID = 'a'.repeat(64); + +describe('loadSigningKey', () => { + it('reads and lowercases a 32-byte hex key', async () => { + const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); + writeFileSync(join(dir, 'sk'), `${VALID.toUpperCase()}\n`); + expect(await loadSigningKey(dir, 'sk')).toBe(VALID); + }); + + it('strips an optional 0x prefix', async () => { + const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); + writeFileSync(join(dir, 'sk'), `0x${VALID}\n`); + expect(await loadSigningKey(dir, 'sk')).toBe(VALID); + }); + + it('rejects a wrong-length key', async () => { + const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); + writeFileSync(join(dir, 'sk'), 'abcd'); + await expect(loadSigningKey(dir, 'sk')).rejects.toThrow(ConfigError); + }); + + it('rejects a missing file', async () => { + await expect(loadSigningKey('/tmp', 'no-such-file')).rejects.toThrow( + ConfigError, + ); + }); +}); diff --git a/packages/deploy/src/loaders/signing-key.ts b/packages/deploy/src/loaders/signing-key.ts new file mode 100644 index 0000000..d36708e --- /dev/null +++ b/packages/deploy/src/loaders/signing-key.ts @@ -0,0 +1,34 @@ +import { readFile } from 'node:fs/promises'; +import { isAbsolute, resolve } from 'node:path'; +import { ConfigError } from '../errors.ts'; + +/** + * Read a 32-byte signing key from `[contracts.X].signing_key_file` and + * return it as lowercase hex (no `0x` prefix). + * + * The signing key is the contract's maintenance authority. We refuse fuzzy + * input formats — exactly 64 hex chars after stripping optional `0x` and + * trimming whitespace — to avoid the foot-gun where midnight-js silently + * auto-samples a key the user then can't recover. + */ +export async function loadSigningKey( + rootDir: string, + path: string, +): Promise { + const abs = isAbsolute(path) ? path : resolve(rootDir, path); + let raw: string; + try { + raw = await readFile(abs, 'utf8'); + } catch (e) { + throw new ConfigError( + `signing_key_file: failed to read ${abs}: ${(e as Error).message}`, + ); + } + const trimmed = raw.trim().replace(/^0x/i, ''); + if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) { + throw new ConfigError( + `signing_key_file ${abs}: expected 32 bytes hex-encoded (64 hex chars)`, + ); + } + return trimmed.toLowerCase(); +} diff --git a/packages/deploy/src/pipeline.ts b/packages/deploy/src/pipeline.ts new file mode 100644 index 0000000..a8a3cb9 --- /dev/null +++ b/packages/deploy/src/pipeline.ts @@ -0,0 +1,510 @@ +import { deployContract } from '@midnight-ntwrk/midnight-js-contracts'; +import { getNetworkId } from '@midnight-ntwrk/midnight-js-network-id'; +import { + type EnvironmentConfiguration, + FaucetClient, + type MidnightWalletProvider, +} from '@midnight-ntwrk/testkit-js'; +import { UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format'; +import type { Logger } from 'pino'; +import * as Rx from 'rxjs'; +import { loadConstructorArgs } from './loaders/args.ts'; +import { type LoadedArtifact, loadArtifact } from './loaders/artifact.ts'; +import { loadConfig } from './config/load.ts'; +import type { + CompactConfig, + ContractConfig, + NetworkConfig, +} from './config/schema.ts'; +import { ConfigError, DeployTxFailedError } from './errors.ts'; +import { loadInitialPrivateState } from './loaders/init-state.ts'; +import { Deployments, type DeploymentRecord } from './deployments.ts'; +import { buildProviders } from './providers/build.ts'; +import { applyNetwork } from './providers/network.ts'; +import { ProofServer } from './providers/proof-server.ts'; +import { loadSigningKey } from './loaders/signing-key.ts'; +import { buildDeployerWallet } from './wallet/build-deployer.ts'; +import { type SeedResolution, resolveSeed } from './wallet/resolve.ts'; + +/** + * Inputs to {@link runPipeline}. The CLI in `bin/compact-deploy.ts` is a + * thin shell that fills these out from argv + env; embedders can construct + * them directly to skip TOML lookup or to inject a shared wallet. + */ +export interface PipelineOptions { + contract: string; + network?: string; + configPath?: string; + seedFile?: string; + proofServer?: string; + skipFaucet?: boolean; + dryRun?: boolean; + argsOverride?: string; + initPrivateStateOverride?: string; + logger: Logger; + promptPassphrase?: (path: string) => Promise; + /** + * Inject an already-built, already-started `MidnightWalletProvider`. When + * set, the pipeline skips seed resolution, wallet build, faucet calls, + * `wallet.start()`, and `wallet.stop()` — the caller owns the wallet's + * lifecycle. + * + * Use this when running many deploys in a single Node process (e.g. + * integration test suites). Each `buildDeployerWallet` rebuilds a wallet + * that syncs from the indexer; under rapid back-to-back deploys the + * indexer can lag and the new wallet sees an already-spent dust UTXO, + * producing a `DustDoubleSpend` rejection. Sharing one wallet across + * the deploys keeps its UTXO view internally consistent. + */ + walletProvider?: MidnightWalletProvider; +} + +/** Final shape returned by {@link runPipeline}; identical in dry-run mode except `dryRun: true` and the on-chain fields are empty. */ +export interface PipelineResult { + contractName: string; + network: string; + address: string; + txHash: string; + txId: string; + blockHeight: number; + signingKey: string; + deployer: string; + artifact: string; + deploymentsFile: string; + dryRun: boolean; +} + +/** + * End-to-end deploy: config → wallet → faucet → providers → submit → persist. + * + * Reads as a linear recipe — every step is a named helper below. Two + * resources need explicit cleanup (the proof-server container if `"auto"`, + * and an owned wallet); both are wrapped in `try/finally` here so the + * helpers can stay free of teardown logic. + */ +export async function runPipeline( + opts: PipelineOptions, +): Promise { + const { logger } = opts; + + const { config, rootDir } = await loadConfig(opts.configPath); + const { networkName, network, contract } = resolveTargets(opts, config); + + const signingKey = await loadSigningKey(rootDir, contract.signing_key_file); + const seedResolution = await maybeResolveSeed(opts, { + config, + rootDir, + networkName, + network, + }); + if (seedResolution) { + logger.debug(`Resolved deployer seed from: ${seedResolution.origin}`); + } + + const proofServer = await ProofServer.start({ + cliOverride: opts.proofServer, + network, + logger, + }); + try { + const { env, faucetUrl } = applyNetwork(network, proofServer.url); + logger.debug( + `Network ID: ${env.networkId}; proof server: ${env.proofServer}`, + ); + + const artifact = await loadArtifact({ + rootDir, + artifactsDir: config.profile.artifacts_dir, + artifact: contract.artifact, + contractName: opts.contract, + witnesses: contract.witnesses, + }); + logger.debug( + `Artifact: ${artifact.artifactPath} (${artifact.circuitNames.length} circuits)`, + ); + + const { wallet, ownsWallet } = await acquireWallet( + opts, + env, + seedResolution, + logger, + ); + try { + if (ownsWallet) { + await maybeRequestFaucet(opts, wallet, env, network, logger); + await wallet.start(true); + } + + const providers = buildProviders({ + env, + wallet, + contractName: opts.contract, + contract, + zkConfigPath: artifact.zkConfigPath, + }); + + const args = await loadConstructorArgs( + contract, + rootDir, + opts.argsOverride, + ); + const initialPrivateState = await loadInitialPrivateState( + contract.init_private_state, + rootDir, + ); + const deployer = wallet.getCoinPublicKey(); + + if (opts.dryRun) { + logDryRun(logger, { + contractName: opts.contract, + networkName, + artifact, + argCount: args.length, + hasPrivateState: initialPrivateState !== undefined, + faucet: !!network.faucet && !opts.skipFaucet, + faucetUrl, + deployer, + }); + return dryRunResult({ + contractName: opts.contract, + networkName, + signingKey, + deployer, + artifact: contract.artifact, + }); + } + + const txResult = await executeDeploy({ + providers, + contractName: opts.contract, + contract, + artifact, + signingKey, + args, + initialPrivateState, + }); + + const record = toDeploymentRecord({ + deployTxData: txResult.deployTxData, + signingKey, + deployer, + artifact: contract.artifact, + }); + + const deployments = new Deployments({ + rootDir, + deploymentsDir: config.profile.deployments_dir, + network: networkName, + }); + const persistResult = await deployments.record(opts.contract, record); + + return successResult({ + contractName: opts.contract, + networkName, + record, + deploymentsFile: persistResult.head, + }); + } finally { + if (ownsWallet) await safeStopWallet(wallet, logger); + } + } finally { + await safeDisposeProofServer(proofServer, logger); + } +} + +// --------------------------------------------------------------------------- +// Helpers — every step of runPipeline lives in its own function. Order +// roughly follows the order each helper runs. +// --------------------------------------------------------------------------- + +interface ResolvedTargets { + networkName: string; + network: NetworkConfig; + contract: ContractConfig; +} + +/** + * Pick the network and contract from `compact.toml`, defaulting the network + * to `[profile].default_network` when `--network` isn't passed. Throws + * {@link ConfigError} with the available set on each invalid lookup. + */ +function resolveTargets( + opts: PipelineOptions, + config: CompactConfig, +): ResolvedTargets { + const networkName = opts.network ?? config.profile.default_network; + if (!networkName) { + throw new ConfigError( + 'No network selected. Pass --network or set [profile].default_network.', + ); + } + const network = config.networks[networkName]; + if (!network) { + throw new ConfigError( + `Network "${networkName}" not defined. Available: ${Object.keys(config.networks).join(', ')}`, + ); + } + const contract = config.contracts[opts.contract]; + if (!contract) { + throw new ConfigError( + `Contract "${opts.contract}" not defined. Available: ${Object.keys(config.contracts).join(', ')}`, + ); + } + return { networkName, network, contract }; +} + +/** + * Resolve a deployer seed unless the caller injected its own wallet + * (`opts.walletProvider` set). Returning `undefined` is the signal to + * {@link acquireWallet} that it should adopt the injected wallet instead of + * building one. + */ +async function maybeResolveSeed( + opts: PipelineOptions, + ctx: { + config: CompactConfig; + rootDir: string; + networkName: string; + network: NetworkConfig; + }, +): Promise { + if (opts.walletProvider) return undefined; + return resolveSeed({ + config: ctx.config, + rootDir: ctx.rootDir, + networkName: ctx.networkName, + network: ctx.network, + seedFile: opts.seedFile, + promptPassphrase: opts.promptPassphrase, + }); +} + +interface AcquiredWallet { + wallet: MidnightWalletProvider; + /** True when the pipeline built the wallet itself and is therefore responsible for `start`/`stop`. */ + ownsWallet: boolean; +} + +/** + * Either return the caller-injected wallet (and skip the lifecycle) or + * build a fresh one from the resolved seed (and own its lifecycle). + */ +async function acquireWallet( + opts: PipelineOptions, + env: EnvironmentConfiguration, + seedResolution: SeedResolution | undefined, + logger: Logger, +): Promise { + if (opts.walletProvider) { + return { wallet: opts.walletProvider, ownsWallet: false }; + } + if (!seedResolution) { + // Should be unreachable — maybeResolveSeed returns a value whenever + // walletProvider is undefined — but the explicit check keeps the type + // narrowing local instead of relying on the caller. + throw new Error('internal: resolvedSeed missing for owned wallet'); + } + const wallet = await buildDeployerWallet(logger, env, seedResolution.seed); + return { wallet, ownsWallet: true }; +} + +/** + * Hit the network's faucet for the deployer address when configured + * (`[networks.X].faucet = true`, not `--skip-faucet`, and the resolved + * `env.faucet` URL is present). Safe to call before `wallet.start()` — we + * read the unshielded address from the wallet's already-running state + * stream. + */ +async function maybeRequestFaucet( + opts: PipelineOptions, + wallet: MidnightWalletProvider, + env: EnvironmentConfiguration, + network: NetworkConfig, + logger: Logger, +): Promise { + if (!network.faucet || opts.skipFaucet || !env.faucet) return; + const initialUnshielded = await Rx.firstValueFrom( + wallet.wallet.unshielded.state, + ); + const address = UnshieldedAddress.codec + .encode(getNetworkId(), initialUnshielded.address) + .toString(); + logger.info(`Requesting faucet tokens for ${address}…`); + await new FaucetClient(env.faucet, logger).requestTokens(address); +} + +interface ExecuteDeployArgs { + providers: Parameters[0]; + contractName: string; + contract: ContractConfig; + artifact: LoadedArtifact; + signingKey: string; + args: unknown[]; + initialPrivateState: unknown; +} + +/** + * Assemble the `deployContract` options (conditionally including the + * private-state pair) and submit. Wraps any failure in + * {@link DeployTxFailedError} so callers can branch on its `exitCode` + * without parsing midnight-js error shapes. + */ +async function executeDeploy({ + providers, + contractName, + contract, + artifact, + signingKey, + args, + initialPrivateState, +}: ExecuteDeployArgs): Promise>> { + const compiled = artifact.compiledContract as Parameters< + typeof deployContract + >[1]['compiledContract']; + const base = { + compiledContract: compiled, + signingKey, + args, + } as Parameters[1]; + const deployOptions = + contract.private_state_id !== undefined + ? { + ...base, + privateStateId: contract.private_state_id, + initialPrivateState, + } + : base; + + try { + return await deployContract(providers, deployOptions); + } catch (e) { + throw new DeployTxFailedError( + `Deploy of "${contractName}" failed: ${(e as Error).message}`, + { cause: e }, + ); + } +} + +type DeployResult = Awaited>; + +/** Map the midnight-js deploy-tx result into the persisted record shape. */ +function toDeploymentRecord({ + deployTxData, + signingKey, + deployer, + artifact, +}: { + deployTxData: DeployResult['deployTxData']; + signingKey: string; + deployer: string; + artifact: string; +}): DeploymentRecord { + return { + address: deployTxData.public.contractAddress, + txHash: deployTxData.public.txHash, + txId: deployTxData.public.txId, + blockHeight: deployTxData.public.blockHeight, + signingKey, + deployer, + artifact, + timestamp: new Date().toISOString(), + }; +} + +/** Emit the same structured `dry-run: would deploy` event the old pipeline did. */ +function logDryRun( + logger: Logger, + details: { + contractName: string; + networkName: string; + artifact: LoadedArtifact; + argCount: number; + hasPrivateState: boolean; + faucet: boolean; + faucetUrl: string | undefined; + deployer: string; + }, +): void { + logger.info( + { + contract: details.contractName, + network: details.networkName, + artifact: details.artifact.artifactPath, + argCount: details.argCount, + hasPrivateState: details.hasPrivateState, + faucet: details.faucet, + faucetUrl: details.faucetUrl, + deployer: details.deployer, + }, + 'dry-run: would deploy', + ); +} + +/** Build the `PipelineResult` returned from a dry run (no on-chain fields). */ +function dryRunResult(params: { + contractName: string; + networkName: string; + signingKey: string; + deployer: string; + artifact: string; +}): PipelineResult { + return { + contractName: params.contractName, + network: params.networkName, + address: '', + txHash: '', + txId: '', + blockHeight: 0, + signingKey: params.signingKey, + deployer: params.deployer, + artifact: params.artifact, + deploymentsFile: '', + dryRun: true, + }; +} + +/** Build the `PipelineResult` returned from a confirmed deploy. */ +function successResult(params: { + contractName: string; + networkName: string; + record: DeploymentRecord; + deploymentsFile: string; +}): PipelineResult { + return { + contractName: params.contractName, + network: params.networkName, + address: params.record.address, + txHash: params.record.txHash, + txId: params.record.txId, + blockHeight: params.record.blockHeight, + signingKey: params.record.signingKey, + deployer: params.record.deployer, + artifact: params.record.artifact, + deploymentsFile: params.deploymentsFile, + dryRun: false, + }; +} + +/** Stop the wallet, swallowing teardown errors with a `warn` log. */ +async function safeStopWallet( + wallet: MidnightWalletProvider, + logger: Logger, +): Promise { + try { + await wallet.stop(); + } catch (e) { + logger.warn({ err: (e as Error).message }, 'Wallet stop failed'); + } +} + +/** Dispose the proof-server container, swallowing teardown errors with a `warn` log. */ +async function safeDisposeProofServer( + proofServer: ProofServer, + logger: Logger, +): Promise { + try { + await proofServer.dispose(); + } catch (e) { + logger.warn({ err: (e as Error).message }, 'Proof server dispose failed'); + } +} diff --git a/packages/deploy/src/providers/build.ts b/packages/deploy/src/providers/build.ts new file mode 100644 index 0000000..9aed924 --- /dev/null +++ b/packages/deploy/src/providers/build.ts @@ -0,0 +1,64 @@ +import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider'; +import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider'; +import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider'; +import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider'; +import type { + MidnightProviders, + PrivateStateProvider, +} from '@midnight-ntwrk/midnight-js-types'; +import type { + EnvironmentConfiguration, + MidnightWalletProvider, +} from '@midnight-ntwrk/testkit-js'; +import type { ContractConfig } from '../config/schema.ts'; +import { derivePrivateStatePassword } from './private-state-password.ts'; + +/** + * Assemble the six-provider bundle midnight-js expects: private state, + * public data (indexer), zk-config, proof, wallet, and midnight (which + * the wallet provider doubles as). + * + * Notes: + * - The private-state store name defaults to `-private-state` + * so multiple contracts in one project don't collide on LevelDB keys. + * - The encryption password for private state is *derived* from the + * wallet's encryption public key (see {@link derivePrivateStatePassword}) + * so it ties to the wallet identity without surfacing a separate secret. + * - ZK config comes from on-disk artifacts via `NodeZkConfigProvider`, + * not from an HTTP fetch — the artifact bundle already contains the + * proving/verifying keys. + */ +export interface BuildProvidersOptions { + env: EnvironmentConfiguration; + wallet: MidnightWalletProvider; + contractName: string; + contract: ContractConfig; + zkConfigPath: string; +} + +export function buildProviders({ + env, + wallet, + contractName, + contract, + zkConfigPath, +}: BuildProvidersOptions): MidnightProviders { + const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath); + + const password = derivePrivateStatePassword(wallet.getEncryptionPublicKey()); + const privateStateProvider: PrivateStateProvider = levelPrivateStateProvider({ + privateStateStoreName: + contract.private_state_store_name ?? `${contractName}-private-state`, + accountId: wallet.getCoinPublicKey(), + privateStoragePasswordProvider: () => password, + }); + + return { + privateStateProvider, + publicDataProvider: indexerPublicDataProvider(env.indexer, env.indexerWS), + zkConfigProvider, + proofProvider: httpClientProofProvider(env.proofServer, zkConfigProvider), + walletProvider: wallet, + midnightProvider: wallet, + }; +} diff --git a/packages/deploy/src/providers/network.ts b/packages/deploy/src/providers/network.ts new file mode 100644 index 0000000..579ce97 --- /dev/null +++ b/packages/deploy/src/providers/network.ts @@ -0,0 +1,56 @@ +import { setNetworkId } from '@midnight-ntwrk/midnight-js-network-id'; +import type { EnvironmentConfiguration } from '@midnight-ntwrk/testkit-js'; +import type { NetworkConfig } from '../config/schema.ts'; +import { ConfigError } from '../errors.ts'; + +/** + * Apply the chosen network to the global midnight-js network-id singleton + * and assemble an `EnvironmentConfiguration` for testkit-js. + * + * `setNetworkId` is a module-level side effect required by midnight-js + * before any wallet/tx code runs — calling it from one well-known spot + * keeps the lifecycle obvious. The accepted set of `network_id` values is + * intentionally closed: we'd rather fail fast on a typo than silently + * accept an unknown id and let midnight-js produce a generic error later. + */ + +const KNOWN_NETWORK_IDS: ReadonlySet = new Set([ + 'undeployed', + 'devnet', + 'qanet', + 'testnet', + 'preview', + 'preprod', + 'mainnet', +]); + +export interface ResolvedEnvironment { + env: EnvironmentConfiguration; + faucetUrl: string | undefined; +} + +export function applyNetwork( + network: NetworkConfig, + proofServerUrl: string, +): ResolvedEnvironment { + if (!KNOWN_NETWORK_IDS.has(network.network_id)) { + throw new ConfigError( + `Unknown network_id "${network.network_id}" (expected one of: ${[...KNOWN_NETWORK_IDS].join(', ')})`, + ); + } + setNetworkId(network.network_id); + + const env: EnvironmentConfiguration = { + walletNetworkId: + network.network_id as EnvironmentConfiguration['walletNetworkId'], + networkId: network.network_id, + indexer: network.indexer, + indexerWS: network.indexer_ws, + node: network.node, + nodeWS: network.node_ws, + proofServer: proofServerUrl, + faucet: network.faucet_url, + }; + + return { env, faucetUrl: network.faucet_url }; +} diff --git a/packages/deploy/src/providers/private-state-password.test.ts b/packages/deploy/src/providers/private-state-password.test.ts new file mode 100644 index 0000000..5dc2838 --- /dev/null +++ b/packages/deploy/src/providers/private-state-password.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { derivePrivateStatePassword } from './private-state-password.ts'; + +describe('derivePrivateStatePassword', () => { + it('is deterministic for the same input', () => { + const a = derivePrivateStatePassword('abcdef1234567890'); + const b = derivePrivateStatePassword('abcdef1234567890'); + expect(a).toBe(b); + }); + + it('differs for different inputs', () => { + const a = derivePrivateStatePassword('abcdef1234567890'); + const b = derivePrivateStatePassword('abcdef1234567891'); + expect(a).not.toBe(b); + }); + + it('never contains 4 identical chars in a row', () => { + for (let i = 0; i < 200; i++) { + const pw = derivePrivateStatePassword(`pubkey-${i}`); + expect(pw).not.toMatch(/(.)\1{3,}/); + } + }); + + it('produces a password with mixed character classes (uppercase + digit + symbol)', () => { + const pw = derivePrivateStatePassword('any input'); + expect(pw).toMatch(/[A-Z]/); + expect(pw).toMatch(/[0-9]/); + expect(pw).toMatch(/[^A-Za-z0-9]/); + }); + + it('handles inputs that would have produced naïve-bad passwords', () => { + // A 64-zero hex (the kind of structured pubkey that breaks + // `${encKey}A!`-style derivations) must still produce a valid password. + const pw = derivePrivateStatePassword('0'.repeat(64)); + expect(pw).not.toMatch(/(.)\1{3,}/); + }); +}); diff --git a/packages/deploy/src/providers/private-state-password.ts b/packages/deploy/src/providers/private-state-password.ts new file mode 100644 index 0000000..398720f --- /dev/null +++ b/packages/deploy/src/providers/private-state-password.ts @@ -0,0 +1,35 @@ +import { createHash } from 'node:crypto'; + +/** + * Derive a private-state-store password from a wallet's encryption public + * key. + * + * The level-private-state-provider validates the password (no 4+ identical + * chars in a row, mixed character classes). Naïve interpolations like + * `${encryptionPublicKey}A!` fail when the hex public key happens to + * contain runs of identical hex digits — which it routinely does for + * structured seeds like `TEST_MNEMONIC` or `0x…0001`. + * + * Strategy: SHA-256 the key, base64url-encode, strip non-alphanumerics, + * and append a fixed `A1!` suffix for guaranteed character-class diversity. + * If the digest happens to contain a 4-in-a-row run (≈0.01% per draw), we + * deterministically rehash with an incrementing counter until clean. Same + * input always produces the same output, so the local leveldb stays + * decryptable across runs. + */ +export function derivePrivateStatePassword(encryptionPublicKey: string): string { + for (let counter = 0; counter < 1024; counter++) { + const body = createHash('sha256') + .update(`${encryptionPublicKey}:${counter}`) + .digest('base64url') + .replace(/[^A-Za-z0-9]/g, ''); + if (!/(.)\1{3,}/.test(body)) { + return `${body}A1!`; + } + } + // Pathologically improbable. Surface explicitly so the deploy fails loud + // rather than silently retrying forever. + throw new Error( + 'derivePrivateStatePassword: unable to find a hash without 4+ repeated chars after 1024 rounds', + ); +} diff --git a/packages/deploy/src/providers/proof-server.ts b/packages/deploy/src/providers/proof-server.ts new file mode 100644 index 0000000..ebd3ca2 --- /dev/null +++ b/packages/deploy/src/providers/proof-server.ts @@ -0,0 +1,94 @@ +import { + DynamicProofServerContainer, + StaticProofServerContainer, +} from '@midnight-ntwrk/testkit-js'; +import type { Logger } from 'pino'; +import type { NetworkConfig } from '../config/schema.ts'; +import { ConfigError } from '../errors.ts'; + +/** + * Inputs to {@link ProofServer.start}; same shape the free function took + * before the class refactor. + */ +export interface ProofServerOptions { + cliOverride?: string; + network: NetworkConfig; + logger: Logger; +} + +/** + * Proof-server handle: a resolved URL plus the lifecycle needed to release + * any underlying container. + * + * Always acquired via {@link ProofServer.start}, which walks the five-step + * precedence chain (CLI > TOML URL > `"auto"` container > `PROOF_SERVER_PORT` + * > `http://127.0.0.1:6300`). Call {@link dispose} on teardown regardless of + * how it was acquired — it's a no-op for static URLs and a container-stop + * for the auto / port paths. + */ +export class ProofServer { + /** Resolved URL the proof provider POSTs to. */ + readonly url: string; + readonly #dispose: () => Promise; + + private constructor(url: string, dispose: () => Promise) { + this.url = url; + this.#dispose = dispose; + } + + /** + * Resolve a proof-server URL for the target network. + * + * Precedence (highest first): + * 1. `cliOverride` (e.g. `--proof-server `) + * 2. `[networks.X].proof_server = ""` (TOML static URL) + * 3. `[networks.X].proof_server = "auto"` (boots a docker container via + * testkit-js; {@link dispose} stops it) + * 4. `PROOF_SERVER_PORT` env (static container on localhost) + * 5. `http://127.0.0.1:6300` (final default) + */ + static async start(opts: ProofServerOptions): Promise { + const { cliOverride, network, logger } = opts; + const explicit = cliOverride ?? network.proof_server; + + if (explicit && explicit !== 'auto') { + logger.debug(`Using configured proof server: ${explicit}`); + return ProofServer.fromStaticUrl(explicit); + } + + if (explicit === 'auto') { + logger.info('Starting proof-server container (auto)…'); + const container = await DynamicProofServerContainer.start( + logger, + undefined, + network.network_id, + ); + return new ProofServer(container.getUrl(), () => container.stop()); + } + + const port = process.env.PROOF_SERVER_PORT; + if (port !== undefined) { + const parsed = Number.parseInt(port, 10); + if (Number.isNaN(parsed)) { + throw new ConfigError(`Invalid PROOF_SERVER_PORT: ${port}`); + } + logger.debug(`Using PROOF_SERVER_PORT=${parsed}`); + const container = new StaticProofServerContainer(parsed); + return new ProofServer(container.getUrl(), () => container.stop()); + } + + logger.debug('Falling back to default proof server at http://127.0.0.1:6300'); + return ProofServer.fromStaticUrl('http://127.0.0.1:6300'); + } + + private static fromStaticUrl(url: string): ProofServer { + return new ProofServer(url, async () => { + /* no container to stop */ + }); + } + + /** Release any underlying container. Idempotent for static-URL instances. */ + async dispose(): Promise { + return this.#dispose(); + } +} diff --git a/packages/deploy/src/wallet/build-deployer.ts b/packages/deploy/src/wallet/build-deployer.ts new file mode 100644 index 0000000..1b21e6b --- /dev/null +++ b/packages/deploy/src/wallet/build-deployer.ts @@ -0,0 +1,70 @@ +import { DustSecretKey, ZswapSecretKeys } from '@midnight-ntwrk/ledger-v8'; +import { + DEFAULT_DUST_OPTIONS, + type DustWalletOptions, + type EnvironmentConfiguration, + FluentWalletBuilder, + MidnightWalletProvider, +} from '@midnight-ntwrk/testkit-js'; +import type { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade'; +import type { Logger } from 'pino'; +import type { WalletSeed } from './normalize.ts'; + +/** + * Build a `MidnightWalletProvider` with dust options tuned for the target + * network. + * + * Two things this fixes vs. the bare `MidnightWalletProvider.build`: + * + * 1. **Dust overhead.** testkit-js' default `additionalFeeOverhead` is + * `1_000n`, which is too low for the dev-preset `undeployed` node — + * every deploy then fails with a generic `SubmissionError`. CMA's + * harness bumps to `5e17` for undeployed; we mirror that. + * + * 2. **Mnemonic-vs-hex routing.** `FluentWalletBuilder.withMnemonic` and + * `.withSeed(hex)` derive *different* wallets from the same input — + * `withMnemonic` runs the BIP39 → seed → wallet path expected by the + * genesis-funded test mnemonic (`TEST_MNEMONIC`), while a hex seed is + * interpreted as already-derived entropy. Keeping the seed's `kind` + * explicit lets us pick the right builder method. + * + * Caller still owns lifecycle: invoke `wallet.start(waitForFunds)` after + * (and any faucet hit) and `wallet.stop()` on teardown. + */ +export async function buildDeployerWallet( + logger: Logger, + env: EnvironmentConfiguration, + seed: WalletSeed, +): Promise { + const dustOptions: DustWalletOptions = { + ...DEFAULT_DUST_OPTIONS, + additionalFeeOverhead: + env.walletNetworkId === 'undeployed' + ? 500_000_000_000_000_000n + : DEFAULT_DUST_OPTIONS.additionalFeeOverhead, + }; + + const builder = FluentWalletBuilder.forEnvironment(env).withDustOptions( + dustOptions, + ); + const seeded = + seed.kind === 'mnemonic' + ? builder.withMnemonic(seed.value) + : builder.withSeed(seed.value); + + const build = await seeded.buildWithoutStarting(); + const { wallet, seeds, keystore } = build as unknown as { + wallet: WalletFacade; + seeds: { shielded: Uint8Array; dust: Uint8Array }; + keystore: Parameters[5]; + }; + + return MidnightWalletProvider.withWallet( + logger, + env, + wallet, + ZswapSecretKeys.fromSeed(seeds.shielded), + DustSecretKey.fromSeed(seeds.dust), + keystore, + ); +} diff --git a/packages/deploy/src/wallet/keystore.test.ts b/packages/deploy/src/wallet/keystore.test.ts new file mode 100644 index 0000000..0b5221b --- /dev/null +++ b/packages/deploy/src/wallet/keystore.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { WalletError } from '../errors.ts'; +import { Keystore, type MidnightKeystore } from './keystore.ts'; + +const FAST_OPTS = { scryptN: 1024, scryptR: 8, scryptP: 1, dklen: 32 }; +const SEED = 'deadbeef'.repeat(8); + +describe('Keystore', () => { + it('round-trips a seed through encrypt → decrypt', () => { + const ks = Keystore.encrypt(SEED, 'hunter2', FAST_OPTS); + const json = ks.toJSON(); + expect(json.version).toBe('midnight-1'); + expect(json.crypto.cipher).toBe('aes-128-ctr'); + expect(json.crypto.kdf).toBe('scrypt'); + expect(ks.decrypt('hunter2')).toBe(SEED); + }); + + it('rejects wrong passphrase with MAC mismatch', () => { + const ks = Keystore.encrypt(SEED, 'hunter2', FAST_OPTS); + expect(() => ks.decrypt('wrong')).toThrow(/MAC mismatch/); + }); + + it('rejects unsupported version at fromJSON', () => { + const ks = Keystore.encrypt(SEED, 'hunter2', FAST_OPTS); + const tampered = { ...ks.toJSON(), version: 'eth-3' } as unknown as MidnightKeystore; + expect(() => Keystore.fromJSON(tampered)).toThrow(WalletError); + }); + + it('produces a different ciphertext on each encryption (random salt/iv)', () => { + const a = Keystore.encrypt(SEED, 'pp', FAST_OPTS).toJSON(); + const b = Keystore.encrypt(SEED, 'pp', FAST_OPTS).toJSON(); + expect(a.crypto.ciphertext).not.toBe(b.crypto.ciphertext); + expect(a.crypto.kdfparams.salt).not.toBe(b.crypto.kdfparams.salt); + }); +}); diff --git a/packages/deploy/src/wallet/keystore.ts b/packages/deploy/src/wallet/keystore.ts new file mode 100644 index 0000000..c8dc3ca --- /dev/null +++ b/packages/deploy/src/wallet/keystore.ts @@ -0,0 +1,227 @@ +/** + * JSON keystore for a 32-byte wallet seed. + * + * Crypto matches Ethereum Web3 Secret Storage v3 conventions + * (scrypt-derived key + AES-128-CTR + SHA-256 MAC over `macKey ‖ ciphertext`), + * with a `version: "midnight-1"` marker so we can migrate the on-disk + * shape later without colliding with Ethereum tooling that reads v3. + * + * Defaults: scrypt N=2^17, r=8, p=1, dklen=32 — same as Foundry's `cast wallet`. + * Files are written with mode `0600`. + */ + +import { + createCipheriv, + createDecipheriv, + createHash, + randomBytes, + randomUUID, + scryptSync, +} from 'node:crypto'; +import { readFile, writeFile } from 'node:fs/promises'; +import { WalletError } from '../errors.ts'; + +const VERSION = 'midnight-1'; + +/** On-disk JSON shape — exported so consumers can transport/serialize keystores verbatim. */ +export interface MidnightKeystore { + version: typeof VERSION; + id: string; + crypto: { + cipher: 'aes-128-ctr'; + ciphertext: string; + cipherparams: { iv: string }; + kdf: 'scrypt'; + kdfparams: { dklen: number; n: number; p: number; r: number; salt: string }; + mac: string; + }; +} + +export interface KeystoreCreateOptions { + scryptN?: number; + scryptP?: number; + scryptR?: number; + dklen?: number; +} + +const DEFAULTS: Required = { + scryptN: 1 << 17, + scryptP: 1, + scryptR: 8, + dklen: 32, +}; + +/** + * Encrypted wallet-seed wrapper. + * + * Always acquired via a named constructor — {@link Keystore.encrypt} to wrap + * a fresh seed, {@link Keystore.readFromFile} or {@link Keystore.fromJSON} + * to adopt an existing one. The version + cipher + KDF invariants are + * enforced at construction so the rest of the package never sees an invalid + * keystore. + */ +export class Keystore { + readonly #data: MidnightKeystore; + + private constructor(data: MidnightKeystore) { + this.#data = data; + } + + /** + * Encrypt a 32-byte hex seed (with or without `0x` prefix) under + * `passphrase`. Uses {@link DEFAULTS} unless overridden — override only + * for tests that need fast scrypt. + */ + static encrypt( + seedHex: string, + passphrase: string, + opts: KeystoreCreateOptions = {}, + ): Keystore { + const seed = seedFromHex(seedHex); + const { scryptN, scryptP, scryptR, dklen } = { ...DEFAULTS, ...opts }; + + const salt = randomBytes(32); + const iv = randomBytes(16); + const derived = scryptSync(Buffer.from(passphrase, 'utf8'), salt, dklen, { + N: scryptN, + p: scryptP, + r: scryptR, + maxmem: 512 * 1024 * 1024, + }); + + const encKey = derived.subarray(0, 16); + const macKey = derived.subarray(16, 32); + + const cipher = createCipheriv('aes-128-ctr', encKey, iv); + const ciphertext = Buffer.concat([cipher.update(seed), cipher.final()]); + const mac = createHash('sha256') + .update(Buffer.concat([macKey, ciphertext])) + .digest(); + + return new Keystore({ + version: VERSION, + id: randomUUID(), + crypto: { + cipher: 'aes-128-ctr', + ciphertext: ciphertext.toString('hex'), + cipherparams: { iv: iv.toString('hex') }, + kdf: 'scrypt', + kdfparams: { + dklen, + n: scryptN, + p: scryptP, + r: scryptR, + salt: salt.toString('hex'), + }, + mac: mac.toString('hex'), + }, + }); + } + + /** + * Read + parse a JSON keystore file. Validates version/cipher/KDF before + * returning — see {@link Keystore.fromJSON}. + */ + static async readFromFile(path: string): Promise { + let raw: string; + try { + raw = await readFile(path, 'utf8'); + } catch (e) { + throw new WalletError( + `Failed to read keystore at ${path}: ${(e as Error).message}`, + ); + } + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (e) { + throw new WalletError( + `Invalid JSON in keystore ${path}: ${(e as Error).message}`, + ); + } + return Keystore.fromJSON(parsed as MidnightKeystore); + } + + /** + * Wrap an already-parsed keystore JSON object. Validates version, + * cipher, and KDF eagerly — invalid keystores throw before any + * decrypt attempt. + */ + static fromJSON(data: MidnightKeystore): Keystore { + if (data.version !== VERSION) { + throw new WalletError( + `Unsupported keystore version: ${data.version} (expected ${VERSION})`, + ); + } + if (data.crypto.kdf !== 'scrypt') { + throw new WalletError( + `Unsupported KDF: ${data.crypto.kdf} (expected scrypt)`, + ); + } + if (data.crypto.cipher !== 'aes-128-ctr') { + throw new WalletError( + `Unsupported cipher: ${data.crypto.cipher} (expected aes-128-ctr)`, + ); + } + return new Keystore(data); + } + + /** + * Recover the hex-encoded seed. Throws {@link WalletError} on MAC + * mismatch (wrong passphrase or corrupted file). + */ + decrypt(passphrase: string): string { + const { kdfparams, ciphertext, cipherparams, mac } = this.#data.crypto; + const derived = scryptSync( + Buffer.from(passphrase, 'utf8'), + Buffer.from(kdfparams.salt, 'hex'), + kdfparams.dklen, + { + N: kdfparams.n, + p: kdfparams.p, + r: kdfparams.r, + maxmem: 512 * 1024 * 1024, + }, + ); + const encKey = derived.subarray(0, 16); + const macKey = derived.subarray(16, 32); + + const cipherBytes = Buffer.from(ciphertext, 'hex'); + const expectedMac = createHash('sha256') + .update(Buffer.concat([macKey, cipherBytes])) + .digest('hex'); + if (expectedMac !== mac) { + throw new WalletError( + 'Keystore MAC mismatch (wrong passphrase or corrupted file)', + ); + } + + const decipher = createDecipheriv( + 'aes-128-ctr', + encKey, + Buffer.from(cipherparams.iv, 'hex'), + ); + const plain = Buffer.concat([decipher.update(cipherBytes), decipher.final()]); + return plain.toString('hex'); + } + + /** Write to disk as pretty JSON with mode `0o600`. */ + async writeToFile(path: string): Promise { + await writeFile(path, `${JSON.stringify(this.#data, null, 2)}\n`, { + mode: 0o600, + }); + } + + /** Return the on-disk JSON shape (e.g. to embed in a multi-keystore file). */ + toJSON(): MidnightKeystore { + return this.#data; + } +} + +function seedFromHex(hex: string): Buffer { + const stripped = hex.startsWith('0x') ? hex.slice(2) : hex; + if (!/^[0-9a-fA-F]+$/.test(stripped) || stripped.length % 2 !== 0) { + throw new WalletError('Seed must be hex-encoded'); + } + return Buffer.from(stripped, 'hex'); +} diff --git a/packages/deploy/src/wallet/local-seeds.ts b/packages/deploy/src/wallet/local-seeds.ts new file mode 100644 index 0000000..a3f993b --- /dev/null +++ b/packages/deploy/src/wallet/local-seeds.ts @@ -0,0 +1,29 @@ +import { TEST_MNEMONIC } from '@midnight-ntwrk/testkit-js'; + +/** + * Prefunded wallets on `midnight-node --preset=dev`. + * + * Slot 0 is the canonical testkit-js BIP39 mnemonic (`abandon × 23 diesel`), + * which the dev preset funds at genesis. Slots 1..4 are the additional hex + * seeds the standalone testkit exposes via `LocalTestEnvironment`. The + * mnemonic is normalised to its 128-char BIP39 hex seed inside + * `normalizeSeed`, so every entry here is the input we pass to + * `FluentWalletBuilder.withSeed(...)` after normalisation. + */ +export const LOCAL_PREFUNDED_SEEDS: readonly string[] = [ + TEST_MNEMONIC, + '0000000000000000000000000000000000000000000000000000000000000001', + '0000000000000000000000000000000000000000000000000000000000000002', + '0000000000000000000000000000000000000000000000000000000000000003', + '0000000000000000000000000000000000000000000000000000000000000004', +] as const; + +export function localPrefundedSeed(index: number): string { + const seed = LOCAL_PREFUNDED_SEEDS[index]; + if (!seed) { + throw new RangeError( + `local wallet index ${index} out of range (0..${LOCAL_PREFUNDED_SEEDS.length - 1})`, + ); + } + return seed; +} diff --git a/packages/deploy/src/wallet/normalize.test.ts b/packages/deploy/src/wallet/normalize.test.ts new file mode 100644 index 0000000..3e08533 --- /dev/null +++ b/packages/deploy/src/wallet/normalize.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { WalletError } from '../errors.ts'; +import { classifySeed } from './normalize.ts'; + +describe('classifySeed', () => { + it('classifies a 64-char hex string as hex (lowercased)', () => { + const hex = 'A'.repeat(64); + expect(classifySeed(hex)).toEqual({ kind: 'hex', value: 'a'.repeat(64) }); + }); + + it('classifies a 128-char hex string as hex', () => { + const hex = `${'0'.repeat(127)}1`; + expect(classifySeed(hex)).toEqual({ kind: 'hex', value: hex }); + }); + + it('classifies a valid BIP39 mnemonic as mnemonic (no conversion)', () => { + const mnemonic = + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; + expect(classifySeed(mnemonic)).toEqual({ kind: 'mnemonic', value: mnemonic }); + }); + + it('rejects empty input', () => { + expect(() => classifySeed(' ')).toThrow(WalletError); + }); + + it('rejects an invalid hex length', () => { + expect(() => classifySeed('abc123')).toThrow(WalletError); + }); + + it('rejects gibberish that is neither hex nor BIP39', () => { + expect(() => classifySeed('this is definitely not valid')).toThrow( + WalletError, + ); + }); +}); diff --git a/packages/deploy/src/wallet/normalize.ts b/packages/deploy/src/wallet/normalize.ts new file mode 100644 index 0000000..d9f406d --- /dev/null +++ b/packages/deploy/src/wallet/normalize.ts @@ -0,0 +1,35 @@ +import { validateMnemonic } from '@scure/bip39'; +import { wordlist } from '@scure/bip39/wordlists/english'; +import { WalletError } from '../errors.ts'; + +/** + * Discriminated representation of a deployer wallet input. + * + * The wallet builder offers two paths — `.withSeed(hex)` and + * `.withMnemonic(phrase)` — that derive *different* wallets from the same + * underlying entropy. Keeping the kind explicit through the resolve chain + * lets the builder pick the matching method instead of force-converting a + * mnemonic to hex (which silently lands on the wrong wallet). + */ +export type WalletSeed = + | { kind: 'hex'; value: string } + | { kind: 'mnemonic'; value: string }; + +export function classifySeed(input: string): WalletSeed { + const trimmed = input.trim(); + if (!trimmed) { + throw new WalletError('Seed cannot be empty'); + } + if ( + /^[0-9a-fA-F]+$/.test(trimmed) && + (trimmed.length === 64 || trimmed.length === 128) + ) { + return { kind: 'hex', value: trimmed.toLowerCase() }; + } + if (validateMnemonic(trimmed, wordlist)) { + return { kind: 'mnemonic', value: trimmed }; + } + throw new WalletError( + 'Invalid seed: expected a 64/128-char hex string or a valid BIP39 mnemonic (12 or 24 words).', + ); +} diff --git a/packages/deploy/src/wallet/resolve.ts b/packages/deploy/src/wallet/resolve.ts new file mode 100644 index 0000000..04e959e --- /dev/null +++ b/packages/deploy/src/wallet/resolve.ts @@ -0,0 +1,92 @@ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { isAbsolute, resolve } from 'node:path'; +import type { CompactConfig, NetworkConfig } from '../config/schema.ts'; +import { WalletError } from '../errors.ts'; +import { Keystore } from './keystore.ts'; +import { localPrefundedSeed } from './local-seeds.ts'; +import { classifySeed, type WalletSeed } from './normalize.ts'; + +/** + * Resolve the deployer seed for a given network, with a documented + * precedence chain. + * + * Order (highest first): + * 1. `--seed-file ` (CLI) + * 2. `MN_DEPLOYER_SEED` (env) + * 3. `[wallet].keystore` (TOML; passphrase prompt required) + * 4. `[networks.local].wallet.source = "local"` (prefunded dev seed) + * + * Fails with {@link WalletError} when none match — with an actionable + * message that lists every path the user can take. + */ +export interface SeedResolution { + seed: WalletSeed; + origin: 'cli' | 'env' | 'keystore' | 'local'; +} + +export interface ResolveOptions { + config: CompactConfig; + rootDir: string; + networkName: string; + network: NetworkConfig; + seedFile?: string; + promptPassphrase?: (path: string) => Promise; +} + +export async function resolveSeed( + opts: ResolveOptions, +): Promise { + if (opts.seedFile) { + const path = absoluteUnder(opts.rootDir, opts.seedFile); + const raw = await safeRead(path, '--seed-file'); + return { seed: classifySeed(raw), origin: 'cli' }; + } + + const envSeed = process.env.MN_DEPLOYER_SEED; + if (envSeed?.trim()) { + return { seed: classifySeed(envSeed), origin: 'env' }; + } + + const keystorePath = opts.config.wallet?.keystore; + if (keystorePath) { + const path = absoluteUnder(opts.rootDir, keystorePath); + if (!existsSync(path)) { + throw new WalletError(`Keystore file not found: ${path}`); + } + if (!opts.promptPassphrase) { + throw new WalletError( + 'Keystore configured but no passphrase prompt provided', + ); + } + const ks = await Keystore.readFromFile(path); + const passphrase = await opts.promptPassphrase(path); + // Keystores store a raw 32-byte hex secret; classify ensures shape. + return { seed: classifySeed(ks.decrypt(passphrase)), origin: 'keystore' }; + } + + if (opts.networkName === 'local' && opts.network.wallet?.source === 'local') { + return { + seed: classifySeed(localPrefundedSeed(opts.network.wallet.index ?? 0)), + origin: 'local', + }; + } + + throw new WalletError( + `No deployer seed for network "${opts.networkName}". Provide --seed-file, set MN_DEPLOYER_SEED, or configure [wallet].keystore in compact.toml.`, + ); +} + +function absoluteUnder(root: string, p: string): string { + return isAbsolute(p) ? p : resolve(root, p); +} + +async function safeRead(path: string, label: string): Promise { + try { + return await readFile(path, 'utf8'); + } catch (e) { + throw new WalletError( + `Failed to read ${label} (${path}): ${(e as Error).message}`, + ); + } +} diff --git a/packages/deploy/tsconfig.json b/packages/deploy/tsconfig.json new file mode 100644 index 0000000..d46d4e6 --- /dev/null +++ b/packages/deploy/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@tsconfig/node24/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "types": ["node"], + "declaration": true, + "skipLibCheck": true, + "sourceMap": true, + "rewriteRelativeImportExtensions": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "src/**/*.test.ts"] +} diff --git a/tests/integrations/.gitignore b/tests/integrations/.gitignore new file mode 100644 index 0000000..e3222f7 --- /dev/null +++ b/tests/integrations/.gitignore @@ -0,0 +1,3 @@ +fixtures/artifacts/ +deployments/ +logs/ diff --git a/tests/integrations/Makefile b/tests/integrations/Makefile new file mode 100644 index 0000000..abf791d --- /dev/null +++ b/tests/integrations/Makefile @@ -0,0 +1,31 @@ +COMPOSE_FILE := local-env.yml +LOGS_DIR := logs +SERVICES := proof-server indexer node + +.PHONY: env-up env-down env-logs env-status compile + +## Start local Midnight stack (proof-server + indexer + node) +env-up: env-down + docker compose -f $(COMPOSE_FILE) up -d + @mkdir -p $(LOGS_DIR) + @for svc in $(SERVICES); do \ + docker compose -f $(COMPOSE_FILE) logs -f --no-log-prefix $$svc > $(LOGS_DIR)/$$svc.log 2>&1 & \ + done + @echo "Logs streaming to $(LOGS_DIR)/" + +## Stop the local stack and remove volumes +env-down: + @-pkill -f "docker compose -f $(COMPOSE_FILE) logs" 2>/dev/null || true + docker compose -f $(COMPOSE_FILE) down -v + +## Tail all logs +env-logs: + tail -f $(LOGS_DIR)/*.log + +## Show container status +env-status: + docker compose -f $(COMPOSE_FILE) ps + +## Compile the fixture contract +compile: + compact compile fixtures/Counter.compact fixtures/artifacts/Counter diff --git a/tests/integrations/README.md b/tests/integrations/README.md new file mode 100644 index 0000000..9199231 --- /dev/null +++ b/tests/integrations/README.md @@ -0,0 +1,54 @@ +# compact-tools — integration tests + +End-to-end tests for `@openzeppelin/compact-deploy` against a real local Midnight stack (proof-server + indexer + node, Docker). + +## Layout + +``` +tests/integrations/ + local-env.yml # Docker compose: proof-server + indexer + node + Makefile # env-up, env-down, compile + vitest.config.ts # Vitest config (forks pool, long timeouts) + compact.toml # Deployer config; paths resolve to this dir + fixtures/ + Counter.compact # Minimal one-circuit fixture + signingkeys/ + Counter.signingkey # CMA signing key (test-only) + artifacts/ # Output of compact-compiler (gitignored) + deploy.local.spec.ts # Specs: dry-run, deploy, history rotation +``` + +This is **not** a workspace package. The root `package.json` adds `@openzeppelin/compact-deploy` as a dev dep (resolved via yarn workspaces), and the root `test:integration` script invokes vitest pointed at this folder. + +## Run + +From the repo root (`compact-tools/`): + +```bash +corepack yarn build # build compact-deploy +make -C tests/integrations env-up # start docker stack +make -C tests/integrations compile # compile Counter.compact +corepack yarn test:integration # run specs +make -C tests/integrations env-down # stop stack +``` + +Or all-in-one with the root aliases: + +```bash +corepack yarn env:up +make -C tests/integrations compile +corepack yarn test:integration +corepack yarn env:down +``` + +## What's covered + +- **dry-run** — loads + validates the config without submitting a tx. +- **deploy** — deploys Counter to the local stack; verifies returned address, txHash, blockHeight, signingKey, and the persisted `deployments/compact/local.json` record. +- **history rotation** — redeploying rotates the previous head into `local.history.json`. + +## Notes + +- Uses the canonical genesis-funded seed `0x…0001` via `[networks.local].wallet = { source = "local", index = 0 }`. +- The CMA signing key in `fixtures/signingkeys/Counter.signingkey` is a fixed test value. Never use it for real deploys. +- The `deployments/` directory is wiped between test runs to keep specs hermetic. diff --git a/tests/integrations/_harness/deployer.ts b/tests/integrations/_harness/deployer.ts new file mode 100644 index 0000000..e6daad3 --- /dev/null +++ b/tests/integrations/_harness/deployer.ts @@ -0,0 +1,36 @@ +import { deploy, type DeployResult } from '@openzeppelin/compact-deploy'; +import { testLogger } from './logger.ts'; +import { localNetworkConfig, setupLocalNetwork } from './network.ts'; +import { CONFIG_PATH } from './paths.ts'; +import { getSharedPool, type PoolAlias } from './walletPool.ts'; + +/** + * Deploy `Counter` against the local stack using the wallet at `alias`. + * + * Each spec is expected to call `deployFixture` with its own alias so the + * pipeline always reuses the same wallet for multiple deploys within that + * spec. Sharing one wallet across multiple `deploy` calls keeps its UTXO + * view internally consistent — a fresh `buildDeployerWallet` per deploy + * syncs from the indexer (which may lag) and can occasionally see an + * already-spent dust UTXO, producing a `DustDoubleSpend` rejection on + * submission. + * + * Wallet lifecycle is owned by the shared pool: built and started on first + * use, stopped via `resetSharedPool()` once at end-of-suite. + */ +export async function deployFixture( + contract: 'Counter', + alias: PoolAlias, + overrides: { dryRun?: boolean; proofServer?: string } = {}, +): Promise { + setupLocalNetwork(); + const wallet = await getSharedPool(localNetworkConfig()).signerFor(alias); + return deploy({ + contract, + network: 'local', + configPath: CONFIG_PATH, + logger: testLogger(), + walletProvider: wallet, + ...overrides, + }); +} diff --git a/tests/integrations/_harness/logger.ts b/tests/integrations/_harness/logger.ts new file mode 100644 index 0000000..5bb20a4 --- /dev/null +++ b/tests/integrations/_harness/logger.ts @@ -0,0 +1,15 @@ +import pino, { type Logger } from 'pino'; + +let sharedLogger: Logger | undefined; + +/** + * Process-shared pino logger, level controlled by `LOG_LEVEL` (defaults to + * `warn` to keep test output clean). Specs that want chatty output can run + * `LOG_LEVEL=debug yarn test:integration`. + */ +export function testLogger(): Logger { + if (!sharedLogger) { + sharedLogger = pino({ level: process.env.LOG_LEVEL ?? 'warn' }); + } + return sharedLogger; +} diff --git a/tests/integrations/_harness/network.ts b/tests/integrations/_harness/network.ts new file mode 100644 index 0000000..9e5c708 --- /dev/null +++ b/tests/integrations/_harness/network.ts @@ -0,0 +1,42 @@ +import { setNetworkId } from '@midnight-ntwrk/midnight-js-network-id'; +import type { EnvironmentConfiguration } from '@midnight-ntwrk/testkit-js'; + +/** + * Local-stack network identifier. The dev-preset `midnight-node` boots with + * this id; every wallet/provider in the suite must agree. + */ +export const LOCAL_NETWORK_ID = 'undeployed'; + +/** + * Endpoints for the local stack brought up by `make env-up`. Each one is + * overridable via a `MIDNIGHT_*` env var so the same harness can be pointed + * at a relocated stack (e.g. a remote CI runner). + */ +export function localNetworkConfig(): EnvironmentConfiguration { + return { + walletNetworkId: LOCAL_NETWORK_ID, + networkId: LOCAL_NETWORK_ID, + indexer: + process.env.MIDNIGHT_INDEXER_URL ?? + 'http://127.0.0.1:8088/api/v4/graphql', + indexerWS: + process.env.MIDNIGHT_INDEXER_WS_URL ?? + 'ws://127.0.0.1:8088/api/v4/graphql/ws', + node: process.env.MIDNIGHT_NODE_URL ?? 'http://127.0.0.1:9944', + nodeWS: process.env.MIDNIGHT_NODE_WS_URL ?? 'ws://127.0.0.1:9944', + proofServer: + process.env.MIDNIGHT_PROOF_SERVER_URL ?? 'http://127.0.0.1:6300', + faucet: undefined, + }; +} + +/** + * Set the process-wide network id once before any provider/wallet is built. + * Idempotent. + */ +let networkIdSet = false; +export function setupLocalNetwork(): void { + if (networkIdSet) return; + setNetworkId(LOCAL_NETWORK_ID); + networkIdSet = true; +} diff --git a/tests/integrations/_harness/paths.ts b/tests/integrations/_harness/paths.ts new file mode 100644 index 0000000..1e97eb3 --- /dev/null +++ b/tests/integrations/_harness/paths.ts @@ -0,0 +1,32 @@ +import { existsSync, rmSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HARNESS_DIR = dirname(fileURLToPath(import.meta.url)); +const INTEGRATION_DIR = resolve(HARNESS_DIR, '..'); + +export const CONFIG_PATH = resolve(INTEGRATION_DIR, 'compact.toml'); +export const ARTIFACT_DIR = resolve( + INTEGRATION_DIR, + 'fixtures/artifacts/Counter', +); +export const DEPLOYMENTS_DIR = resolve( + INTEGRATION_DIR, + 'deployments/compact', +); + +/** Throw with a helpful hint if the fixture hasn't been compiled yet. */ +export function requireFixtureArtifact(): void { + if (existsSync(ARTIFACT_DIR)) return; + throw new Error( + `Missing compiled artifact at ${ARTIFACT_DIR}.\n` + + 'Run `make -C tests/integrations compile` first.', + ); +} + +/** Reset the deployments directory between specs. */ +export function wipeDeployments(): void { + if (existsSync(DEPLOYMENTS_DIR)) { + rmSync(DEPLOYMENTS_DIR, { recursive: true, force: true }); + } +} diff --git a/tests/integrations/_harness/teardown.ts b/tests/integrations/_harness/teardown.ts new file mode 100644 index 0000000..16248fc --- /dev/null +++ b/tests/integrations/_harness/teardown.ts @@ -0,0 +1,12 @@ +import { resetSharedPool } from './walletPool.ts'; + +/** + * Vitest `globalSetup` hook. The returned function runs once after the + * suite, stopping every wallet built across all specs so the process + * exits cleanly. + */ +export default function globalSetup(): () => Promise { + return async () => { + await resetSharedPool(); + }; +} diff --git a/tests/integrations/_harness/walletPool.ts b/tests/integrations/_harness/walletPool.ts new file mode 100644 index 0000000..1242074 --- /dev/null +++ b/tests/integrations/_harness/walletPool.ts @@ -0,0 +1,97 @@ +import { + type EnvironmentConfiguration, + type MidnightWalletProvider, + TEST_MNEMONIC, +} from '@midnight-ntwrk/testkit-js'; +import { buildDeployerWallet, classifySeed } from '@openzeppelin/compact-deploy'; +import { testLogger } from './logger.ts'; + +/** + * Aliases mapped to seeds prefunded by `midnight-node --preset=dev`. + * + * - `DEPLOYER` uses `TEST_MNEMONIC`, the canonical `abandon × 23 diesel` + * BIP39 phrase recognised by the dev preset as the genesis-funded + * account. Routed through `FluentWalletBuilder.withMnemonic`. + * - `ALICE`/`BOB`/`CHARLIE`/`DAVE` map to the hex seeds the standalone + * testkit exposes via `LocalTestEnvironment.genesisMintWalletSeed`. + * Routed through `FluentWalletBuilder.withSeed`. + */ +export const PREFUNDED_SEEDS = { + DEPLOYER: TEST_MNEMONIC, + ALICE: '0000000000000000000000000000000000000000000000000000000000000001', + BOB: '0000000000000000000000000000000000000000000000000000000000000002', + CHARLIE: '0000000000000000000000000000000000000000000000000000000000000003', + DAVE: '0000000000000000000000000000000000000000000000000000000000000004', +} as const; + +export type PoolAlias = keyof typeof PREFUNDED_SEEDS; + +/** + * Process-shared pool of test wallets keyed by alias. + * + * Wallet startup (`build` + sync) is the slowest part of the suite, so the + * pool caches one promise per alias. `signerFor()` is safe to call from + * `beforeAll` in every spec — repeated calls return the same warm wallet. + * Specs that need wallet isolation can construct their own pool instance. + */ +export class WalletPool { + private cache = new Map>(); + + constructor(private readonly env: EnvironmentConfiguration) {} + + signerFor(alias: PoolAlias): Promise { + const seedString = PREFUNDED_SEEDS[alias]; + if (seedString === undefined) { + throw new Error( + `WalletPool: unknown alias '${alias}'. Available: ${Object.keys(PREFUNDED_SEEDS).join(', ')}`, + ); + } + const cached = this.cache.get(alias); + if (cached) return cached; + + const built = (async () => { + const wallet = await buildDeployerWallet( + testLogger(), + this.env, + classifySeed(seedString), + ); + await wallet.start(true); + return wallet; + })(); + this.cache.set(alias, built); + return built; + } + + /** Stop every cached wallet and clear the cache. Call from `afterAll()`. */ + async reset(): Promise { + const entries = Array.from(this.cache.values()); + this.cache.clear(); + await Promise.all( + entries.map(async (p) => { + try { + const w = await p; + await w.stop(); + } catch { + /* ignore stop errors during teardown */ + } + }), + ); + } +} + +let sharedPool: WalletPool | undefined; + +/** + * Process-singleton pool. First call builds it against `env`; subsequent + * calls return the cached instance. Reset via `resetSharedPool()`. + */ +export function getSharedPool(env: EnvironmentConfiguration): WalletPool { + if (!sharedPool) sharedPool = new WalletPool(env); + return sharedPool; +} + +export async function resetSharedPool(): Promise { + if (!sharedPool) return; + await sharedPool.reset(); + sharedPool = undefined; +} diff --git a/tests/integrations/compact.toml b/tests/integrations/compact.toml new file mode 100644 index 0000000..c98ebd4 --- /dev/null +++ b/tests/integrations/compact.toml @@ -0,0 +1,20 @@ +# Integration-test deployer config. All paths resolve against this file's dir. + +[profile] +default_network = "local" +artifacts_dir = "fixtures/artifacts" +deployments_dir = "deployments/compact" + +[networks.local] +network_id = "undeployed" +indexer = "http://127.0.0.1:8088/api/v4/graphql" +indexer_ws = "ws://127.0.0.1:8088/api/v4/graphql/ws" +node = "http://127.0.0.1:9944" +node_ws = "ws://127.0.0.1:9944" +proof_server = "http://127.0.0.1:6300" +wallet = { source = "local", index = 0 } +faucet = false + +[contracts.Counter] +artifact = "Counter" +signing_key_file = "fixtures/signingkeys/Counter.signingkey" diff --git a/tests/integrations/fixtures/Counter.compact b/tests/integrations/fixtures/Counter.compact new file mode 100644 index 0000000..4a5959a --- /dev/null +++ b/tests/integrations/fixtures/Counter.compact @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// Integration-test fixture for @openzeppelin/compact-deploy. +// Minimal single-circuit counter. + +pragma language_version >= 0.21.0; + +import CompactStandardLibrary; + +export ledger round: Counter; + +export circuit increment(): [] { + round.increment(1); +} diff --git a/tests/integrations/fixtures/signingkeys/Counter.signingkey b/tests/integrations/fixtures/signingkeys/Counter.signingkey new file mode 100644 index 0000000..9c442d4 --- /dev/null +++ b/tests/integrations/fixtures/signingkeys/Counter.signingkey @@ -0,0 +1 @@ +0000000000000000000000000000000000000000000000000000000000000042 diff --git a/tests/integrations/local-env.yml b/tests/integrations/local-env.yml new file mode 100644 index 0000000..3969782 --- /dev/null +++ b/tests/integrations/local-env.yml @@ -0,0 +1,61 @@ +# WARNING: Insecure default credentials below. For local development only — do not use in production. +services: + proof-server: + image: 'midnightntwrk/proof-server:latest' + command: ['midnight-proof-server -v'] + ports: + - '6300:6300' + environment: + RUST_BACKTRACE: 'full' + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:6300/version'] + interval: 10s + timeout: 5s + retries: 20 + start_period: 10s + + indexer: + image: 'midnightntwrk/indexer-standalone:latest' + ports: + - '8088:8088' + environment: + RUST_LOG: 'indexer=info,chain_indexer=info,indexer_api=info,wallet_indexer=info,indexer_common=info,info' + APP__INFRA__NODE__URL: 'ws://node:9944' + APP__APPLICATION__NETWORK_ID: 'undeployed' + APP__INFRA__STORAGE__PASSWORD: 'indexer' + APP__INFRA__PUB_SUB__PASSWORD: 'indexer' + APP__INFRA__LEDGER_STATE_STORAGE__PASSWORD: 'indexer' + APP__INFRA__SECRET: '303132333435363738393031323334353637383930313233343536373839303132' + healthcheck: + test: ['CMD-SHELL', 'cat /var/run/indexer-standalone/running'] + interval: 10s + timeout: 5s + retries: 20 + start_period: 10s + depends_on: + node: + condition: service_healthy + logging: + driver: local + options: + max-size: '10m' + max-file: '3' + + node: + image: 'midnightntwrk/midnight-node:0.22.2' + ports: + - '9944:9944' + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:9944/health'] + interval: 2s + timeout: 5s + retries: 20 + start_period: 5s + environment: + CFG_PRESET: 'dev' + SIDECHAIN_BLOCK_BENEFICIARY: '04bcf7ad3be7a5c790460be82a713af570f22e0f801f6659ab8e84a52be6969e' + logging: + driver: local + options: + max-size: '10m' + max-file: '3' diff --git a/tests/integrations/specs/deploy.spec.ts b/tests/integrations/specs/deploy.spec.ts new file mode 100644 index 0000000..8e5e397 --- /dev/null +++ b/tests/integrations/specs/deploy.spec.ts @@ -0,0 +1,54 @@ +import { readFile } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { deployFixture } from '../_harness/deployer.ts'; +import { + DEPLOYMENTS_DIR, + requireFixtureArtifact, + wipeDeployments, +} from '../_harness/paths.ts'; + +/** + * Spec: a fresh `compact-deploy` invocation puts Counter on the local + * chain and writes a complete deployment record. Exercises the full + * pipeline end-to-end against the live Midnight stack. + */ +describe('compact-deploy — Counter deploys to local stack', () => { + beforeAll(() => { + requireFixtureArtifact(); + wipeDeployments(); + }); + + afterAll(() => { + wipeDeployments(); + }); + + it('returns an address, txHash, signingKey, and block height', async () => { + const result = await deployFixture('Counter', 'DEPLOYER'); + + expect(result.dryRun).toBe(false); + expect(result.contractName).toBe('Counter'); + expect(result.network).toBe('local'); + expect(result.address).toMatch(/^[0-9a-f]+$/i); + expect(result.txId).toMatch(/^[0-9a-f]+$/i); + expect(result.txHash).toMatch(/^[0-9a-f]+$/i); + expect(result.blockHeight).toBeGreaterThan(0); + expect(result.signingKey).toMatch(/^[0-9a-f]{64}$/); + expect(result.deployer).toBeTruthy(); + }); + + it('persists the deployment record at deployments/compact/local.json', async () => { + const headPath = resolve(DEPLOYMENTS_DIR, 'local.json'); + expect(existsSync(headPath)).toBe(true); + + const head = JSON.parse(await readFile(headPath, 'utf8')); + expect(head.Counter).toBeDefined(); + expect(head.Counter.address).toMatch(/^[0-9a-f]+$/i); + expect(head.Counter.txHash).toMatch(/^[0-9a-f]+$/i); + expect(head.Counter.signingKey).toMatch(/^[0-9a-f]{64}$/); + expect(head.Counter.deployer).toBeTruthy(); + expect(head.Counter.artifact).toBe('Counter'); + expect(head.Counter.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/); + }); +}); diff --git a/tests/integrations/specs/dryRun.spec.ts b/tests/integrations/specs/dryRun.spec.ts new file mode 100644 index 0000000..452fd02 --- /dev/null +++ b/tests/integrations/specs/dryRun.spec.ts @@ -0,0 +1,42 @@ +import { existsSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { deployFixture } from '../_harness/deployer.ts'; +import { + DEPLOYMENTS_DIR, + requireFixtureArtifact, + wipeDeployments, +} from '../_harness/paths.ts'; + +/** + * Spec: `--dry-run` performs every validation step (config, artifact, + * wallet seed, providers) without submitting a transaction. No + * deployments file should be written. + */ +describe('compact-deploy — --dry-run validates without submitting', () => { + beforeAll(() => { + requireFixtureArtifact(); + wipeDeployments(); + }); + + afterAll(() => { + wipeDeployments(); + }); + + it('returns dryRun=true and an empty address', async () => { + const result = await deployFixture('Counter', 'ALICE', { dryRun: true }); + + expect(result.dryRun).toBe(true); + expect(result.address).toBe(''); + expect(result.contractName).toBe('Counter'); + expect(result.network).toBe('local'); + expect(result.signingKey).toMatch(/^[0-9a-f]{64}$/); + }); + + it('does not write a deployments file', () => { + expect(existsSync(resolve(DEPLOYMENTS_DIR, 'local.json'))).toBe(false); + expect(existsSync(resolve(DEPLOYMENTS_DIR, 'local.history.json'))).toBe( + false, + ); + }); +}); diff --git a/tests/integrations/specs/errors.spec.ts b/tests/integrations/specs/errors.spec.ts new file mode 100644 index 0000000..4823352 --- /dev/null +++ b/tests/integrations/specs/errors.spec.ts @@ -0,0 +1,47 @@ +import { deploy, ConfigError } from '@openzeppelin/compact-deploy'; +import { describe, expect, it } from 'vitest'; +import { testLogger } from '../_harness/logger.ts'; +import { CONFIG_PATH, requireFixtureArtifact } from '../_harness/paths.ts'; + +/** + * Spec: pipeline surfaces typed `ConfigError`s for foreseeable user + * mistakes, with messages that name the offending key/value. These run + * against the live stack but never get past the config-validation phase, + * so they're fast. + */ +describe('compact-deploy — config errors are typed and actionable', () => { + it('rejects an unknown contract name', async () => { + requireFixtureArtifact(); + await expect( + deploy({ + contract: 'Nonexistent', + network: 'local', + configPath: CONFIG_PATH, + logger: testLogger(), + }), + ).rejects.toThrow(ConfigError); + }); + + it('rejects an unknown network name', async () => { + requireFixtureArtifact(); + await expect( + deploy({ + contract: 'Counter', + network: 'unknown-network', + configPath: CONFIG_PATH, + logger: testLogger(), + }), + ).rejects.toThrow(ConfigError); + }); + + it('rejects a missing compact.toml path', async () => { + await expect( + deploy({ + contract: 'Counter', + network: 'local', + configPath: '/nonexistent/compact.toml', + logger: testLogger(), + }), + ).rejects.toThrow(ConfigError); + }); +}); diff --git a/tests/integrations/specs/historyRotation.spec.ts b/tests/integrations/specs/historyRotation.spec.ts new file mode 100644 index 0000000..f956006 --- /dev/null +++ b/tests/integrations/specs/historyRotation.spec.ts @@ -0,0 +1,55 @@ +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { deployFixture } from '../_harness/deployer.ts'; +import { + DEPLOYMENTS_DIR, + requireFixtureArtifact, + wipeDeployments, +} from '../_harness/paths.ts'; + +/** + * Spec: redeploying the same contract rotates the previous head into + * `.history.json`. Verifies the persist module's append-to-history + * behaviour against real deploy results. + */ +describe('compact-deploy — redeploy rotates head into history', () => { + let firstAddress: string; + let secondAddress: string; + + beforeAll(async () => { + requireFixtureArtifact(); + wipeDeployments(); + firstAddress = (await deployFixture('Counter', 'BOB')).address; + secondAddress = (await deployFixture('Counter', 'BOB')).address; + }); + + afterAll(() => { + wipeDeployments(); + }); + + it('produces distinct addresses on each deploy', () => { + expect(firstAddress).not.toBe(secondAddress); + expect(firstAddress).toMatch(/^[0-9a-f]+$/i); + expect(secondAddress).toMatch(/^[0-9a-f]+$/i); + }); + + it('keeps the latest deployment at the head', async () => { + const head = JSON.parse( + await readFile(resolve(DEPLOYMENTS_DIR, 'local.json'), 'utf8'), + ); + expect(head.Counter.address).toBe(secondAddress); + }); + + it('moves the previous head into .history.json', async () => { + const history = JSON.parse( + await readFile( + resolve(DEPLOYMENTS_DIR, 'local.history.json'), + 'utf8', + ), + ); + expect(Array.isArray(history.Counter)).toBe(true); + expect(history.Counter.length).toBeGreaterThanOrEqual(1); + expect(history.Counter[0].address).toBe(firstAddress); + }); +}); diff --git a/tests/integrations/specs/walletPool.spec.ts b/tests/integrations/specs/walletPool.spec.ts new file mode 100644 index 0000000..2ad3246 --- /dev/null +++ b/tests/integrations/specs/walletPool.spec.ts @@ -0,0 +1,59 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { localNetworkConfig, setupLocalNetwork } from '../_harness/network.ts'; +import { + getSharedPool, + PREFUNDED_SEEDS, + resetSharedPool, + type PoolAlias, +} from '../_harness/walletPool.ts'; + +/** + * Spec: every alias in `PREFUNDED_SEEDS` (DEPLOYER via TEST_MNEMONIC, the + * four hex-seed accounts) is genesis-funded on the dev-preset node, so the + * pool can hand out a synced wallet for each without needing the faucet. + * + * This is the property `compact-deploy`'s local resolution depends on — + * if it breaks (e.g. a new node release changes the prefunded set), every + * other spec will start failing with InsufficientFunds. + */ +describe('compact-deploy — prefunded wallet pool', () => { + beforeAll(() => { + setupLocalNetwork(); + }); + + afterAll(async () => { + await resetSharedPool(); + }); + + const aliases = Object.keys(PREFUNDED_SEEDS) as PoolAlias[]; + + it.each(aliases)( + 'builds a synced, funded wallet for %s', + async (alias) => { + const pool = getSharedPool(localNetworkConfig()); + const wallet = await pool.signerFor(alias); + + const coinPublicKey = wallet.getCoinPublicKey(); + expect(typeof coinPublicKey).toBe('string'); + expect((coinPublicKey as unknown as string).length).toBeGreaterThan(0); + + const encryptionPublicKey = wallet.getEncryptionPublicKey(); + expect(typeof encryptionPublicKey).toBe('string'); + }, + 180_000, + ); + + it('returns the same wallet instance for repeated `signerFor` calls', async () => { + const pool = getSharedPool(localNetworkConfig()); + const a = await pool.signerFor('ALICE'); + const b = await pool.signerFor('ALICE'); + expect(a).toBe(b); + }); + + it('produces distinct addresses for distinct aliases', async () => { + const pool = getSharedPool(localNetworkConfig()); + const alice = await pool.signerFor('ALICE'); + const bob = await pool.signerFor('BOB'); + expect(alice.getCoinPublicKey()).not.toBe(bob.getCoinPublicKey()); + }); +}); diff --git a/tests/integrations/vitest.config.ts b/tests/integrations/vitest.config.ts new file mode 100644 index 0000000..0874945 --- /dev/null +++ b/tests/integrations/vitest.config.ts @@ -0,0 +1,18 @@ +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + root: dirname(fileURLToPath(import.meta.url)), + test: { + include: ['specs/**/*.spec.ts'], + testTimeout: 240_000, + hookTimeout: 300_000, + teardownTimeout: 60_000, + pool: 'forks', + poolOptions: { + forks: { singleFork: true }, + }, + globalSetup: ['./_harness/teardown.ts'], + }, +}); diff --git a/yarn.lock b/yarn.lock index 9352729..e098311 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,49 @@ __metadata: version: 8 cacheKey: 10 +"@apollo/client@npm:^3.13.8": + version: 3.14.1 + resolution: "@apollo/client@npm:3.14.1" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + "@wry/caches": "npm:^1.0.0" + "@wry/equality": "npm:^0.5.6" + "@wry/trie": "npm:^0.5.0" + graphql-tag: "npm:^2.12.6" + hoist-non-react-statics: "npm:^3.3.2" + optimism: "npm:^0.18.0" + prop-types: "npm:^15.7.2" + rehackt: "npm:^0.1.0" + symbol-observable: "npm:^4.0.0" + ts-invariant: "npm:^0.10.3" + tslib: "npm:^2.3.0" + zen-observable-ts: "npm:^1.2.5" + peerDependencies: + graphql: ^15.0.0 || ^16.0.0 + graphql-ws: ^5.5.5 || ^6.0.3 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc + subscriptions-transport-ws: ^0.9.0 || ^0.11.0 + peerDependenciesMeta: + graphql-ws: + optional: true + react: + optional: true + react-dom: + optional: true + subscriptions-transport-ws: + optional: true + checksum: 10/e22f3449b0d3710e5e1e7be9668cf83f69af907b8647c6caf18d58a421dd0f7cb2921f13733930613f243a6da59aa68a8add60ef24f8229a226881450e1ee07b + languageName: node + linkType: hard + +"@balena/dockerignore@npm:^1.0.2": + version: 1.0.2 + resolution: "@balena/dockerignore@npm:1.0.2" + checksum: 10/13d654fdd725008577d32e721c720275bdc48f72bce612326363d5bed449febbed856c517a0b23c7c40d87cb531e63432804550b4ecc13e365d26fee38fb6c8a + languageName: node + linkType: hard + "@biomejs/biome@npm:2.3.8": version: 2.3.8 resolution: "@biomejs/biome@npm:2.3.8" @@ -105,6 +148,32 @@ __metadata: languageName: node linkType: hard +"@effect/platform@npm:^0.95.0": + version: 0.95.0 + resolution: "@effect/platform@npm:0.95.0" + dependencies: + find-my-way-ts: "npm:^0.1.6" + msgpackr: "npm:^1.11.4" + multipasta: "npm:^0.2.7" + peerDependencies: + effect: ^3.20.0 + checksum: 10/ae3f3bd441f77bb0f3bb71f954d3a06be2565e4d924eba8c7d5c898da32d893f42c4af0e5c6fee5a1ba087ab7d2d1dae8734a4b1e830baeb654fcccd63c996bb + languageName: node + linkType: hard + +"@effect/platform@npm:^0.96.0": + version: 0.96.1 + resolution: "@effect/platform@npm:0.96.1" + dependencies: + find-my-way-ts: "npm:^0.1.6" + msgpackr: "npm:^1.11.10" + multipasta: "npm:^0.2.7" + peerDependencies: + effect: ^3.21.2 + checksum: 10/36d8b1d43d636be02f9119e0e6d981565a88801ec097bda1cae0ed65bea9fb1963226140b3f6b714f4ea9091a248bc20bf2cc0d775b238a9ef4010ddea48fa65 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.27.7": version: 0.27.7 resolution: "@esbuild/aix-ppc64@npm:0.27.7" @@ -287,6 +356,60 @@ __metadata: languageName: node linkType: hard +"@fastify/busboy@npm:^2.0.0": + version: 2.1.1 + resolution: "@fastify/busboy@npm:2.1.1" + checksum: 10/2bb8a7eca8289ed14c9eb15239bc1019797454624e769b39a0b90ed204d032403adc0f8ed0d2aef8a18c772205fa7808cf5a1b91f21c7bfc7b6032150b1062c5 + languageName: node + linkType: hard + +"@graphql-typed-document-node/core@npm:^3.1.1, @graphql-typed-document-node/core@npm:^3.2.0": + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/fa44443accd28c8cf4cb96aaaf39d144a22e8b091b13366843f4e97d19c7bfeaf609ce3c7603a4aeffe385081eaf8ea245d078633a7324c11c5ec4b2011bb76d + languageName: node + linkType: hard + +"@grpc/grpc-js@npm:^1.11.1": + version: 1.14.3 + resolution: "@grpc/grpc-js@npm:1.14.3" + dependencies: + "@grpc/proto-loader": "npm:^0.8.0" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10/bb9bfe2f749179ae5ac7774d30486dfa2e0b004518c28de158b248e0f6f65f40138f01635c48266fa540670220f850216726e3724e1eb29d078817581c96e4db + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.7.13": + version: 0.7.15 + resolution: "@grpc/proto-loader@npm:0.7.15" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.2.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/2e2b33ace8bc34211522751a9e654faf9ac997577a9e9291b1619b4c05d7878a74d2101c3bc43b2b2b92bca7509001678fb191d4eb100684cc2910d66f36c373 + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.8.0": + version: 0.8.1 + resolution: "@grpc/proto-loader@npm:0.8.1" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.5.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/d9ef734a43fa3003b9fea4ad9392137f353b79d62b6452b68f8f6b1d8f97947139141d111108ba3e858642989e966e4aa1211012a657d1e41f80a9c7540070ec + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -334,6 +457,27 @@ __metadata: languageName: node linkType: hard +"@js-sdsl/ordered-map@npm:^4.4.2": + version: 4.4.2 + resolution: "@js-sdsl/ordered-map@npm:4.4.2" + checksum: 10/ac64e3f0615ecc015461c9f527f124d2edaa9e68de153c1e270c627e01e83d046522d7e872692fd57a8c514578b539afceff75831c0d8b2a9a7a347fbed35af4 + languageName: node + linkType: hard + +"@midnight-ntwrk/compact-js@npm:2.5.0": + version: 2.5.0 + resolution: "@midnight-ntwrk/compact-js@npm:2.5.0" + dependencies: + "@effect/platform": "npm:^0.95.0" + "@midnight-ntwrk/compact-runtime": "npm:0.15.0" + "@midnight-ntwrk/ledger-v8": "npm:^8.0.3" + "@midnight-ntwrk/platform-js": "npm:^2.2.4" + effect: "npm:^3.20.0" + tslib: "npm:^2.8.1" + checksum: 10/4b5c5e0630d242740a19c51cdef825dab5408b3fa4a360d81fd37652bb77bf83227867d621c0acd6ddccc6a57f68f89e1df045daf7edb77154f80e245598ed9d + languageName: node + linkType: hard + "@midnight-ntwrk/compact-runtime@npm:0.14.0": version: 0.14.0 resolution: "@midnight-ntwrk/compact-runtime@npm:0.14.0" @@ -345,6 +489,28 @@ __metadata: languageName: node linkType: hard +"@midnight-ntwrk/compact-runtime@npm:0.15.0": + version: 0.15.0 + resolution: "@midnight-ntwrk/compact-runtime@npm:0.15.0" + dependencies: + "@midnight-ntwrk/onchain-runtime-v3": "npm:^3.0.0" + "@types/object-inspect": "npm:^1.8.1" + object-inspect: "npm:^1.12.3" + checksum: 10/12ac86a114a404386037547a6eb021694537c0636d24d281b101c5be75e3f5703bad9e0bbcc7ea2a39a96e167d200860049a9957dbb4dbdeb585c3fba696909c + languageName: node + linkType: hard + +"@midnight-ntwrk/compact-runtime@npm:0.16.0": + version: 0.16.0 + resolution: "@midnight-ntwrk/compact-runtime@npm:0.16.0" + dependencies: + "@midnight-ntwrk/onchain-runtime-v3": "npm:^3.0.0" + "@types/object-inspect": "npm:^1.8.1" + object-inspect: "npm:^1.12.3" + checksum: 10/ef0c68d53bba6a04f336094c82c26b781082d7ce4ee09f0539009fb108776b36ea24b9a774292d9bbf9722b8a78d47254b5f80a613d4010a7f7d108514243023 + languageName: node + linkType: hard + "@midnight-ntwrk/ledger-v7@npm:^7.0.0": version: 7.0.2 resolution: "@midnight-ntwrk/ledger-v7@npm:7.0.2" @@ -352,219 +518,1297 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/onchain-runtime-v2@npm:^2.0.0": - version: 2.0.1 - resolution: "@midnight-ntwrk/onchain-runtime-v2@npm:2.0.1" - checksum: 10/40ffba7809ecbf9e7e4fd98e7e025922ba72ff667d15f7737b9a2b913558688f19552ef40a63a1379b348a4e5c85e4257f6f485d6b09d15c2b5e4ca0149613b0 +"@midnight-ntwrk/ledger-v8@npm:8.0.3": + version: 8.0.3 + resolution: "@midnight-ntwrk/ledger-v8@npm:8.0.3" + checksum: 10/93d24ddeff967a5f5d566a7e8fc0c5586f309e954adf56761fff4ab67874b846c2a4f3f2aede4f51a9e1445d01f52a7446da121473f0120793bc622feeeed207 languageName: node linkType: hard -"@npmcli/agent@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/agent@npm:3.0.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10/775c9a7eb1f88c195dfb3bce70c31d0fe2a12b28b754e25c08a3edb4bc4816bfedb7ac64ef1e730579d078ca19dacf11630e99f8f3c3e0fd7b23caa5fd6d30a6 +"@midnight-ntwrk/midnight-js-compact@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-compact@npm:4.0.2" + bin: + fetch-compactc: dist/fetch-compact.mjs + run-compactc: dist/run-compactc.cjs + checksum: 10/a6c162c47205149155035c5c3e50412d621f8db098dd89bcce7861610a63b4b6d5db2e4e88c2674d5f480bb6a3df3d64a020b803ccf0fb1e84bbb98da3f9cb1b languageName: node linkType: hard -"@npmcli/fs@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/fs@npm:4.0.0" +"@midnight-ntwrk/midnight-js-contracts@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-contracts@npm:4.0.2" dependencies: - semver: "npm:^7.3.5" - checksum: 10/405c4490e1ff11cf299775449a3c254a366a4b1ffc79d87159b0ee7d5558ac9f6a2f8c0735fd6ff3873cef014cb1a44a5f9127cb6a1b2dbc408718cca9365b5a + "@midnight-ntwrk/compact-js": "npm:2.5.0" + "@midnight-ntwrk/midnight-js-network-id": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-types": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-utils": "npm:4.0.2" + checksum: 10/04df0557f4859db4a17531924ed9fdd64cdb6239146fc0a06ff950ea5ad65e893970b6046f175aeadddbc028d148f6a534ec6d0ea8d490bca7f190c487be42f4 languageName: node linkType: hard -"@openzeppelin/compact-builder@workspace:^, @openzeppelin/compact-builder@workspace:packages/builder": - version: 0.0.0-use.local - resolution: "@openzeppelin/compact-builder@workspace:packages/builder" +"@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:4.0.2" dependencies: - "@tsconfig/node24": "npm:^24.0.3" - "@types/node": "npm:24.10.1" - "@types/shell-quote": "npm:^1.7.5" - chalk: "npm:^5.6.2" - log-symbols: "npm:^7.0.0" - ora: "npm:^9.0.0" - shell-quote: "npm:^1.8.3" - typescript: "npm:^5.9.3" - vitest: "npm:^4.0.15" - languageName: unknown - linkType: soft + "@midnight-ntwrk/midnight-js-contracts": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-network-id": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-types": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-utils": "npm:4.0.2" + cross-fetch: "npm:^4.1.0" + fetch-retry: "npm:^6.0.0" + lodash: "npm:^4.17.23" + checksum: 10/a0efb544bfd0739ec2c90bffff08377657f64173320a2c716f40539ffb3cd0574b6609801d35522465d4cc57b7d85f7ade5c5086945f28d70fa12c91bbe030bb + languageName: node + linkType: hard -"@openzeppelin/compact-cli@workspace:packages/cli": - version: 0.0.0-use.local - resolution: "@openzeppelin/compact-cli@workspace:packages/cli" +"@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:4.0.2" dependencies: - "@openzeppelin/compact-builder": "workspace:^" - "@tsconfig/node24": "npm:^24.0.3" - "@types/node": "npm:24.10.1" - chalk: "npm:^5.6.2" - ora: "npm:^9.0.0" - typescript: "npm:^5.9.3" - vitest: "npm:^4.0.15" - bin: - compact-builder: dist/runBuilder.js - compact-compiler: dist/runCompiler.js - languageName: unknown - linkType: soft - -"@openzeppelin/compact-simulator@workspace:packages/simulator": - version: 0.0.0-use.local - resolution: "@openzeppelin/compact-simulator@workspace:packages/simulator" + "@apollo/client": "npm:^3.13.8" + "@midnight-ntwrk/midnight-js-network-id": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-types": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-utils": "npm:4.0.2" + buffer: "npm:^6.0.3" + cross-fetch: "npm:^4.1.0" + graphql: "npm:^16.8.0" + graphql-ws: "npm:^6.0.7" + isomorphic-ws: "npm:^5.0.0" + rxjs: "npm:^7.5.0" + ws: "npm:^8.14.2" + zen-observable-ts: "npm:^1.1.0" + checksum: 10/6600ec196a71750f214e60eb4bb086f1abb5236cde8fd56699423b5c2f0caa23b7fc04db64c43dbd0f170c560eea5a161c50cfacc9da77ba5dc427135d45caea + languageName: node + linkType: hard + +"@midnight-ntwrk/midnight-js-level-private-state-provider@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-level-private-state-provider@npm:4.0.2" dependencies: - "@midnight-ntwrk/compact-runtime": "npm:0.14.0" - "@midnight-ntwrk/ledger-v7": "npm:^7.0.0" - "@tsconfig/node24": "npm:^24.0.3" - "@types/node": "npm:24.10.1" - fast-check: "npm:^4.5.2" - typescript: "npm:^5.8.2" - vitest: "npm:^4.0.15" - languageName: unknown - linkType: soft + "@midnight-ntwrk/midnight-js-types": "npm:4.0.2" + abstract-level: "npm:^3.0.0" + buffer: "npm:^6.0.3" + fp-ts: "npm:^2.16.1" + io-ts: "npm:^2.2.20" + level: "npm:^10.0.0" + superjson: "npm:^2.0.0" + checksum: 10/60f0df013d1e091667c555655d07752a518f0c8bb0ed981433c3d3051b6938f6ae3658390761c5b3cb20f5aa8075768aee4642425bb7bd4930e9193d472d5169 + languageName: node + linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff +"@midnight-ntwrk/midnight-js-network-id@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-network-id@npm:4.0.2" + checksum: 10/79326b56bdb6cbc7591b1c2ba54ed96f6c9719ab103a1daa13bdb7e15856324ddd736319c199ea8b6003884ef32bb4803b34b79af906166ec7470f104934b90c languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.60.3" - conditions: os=android & cpu=arm +"@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:4.0.2" + dependencies: + "@midnight-ntwrk/midnight-js-types": "npm:4.0.2" + checksum: 10/ecb7e7979b199c8555ff30d6d0f7a8dfcdd45ca60ad7e7c1c53e29971c1a5ed6c44696326891615efbd27f43522be3a3628633fd6de2e92eeb44630e336eeb6d languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-android-arm64@npm:4.60.3" - conditions: os=android & cpu=arm64 +"@midnight-ntwrk/midnight-js-types@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-types@npm:4.0.2" + dependencies: + "@midnight-ntwrk/compact-js": "npm:2.5.0" + "@midnight-ntwrk/platform-js": "npm:2.2.4" + rxjs: "npm:^7.5.0" + checksum: 10/2a7064f415a514d004f313da6bf926d22609f372a06ffc4e758bf47eed19440c8edb439d20b95ea7ebf1815b82657f9f833e6ff526a56e3eaa618b662010da31 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-darwin-arm64@npm:4.60.3" - conditions: os=darwin & cpu=arm64 +"@midnight-ntwrk/midnight-js-utils@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/midnight-js-utils@npm:4.0.2" + dependencies: + "@midnight-ntwrk/midnight-js-network-id": "npm:4.0.2" + "@scure/base": "npm:^2.0.0" + checksum: 10/4af542911974cee2448ea27013d86df4b61e93aecb6386e37aef16fe86864580489353abb978dee7534ba9cd2a0eaf164f4f72900e45409cd0b7b3a170e1da77 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-darwin-x64@npm:4.60.3" - conditions: os=darwin & cpu=x64 +"@midnight-ntwrk/onchain-runtime-v2@npm:^2.0.0": + version: 2.0.1 + resolution: "@midnight-ntwrk/onchain-runtime-v2@npm:2.0.1" + checksum: 10/40ffba7809ecbf9e7e4fd98e7e025922ba72ff667d15f7737b9a2b913558688f19552ef40a63a1379b348a4e5c85e4257f6f485d6b09d15c2b5e4ca0149613b0 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.60.3" - conditions: os=freebsd & cpu=arm64 +"@midnight-ntwrk/onchain-runtime-v3@npm:^3.0.0": + version: 3.0.0 + resolution: "@midnight-ntwrk/onchain-runtime-v3@npm:3.0.0" + checksum: 10/873aeb9e631c3678373c62b5aef847de454de94427028fb3d3f28bfdc8b2c02a3c770bd79d9bfef183eb9db6fb8c23e6826636f2e512ffd6eacbcf7cc0651c5d languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-freebsd-x64@npm:4.60.3" - conditions: os=freebsd & cpu=x64 +"@midnight-ntwrk/platform-js@npm:2.2.4, @midnight-ntwrk/platform-js@npm:^2.2.4": + version: 2.2.4 + resolution: "@midnight-ntwrk/platform-js@npm:2.2.4" + dependencies: + "@effect/platform": "npm:^0.95.0" + effect: "npm:^3.20.0" + tslib: "npm:^2.8.1" + checksum: 10/1650bb7e54a64740aaaf27f7e84b7bffdb08611c994bbf54208db43a0a11d10ea8994f05d82e848d60d6fcee8a9b3a5db770d306262b99547e71185d52614825 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.60.3" - conditions: os=linux & cpu=arm & libc=glibc +"@midnight-ntwrk/testkit-js@npm:4.0.2": + version: 4.0.2 + resolution: "@midnight-ntwrk/testkit-js@npm:4.0.2" + dependencies: + "@midnight-ntwrk/compact-js": "npm:2.5.0" + "@midnight-ntwrk/ledger-v8": "npm:8.0.3" + "@midnight-ntwrk/midnight-js-compact": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-contracts": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-level-private-state-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-network-id": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-types": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-utils": "npm:4.0.2" + "@midnight-ntwrk/wallet-sdk-hd": "npm:3.0.1" + buffer: "npm:^6.0.3" + cross-fetch: "npm:^4.0.0" + rxjs: "npm:^7.8.1" + ws: "npm:^8.14.2" + checksum: 10/9484c94129560620c9fe864a96286fa3043e95312fbc4b2faa8f9d0ab7344586f1784cf2fd71c63bc5c9a4bdecad30ecf29ea59540bb19d8d0085da9647f9780 + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-abstractions@npm:2.0.0": + version: 2.0.0 + resolution: "@midnight-ntwrk/wallet-sdk-abstractions@npm:2.0.0" + dependencies: + effect: "npm:^3.19.19" + checksum: 10/b018375c23ee0eaef963642ec74c2ad3e3c88a5fc3a7de021d3547f8b435f2e73062b814113a5b42c5d01d58e53d7449618be5c7da1596392988a76bb36747e3 languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.60.3" - conditions: os=linux & cpu=arm & libc=musl +"@midnight-ntwrk/wallet-sdk-abstractions@npm:2.1.0, @midnight-ntwrk/wallet-sdk-abstractions@npm:^2.0.0, @midnight-ntwrk/wallet-sdk-abstractions@npm:^2.1.0": + version: 2.1.0 + resolution: "@midnight-ntwrk/wallet-sdk-abstractions@npm:2.1.0" + dependencies: + effect: "npm:^3.19.19" + checksum: 10/acd476877ab4d32a2580d0b8c4a22a4458a9f5f3bd61b3220fc8a9da63a5cc61ccb5fd95d47506fe47999e708ade7a37d4eca74707cffe9a6b9b648c9ed28596 languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.60.3" - conditions: os=linux & cpu=arm64 & libc=glibc +"@midnight-ntwrk/wallet-sdk-address-format@npm:3.1.0": + version: 3.1.0 + resolution: "@midnight-ntwrk/wallet-sdk-address-format@npm:3.1.0" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.2" + "@scure/base": "npm:^2.0.0" + "@subsquid/scale-codec": "npm:^4.0.1" + checksum: 10/be1cfde40a7753c62377a914ec72fe29ac57e38895b33d14861b22b7193aa1fdd1989680f5b5907b73a24d9c080e5ea6711a88b4442192fb1b1fa37da0a9009a languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.60.3" - conditions: os=linux & cpu=arm64 & libc=musl +"@midnight-ntwrk/wallet-sdk-address-format@npm:^3.1.0": + version: 3.1.1 + resolution: "@midnight-ntwrk/wallet-sdk-address-format@npm:3.1.1" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.3" + "@scure/base": "npm:^2.0.0" + "@subsquid/scale-codec": "npm:^4.0.1" + checksum: 10/d92eb47928ae9dfc93bd8b549ba9c32b54b43eaae34ed7031c46b6654a55c92173eed47732f170307a4b372ed692bf3637d0b78fc58fdc3f5635d97bb782be4a languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.60.3" - conditions: os=linux & cpu=loong64 & libc=glibc +"@midnight-ntwrk/wallet-sdk-capabilities@npm:3.2.0": + version: 3.2.0 + resolution: "@midnight-ntwrk/wallet-sdk-capabilities@npm:3.2.0" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.2" + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:^2.0.0" + "@midnight-ntwrk/wallet-sdk-indexer-client": "npm:^1.2.0" + "@midnight-ntwrk/wallet-sdk-node-client": "npm:^1.1.0" + "@midnight-ntwrk/wallet-sdk-prover-client": "npm:^1.2.0" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:^1.1.0" + "@midnight-ntwrk/zkir-v2": "npm:^2.1.0" + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/d8015651d2e67305bc858706e089a5ac34001b390618ecece0cb5de33a41e692ce1030c4128e72b4235e2796f101563f485d5091abb25159de861108610ca3a5 languageName: node linkType: hard -"@rollup/rollup-linux-loong64-musl@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-loong64-musl@npm:4.60.3" - conditions: os=linux & cpu=loong64 & libc=musl +"@midnight-ntwrk/wallet-sdk-capabilities@npm:^3.2.0": + version: 3.3.0 + resolution: "@midnight-ntwrk/wallet-sdk-capabilities@npm:3.3.0" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.3" + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:^2.1.0" + "@midnight-ntwrk/wallet-sdk-indexer-client": "npm:^1.2.1" + "@midnight-ntwrk/wallet-sdk-node-client": "npm:^1.1.1" + "@midnight-ntwrk/wallet-sdk-prover-client": "npm:^1.2.1" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:^1.1.1" + "@midnight-ntwrk/zkir-v2": "npm:^2.1.0" + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/dab6a7c2862a0181e16b1e94f882e9de655de16644a5476cb784d503847febe682cb8a565defdd90eef50247f5363a0eb1c8cd4a0702c279831c4a9e62b7e5a7 languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.60.3" - conditions: os=linux & cpu=ppc64 & libc=glibc +"@midnight-ntwrk/wallet-sdk-dust-wallet@npm:^3.0.0": + version: 3.0.0 + resolution: "@midnight-ntwrk/wallet-sdk-dust-wallet@npm:3.0.0" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.2" + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:2.0.0" + "@midnight-ntwrk/wallet-sdk-address-format": "npm:3.1.0" + "@midnight-ntwrk/wallet-sdk-capabilities": "npm:3.2.0" + "@midnight-ntwrk/wallet-sdk-indexer-client": "npm:1.2.0" + "@midnight-ntwrk/wallet-sdk-runtime": "npm:1.0.2" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.0" + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/5ea6b8227f602d90f0c7ff0da19bf56b5fc14700129cfabc0a75ca311fc33f04253f753166c8d062f81c999b94c8b9555f27886e4724499f29978a09290c0cb3 languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-musl@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.60.3" - conditions: os=linux & cpu=ppc64 & libc=musl +"@midnight-ntwrk/wallet-sdk-facade@npm:3.0.0": + version: 3.0.0 + resolution: "@midnight-ntwrk/wallet-sdk-facade@npm:3.0.0" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.2" + "@midnight-ntwrk/wallet-sdk-address-format": "npm:^3.1.0" + "@midnight-ntwrk/wallet-sdk-capabilities": "npm:^3.2.0" + "@midnight-ntwrk/wallet-sdk-dust-wallet": "npm:^3.0.0" + "@midnight-ntwrk/wallet-sdk-indexer-client": "npm:^1.2.0" + "@midnight-ntwrk/wallet-sdk-shielded": "npm:^2.1.0" + "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "npm:^2.1.0" + rxjs: "npm:^7.8.2" + checksum: 10/34a69b9b7d9925a784111a3a4880969f5b586693f98d80d480fa58ecc6f2d83fca361b998c155c655dfb3058cf550fe5ec7a26c729a8a36f6b7b7d4d9a136cd0 languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.60.3" - conditions: os=linux & cpu=riscv64 & libc=glibc +"@midnight-ntwrk/wallet-sdk-hd@npm:3.0.1": + version: 3.0.1 + resolution: "@midnight-ntwrk/wallet-sdk-hd@npm:3.0.1" + dependencies: + "@scure/bip32": "npm:^2.0.1" + "@scure/bip39": "npm:^2.0.1" + checksum: 10/ebc790355cf79423abed8c3e79621093df2817cb8a05f01f89b1afa35834dd3b9f3aef55e84839529d8a0824c6aa0391d5d668ca2174b9bc59d7092e9c9a580f languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.60.3" - conditions: os=linux & cpu=riscv64 & libc=musl +"@midnight-ntwrk/wallet-sdk-indexer-client@npm:1.2.0": + version: 1.2.0 + resolution: "@midnight-ntwrk/wallet-sdk-indexer-client@npm:1.2.0" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.2.0" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.0" + effect: "npm:^3.19.19" + graphql: "npm:^16.13.0" + graphql-http: "npm:^1.22.4" + graphql-ws: "npm:^6.0.7" + checksum: 10/a52c0f617ac35860d82d4d706b3fcc59d739a9764bf9ee5667804d9f89d7b592fbf4a9a0b7e2a0756176d47a1e0673d101ad27382fe985e6f6afc39b4358500d languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.60.3" - conditions: os=linux & cpu=s390x & libc=glibc +"@midnight-ntwrk/wallet-sdk-indexer-client@npm:^1.2.0, @midnight-ntwrk/wallet-sdk-indexer-client@npm:^1.2.1": + version: 1.2.1 + resolution: "@midnight-ntwrk/wallet-sdk-indexer-client@npm:1.2.1" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.2.0" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.1" + effect: "npm:^3.19.19" + graphql: "npm:^16.13.0" + graphql-http: "npm:^1.22.4" + graphql-ws: "npm:^6.0.7" + checksum: 10/419c9fe66e100659a4ae958ea7b55d885f2e201d8ef67ce49ad3802be7e606419f4909b3c7c0b1892cbf065a21263bff05b216f99b007af17a132c11757dfdbf languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.60.3" - conditions: os=linux & cpu=x64 & libc=glibc +"@midnight-ntwrk/wallet-sdk-node-client@npm:^1.1.0, @midnight-ntwrk/wallet-sdk-node-client@npm:^1.1.1": + version: 1.1.1 + resolution: "@midnight-ntwrk/wallet-sdk-node-client@npm:1.1.1" + dependencies: + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:2.1.0" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.1" + "@polkadot/api": "npm:^16.5.4" + "@polkadot/types": "npm:^16.5.4" + "@polkadot/util": "npm:^14.0.1" + "@types/bn.js": "npm:^5.2.0" + bn.js: "npm:^5.2.3" + effect: "npm:^3.19.19" + checksum: 10/e2c32fbfc4a475891f31ff786887a20b33a315c005b231aa66da8eb54d923728c113fa7bf629c5f328a92aadb821feabefcf51fb18c21b161440991caa15cf9d languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.60.3": - version: 4.60.3 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.60.3" - conditions: os=linux & cpu=x64 & libc=musl +"@midnight-ntwrk/wallet-sdk-prover-client@npm:^1.2.0, @midnight-ntwrk/wallet-sdk-prover-client@npm:^1.2.1": + version: 1.2.1 + resolution: "@midnight-ntwrk/wallet-sdk-prover-client@npm:1.2.1" + dependencies: + "@effect/platform": "npm:^0.96.0" + "@midnight-ntwrk/ledger-v8": "npm:^8.0.3" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.1" + "@midnight-ntwrk/zkir-v2": "npm:2.1.0" + effect: "npm:^3.19.19" + web-worker: "npm:^1.5.0" + checksum: 10/ec5c0cf6d5ab382d342655d4cd2dc08fa0d74969d63bcfc781cc13c187340db3744b9fcb0dbd95cf92966752c20334e668aba9ef1ef6a7059d97f374defda0a8 + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-runtime@npm:1.0.2": + version: 1.0.2 + resolution: "@midnight-ntwrk/wallet-sdk-runtime@npm:1.0.2" + dependencies: + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:2.0.0" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.0" + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/5bffdea2fdd596ab7fd6a512a559a08c07dd5944a88017122431ed28f6f31efac559b451e3b18613f5861dc700fcef3637e9f605288899dce32a91ea1cdceb2d + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-shielded@npm:2.1.0, @midnight-ntwrk/wallet-sdk-shielded@npm:^2.1.0": + version: 2.1.0 + resolution: "@midnight-ntwrk/wallet-sdk-shielded@npm:2.1.0" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.2" + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:2.0.0" + "@midnight-ntwrk/wallet-sdk-address-format": "npm:3.1.0" + "@midnight-ntwrk/wallet-sdk-capabilities": "npm:3.2.0" + "@midnight-ntwrk/wallet-sdk-indexer-client": "npm:1.2.0" + "@midnight-ntwrk/wallet-sdk-runtime": "npm:1.0.2" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.0" + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/6778adb165dd47d951d20171e03456ada907e28de16154e73a54603318567b264146402f4179cd9fce86be7b104466f3a04fca6ca6768696f5abec10d70fc54c + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-unshielded-wallet@npm:2.1.0, @midnight-ntwrk/wallet-sdk-unshielded-wallet@npm:^2.1.0": + version: 2.1.0 + resolution: "@midnight-ntwrk/wallet-sdk-unshielded-wallet@npm:2.1.0" + dependencies: + "@midnight-ntwrk/ledger-v8": "npm:^8.0.2" + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:2.0.0" + "@midnight-ntwrk/wallet-sdk-address-format": "npm:3.1.0" + "@midnight-ntwrk/wallet-sdk-capabilities": "npm:3.2.0" + "@midnight-ntwrk/wallet-sdk-indexer-client": "npm:1.2.0" + "@midnight-ntwrk/wallet-sdk-runtime": "npm:1.0.2" + "@midnight-ntwrk/wallet-sdk-utilities": "npm:1.1.0" + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/0863d3661fbd94d8c9263e81a59b5de61f7112ccccb5b51bd8b591eb7d174d1ac9fb073558be45c2a66d49b104b106bd962362d4b709b593df18fc755b3aab6c + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-utilities@npm:1.1.0": + version: 1.1.0 + resolution: "@midnight-ntwrk/wallet-sdk-utilities@npm:1.1.0" + dependencies: + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/f3c6c05475931d643644a31d7fcc45e74d4956c6d56ad1eb70710c0de7b0f9a1b753c0ceb2613684c35888b95502444e90233ed544bac23e65eaf0cebcd258f6 + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-utilities@npm:1.1.1, @midnight-ntwrk/wallet-sdk-utilities@npm:^1.1.0, @midnight-ntwrk/wallet-sdk-utilities@npm:^1.1.1": + version: 1.1.1 + resolution: "@midnight-ntwrk/wallet-sdk-utilities@npm:1.1.1" + dependencies: + effect: "npm:^3.19.19" + rxjs: "npm:^7.8.2" + checksum: 10/1775ac559ba003274fde80b839f296d5e1bba8c580cd6aae31db9df97f8ab5682ead4b76adbd3db01ad3af051fb81d0e24be2567cffdce51d1d55e864c6104a8 + languageName: node + linkType: hard + +"@midnight-ntwrk/zkir-v2@npm:2.1.0, @midnight-ntwrk/zkir-v2@npm:^2.1.0": + version: 2.1.0 + resolution: "@midnight-ntwrk/zkir-v2@npm:2.1.0" + checksum: 10/c16761489c3abbf858a4b7c2c4dd99d498f40554b5f1a57a93534b21c66390d4c6b0035dee8923fb5972418c75ac1f80e2e0675d8f3eb2a96dce7e7555fb2b7d + languageName: node + linkType: hard + +"@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3": + version: 3.0.3 + resolution: "@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@msgpackr-extract/msgpackr-extract-darwin-x64@npm:3.0.3": + version: 3.0.3 + resolution: "@msgpackr-extract/msgpackr-extract-darwin-x64@npm:3.0.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@msgpackr-extract/msgpackr-extract-linux-arm64@npm:3.0.3": + version: 3.0.3 + resolution: "@msgpackr-extract/msgpackr-extract-linux-arm64@npm:3.0.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@msgpackr-extract/msgpackr-extract-linux-arm@npm:3.0.3": + version: 3.0.3 + resolution: "@msgpackr-extract/msgpackr-extract-linux-arm@npm:3.0.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@msgpackr-extract/msgpackr-extract-linux-x64@npm:3.0.3": + version: 3.0.3 + resolution: "@msgpackr-extract/msgpackr-extract-linux-x64@npm:3.0.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@msgpackr-extract/msgpackr-extract-win32-x64@npm:3.0.3": + version: 3.0.3 + resolution: "@msgpackr-extract/msgpackr-extract-win32-x64@npm:3.0.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@noble/curves@npm:2.2.0": + version: 2.2.0 + resolution: "@noble/curves@npm:2.2.0" + dependencies: + "@noble/hashes": "npm:2.2.0" + checksum: 10/f9545e55bb8b6cdf2618c936870b9229339c90b25f129fc368b4b534e723f274e5c0daf8abca2f891bcf0a59c3b49c5ac5205899aec07f5251f545ec616e3aa9 + languageName: node + linkType: hard + +"@noble/curves@npm:^1.3.0, @noble/curves@npm:~1.9.2": + version: 1.9.7 + resolution: "@noble/curves@npm:1.9.7" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: 10/3cfe2735ea94972988ca9e217e0ebb2044372a7160b2079bf885da789492a6291fc8bf76ca3d8bf8dee477847ee2d6fac267d1e6c4f555054059f5e8c4865d44 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:~1.8.0": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: 10/474b7f56bc6fb2d5b3a42132561e221b0ea4f91e590f4655312ca13667840896b34195e2b53b7f097ec080a1fdd3b58d902c2a8d0fbdf51d2e238b53808a177e + languageName: node + linkType: hard + +"@noble/hashes@npm:2.2.0": + version: 2.2.0 + resolution: "@noble/hashes@npm:2.2.0" + checksum: 10/b1b78bedc2a01394be047429f3d888905015fe8a09f1b7e43e0b5736b54133df62f73dcc73ede43af38e96e86156afb45b86973fdeaa95d9f0880333c3fc0907 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10/775c9a7eb1f88c195dfb3bce70c31d0fe2a12b28b754e25c08a3edb4bc4816bfedb7ac64ef1e730579d078ca19dacf11630e99f8f3c3e0fd7b23caa5fd6d30a6 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/405c4490e1ff11cf299775449a3c254a366a4b1ffc79d87159b0ee7d5558ac9f6a2f8c0735fd6ff3873cef014cb1a44a5f9127cb6a1b2dbc408718cca9365b5a + languageName: node + linkType: hard + +"@openzeppelin/compact-builder@workspace:^, @openzeppelin/compact-builder@workspace:packages/builder": + version: 0.0.0-use.local + resolution: "@openzeppelin/compact-builder@workspace:packages/builder" + dependencies: + "@tsconfig/node24": "npm:^24.0.3" + "@types/node": "npm:24.10.1" + "@types/shell-quote": "npm:^1.7.5" + chalk: "npm:^5.6.2" + log-symbols: "npm:^7.0.0" + ora: "npm:^9.0.0" + shell-quote: "npm:^1.8.3" + typescript: "npm:^5.9.3" + vitest: "npm:^4.0.15" + languageName: unknown + linkType: soft + +"@openzeppelin/compact-cli@workspace:packages/cli": + version: 0.0.0-use.local + resolution: "@openzeppelin/compact-cli@workspace:packages/cli" + dependencies: + "@openzeppelin/compact-builder": "workspace:^" + "@openzeppelin/compact-deploy": "workspace:^" + "@tsconfig/node24": "npm:^24.0.3" + "@types/node": "npm:24.10.1" + "@types/ws": "npm:^8.5.10" + chalk: "npm:^5.6.2" + ora: "npm:^9.0.0" + pino: "npm:^9.7.0" + pino-pretty: "npm:^13.0.0" + typescript: "npm:^5.9.3" + vitest: "npm:^4.0.15" + ws: "npm:^8.16.0" + bin: + compact-builder: dist/runBuilder.js + compact-compiler: dist/runCompiler.js + compact-deploy: dist/runDeploy.js + languageName: unknown + linkType: soft + +"@openzeppelin/compact-deploy@workspace:^, @openzeppelin/compact-deploy@workspace:packages/deploy": + version: 0.0.0-use.local + resolution: "@openzeppelin/compact-deploy@workspace:packages/deploy" + dependencies: + "@midnight-ntwrk/compact-js": "npm:2.5.0" + "@midnight-ntwrk/compact-runtime": "npm:0.16.0" + "@midnight-ntwrk/ledger-v8": "npm:8.0.3" + "@midnight-ntwrk/midnight-js-contracts": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-level-private-state-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-network-id": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-types": "npm:4.0.2" + "@midnight-ntwrk/midnight-js-utils": "npm:4.0.2" + "@midnight-ntwrk/testkit-js": "npm:4.0.2" + "@midnight-ntwrk/wallet-sdk-address-format": "npm:3.1.0" + "@midnight-ntwrk/wallet-sdk-facade": "npm:3.0.0" + "@midnight-ntwrk/wallet-sdk-hd": "npm:3.0.1" + "@midnight-ntwrk/wallet-sdk-shielded": "npm:2.1.0" + "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "npm:2.1.0" + "@scure/bip39": "npm:^1.2.1" + "@tsconfig/node24": "npm:^24.0.3" + "@types/node": "npm:24.10.1" + axios: "npm:^1.12.0" + pino: "npm:^9.7.0" + rxjs: "npm:^7.8.1" + smol-toml: "npm:^1.3.4" + testcontainers: "npm:^10.28.0" + typescript: "npm:^5.9.3" + vitest: "npm:^4.0.15" + zod: "npm:^3.23.8" + languageName: unknown + linkType: soft + +"@openzeppelin/compact-simulator@workspace:packages/simulator": + version: 0.0.0-use.local + resolution: "@openzeppelin/compact-simulator@workspace:packages/simulator" + dependencies: + "@midnight-ntwrk/compact-runtime": "npm:0.14.0" + "@midnight-ntwrk/ledger-v7": "npm:^7.0.0" + "@tsconfig/node24": "npm:^24.0.3" + "@types/node": "npm:24.10.1" + fast-check: "npm:^4.5.2" + typescript: "npm:^5.8.2" + vitest: "npm:^4.0.15" + languageName: unknown + linkType: soft + +"@pinojs/redact@npm:^0.4.0": + version: 0.4.0 + resolution: "@pinojs/redact@npm:0.4.0" + checksum: 10/2210ffb6b38357853d47239fd0532cc9edb406325270a81c440a35cece22090127c30c2ead3eefa3e608f2244087485308e515c431f4f69b6bd2e16cbd32812b + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff + languageName: node + linkType: hard + +"@polkadot-api/json-rpc-provider-proxy@npm:^0.1.0": + version: 0.1.0 + resolution: "@polkadot-api/json-rpc-provider-proxy@npm:0.1.0" + checksum: 10/1a232337a4f6f32f3ec0350d5aaceaab21547ccee3cca63318d4b9238982efa5ff2406b033c320318c72d067b73508c0a1af21eb47acabaff714c1c21477bafa + languageName: node + linkType: hard + +"@polkadot-api/json-rpc-provider@npm:0.0.1, @polkadot-api/json-rpc-provider@npm:^0.0.1": + version: 0.0.1 + resolution: "@polkadot-api/json-rpc-provider@npm:0.0.1" + checksum: 10/1f315bdadcba7def7145011132e6127b983c6f91f976be217ad7d555bb96a67f3a270fe4a46e427531822c5d54d353d84a6439d112a99cdfc07013d3b662ee3c + languageName: node + linkType: hard + +"@polkadot-api/metadata-builders@npm:0.3.2": + version: 0.3.2 + resolution: "@polkadot-api/metadata-builders@npm:0.3.2" + dependencies: + "@polkadot-api/substrate-bindings": "npm:0.6.0" + "@polkadot-api/utils": "npm:0.1.0" + checksum: 10/874b38e1fb92beea99b98b889143f25671f137e54113767aeabb79ff5cdf7d61cadb0121f08c7a9a40718b924d7c9a1dd700f81e7e287bc55923b0129e2a6160 + languageName: node + linkType: hard + +"@polkadot-api/observable-client@npm:^0.3.0": + version: 0.3.2 + resolution: "@polkadot-api/observable-client@npm:0.3.2" + dependencies: + "@polkadot-api/metadata-builders": "npm:0.3.2" + "@polkadot-api/substrate-bindings": "npm:0.6.0" + "@polkadot-api/utils": "npm:0.1.0" + peerDependencies: + "@polkadot-api/substrate-client": 0.1.4 + rxjs: ">=7.8.0" + checksum: 10/91b95a06e3ddd477c2489110d7cffdcfaf87a222054b437013c701dc43eac6a5d30438b1ac8fb130166ba039a67808e6199ccb3b2eaac7dcf8d2ef7a835f047b + languageName: node + linkType: hard + +"@polkadot-api/substrate-bindings@npm:0.6.0": + version: 0.6.0 + resolution: "@polkadot-api/substrate-bindings@npm:0.6.0" + dependencies: + "@noble/hashes": "npm:^1.3.1" + "@polkadot-api/utils": "npm:0.1.0" + "@scure/base": "npm:^1.1.1" + scale-ts: "npm:^1.6.0" + checksum: 10/01926a9083f608514a55c3d23563ebef139e2963d4adbebe7dcd99b65e1a08f1551fc0e147e787a31c749402767333c96eb1399f85a6c71654cfa1cc9d26e445 + languageName: node + linkType: hard + +"@polkadot-api/substrate-client@npm:^0.1.2": + version: 0.1.4 + resolution: "@polkadot-api/substrate-client@npm:0.1.4" + dependencies: + "@polkadot-api/json-rpc-provider": "npm:0.0.1" + "@polkadot-api/utils": "npm:0.1.0" + checksum: 10/e7172696db404676d297cd5661b195de110593769f9ce37f32bdb5576ca00c56d32fcb04172a91102986fdda27a13962d909ad9466869a2991611d658ee6ac92 + languageName: node + linkType: hard + +"@polkadot-api/utils@npm:0.1.0": + version: 0.1.0 + resolution: "@polkadot-api/utils@npm:0.1.0" + checksum: 10/c557daea91ddb03e16b93c7c5a75533495c7b77cbbbdc2b4f5e97af0c1e1132a47e434c9c729a08241bd7b3624b6644ac0950f914aa8b29a0f419bf0fd224c7c + languageName: node + linkType: hard + +"@polkadot/api-augment@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/api-augment@npm:16.5.6" + dependencies: + "@polkadot/api-base": "npm:16.5.6" + "@polkadot/rpc-augment": "npm:16.5.6" + "@polkadot/types": "npm:16.5.6" + "@polkadot/types-augment": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + tslib: "npm:^2.8.1" + checksum: 10/155e90fb8b11ae9d6fc1db1108ddb231187764ab5f42f0b2dca0c0d2a5e8ac5f833a7a32cfb9f401dea4395b631af99354e312432b41973281358e7fa05c5a26 + languageName: node + linkType: hard + +"@polkadot/api-base@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/api-base@npm:16.5.6" + dependencies: + "@polkadot/rpc-core": "npm:16.5.6" + "@polkadot/types": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + rxjs: "npm:^7.8.1" + tslib: "npm:^2.8.1" + checksum: 10/28c238896a3150f3cd405c7d204992b70e9704b04075e7bee440b590701ed025f5baa5a25d81c7396aa0e2d77a63ed7c17a489451d758edd75183198b4552a69 + languageName: node + linkType: hard + +"@polkadot/api-derive@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/api-derive@npm:16.5.6" + dependencies: + "@polkadot/api": "npm:16.5.6" + "@polkadot/api-augment": "npm:16.5.6" + "@polkadot/api-base": "npm:16.5.6" + "@polkadot/rpc-core": "npm:16.5.6" + "@polkadot/types": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + "@polkadot/util-crypto": "npm:^14.0.3" + rxjs: "npm:^7.8.1" + tslib: "npm:^2.8.1" + checksum: 10/493be1bfa7807d6c39f8bef9569f1d5ae9e87e2330bd561a2dcf59a3bfec71c2cd260e33005c752d17a6e24195184e18db7a1a80309af9738bb0070a7f3b90db + languageName: node + linkType: hard + +"@polkadot/api@npm:16.5.6, @polkadot/api@npm:^16.5.4": + version: 16.5.6 + resolution: "@polkadot/api@npm:16.5.6" + dependencies: + "@polkadot/api-augment": "npm:16.5.6" + "@polkadot/api-base": "npm:16.5.6" + "@polkadot/api-derive": "npm:16.5.6" + "@polkadot/keyring": "npm:^14.0.3" + "@polkadot/rpc-augment": "npm:16.5.6" + "@polkadot/rpc-core": "npm:16.5.6" + "@polkadot/rpc-provider": "npm:16.5.6" + "@polkadot/types": "npm:16.5.6" + "@polkadot/types-augment": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/types-create": "npm:16.5.6" + "@polkadot/types-known": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + "@polkadot/util-crypto": "npm:^14.0.3" + eventemitter3: "npm:^5.0.1" + rxjs: "npm:^7.8.1" + tslib: "npm:^2.8.1" + checksum: 10/bfd3c7d8f4e69fa405eafcc437abfe7d69754301f280459c4665cc4bb2d55e62741967cd72bfbec15dbbacc343c261f9480e073fd5d534da24aabc013be0b7da + languageName: node + linkType: hard + +"@polkadot/keyring@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/keyring@npm:14.0.3" + dependencies: + "@polkadot/util": "npm:14.0.3" + "@polkadot/util-crypto": "npm:14.0.3" + tslib: "npm:^2.8.0" + peerDependencies: + "@polkadot/util": 14.0.3 + "@polkadot/util-crypto": 14.0.3 + checksum: 10/69f9f776363f8327d72b43794262ae709fc2824182637e499ed6e9ca94315645d78005bf1f25bdfb7305e5d79879cb932c114e6612467ddf21a760117834e8a2 + languageName: node + linkType: hard + +"@polkadot/networks@npm:14.0.3, @polkadot/networks@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/networks@npm:14.0.3" + dependencies: + "@polkadot/util": "npm:14.0.3" + "@substrate/ss58-registry": "npm:^1.51.0" + tslib: "npm:^2.8.0" + checksum: 10/eb006f537f103b0d417e52966d0098b528326d1ebbae84e4c7834627bb3e863b7b849856992aa58c4a0aeb0ed1e1838a9619aeba7610d0e7c75e99ffcc6c9ecd + languageName: node + linkType: hard + +"@polkadot/rpc-augment@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/rpc-augment@npm:16.5.6" + dependencies: + "@polkadot/rpc-core": "npm:16.5.6" + "@polkadot/types": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + tslib: "npm:^2.8.1" + checksum: 10/77abf8d1ced793a489a6b0888f190ac0d3b1fe03f310ec34f2f2dc5b646bd23606cf6dd93e660cb7383995931672a36e1e9ab642e9c8010d60fab83ccdd0ac42 + languageName: node + linkType: hard + +"@polkadot/rpc-core@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/rpc-core@npm:16.5.6" + dependencies: + "@polkadot/rpc-augment": "npm:16.5.6" + "@polkadot/rpc-provider": "npm:16.5.6" + "@polkadot/types": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + rxjs: "npm:^7.8.1" + tslib: "npm:^2.8.1" + checksum: 10/795d504e109367d1bf41f27e90b440968e06f5b86c1ef9e5806d98bd38036cc1dd5bbe9aeb539b1e81865d78a0957a22341b9397372c0e6b748cdc51ca79ea30 + languageName: node + linkType: hard + +"@polkadot/rpc-provider@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/rpc-provider@npm:16.5.6" + dependencies: + "@polkadot/keyring": "npm:^14.0.3" + "@polkadot/types": "npm:16.5.6" + "@polkadot/types-support": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + "@polkadot/util-crypto": "npm:^14.0.3" + "@polkadot/x-fetch": "npm:^14.0.3" + "@polkadot/x-global": "npm:^14.0.3" + "@polkadot/x-ws": "npm:^14.0.3" + "@substrate/connect": "npm:0.8.11" + eventemitter3: "npm:^5.0.1" + mock-socket: "npm:^9.3.1" + nock: "npm:^13.5.5" + tslib: "npm:^2.8.1" + dependenciesMeta: + "@substrate/connect": + optional: true + checksum: 10/06913cb6887652896a47aef6fef3cb811d9bed577a4d13c570baa0c8df401ecfcaec58f27d338d0d6c6319acbfc3b6a4b4a837679fae089dcec0bd1babd9e418 + languageName: node + linkType: hard + +"@polkadot/types-augment@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/types-augment@npm:16.5.6" + dependencies: + "@polkadot/types": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + tslib: "npm:^2.8.1" + checksum: 10/b2b300af0cac2394d1b95a907e25b1f78d3af7502186c6bc2f3eef51928c6638d6db8e55de57a6ddbef0b621d5d6a36311aefa1820f23d61bd86f3a6d20108c8 + languageName: node + linkType: hard + +"@polkadot/types-codec@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/types-codec@npm:16.5.6" + dependencies: + "@polkadot/util": "npm:^14.0.3" + "@polkadot/x-bigint": "npm:^14.0.3" + tslib: "npm:^2.8.1" + checksum: 10/80cd00315e19d5521732ee0c676444dbf7081ff056ccd070b665064cda0d364a7b434c39a23a68af89c20e2020b93ce281eef8d4a7db28161ce88ee92ce7dd07 + languageName: node + linkType: hard + +"@polkadot/types-create@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/types-create@npm:16.5.6" + dependencies: + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + tslib: "npm:^2.8.1" + checksum: 10/553c023d34fefdac5461cdc8c8d451a669dfbc15c2bd1f24b0836a68829ad06b5329487091a21bd7d557f76b2fb364a53f33a32f9da1ae8e3474a32f2da61127 + languageName: node + linkType: hard + +"@polkadot/types-known@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/types-known@npm:16.5.6" + dependencies: + "@polkadot/networks": "npm:^14.0.3" + "@polkadot/types": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/types-create": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + tslib: "npm:^2.8.1" + checksum: 10/6681e5189e0f16127379981c44d6abb35829e2731961ed6996c06bfc8c5f811fc26010f4213ea2e1f06c36b174576ef2f64f783bebd7e38c735cc06445ee557f + languageName: node + linkType: hard + +"@polkadot/types-support@npm:16.5.6": + version: 16.5.6 + resolution: "@polkadot/types-support@npm:16.5.6" + dependencies: + "@polkadot/util": "npm:^14.0.3" + tslib: "npm:^2.8.1" + checksum: 10/d43b902392af367adde8d9492161ca7a5ae6acc7d3c9b87e9633896b25d3ba783a96e5a00436a137e55c231d1465ae9c5d15472ec674051c917401106655de80 + languageName: node + linkType: hard + +"@polkadot/types@npm:16.5.6, @polkadot/types@npm:^16.5.4": + version: 16.5.6 + resolution: "@polkadot/types@npm:16.5.6" + dependencies: + "@polkadot/keyring": "npm:^14.0.3" + "@polkadot/types-augment": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/types-create": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + "@polkadot/util-crypto": "npm:^14.0.3" + rxjs: "npm:^7.8.1" + tslib: "npm:^2.8.1" + checksum: 10/85c3ad043d16216f9b49fbb613d17c0af70ba817f20c3fa287e0ff628d3a5338ce4e7505e74a59610f1eb0b4f26b2a8701c3f25c1e90f7c95f2e3bde1fc5391b + languageName: node + linkType: hard + +"@polkadot/util-crypto@npm:14.0.3, @polkadot/util-crypto@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/util-crypto@npm:14.0.3" + dependencies: + "@noble/curves": "npm:^1.3.0" + "@noble/hashes": "npm:^1.3.3" + "@polkadot/networks": "npm:14.0.3" + "@polkadot/util": "npm:14.0.3" + "@polkadot/wasm-crypto": "npm:^7.5.3" + "@polkadot/wasm-util": "npm:^7.5.3" + "@polkadot/x-bigint": "npm:14.0.3" + "@polkadot/x-randomvalues": "npm:14.0.3" + "@scure/base": "npm:^1.1.7" + "@scure/sr25519": "npm:^0.2.0" + tslib: "npm:^2.8.0" + peerDependencies: + "@polkadot/util": 14.0.3 + checksum: 10/e8f2da806cb81d3c014415bdd633f0fc5871132ce790ca892f65899010386d64fa25f7c047574cc96402afa03b5ff77e4dff904e69b90e714a7150e18ef0f507 + languageName: node + linkType: hard + +"@polkadot/util@npm:14.0.3, @polkadot/util@npm:^14.0.1, @polkadot/util@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/util@npm:14.0.3" + dependencies: + "@polkadot/x-bigint": "npm:14.0.3" + "@polkadot/x-global": "npm:14.0.3" + "@polkadot/x-textdecoder": "npm:14.0.3" + "@polkadot/x-textencoder": "npm:14.0.3" + "@types/bn.js": "npm:^5.1.6" + bn.js: "npm:^5.2.1" + tslib: "npm:^2.8.0" + checksum: 10/7731f26f363696a2e313fdd44d870d711924e8d24200e1c5e88769e02c220af99382460372caa1715511548753e1e3d5c1466a02308b0d4dec0700ec0ab4e88b + languageName: node + linkType: hard + +"@polkadot/wasm-bridge@npm:7.5.4": + version: 7.5.4 + resolution: "@polkadot/wasm-bridge@npm:7.5.4" + dependencies: + "@polkadot/wasm-util": "npm:7.5.4" + tslib: "npm:^2.7.0" + peerDependencies: + "@polkadot/util": "*" + "@polkadot/x-randomvalues": "*" + checksum: 10/64db5db90a82396032c31e6745b2e77817b8e9258841b72e506370ecf3ac63497efc654ca113419baf3c9b5fabda86bb21b29e1b508f192ab4e07beab8ef6d04 + languageName: node + linkType: hard + +"@polkadot/wasm-crypto-asmjs@npm:7.5.4": + version: 7.5.4 + resolution: "@polkadot/wasm-crypto-asmjs@npm:7.5.4" + dependencies: + tslib: "npm:^2.7.0" + peerDependencies: + "@polkadot/util": "*" + checksum: 10/9e03f052b871bc9e33268b01025fe43789f2af40e4aabbe3b7d8348a0752001cd137c20ba66c58ee7d692e798d957024c7cbd0cbf1a8cf3e6baebbe67696e781 + languageName: node + linkType: hard + +"@polkadot/wasm-crypto-init@npm:7.5.4": + version: 7.5.4 + resolution: "@polkadot/wasm-crypto-init@npm:7.5.4" + dependencies: + "@polkadot/wasm-bridge": "npm:7.5.4" + "@polkadot/wasm-crypto-asmjs": "npm:7.5.4" + "@polkadot/wasm-crypto-wasm": "npm:7.5.4" + "@polkadot/wasm-util": "npm:7.5.4" + tslib: "npm:^2.7.0" + peerDependencies: + "@polkadot/util": "*" + "@polkadot/x-randomvalues": "*" + checksum: 10/c1077a74156bd6356487043b23a849b214274c74fc44f1e2c203ec58f152c47c577f9da920ebf79ef746cfdfd2f246b1dd6a97c5796556f1c00e63d795eb896f + languageName: node + linkType: hard + +"@polkadot/wasm-crypto-wasm@npm:7.5.4": + version: 7.5.4 + resolution: "@polkadot/wasm-crypto-wasm@npm:7.5.4" + dependencies: + "@polkadot/wasm-util": "npm:7.5.4" + tslib: "npm:^2.7.0" + peerDependencies: + "@polkadot/util": "*" + checksum: 10/338b5d4b347116efa09aba7f27f1d13e84a4ef62680ab02e2c47bbd43180844434cf49f8c954528cbb8bebef69bdf101be33e3a6fe093efd3f5ab2245f5e7faf + languageName: node + linkType: hard + +"@polkadot/wasm-crypto@npm:^7.5.3": + version: 7.5.4 + resolution: "@polkadot/wasm-crypto@npm:7.5.4" + dependencies: + "@polkadot/wasm-bridge": "npm:7.5.4" + "@polkadot/wasm-crypto-asmjs": "npm:7.5.4" + "@polkadot/wasm-crypto-init": "npm:7.5.4" + "@polkadot/wasm-crypto-wasm": "npm:7.5.4" + "@polkadot/wasm-util": "npm:7.5.4" + tslib: "npm:^2.7.0" + peerDependencies: + "@polkadot/util": "*" + "@polkadot/x-randomvalues": "*" + checksum: 10/d4edce7bc9e8fa8387abe1d3fa4433937ab40faf4889a949a5a64c42f852837e3da96c00a73fb383fc8ef3fe177ac40dc85a13bcd43b059f2d04bab52f537801 + languageName: node + linkType: hard + +"@polkadot/wasm-util@npm:7.5.4, @polkadot/wasm-util@npm:^7.5.3": + version: 7.5.4 + resolution: "@polkadot/wasm-util@npm:7.5.4" + dependencies: + tslib: "npm:^2.7.0" + peerDependencies: + "@polkadot/util": "*" + checksum: 10/4dda837f3ac84705d709a2e62fc0f9ec54518dbae88d3bf9dc68b65f17f50eadf7fff4289f3deaf51f93d79d5ac0631ecf57ad572d55f98a11149beaa3b2bcc4 + languageName: node + linkType: hard + +"@polkadot/x-bigint@npm:14.0.3, @polkadot/x-bigint@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/x-bigint@npm:14.0.3" + dependencies: + "@polkadot/x-global": "npm:14.0.3" + tslib: "npm:^2.8.0" + checksum: 10/82017c7046c9d65af15cead3ebbaea08e07992e7fb081f7cc9175dae61988a0a352d923da57da5ee86fb8d671ab5449f6e630798b889002ea8b899d7e3d1b5d3 + languageName: node + linkType: hard + +"@polkadot/x-fetch@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/x-fetch@npm:14.0.3" + dependencies: + "@polkadot/x-global": "npm:14.0.3" + node-fetch: "npm:^3.3.2" + tslib: "npm:^2.8.0" + checksum: 10/cf9add8a351d8021ea9728ea648ad34d3244de2848cf90cb08037d73b16b63251577beb4590669dcff1bd1f64c99b62cb059831b333ea07a047bc0b33f79a0e7 + languageName: node + linkType: hard + +"@polkadot/x-global@npm:14.0.3, @polkadot/x-global@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/x-global@npm:14.0.3" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10/5d75b2097ae7f279efdc49c02e7f4deb5ffa131250f25439bcf7f1a334e3ae525467520521424cca62a198f396ee9f5c321f591cb9b55f1b2aeaf69cd129c829 + languageName: node + linkType: hard + +"@polkadot/x-randomvalues@npm:14.0.3": + version: 14.0.3 + resolution: "@polkadot/x-randomvalues@npm:14.0.3" + dependencies: + "@polkadot/x-global": "npm:14.0.3" + tslib: "npm:^2.8.0" + peerDependencies: + "@polkadot/util": 14.0.3 + "@polkadot/wasm-util": "*" + checksum: 10/03aa905b34f2eefc038d1a8edaf41a631aef36e229235d40d965a460ca127c027753bad0954ca889967877ba7d13d1fc5b49dc86d6637c1f98596c9ad600cb04 + languageName: node + linkType: hard + +"@polkadot/x-textdecoder@npm:14.0.3": + version: 14.0.3 + resolution: "@polkadot/x-textdecoder@npm:14.0.3" + dependencies: + "@polkadot/x-global": "npm:14.0.3" + tslib: "npm:^2.8.0" + checksum: 10/3ec2210f9d3b0f5cab0a2b39575dd3d0393aed141e8cb9cc743573b17ea201d08c6f28aebc6acafd9eae9362ad6b223091486131a53409b684a3ddecbce19250 + languageName: node + linkType: hard + +"@polkadot/x-textencoder@npm:14.0.3": + version: 14.0.3 + resolution: "@polkadot/x-textencoder@npm:14.0.3" + dependencies: + "@polkadot/x-global": "npm:14.0.3" + tslib: "npm:^2.8.0" + checksum: 10/541fd458433e153683ac41e8d6c060a2e46dd29ff5638abf992dd5ea7838a3514b4ee1d9ca11d50b384d3d001fb1347f01e176531cca10bfc4840b4736cdd474 + languageName: node + linkType: hard + +"@polkadot/x-ws@npm:^14.0.3": + version: 14.0.3 + resolution: "@polkadot/x-ws@npm:14.0.3" + dependencies: + "@polkadot/x-global": "npm:14.0.3" + tslib: "npm:^2.8.0" + ws: "npm:^8.18.0" + checksum: 10/c66b7f9c5857884ec94abe5796372816d1029e2f81078f026eef12456ef0971f59e2d678fec347f3bdf6f755834a41074b4b6177f10ec2a7b56a19d35825ac8b + languageName: node + linkType: hard + +"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/aspromise@npm:1.1.2" + checksum: 10/8a938d84fe4889411296db66b29287bd61ea3c14c2d23e7a8325f46a2b8ce899857c5f038d65d7641805e6c1d06b495525c7faf00c44f85a7ee6476649034969 + languageName: node + linkType: hard + +"@protobufjs/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/base64@npm:1.1.2" + checksum: 10/c71b100daeb3c9bdccab5cbc29495b906ba0ae22ceedc200e1ba49717d9c4ab15a6256839cebb6f9c6acae4ed7c25c67e0a95e734f612b258261d1a3098fe342 + languageName: node + linkType: hard + +"@protobufjs/codegen@npm:^2.0.5": + version: 2.0.5 + resolution: "@protobufjs/codegen@npm:2.0.5" + checksum: 10/290335fa114f26202abc0695f279d53e2fd516b01cfd8298923591e0bda011295ff40e3582a1cda0a0f27cbc5039a0292082d5ad08872bb5d6243a614ac15c88 + languageName: node + linkType: hard + +"@protobufjs/eventemitter@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/eventemitter@npm:1.1.0" + checksum: 10/03af3e99f17ad421283d054c88a06a30a615922a817741b43ca1b13e7c6b37820a37f6eba9980fb5150c54dba6e26cb6f7b64a6f7d8afa83596fafb3afa218c3 + languageName: node + linkType: hard + +"@protobufjs/fetch@npm:^1.1.1": + version: 1.1.1 + resolution: "@protobufjs/fetch@npm:1.1.1" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.1" + checksum: 10/427cf2da8c69b494b0df3b2fb1f43c97f0f71ca2c8ef8232dac7e44f2527ad0cc9cecb243eda14a918e86018bfa6d54d92252240d2b37ed205b13adb5506fa1d + languageName: node + linkType: hard + +"@protobufjs/float@npm:^1.0.2": + version: 1.0.2 + resolution: "@protobufjs/float@npm:1.0.2" + checksum: 10/634c2c989da0ef2f4f19373d64187e2a79f598c5fb7991afb689d29a2ea17c14b796b29725945fa34b9493c17fb799e08ac0a7ccaae460ee1757d3083ed35187 + languageName: node + linkType: hard + +"@protobufjs/inquire@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/inquire@npm:1.1.2" + checksum: 10/259756489c75a751552df60d18f82503d2534855646397b96b91cf15807fa852e99bd9eb73dabb64da37aec7913844032ecb031a4326d82aae622f5e4c2f8a17 + languageName: node + linkType: hard + +"@protobufjs/path@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/path@npm:1.1.2" + checksum: 10/bb709567935fd385a86ad1f575aea98131bbd719c743fb9b6edd6b47ede429ff71a801cecbd64fc72deebf4e08b8f1bd8062793178cdaed3713b8d15771f9b83 + languageName: node + linkType: hard + +"@protobufjs/pool@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/pool@npm:1.1.0" + checksum: 10/b9c7047647f6af28e92aac54f6f7c1f7ff31b201b4bfcc7a415b2861528854fce3ec666d7e7e10fd744da905f7d4aef2205bbcc8944ca0ca7a82e18134d00c46 + languageName: node + linkType: hard + +"@protobufjs/utf8@npm:^1.1.1": + version: 1.1.1 + resolution: "@protobufjs/utf8@npm:1.1.1" + checksum: 10/ed0c3f9ff1afd602a0aed54c4c03a0b8f641686a5587d8949e088dcac653fb2019d15691ed92eef23dfdf9f4293249532d0508ecd15cef810acf026917719a19 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.60.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-android-arm64@npm:4.60.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-darwin-arm64@npm:4.60.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-darwin-x64@npm:4.60.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.60.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-freebsd-x64@npm:4.60.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.60.3" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.60.3" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.60.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.60.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.60.3" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.60.3" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.60.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.60.3" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.60.3" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.60.3" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.60.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.60.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.60.3": + version: 4.60.3 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.60.3" + conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -610,6 +1854,61 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:2.2.0, @scure/base@npm:^2.0.0": + version: 2.2.0 + resolution: "@scure/base@npm:2.2.0" + checksum: 10/b52ec9cd54bad77e22f881b6924ccab692dc1c6dd10287d1787bf263e9f1e560d6d2bda906538fb9a39615d61a1b5c2f53f57a511667fd10e93b9cdaa6fb5d2a + languageName: node + linkType: hard + +"@scure/base@npm:^1.1.1, @scure/base@npm:^1.1.7, @scure/base@npm:~1.2.5": + version: 1.2.6 + resolution: "@scure/base@npm:1.2.6" + checksum: 10/c1a7bd5e0b0c8f94c36fbc220f4a67cc832b00e2d2065c7d8a404ed81ab1c94c5443def6d361a70fc382db3496e9487fb9941728f0584782b274c18a4bed4187 + languageName: node + linkType: hard + +"@scure/bip32@npm:^2.0.1": + version: 2.2.0 + resolution: "@scure/bip32@npm:2.2.0" + dependencies: + "@noble/curves": "npm:2.2.0" + "@noble/hashes": "npm:2.2.0" + "@scure/base": "npm:2.2.0" + checksum: 10/595875bdfdd153621a35d71b73bb77e1406b5d659bbd20fc4db3fed697d72d39a62c8a6b2bb9816ce4e50199200252008ae203cd637f3acf1e0821180755cd3d + languageName: node + linkType: hard + +"@scure/bip39@npm:^1.2.1": + version: 1.6.0 + resolution: "@scure/bip39@npm:1.6.0" + dependencies: + "@noble/hashes": "npm:~1.8.0" + "@scure/base": "npm:~1.2.5" + checksum: 10/63e60c40fa1bda2c1b50351546fee6d7b0947cc814aa7a4209dcedd3693b5053302c8fca28292f5f50735e11c613265359acdc019127393dbab17e53489fc449 + languageName: node + linkType: hard + +"@scure/bip39@npm:^2.0.1": + version: 2.2.0 + resolution: "@scure/bip39@npm:2.2.0" + dependencies: + "@noble/hashes": "npm:2.2.0" + "@scure/base": "npm:2.2.0" + checksum: 10/f8f05c9f1337f694e1b490dcc795ac0da87e3cb4e5377889c19caa910c46567aa6b4071f2fc102fffb76020c221e09ffe9e1dde471728224335713c55cbfb182 + languageName: node + linkType: hard + +"@scure/sr25519@npm:^0.2.0": + version: 0.2.0 + resolution: "@scure/sr25519@npm:0.2.0" + dependencies: + "@noble/curves": "npm:~1.9.2" + "@noble/hashes": "npm:~1.8.0" + checksum: 10/3c47b474811642b43fd8c96f7846c9d88c9a06eefa7d6360b6421ebdfb6cf582e1e8fdce9ae4708b088a0e323cd6519c883c3a33a284c2fad592414b02f19049 + languageName: node + linkType: hard + "@standard-schema/spec@npm:^1.0.0": version: 1.0.0 resolution: "@standard-schema/spec@npm:1.0.0" @@ -617,6 +1916,82 @@ __metadata: languageName: node linkType: hard +"@subsquid/scale-codec@npm:^4.0.1": + version: 4.0.1 + resolution: "@subsquid/scale-codec@npm:4.0.1" + dependencies: + "@subsquid/util-internal-hex": "npm:^1.2.2" + "@subsquid/util-internal-json": "npm:^1.2.2" + checksum: 10/d0c81f43c6c93d6885baa0992dd170c94e8259b2eb500694b62b8ca25624c78bb7e4815b1120bbb7f3ed0e7eda02cd02233e1d8b5bac903322731ff3c9fb42bc + languageName: node + linkType: hard + +"@subsquid/util-internal-hex@npm:^1.2.2": + version: 1.2.3 + resolution: "@subsquid/util-internal-hex@npm:1.2.3" + checksum: 10/d3feeb16e130d7a5281bbd98c0ddc9a44d3c49f2655766d4e97d16407c8466b3b246bbefecfb397580f2402dc62b45065c8e62ce986b14935246b1252e66d347 + languageName: node + linkType: hard + +"@subsquid/util-internal-json@npm:^1.2.2": + version: 1.2.3 + resolution: "@subsquid/util-internal-json@npm:1.2.3" + dependencies: + "@subsquid/util-internal-hex": "npm:^1.2.2" + checksum: 10/9a518c8fc56066778b0535ed243024e17f958d9020d99d5444657fd877d7da3adc1f34b3f0e621cb8365729bc9e10aeb63bb24b91e579eb413ef8cbbab66c81d + languageName: node + linkType: hard + +"@substrate/connect-extension-protocol@npm:^2.0.0": + version: 2.2.2 + resolution: "@substrate/connect-extension-protocol@npm:2.2.2" + checksum: 10/b5427526dafcbd0ec45d3ce7ef7a3d1018496cae7d8ef60f545d4e143420b3e51fe37af966f493e73f4cb9383bc78af756cdc19294e633240c8a86c620b3d8b5 + languageName: node + linkType: hard + +"@substrate/connect-known-chains@npm:^1.1.5": + version: 1.10.3 + resolution: "@substrate/connect-known-chains@npm:1.10.3" + checksum: 10/b0b4e2914a9c8c0576196ff78f7d0a1ccaf3ee2a02f0b710ee5e79153fdcd4be36e5b7a58998ea72d13f9251dc13d448967114da14efc6aa1891eda284d066bb + languageName: node + linkType: hard + +"@substrate/connect@npm:0.8.11": + version: 0.8.11 + resolution: "@substrate/connect@npm:0.8.11" + dependencies: + "@substrate/connect-extension-protocol": "npm:^2.0.0" + "@substrate/connect-known-chains": "npm:^1.1.5" + "@substrate/light-client-extension-helpers": "npm:^1.0.0" + smoldot: "npm:2.0.26" + checksum: 10/380ba85aa3aec4439fae2ee42173376615ca60262d9c37e6e43d1d65d0d0f63f38c009bb476e9a612b0b9985c1b5808c4d9a75aff9e1828c77e75c8b7584d824 + languageName: node + linkType: hard + +"@substrate/light-client-extension-helpers@npm:^1.0.0": + version: 1.0.0 + resolution: "@substrate/light-client-extension-helpers@npm:1.0.0" + dependencies: + "@polkadot-api/json-rpc-provider": "npm:^0.0.1" + "@polkadot-api/json-rpc-provider-proxy": "npm:^0.1.0" + "@polkadot-api/observable-client": "npm:^0.3.0" + "@polkadot-api/substrate-client": "npm:^0.1.2" + "@substrate/connect-extension-protocol": "npm:^2.0.0" + "@substrate/connect-known-chains": "npm:^1.1.5" + rxjs: "npm:^7.8.1" + peerDependencies: + smoldot: 2.x + checksum: 10/ca0726e8271aa9eb4f1edbb13e7f6986d45c9a4ae9a73a1a14aa9a41552821ca291a33459b7e8fc1ec1bde1ead9336a8bca4fb8781c060d5cbdd7e59ca96cb2d + languageName: node + linkType: hard + +"@substrate/ss58-registry@npm:^1.51.0": + version: 1.51.0 + resolution: "@substrate/ss58-registry@npm:1.51.0" + checksum: 10/34eb21292f543a8be7c62ad3bcdae89d61c8a51e35a0be4687b6b4e955b5180a90a7691a9e6779f7509f8dfcfdfa372d8278087a9668521b9c501adb85c915b6 + languageName: node + linkType: hard + "@tsconfig/node10@npm:^1.0.7": version: 1.0.11 resolution: "@tsconfig/node10@npm:1.0.11" @@ -652,6 +2027,15 @@ __metadata: languageName: node linkType: hard +"@types/bn.js@npm:^5.1.6, @types/bn.js@npm:^5.2.0": + version: 5.2.0 + resolution: "@types/bn.js@npm:5.2.0" + dependencies: + "@types/node": "npm:*" + checksum: 10/06c93841f74e4a5e5b81b74427d56303b223c9af36389b4cd3c562bda93f43c425c7e241aee1b0b881dde57238dc2e07f21d30d412b206a7dae4435af4c054e8 + languageName: node + linkType: hard + "@types/chai@npm:^5.2.2": version: 5.2.3 resolution: "@types/chai@npm:5.2.3" @@ -669,6 +2053,27 @@ __metadata: languageName: node linkType: hard +"@types/docker-modem@npm:*": + version: 3.0.6 + resolution: "@types/docker-modem@npm:3.0.6" + dependencies: + "@types/node": "npm:*" + "@types/ssh2": "npm:*" + checksum: 10/cc58e8189f6ec5a2b8ca890207402178a97ddac8c80d125dc65d8ab29034b5db736de15e99b91b2d74e66d14e26e73b6b8b33216613dd15fd3aa6b82c11a83ed + languageName: node + linkType: hard + +"@types/dockerode@npm:^3.3.35": + version: 3.3.47 + resolution: "@types/dockerode@npm:3.3.47" + dependencies: + "@types/docker-modem": "npm:*" + "@types/node": "npm:*" + "@types/ssh2": "npm:*" + checksum: 10/b840ae7872398a3b02e5789006a69d0cf5bb7ec6c0eb714c7ca04ca093add8de4cd06204ecd8f01388e347e62927cf4c599e8b7dba53e81c1350910da766d517 + languageName: node + linkType: hard + "@types/estree@npm:1.0.8, @types/estree@npm:^1.0.0": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" @@ -676,6 +2081,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:*, @types/node@npm:>=13.7.0": + version: 25.9.0 + resolution: "@types/node@npm:25.9.0" + dependencies: + undici-types: "npm:>=7.24.0 <7.24.7" + checksum: 10/8725e4e3191ba81626b322cfb80b62064c687d5da2983d7318068069f940a9c019e6f342a674ccc4ad26ef6f0a5dcbc7451a81610155ca2c6d5202800b144a19 + languageName: node + linkType: hard + "@types/node@npm:24.10.1": version: 24.10.1 resolution: "@types/node@npm:24.10.1" @@ -685,6 +2099,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^18.11.18": + version: 18.19.130 + resolution: "@types/node@npm:18.19.130" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10/ebb85c6edcec78df926de27d828ecbeb1b3d77c165ceef95bfc26e171edbc1924245db4eb2d7d6230206fe6b1a1f7665714fe1c70739e9f5980d8ce31af6ef82 + languageName: node + linkType: hard + "@types/object-inspect@npm:^1.8.1": version: 1.13.0 resolution: "@types/object-inspect@npm:1.13.0" @@ -699,6 +2122,43 @@ __metadata: languageName: node linkType: hard +"@types/ssh2-streams@npm:*": + version: 0.1.13 + resolution: "@types/ssh2-streams@npm:0.1.13" + dependencies: + "@types/node": "npm:*" + checksum: 10/182c9de8384e11fcfed04e447c3c1d37f898ed4e7f0be0cc58b3bd5b23e22957c17939b68f709092cece758a4befa92913dd967115f643fa0e2dc629fc2e2383 + languageName: node + linkType: hard + +"@types/ssh2@npm:*": + version: 1.15.5 + resolution: "@types/ssh2@npm:1.15.5" + dependencies: + "@types/node": "npm:^18.11.18" + checksum: 10/dd6f29f4e96ea43aa61d29a4a3ad87ad8d11bf1bef637b2848958abd94b05d28754cc611eac13f52d43bd1f51afe7c660cd1c8533ae06878b5739888f4ea0d99 + languageName: node + linkType: hard + +"@types/ssh2@npm:^0.5.48": + version: 0.5.52 + resolution: "@types/ssh2@npm:0.5.52" + dependencies: + "@types/node": "npm:*" + "@types/ssh2-streams": "npm:*" + checksum: 10/fc2584af091da49da9d6628dd8a5e851b217bb9b1b732b0361903894f2730ab3fdf8634f954be34c5a513f7eb0b2772d059d64062bcf6b4a0eb73bfc83c4b858 + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.10": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" + dependencies: + "@types/node": "npm:*" + checksum: 10/1ce05e3174dcacf28dae0e9b854ef1c9a12da44c7ed73617ab6897c5cbe4fccbb155a20be5508ae9a7dde2f83bd80f5cf3baa386b934fc4b40889ec963e94f3a + languageName: node + linkType: hard + "@vitest/expect@npm:4.0.15": version: 4.0.15 resolution: "@vitest/expect@npm:4.0.15" @@ -755,27 +2215,63 @@ __metadata: version: 4.0.15 resolution: "@vitest/snapshot@npm:4.0.15" dependencies: - "@vitest/pretty-format": "npm:4.0.15" - magic-string: "npm:^0.30.21" - pathe: "npm:^2.0.3" - checksum: 10/f881257fc1c520541131296f9762d627ad61eb167a3d7129942a5c2dce46e870af1a8446fbf94d2fcdc5a31ab787ffff113f2b8dbd75b15d0494fe43db649682 + "@vitest/pretty-format": "npm:4.0.15" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10/f881257fc1c520541131296f9762d627ad61eb167a3d7129942a5c2dce46e870af1a8446fbf94d2fcdc5a31ab787ffff113f2b8dbd75b15d0494fe43db649682 + languageName: node + linkType: hard + +"@vitest/spy@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/spy@npm:4.0.15" + checksum: 10/700b06beb4fd33c1430bc5061e7c3055df9ad1e64500a0a02edba6a52e37ba3bf800eadfda1f617e1eeca53d7ab6941a69ba2812980347fcc3c3b736c5ae5a56 + languageName: node + linkType: hard + +"@vitest/utils@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/utils@npm:4.0.15" + dependencies: + "@vitest/pretty-format": "npm:4.0.15" + tinyrainbow: "npm:^3.0.3" + checksum: 10/54d3fd272e05ad43913d842a25dce705eb71db8591511f28fa4a6d0c28fd5eb109c580072e9f8dbc0f431425c890b74494c9d0b14f78d0be18ab87071f06d020 + languageName: node + linkType: hard + +"@wry/caches@npm:^1.0.0": + version: 1.0.1 + resolution: "@wry/caches@npm:1.0.1" + dependencies: + tslib: "npm:^2.3.0" + checksum: 10/055f592ee52b5fd9aa86e274e54e4a8b2650f619000bf6f61880ce14aaf47eb2ab34f3ada2eab964fe8b2f19bf8097ecacddcea4638fcc64c3d3a0a512aaa07c + languageName: node + linkType: hard + +"@wry/context@npm:^0.7.0": + version: 0.7.4 + resolution: "@wry/context@npm:0.7.4" + dependencies: + tslib: "npm:^2.3.0" + checksum: 10/70d648949a97a035b2be2d6ddb716d4162113e850ab2c4c86331b2da94a7e826204080ce04eee2a95665bd3a0b245bf2ea3aae9adfa57b004ae0d2d49bdb5c8f languageName: node linkType: hard -"@vitest/spy@npm:4.0.15": - version: 4.0.15 - resolution: "@vitest/spy@npm:4.0.15" - checksum: 10/700b06beb4fd33c1430bc5061e7c3055df9ad1e64500a0a02edba6a52e37ba3bf800eadfda1f617e1eeca53d7ab6941a69ba2812980347fcc3c3b736c5ae5a56 +"@wry/equality@npm:^0.5.6": + version: 0.5.7 + resolution: "@wry/equality@npm:0.5.7" + dependencies: + tslib: "npm:^2.3.0" + checksum: 10/69dccf33c0c41fd7ec5550f5703b857c6484a949412ad747001da941270ea436648c3ab988a2091765304249585ac30c7b417fad8be9a7ce19c1221f71548e35 languageName: node linkType: hard -"@vitest/utils@npm:4.0.15": - version: 4.0.15 - resolution: "@vitest/utils@npm:4.0.15" +"@wry/trie@npm:^0.5.0": + version: 0.5.0 + resolution: "@wry/trie@npm:0.5.0" dependencies: - "@vitest/pretty-format": "npm:4.0.15" - tinyrainbow: "npm:^3.0.3" - checksum: 10/54d3fd272e05ad43913d842a25dce705eb71db8591511f28fa4a6d0c28fd5eb109c580072e9f8dbc0f431425c890b74494c9d0b14f78d0be18ab87071f06d020 + tslib: "npm:^2.3.0" + checksum: 10/578a08f3a96256c9b163230337183d9511fd775bdfe147a30561ccaacedc9ce33b9731ee6e591bb1f5f53e41b26789e519b47dff5100c7bf4e1cd2df3062f797 languageName: node linkType: hard @@ -786,6 +2282,29 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 10/ed84af329f1828327798229578b4fe03a4dd2596ba304083ebd2252666bdc1d7647d66d0b18704477e1f8aa315f055944aa6e859afebd341f12d0a53c37b4b40 + languageName: node + linkType: hard + +"abstract-level@npm:^3.0.0, abstract-level@npm:^3.1.0": + version: 3.1.1 + resolution: "abstract-level@npm:3.1.1" + dependencies: + buffer: "npm:^6.0.3" + is-buffer: "npm:^2.0.5" + level-supports: "npm:^6.2.0" + level-transcoder: "npm:^1.0.1" + maybe-combine-errors: "npm:^1.0.0" + module-error: "npm:^1.0.1" + checksum: 10/1a4d19efac7a8781972aa5e8a57dce39b3ada75a15c1ee25c8dce5978d72b5f9e2bc8d7fbfabafdc49b5941c5b1913465331864b3061fd0d0ed351a397624b46 + languageName: node + linkType: hard + "acorn-walk@npm:^8.1.1": version: 8.3.4 resolution: "acorn-walk@npm:8.3.4" @@ -804,6 +2323,15 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10/21fb903e0917e5cb16591b4d0ef6a028a54b83ac30cd1fca58dece3d4e0990512a8723f9f83130d88a41e2af8b1f7be1386fda3ea2d181bb1a62155e75e95e23 + languageName: node + linkType: hard + "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.4 resolution: "agent-base@npm:7.1.4" @@ -841,6 +2369,36 @@ __metadata: languageName: node linkType: hard +"archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2": + version: 5.0.2 + resolution: "archiver-utils@npm:5.0.2" + dependencies: + glob: "npm:^10.0.0" + graceful-fs: "npm:^4.2.0" + is-stream: "npm:^2.0.1" + lazystream: "npm:^1.0.0" + lodash: "npm:^4.17.15" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^4.0.0" + checksum: 10/9dde4aa3f0cb1bdfe0b3d4c969f82e6cca9ae76338b7fee6f0071a14a2a38c0cdd1c41ecd3e362466585aa6cc5d07e9e435abea8c94fd9c7ace35f184abef9e4 + languageName: node + linkType: hard + +"archiver@npm:^7.0.1": + version: 7.0.1 + resolution: "archiver@npm:7.0.1" + dependencies: + archiver-utils: "npm:^5.0.2" + async: "npm:^3.2.4" + buffer-crc32: "npm:^1.0.0" + readable-stream: "npm:^4.0.0" + readdir-glob: "npm:^1.1.2" + tar-stream: "npm:^3.0.0" + zip-stream: "npm:^6.0.1" + checksum: 10/81c6102db99d7ffd5cb2aed02a678f551c6603991a059ca66ef59249942b835a651a3d3b5240af4f8bec4e61e13790357c9d1ad4a99982bd2cc4149575c31d67 + languageName: node + linkType: hard + "arg@npm:^4.1.0": version: 4.1.3 resolution: "arg@npm:4.1.3" @@ -848,6 +2406,15 @@ __metadata: languageName: node linkType: hard +"asn1@npm:^0.2.6": + version: 0.2.6 + resolution: "asn1@npm:0.2.6" + dependencies: + safer-buffer: "npm:~2.1.0" + checksum: 10/cf629291fee6c1a6f530549939433ebf32200d7849f38b810ff26ee74235e845c0c12b2ed0f1607ac17383d19b219b69cefa009b920dab57924c5c544e495078 + languageName: node + linkType: hard + "assertion-error@npm:^2.0.1": version: 2.0.1 resolution: "assertion-error@npm:2.0.1" @@ -855,6 +2422,72 @@ __metadata: languageName: node linkType: hard +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10/3d49e7acbeee9e84537f4cb0e0f91893df8eba976759875ae8ee9e3d3c82f6ecdebdb347c2fad9926b92596d93cdfc78ecc988bcdf407e40433e8e8e6fe5d78e + languageName: node + linkType: hard + +"async-lock@npm:^1.4.1": + version: 1.4.1 + resolution: "async-lock@npm:1.4.1" + checksum: 10/80d55ac95f920e880a865968b799963014f6d987dd790dd08173fae6e1af509d8cd0ab45a25daaca82e3ef8e7c939f5d128cd1facfcc5c647da8ac2409e20ef9 + languageName: node + linkType: hard + +"async@npm:^3.2.4": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10/3ce727cbc78f69d6a4722517a58ee926c8c21083633b1d3fdf66fd688f6c127a53a592141bd4866f9b63240a86e9d8e974b13919450bd17fa33c2d22c4558ad8 + languageName: node + linkType: hard + +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 10/3ab6d2cf46b31394b4607e935ec5c1c3c4f60f3e30f0913d35ea74b51b3585e84f590d09e58067f11762eec71c87d25314ce859030983dc0e4397eed21daa12e + languageName: node + linkType: hard + +"axios@npm:^1.12.0": + version: 1.16.1 + resolution: "axios@npm:1.16.1" + dependencies: + follow-redirects: "npm:^1.16.0" + form-data: "npm:^4.0.5" + https-proxy-agent: "npm:^5.0.1" + proxy-from-env: "npm:^2.1.0" + checksum: 10/9b6218cf96321cfbbf8f160658d695367114bcf4fb62492bdc1ccd647f184b5c71ae400e5ecaaf41079bc561de2ecbaf1fec63f398b3ec53389beff7694df64c + languageName: node + linkType: hard + +"b4a@npm:^1.6.4": + version: 1.8.1 + resolution: "b4a@npm:1.8.1" + peerDependencies: + react-native-b4a: "*" + peerDependenciesMeta: + react-native-b4a: + optional: true + checksum: 10/8536650b525f9f916e8fff9f5976fbeba2fc3238f047cad52e91073cf9825306ce7a68d0077ba2d06e3d20c95b445dccc2ab97ed45773331244d82251329cf8d + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -862,7 +2495,117 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^2.0.2": +"bare-events@npm:^2.5.4, bare-events@npm:^2.7.0": + version: 2.8.3 + resolution: "bare-events@npm:2.8.3" + peerDependencies: + bare-abort-controller: "*" + peerDependenciesMeta: + bare-abort-controller: + optional: true + checksum: 10/704252793362d4a422959f3b5d134a3f893f020b515cccf55965c8076941d6e7fd8c23268560693f2300270378a00384156237e4390edda2d4ca0e641bfe774e + languageName: node + linkType: hard + +"bare-fs@npm:^4.0.1, bare-fs@npm:^4.5.5": + version: 4.7.1 + resolution: "bare-fs@npm:4.7.1" + dependencies: + bare-events: "npm:^2.5.4" + bare-path: "npm:^3.0.0" + bare-stream: "npm:^2.6.4" + bare-url: "npm:^2.2.2" + fast-fifo: "npm:^1.3.2" + peerDependencies: + bare-buffer: "*" + peerDependenciesMeta: + bare-buffer: + optional: true + checksum: 10/bb873bf8d22c45fd14444b0f9731315a77b696c9387b09cc0df9975b998d1b5db9f4c88aa4b264ce59edeade573689ba9e0ba172003cc8900b2c2ad803f9275b + languageName: node + linkType: hard + +"bare-os@npm:^3.0.1": + version: 3.9.1 + resolution: "bare-os@npm:3.9.1" + checksum: 10/2a106aca9eeb1cf41e30403410c9fa81a9e13c25818debc21444f2485158e01e65f10daff37acab0cbf9460c00e64e6bcaedef07b25a9171ec1e45485213ff50 + languageName: node + linkType: hard + +"bare-path@npm:^3.0.0": + version: 3.0.0 + resolution: "bare-path@npm:3.0.0" + dependencies: + bare-os: "npm:^3.0.1" + checksum: 10/712d90e9cd8c3263cc11b0e0d386d1531a452706d7840c081ee586b34b00d72544e65df7a40013d47c1b177277495225deeede65cb2984db88a979cb65aaa2ff + languageName: node + linkType: hard + +"bare-stream@npm:^2.6.4": + version: 2.13.1 + resolution: "bare-stream@npm:2.13.1" + dependencies: + streamx: "npm:^2.25.0" + teex: "npm:^1.0.1" + peerDependencies: + bare-abort-controller: "*" + bare-buffer: "*" + bare-events: "*" + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + checksum: 10/50aa90a7005d71c1af8fafcc84f378bd4d7c2dd293a581ffe3899bee39b0d2eb07c47e1092f581fa5b199a63c0ad2618b150c0ab716658727e3fcc7fd7d1e401 + languageName: node + linkType: hard + +"bare-url@npm:^2.2.2": + version: 2.4.3 + resolution: "bare-url@npm:2.4.3" + dependencies: + bare-path: "npm:^3.0.0" + checksum: 10/e2c16dd57e0c4b974813d9acd626b96e83a8894e19b0bf780de4bef40a7000c697984a47c398c8f612aa7991974bfb97f1c3c3fd410085a55fa5db15d1ba6309 + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10/669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 + languageName: node + linkType: hard + +"bcrypt-pbkdf@npm:^1.0.2": + version: 1.0.2 + resolution: "bcrypt-pbkdf@npm:1.0.2" + dependencies: + tweetnacl: "npm:^0.14.3" + checksum: 10/13a4cde058250dbf1fa77a4f1b9a07d32ae2e3b9e28e88a0c7a1827835bc3482f3e478c4a0cfd4da6ff0c46dae07da1061123a995372b32cc563d9975f975404 + languageName: node + linkType: hard + +"bl@npm:^4.0.3": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10/b7904e66ed0bdfc813c06ea6c3e35eafecb104369dbf5356d0f416af90c1546de3b74e5b63506f0629acf5e16a6f87c3798f16233dcff086e9129383aa02ab55 + languageName: node + linkType: hard + +"bn.js@npm:^5.2.1, bn.js@npm:^5.2.3": + version: 5.2.3 + resolution: "bn.js@npm:5.2.3" + checksum: 10/dfb3927e0d531e6ec4f191597ce6f7f7665310c356fef5f968ada676b8058027f959af42eaa37b5f5c63617e819d3741813025ab15dd71a90f2e74698df0b58e + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1, brace-expansion@npm:^2.0.2": version: 2.1.0 resolution: "brace-expansion@npm:2.1.0" dependencies: @@ -871,6 +2614,56 @@ __metadata: languageName: node linkType: hard +"browser-level@npm:^3.0.0": + version: 3.0.0 + resolution: "browser-level@npm:3.0.0" + dependencies: + abstract-level: "npm:^3.1.0" + checksum: 10/719e9aa36fb85ed7bd9d06267961c7b151866422e4ff4e97cc82966c6fdefcc13a19bbd2cefe151d57af21bf7d2e2419e758f8646af445dca47d8ab191e7236b + languageName: node + linkType: hard + +"buffer-crc32@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-crc32@npm:1.0.0" + checksum: 10/ef3b7c07622435085c04300c9a51e850ec34a27b2445f758eef69b859c7827848c2282f3840ca6c1eef3829145a1580ce540cab03ccf4433827a2b95d3b09ca7 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10/997434d3c6e3b39e0be479a80288875f71cd1c07d75a3855e6f08ef848a3c966023f79534e22e415ff3a5112708ce06127277ab20e527146d55c84566405c7c6 + languageName: node + linkType: hard + +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1 + languageName: node + linkType: hard + +"buildcheck@npm:~0.0.6": + version: 0.0.7 + resolution: "buildcheck@npm:0.0.7" + checksum: 10/cca174bcc917ee9dc00b1be404b4f22656d9c243d439d3456e6bd52263f05ad5f5d3c77e62a1f6ccaf1d36cb65efc5ee3bb30ed10e1675f22a1abdfad99eb9b3 + languageName: node + linkType: hard + +"byline@npm:^5.0.0": + version: 5.0.0 + resolution: "byline@npm:5.0.0" + checksum: 10/737ca83e8eda2976728dae62e68bc733aea095fab08db4c6f12d3cee3cf45b6f97dce45d1f6b6ff9c2c947736d10074985b4425b31ce04afa1985a4ef3d334a7 + languageName: node + linkType: hard + "cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" @@ -891,6 +2684,16 @@ __metadata: languageName: node linkType: hard +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 + languageName: node + linkType: hard + "chai@npm:^6.2.1": version: 6.2.1 resolution: "chai@npm:6.2.1" @@ -905,6 +2708,13 @@ __metadata: languageName: node linkType: hard +"chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 10/115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d + languageName: node + linkType: hard + "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -912,6 +2722,19 @@ __metadata: languageName: node linkType: hard +"classic-level@npm:^3.0.0": + version: 3.0.0 + resolution: "classic-level@npm:3.0.0" + dependencies: + abstract-level: "npm:^3.1.0" + module-error: "npm:^1.0.1" + napi-macros: "npm:^2.2.2" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: 10/96c07b0ca6f38dc5535c040804fdb845f728dcabd12838dafbcb379ca4b4cce906fb14c4ab8d871b3798f0e27a7815b9f584be535d1e00089f1104da97e44f95 + languageName: node + linkType: hard + "cli-cursor@npm:^5.0.0": version: 5.0.0 resolution: "cli-cursor@npm:5.0.0" @@ -928,6 +2751,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10/eaa5561aeb3135c2cddf7a3b3f562fc4238ff3b3fc666869ef2adf264be0f372136702f16add9299087fb1907c2e4ec5dbfe83bd24bce815c70a80c6c1a2e950 + languageName: node + linkType: hard + "color-convert@npm:^2.0.1": version: 2.0.1 resolution: "color-convert@npm:2.0.1" @@ -944,12 +2778,30 @@ __metadata: languageName: node linkType: hard +"colorette@npm:^2.0.7": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 10/0b8de48bfa5d10afc160b8eaa2b9938f34a892530b2f7d7897e0458d9535a066e3998b49da9d21161c78225b272df19ae3a64d6df28b4c9734c0e55bbd02406f + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10/2e969e637d05d09fa50b02d74c83a1186f6914aae89e6653b62595cc75a221464f884f55f231b8f4df7a49537fba60bdc0427acd2bf324c09a1dbb84837e36e4 + languageName: node + linkType: hard + "compact-tools-monorepo@workspace:.": version: 0.0.0-use.local resolution: "compact-tools-monorepo@workspace:." dependencies: "@biomejs/biome": "npm:2.3.8" + "@openzeppelin/compact-deploy": "workspace:^" "@types/node": "npm:24.10.1" + pino: "npm:^9.7.0" ts-node: "npm:^10.9.2" turbo: "npm:^2.6.1" typescript: "npm:^5.9.3" @@ -957,6 +2809,65 @@ __metadata: languageName: unknown linkType: soft +"compress-commons@npm:^6.0.2": + version: 6.0.2 + resolution: "compress-commons@npm:6.0.2" + dependencies: + crc-32: "npm:^1.2.0" + crc32-stream: "npm:^6.0.0" + is-stream: "npm:^2.0.1" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^4.0.0" + checksum: 10/78e3ba10aeef919a1c5bbac21e120f3e1558a31b2defebbfa1635274fc7f7e8a3a0ee748a06249589acd0b33a0d58144b8238ff77afc3220f8d403a96fcc13aa + languageName: node + linkType: hard + +"copy-anything@npm:^4": + version: 4.0.5 + resolution: "copy-anything@npm:4.0.5" + dependencies: + is-what: "npm:^5.2.0" + checksum: 10/1ee7e6f55c1016a47871ecd09aa765ca825c1ec89c46e6f58686016c80c6fe3d36452a6010d8498c766ea5d60bc5d892d9511b41310a7355b48ac10b39c90c9a + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10/9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 + languageName: node + linkType: hard + +"cpu-features@npm:~0.0.10": + version: 0.0.10 + resolution: "cpu-features@npm:0.0.10" + dependencies: + buildcheck: "npm:~0.0.6" + nan: "npm:^2.19.0" + node-gyp: "npm:latest" + checksum: 10/941b828ffe77582b2bdc03e894c913e2e2eeb5c6043ccb01338c34446d026f6888dc480ecb85e684809f9c3889d245f3648c7907eb61a92bdfc6aed039fcda8d + languageName: node + linkType: hard + +"crc-32@npm:^1.2.0": + version: 1.2.2 + resolution: "crc-32@npm:1.2.2" + bin: + crc32: bin/crc32.njs + checksum: 10/824f696a5baaf617809aa9cd033313c8f94f12d15ebffa69f10202480396be44aef9831d900ab291638a8022ed91c360696dd5b1ba691eb3f34e60be8835b7c3 + languageName: node + linkType: hard + +"crc32-stream@npm:^6.0.0": + version: 6.0.0 + resolution: "crc32-stream@npm:6.0.0" + dependencies: + crc-32: "npm:^1.2.0" + readable-stream: "npm:^4.0.0" + checksum: 10/e6edc2f81bc387daef6d18b2ac18c2ffcb01b554d3b5c7d8d29b177505aafffba574658fdd23922767e8dab1183d1962026c98c17e17fb272794c33293ef607c + languageName: node + linkType: hard + "create-require@npm:^1.1.0": version: 1.1.1 resolution: "create-require@npm:1.1.1" @@ -964,33 +2875,117 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.6": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" +"cross-fetch@npm:^4.0.0, cross-fetch@npm:^4.1.0": + version: 4.1.0 + resolution: "cross-fetch@npm:4.1.0" + dependencies: + node-fetch: "npm:^2.7.0" + checksum: 10/07624940607b64777d27ec9c668ddb6649e8c59ee0a5a10e63a51ce857e2bbb1294a45854a31c10eccb91b65909a5b199fcb0217339b44156f85900a7384f489 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 + languageName: node + linkType: hard + +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 10/0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c + languageName: node + linkType: hard + +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10/5c149c91bf9ce2142c89f84eee4c585f0cb1f6faf2536b1af89873f862666a28529d1ccafc44750aa01384da2197c4f76f4e149a3cc0c1cb2c46f5cc45f2bcb5 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.3.5": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10/46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.1": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10/b736c8d97d5d46164c0d1bed53eb4e6a3b1d8530d460211e2d52f1c552875e706c58a5376854e4e54f8b828c9cada58c855288c968522eb93ac7696d65970766 + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 10/ec09ec2101934ca5966355a229d77afcad5911c92e2a77413efda5455636c4cf2ce84057e2d7715227a2eeeda04255b849bd3ae3a4dd22eb22e86e76456df069 + languageName: node + linkType: hard + +"docker-compose@npm:^0.24.8": + version: 0.24.8 + resolution: "docker-compose@npm:0.24.8" + dependencies: + yaml: "npm:^2.2.2" + checksum: 10/2b8526f9797a55c819ff2d7dcea57085b012b3a3d77bc2e1a6b45c3fc9e82196312f5298cbe8299966462454a5ac8f68814bb407736b4385e0d226a2a39e877a + languageName: node + linkType: hard + +"docker-modem@npm:^5.0.7": + version: 5.0.7 + resolution: "docker-modem@npm:5.0.7" dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 + debug: "npm:^4.1.1" + readable-stream: "npm:^3.5.0" + split-ca: "npm:^1.0.1" + ssh2: "npm:^1.15.0" + checksum: 10/8c0dc9908e10fbc91c35b187fc6a67a0dcbe4b33a2198dfa67cd8304e0f2452325e1639215674d6e441731d0bf27f06339550f6c3767585b877601d2f16e43e2 languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.3.4": - version: 4.4.3 - resolution: "debug@npm:4.4.3" +"dockerode@npm:^4.0.5": + version: 4.0.12 + resolution: "dockerode@npm:4.0.12" dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad + "@balena/dockerignore": "npm:^1.0.2" + "@grpc/grpc-js": "npm:^1.11.1" + "@grpc/proto-loader": "npm:^0.7.13" + docker-modem: "npm:^5.0.7" + protobufjs: "npm:^7.3.2" + tar-fs: "npm:^2.1.4" + uuid: "npm:^10.0.0" + checksum: 10/e08b15ba2ba41e93e61cac472e525efff48851b0eaaba75e5075cf540760099658f57883b08334ccc3fee021c4ca286013c76a00890b5d0716892b8ff678b2d1 languageName: node linkType: hard -"diff@npm:^4.0.1": - version: 4.0.2 - resolution: "diff@npm:4.0.2" - checksum: 10/ec09ec2101934ca5966355a229d77afcad5911c92e2a77413efda5455636c4cf2ce84057e2d7715227a2eeeda04255b849bd3ae3a4dd22eb22e86e76456df069 +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 languageName: node linkType: hard @@ -1001,6 +2996,16 @@ __metadata: languageName: node linkType: hard +"effect@npm:^3.19.19, effect@npm:^3.20.0": + version: 3.21.2 + resolution: "effect@npm:3.21.2" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + fast-check: "npm:^3.23.1" + checksum: 10/e1bf90d9010e6b4d8389937e80e96884e49164b8b1658230cf2aaf9d2a3844d1698a6854fd8183a82a0335bdcbc37879d9af84491b52a57bf16ab52052cf6f46 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -1024,6 +3029,15 @@ __metadata: languageName: node linkType: hard +"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: "npm:^1.4.0" + checksum: 10/1e0cfa6e7f49887544e03314f9dfc56a8cb6dde910cbb445983ecc2ff426fc05946df9d75d8a21a3a64f2cecfe1bf88f773952029f46756b2ed64a24e95b1fb8 + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -1038,6 +3052,20 @@ __metadata: languageName: node linkType: hard +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 + languageName: node + linkType: hard + "es-module-lexer@npm:^1.7.0": version: 1.7.0 resolution: "es-module-lexer@npm:1.7.0" @@ -1045,6 +3073,27 @@ __metadata: languageName: node linkType: hard +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f + languageName: node + linkType: hard + "esbuild@npm:^0.27.0": version: 0.27.7 resolution: "esbuild@npm:0.27.7" @@ -1134,6 +3183,13 @@ __metadata: languageName: node linkType: hard +"escalade@npm:^3.1.1": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 + languageName: node + linkType: hard + "estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -1143,6 +3199,36 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 10/49ff46c3a7facbad3decb31f597063e761785d7fdb3920d4989d7b08c97a61c2f51183e2f3a03130c9088df88d4b489b1b79ab632219901f184f85158508f4c8 + languageName: node + linkType: hard + +"eventemitter3@npm:^5.0.1": + version: 5.0.4 + resolution: "eventemitter3@npm:5.0.4" + checksum: 10/54f5c8c543650d65f92d03dbef1bb73a682a920490c44699ad8f863a6b19bbca42fb7409aa09ca09cb98a44149d9a7bc1dffd55ca88a740bd928c7be0ad666a0 + languageName: node + linkType: hard + +"events-universal@npm:^1.0.0": + version: 1.0.1 + resolution: "events-universal@npm:1.0.1" + dependencies: + bare-events: "npm:^2.7.0" + checksum: 10/71b2e6079b4dc030c613ef73d99f1acb369dd3ddb6034f49fd98b3e2c6632cde9f61c15fb1351004339d7c79672252a4694ecc46a6124dc794b558be50a83867 + languageName: node + linkType: hard + +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be + languageName: node + linkType: hard + "expect-type@npm:^1.2.2": version: 1.2.2 resolution: "expect-type@npm:1.2.2" @@ -1157,6 +3243,15 @@ __metadata: languageName: node linkType: hard +"fast-check@npm:^3.23.1": + version: 3.23.2 + resolution: "fast-check@npm:3.23.2" + dependencies: + pure-rand: "npm:^6.1.0" + checksum: 10/dab344146b778e8bc2973366ea55528d1b58d3e3037270262b877c54241e800c4d744957722c24705c787020d702aece11e57c9e3dbd5ea19c3e10926bf1f3fe + languageName: node + linkType: hard + "fast-check@npm:^4.5.2": version: 4.5.2 resolution: "fast-check@npm:4.5.2" @@ -1166,6 +3261,27 @@ __metadata: languageName: node linkType: hard +"fast-copy@npm:^4.0.0": + version: 4.0.3 + resolution: "fast-copy@npm:4.0.3" + checksum: 10/1e74e8b18a83f125b697b0dc7d802b4c73ec2aba7b181458e5e72d46a261faefcdee22ad9fa682c77f4606133451342f95de9835c2c804c481472585fa6ded26 + languageName: node + linkType: hard + +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: 10/6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 + languageName: node + linkType: hard + +"fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10/dc1f063c2c6ac9533aee14d406441f86783a8984b2ca09b19c2fe281f9ff59d315298bc7bc22fd1f83d26fe19ef2f20e2ddb68e96b15040292e555c5ced0c1e4 + languageName: node + linkType: hard + "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -1178,6 +3294,40 @@ __metadata: languageName: node linkType: hard +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: "npm:^1.0.0" + web-streams-polyfill: "npm:^3.0.3" + checksum: 10/5264ecceb5fdc19eb51d1d0359921f12730941e333019e673e71eb73921146dceabcb0b8f534582be4497312d656508a439ad0f5edeec2b29ab2e10c72a1f86b + languageName: node + linkType: hard + +"fetch-retry@npm:^6.0.0": + version: 6.0.0 + resolution: "fetch-retry@npm:6.0.0" + checksum: 10/0c8d3082e2d76fff2df75adef6280bc854bc36fd3ef38506674f0216d0d819e2efd14da7477d3f1732415aea1d2cfde7cd3e1aeae46f45f2adbfc5133296e8de + languageName: node + linkType: hard + +"find-my-way-ts@npm:^0.1.6": + version: 0.1.6 + resolution: "find-my-way-ts@npm:0.1.6" + checksum: 10/b95bf644011f0d341e5963aa4cac55b2ee59e2435d3f65ae5cf9ee80e52f0fc7db0cee9a55e7420a62a2cec7d8bec7538399dada45e024c05488daa754451bcc + languageName: node + linkType: hard + +"follow-redirects@npm:^1.16.0": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" + peerDependenciesMeta: + debug: + optional: true + checksum: 10/3fbe3d80b3b544c22705d837aa5d4a0d07a740d913534a2620b0a004c610af4148e3b58723536dd099aaa1c9d3a155964bde9665d6e5cb331460809a1fc572fd + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" @@ -1188,6 +3338,42 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^4.0.5": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10/52ecd6e927c8c4e215e68a7ad5e0f7c1031397439672fd9741654b4a94722c4182e74cc815b225dcb5be3f4180f36428f67c6dd39eaa98af0dcfdd26c00c19cd + languageName: node + linkType: hard + +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: "npm:^3.1.2" + checksum: 10/9b5001d2edef3c9449ac3f48bd4f8cc92e7d0f2e7c1a5c8ba555ad4e77535cc5cf621fabe49e97f304067037282dd9093b9160a3cb533e46420b446c4e6bc06f + languageName: node + linkType: hard + +"fp-ts@npm:^2.16.1": + version: 2.16.11 + resolution: "fp-ts@npm:2.16.11" + checksum: 10/4c034326728c43a28b3a0f3a88c1218bca13c69ee3c420ab7a52636aa0d68cb68990308e72d93b3e5cc952e998019f7e3ba16389bb1ecc0174574536c29d3ab3 + languageName: node + linkType: hard + +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 10/18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d + languageName: node + linkType: hard + "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -1216,6 +3402,27 @@ __metadata: languageName: node linkType: hard +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10/eb7e7eb896c5433f3d40982b2ccacdb3dd990dd3499f14040e002b5d54572476513be8a2e6f9609f6e41ab29f2c4469307611ddbfc37ff4e46b765c326663805 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + "get-east-asian-width@npm:^1.3.0": version: 1.4.0 resolution: "get-east-asian-width@npm:1.4.0" @@ -1223,7 +3430,45 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2": +"get-intrinsic@npm:^1.2.6": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/bb579dda84caa4a3a41611bdd483dade7f00f246f2a7992eb143c5861155290df3fdb48a8406efa3dfb0b434e2c8fafa4eebd469e409d0439247f85fc3fa2cc1 + languageName: node + linkType: hard + +"get-port@npm:^7.1.0": + version: 7.2.0 + resolution: "get-port@npm:7.2.0" + checksum: 10/f8785ccdcc52b1e03f1b1de3fcd46dbc41fe4079e234f2727c3e154ca76bb94318fb0d341daa28a6c87eff24ad4016eaa8b1b4e26eff0d6a2196dd1c1ffc63a1 + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + +"glob@npm:^10.0.0, glob@npm:^10.2.2": version: 10.5.0 resolution: "glob@npm:10.5.0" dependencies: @@ -1239,13 +3484,107 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.2.6": +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 languageName: node linkType: hard +"graphql-http@npm:^1.22.4": + version: 1.22.4 + resolution: "graphql-http@npm:1.22.4" + peerDependencies: + graphql: ">=0.11 <=16" + checksum: 10/ef81c3d86ac75743509d225aaf88a79262adee8801035712e5af655deedd5755afb0060e68306ca54aa54067c4ef0a382a03b2ecde016e0fb43454b73184a04d + languageName: node + linkType: hard + +"graphql-tag@npm:^2.12.6": + version: 2.12.6 + resolution: "graphql-tag@npm:2.12.6" + dependencies: + tslib: "npm:^2.1.0" + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/23a2bc1d3fbeae86444204e0ac08522e09dc369559ba75768e47421a7321b59f352fb5b2c9a5c37d3cf6de890dca4e5ac47e740c7cc622e728572ecaa649089e + languageName: node + linkType: hard + +"graphql-ws@npm:^6.0.7": + version: 6.0.8 + resolution: "graphql-ws@npm:6.0.8" + peerDependencies: + "@fastify/websocket": ^10 || ^11 + crossws: ~0.3 + graphql: ^15.10.1 || ^16 + ws: ^8 + peerDependenciesMeta: + "@fastify/websocket": + optional: true + crossws: + optional: true + ws: + optional: true + checksum: 10/503d581c7dab4b9a884dad844fa9642a896803161aa1f1c8d3f12619e4e428f43cb39fe06a198c30bb685a521689d525b2870539c07bd68bb4bf704d039bdd9a + languageName: node + linkType: hard + +"graphql@npm:^16.13.0, graphql@npm:^16.8.0": + version: 16.14.0 + resolution: "graphql@npm:16.14.0" + checksum: 10/019bed00a1d62c90d38bd8971f827af9be479bd1935ac990b62edce8dbe5d9e1d93cae72e986199fdeb7108ee83e3f73c7492989ec08fcaf446b6bd79d533741 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.3 + resolution: "hasown@npm:2.0.3" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10/619526379cda755409d856cbf3c65b82ea342151719a0a550920cf7d6a7f58f7cf079e5a78f3acd162324fc784a3d3d6f6f61aff613b47a0163c16fbe09ea89f + languageName: node + linkType: hard + +"help-me@npm:^5.0.0": + version: 5.0.0 + resolution: "help-me@npm:5.0.0" + checksum: 10/5f99bd91dae93d02867175c3856c561d7e3a24f16999b08f5fc79689044b938d7ed58457f4d8c8744c01403e6e0470b7896baa344d112b2355842fd935a75d69 + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.3.2": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10/1acbe85f33e5a39f90c822ad4d28b24daeb60f71c545279431dc98c312cd28a54f8d64788e477fe21dc502b0e3cf58589ebe5c1ad22af27245370391c2d24ea6 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" @@ -1263,6 +3602,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^5.0.1": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10/f0dce7bdcac5e8eaa0be3c7368bb8836ed010fb5b6349ffb412b172a203efe8f807d9a6681319105ea1b6901e1972c7b5ea899672a7b9aad58309f766dcbe0df + languageName: node + linkType: hard + "https-proxy-agent@npm:^7.0.1": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" @@ -1282,6 +3631,13 @@ __metadata: languageName: node linkType: hard +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4 + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -1289,6 +3645,22 @@ __metadata: languageName: node linkType: hard +"inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 + languageName: node + linkType: hard + +"io-ts@npm:^2.2.20": + version: 2.2.22 + resolution: "io-ts@npm:2.2.22" + peerDependencies: + fp-ts: ^2.5.0 + checksum: 10/c5eb8ca848f6e9586b5430773c62c8577902a6ca621349339e4d238c9ac4aba8df8de3e4d4317ff6593dcf38eb804445e0a5ba87afd7a2b8d29344ea9b6dc151 + languageName: node + linkType: hard + "ip-address@npm:^10.0.1": version: 10.2.0 resolution: "ip-address@npm:10.2.0" @@ -1296,6 +3668,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:^2.0.5": + version: 2.0.5 + resolution: "is-buffer@npm:2.0.5" + checksum: 10/3261a8b858edcc6c9566ba1694bf829e126faa88911d1c0a747ea658c5d81b14b6955e3a702d59dabadd58fdd440c01f321aa71d6547105fd21d03f94d0597e7 + languageName: node + linkType: hard + "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -1310,6 +3689,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^2.0.1": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10/b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 + languageName: node + linkType: hard + "is-unicode-supported@npm:^2.0.0, is-unicode-supported@npm:^2.1.0": version: 2.1.0 resolution: "is-unicode-supported@npm:2.1.0" @@ -1317,6 +3703,20 @@ __metadata: languageName: node linkType: hard +"is-what@npm:^5.2.0": + version: 5.5.0 + resolution: "is-what@npm:5.5.0" + checksum: 10/d53a6ea1aebf953f3bcf711a28e8463bfe79fc0e4e87575d77c692a30fd3d98f87b88d4c006c06753bf85f771c9d2c1d05b2c6b03c246883261fe190526195d9 + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10/f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -1331,6 +3731,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "jackspeak@npm:^3.1.2": version: 3.4.3 resolution: "jackspeak@npm:3.4.3" @@ -1344,13 +3753,103 @@ __metadata: languageName: node linkType: hard +"joycon@npm:^3.1.1": + version: 3.1.1 + resolution: "joycon@npm:3.1.1" + checksum: 10/4b36e3479144ec196425f46b3618f8a96ce7e1b658f091a309cd4906215f5b7a402d7df331a3e0a09681381a658d0c5f039cb3cf6907e0a1e17ed847f5d37775 + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10/af37d0d913fb56aec6dc0074c163cc71cd23c0b8aad5c2350747b6721d37ba118af35abdd8b33c47ec2800de07dedb16a527ca9c530ee004093e04958bd0cbf2 + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10/59169a081e4eeb6f9559ae1f938f656191c000e0512aa6df9f3c8b2437a4ab1823819c6b9fd1818a4e39593ccfd72e9a051fdd3e2d1e340ed913679e888ded8c + languageName: node + linkType: hard + +"lazystream@npm:^1.0.0": + version: 1.0.1 + resolution: "lazystream@npm:1.0.1" + dependencies: + readable-stream: "npm:^2.0.5" + checksum: 10/35f8cf8b5799c76570b211b079d4d706a20cbf13a4936d44cc7dbdacab1de6b346ab339ed3e3805f4693155ee5bbebbda4050fa2b666d61956e89a573089e3d4 + languageName: node + linkType: hard + +"level-supports@npm:^6.2.0": + version: 6.2.0 + resolution: "level-supports@npm:6.2.0" + checksum: 10/450c04839cf42ac7c73085b4928f1c1c51d9ab179aac9102cc8ef2389faf2d06cebaf57df2d025da89d78465004ccf29bfd972a04b0b35d5d423fa3f4516f906 + languageName: node + linkType: hard + +"level-transcoder@npm:^1.0.1": + version: 1.0.1 + resolution: "level-transcoder@npm:1.0.1" + dependencies: + buffer: "npm:^6.0.3" + module-error: "npm:^1.0.1" + checksum: 10/2fb41a1d8037fc279f851ead8cdc3852b738f1f935ac2895183cd606aae3e57008e085c7c2bd2b2d43cfd057333108cfaed604092e173ac2abdf5ab1b8333f9e + languageName: node + linkType: hard + +"level@npm:^10.0.0": + version: 10.0.0 + resolution: "level@npm:10.0.0" + dependencies: + abstract-level: "npm:^3.1.0" + browser-level: "npm:^3.0.0" + classic-level: "npm:^3.0.0" + checksum: 10/c04a81530e0472b7dbcd061ee32fb498675574b45e1121ec3ed8407734ed45a7b4ca7ef72a70a710c53b35a3d77223fc90092877e807e9f21a557c5219e9d54b + languageName: node + linkType: hard + +"lodash.camelcase@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.camelcase@npm:4.3.0" + checksum: 10/c301cc379310441dc73cd6cebeb91fb254bea74e6ad3027f9346fc43b4174385153df420ffa521654e502fd34c40ef69ca4e7d40ee7129a99e06f306032bfc65 + languageName: node + linkType: hard + +"lodash@npm:^4.17.15, lodash@npm:^4.17.23": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10/306fea53dfd39dad1f03d45ba654a2405aebd35797b673077f401edb7df2543623dc44b9effbb98f69b32152295fff725a4cec99c684098947430600c6af0c3f + languageName: node + linkType: hard + "log-symbols@npm:^7.0.0, log-symbols@npm:^7.0.1": version: 7.0.1 resolution: "log-symbols@npm:7.0.1" dependencies: - is-unicode-supported: "npm:^2.0.0" - yoctocolors: "npm:^2.1.1" - checksum: 10/0862313d84826b551582e39659b8586c56b65130c5f4f976420e2c23985228334f2a26fc4251ac22bf0a5b415d9430e86bf332557d934c10b036f9a549d63a09 + is-unicode-supported: "npm:^2.0.0" + yoctocolors: "npm:^2.1.1" + checksum: 10/0862313d84826b551582e39659b8586c56b65130c5f4f976420e2c23985228334f2a26fc4251ac22bf0a5b415d9430e86bf332557d934c10b036f9a549d63a09 + languageName: node + linkType: hard + +"long@npm:^5.0.0, long@npm:^5.3.2": + version: 5.3.2 + resolution: "long@npm:5.3.2" + checksum: 10/b6b55ddae56fcce2864d37119d6b02fe28f6dd6d9e44fd22705f86a9254b9321bd69e9ffe35263b4846d54aba197c64882adcb8c543f2383c1e41284b321ea64 + languageName: node + linkType: hard + +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10/6517e24e0cad87ec9888f500c5b5947032cdfe6ef65e1c1936a0c48a524b81e65542c9c3edc91c97d5bddc806ee2a985dbc79be89215d613b1de5db6d1cfe6f4 languageName: node linkType: hard @@ -1396,6 +3895,36 @@ __metadata: languageName: node linkType: hard +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd + languageName: node + linkType: hard + +"maybe-combine-errors@npm:^1.0.0": + version: 1.0.0 + resolution: "maybe-combine-errors@npm:1.0.0" + checksum: 10/16bb6d3dcf79fc61f5a04abe948c4c81cae0da6ee5da9a1d8196f1723b069d6ab60f752bc208e18481e2b82de146e068bc462558c65ecdf96fed0d021a1aa6ab + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10/89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a + languageName: node + linkType: hard + "mimic-function@npm:^5.0.0": version: 5.0.1 resolution: "mimic-function@npm:5.0.1" @@ -1403,6 +3932,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^5.1.0": + version: 5.1.9 + resolution: "minimatch@npm:5.1.9" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/23b4feb64dcb77ba93b70a72be551eb2e2677ac02178cf1ed3d38836cc4cd84802d90b77f60ef87f2bac64d270d2d8eba242e428f0554ea4e36bfdb7e9d25d0c + languageName: node + linkType: hard + "minimatch@npm:^9.0.4": version: 9.0.9 resolution: "minimatch@npm:9.0.9" @@ -1412,6 +3950,13 @@ __metadata: languageName: node linkType: hard +"minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f + languageName: node + linkType: hard + "minipass-collect@npm:^2.0.1": version: 2.0.1 resolution: "minipass-collect@npm:2.0.1" @@ -1488,6 +4033,36 @@ __metadata: languageName: node linkType: hard +"mkdirp-classic@npm:^0.5.2": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 10/3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10/d71b8dcd4b5af2fe13ecf3bd24070263489404fe216488c5ba7e38ece1f54daf219e72a833a3a2dc404331e870e9f44963a33399589490956bff003a3404d3b2 + languageName: node + linkType: hard + +"mock-socket@npm:^9.3.1": + version: 9.3.1 + resolution: "mock-socket@npm:9.3.1" + checksum: 10/c5c07568f2859db6926d79cb61580c07e67958b5cd6b52d1270fdfa17ae066d7f74a18a4208fc4386092eea4e1ee001aa23f015c88a1774265994e4fae34d18e + languageName: node + linkType: hard + +"module-error@npm:^1.0.1": + version: 1.0.2 + resolution: "module-error@npm:1.0.2" + checksum: 10/5d653e35bd55b3e95f8aee2cdac108082ea892e71b8f651be92cde43e4ee86abee4fa8bd7fc3fe5e68b63926d42f63c54cd17b87a560c31f18739295575a3962 + languageName: node + linkType: hard + "ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -1495,6 +4070,65 @@ __metadata: languageName: node linkType: hard +"msgpackr-extract@npm:^3.0.2": + version: 3.0.3 + resolution: "msgpackr-extract@npm:3.0.3" + dependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "npm:3.0.3" + "@msgpackr-extract/msgpackr-extract-darwin-x64": "npm:3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-arm": "npm:3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-arm64": "npm:3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-x64": "npm:3.0.3" + "@msgpackr-extract/msgpackr-extract-win32-x64": "npm:3.0.3" + node-gyp: "npm:latest" + node-gyp-build-optional-packages: "npm:5.2.2" + dependenciesMeta: + "@msgpackr-extract/msgpackr-extract-darwin-arm64": + optional: true + "@msgpackr-extract/msgpackr-extract-darwin-x64": + optional: true + "@msgpackr-extract/msgpackr-extract-linux-arm": + optional: true + "@msgpackr-extract/msgpackr-extract-linux-arm64": + optional: true + "@msgpackr-extract/msgpackr-extract-linux-x64": + optional: true + "@msgpackr-extract/msgpackr-extract-win32-x64": + optional: true + bin: + download-msgpackr-prebuilds: bin/download-prebuilds.js + checksum: 10/4bfe45cf6968310570765951691f1b8e85b6a837e5197b8232fc9285eef4b457992e73118d9d07c92a52cc23f9e837897b135e17ea0f73e3604540434051b62f + languageName: node + linkType: hard + +"msgpackr@npm:^1.11.10, msgpackr@npm:^1.11.4": + version: 1.11.12 + resolution: "msgpackr@npm:1.11.12" + dependencies: + msgpackr-extract: "npm:^3.0.2" + dependenciesMeta: + msgpackr-extract: + optional: true + checksum: 10/8077d7ebf661df831ba119a277588b7e00149d25b6f5630e311c2415504553ce695347a351a7198cdf1f596feaaf91121adc3181e483f7d2c9822484b73babf2 + languageName: node + linkType: hard + +"multipasta@npm:^0.2.7": + version: 0.2.7 + resolution: "multipasta@npm:0.2.7" + checksum: 10/244a7194ff508b3c5c1724f11c303f1c446cf6142cdbe82e57d5e59c44abb4942b1b983dd8c0d9c63080e684b2a8fa10f511df70d42dbef4d215ed7d41e76fcc + languageName: node + linkType: hard + +"nan@npm:^2.19.0, nan@npm:^2.23.0": + version: 2.27.0 + resolution: "nan@npm:2.27.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/bdce0630e417740501394c412bd9f0ed1c287825e3b8f9b7efb95cc3acd3ef69de60479b5f00a2d039b79321e5ce29b672b0b263cfe0e4d8f47c8f810a24a5ee + languageName: node + linkType: hard + "nanoid@npm:^3.3.11": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -1504,6 +4138,13 @@ __metadata: languageName: node linkType: hard +"napi-macros@npm:^2.2.2": + version: 2.2.2 + resolution: "napi-macros@npm:2.2.2" + checksum: 10/2cdb9c40ad4b424b14fbe5e13c5329559e2b511665acf41cdcda172fd2270202dc747a2d288b687c72bc70f654c797bc24a93adb67631128d62461588d7cc070 + languageName: node + linkType: hard + "negotiator@npm:^1.0.0": version: 1.0.0 resolution: "negotiator@npm:1.0.0" @@ -1511,6 +4152,73 @@ __metadata: languageName: node linkType: hard +"nock@npm:^13.5.5": + version: 13.5.6 + resolution: "nock@npm:13.5.6" + dependencies: + debug: "npm:^4.1.0" + json-stringify-safe: "npm:^5.0.1" + propagate: "npm:^2.0.0" + checksum: 10/a57c265b75e5f7767e2f8baf058773cdbf357c31c5fea2761386ec03a008a657f9df921899fe2a9502773b47145b708863b32345aef529b3c45cba4019120f88 + languageName: node + linkType: hard + +"node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: 10/e332522f242348c511640c25a6fc7da4f30e09e580c70c6b13cb0be83c78c3e71c8d4665af2527e869fc96848924a4316ae7ec9014c091e2156f41739d4fa233 + languageName: node + linkType: hard + +"node-fetch@npm:^2.7.0": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 + languageName: node + linkType: hard + +"node-fetch@npm:^3.3.2": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: "npm:^4.0.0" + fetch-blob: "npm:^3.1.4" + formdata-polyfill: "npm:^4.0.10" + checksum: 10/24207ca8c81231c7c59151840e3fded461d67a31cf3e3b3968e12201a42f89ce4a0b5fb7079b1fa0a4655957b1ca9257553200f03a9f668b45ebad265ca5593d + languageName: node + linkType: hard + +"node-gyp-build-optional-packages@npm:5.2.2": + version: 5.2.2 + resolution: "node-gyp-build-optional-packages@npm:5.2.2" + dependencies: + detect-libc: "npm:^2.0.1" + bin: + node-gyp-build-optional-packages: bin.js + node-gyp-build-optional-packages-optional: optional.js + node-gyp-build-optional-packages-test: build-test.js + checksum: 10/f448a328cf608071dc8cc4426ac5be0daec4788e4e1759e9f7ffcd286822cc799384edce17a8c79e610c4bbfc8e3aff788f3681f1d88290e0ca7aaa5342a090f + languageName: node + linkType: hard + +"node-gyp-build@npm:^4.3.0": + version: 4.8.4 + resolution: "node-gyp-build@npm:4.8.4" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10/6a7d62289d1afc419fc8fc9bd00aa4e554369e50ca0acbc215cb91446148b75ff7e2a3b53c2c5b2c09a39d416d69f3d3237937860373104b5fe429bf30ad9ac5 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 11.5.0 resolution: "node-gyp@npm:11.5.0" @@ -1542,6 +4250,20 @@ __metadata: languageName: node linkType: hard +"normalize-path@npm:^3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10/88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f + languageName: node + linkType: hard + "object-inspect@npm:^1.12.3": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" @@ -1556,6 +4278,22 @@ __metadata: languageName: node linkType: hard +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.2 + resolution: "on-exit-leak-free@npm:2.1.2" + checksum: 10/f7b4b7200026a08f6e4a17ba6d72e6c5cbb41789ed9cf7deaf9d9e322872c7dc5a7898549a894651ee0ee9ae635d34a678115bf8acdfba8ebd2ba2af688b563c + languageName: node + linkType: hard + +"once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10/cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 + languageName: node + linkType: hard + "onetime@npm:^7.0.0": version: 7.0.0 resolution: "onetime@npm:7.0.0" @@ -1565,6 +4303,18 @@ __metadata: languageName: node linkType: hard +"optimism@npm:^0.18.0": + version: 0.18.1 + resolution: "optimism@npm:0.18.1" + dependencies: + "@wry/caches": "npm:^1.0.0" + "@wry/context": "npm:^0.7.0" + "@wry/trie": "npm:^0.5.0" + tslib: "npm:^2.3.0" + checksum: 10/d805f5995d61a417d4fd49a923749db1aa310d1ae8de084ec3a5f589f8b185d9a41b7b4422d33ee75ce43115c264e14bca086f8be2bb182c76448ad08997213a + languageName: node + linkType: hard + "ora@npm:^9.0.0": version: 9.0.0 resolution: "ora@npm:9.0.0" @@ -1603,69 +4353,332 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10/01e9a69928f39087d96e1751ce7d6d50da8c39abf9a12e0ac2389c42c83bc76f78c45a475bd9026a02e6a6f79be63acc75667df855862fe567d99a00a540d23d + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10/f6ef80a3590827ce20378ae110ac78209cc4f74d39236370f1780f957b7ee41c12acde0e4651b90f39983506fd2f5e449994716f516db2e9752924aff8de93ce + languageName: node + linkType: hard + +"pino-abstract-transport@npm:^2.0.0": + version: 2.0.0 + resolution: "pino-abstract-transport@npm:2.0.0" + dependencies: + split2: "npm:^4.0.0" + checksum: 10/e5699ecb06c7121055978e988e5cecea5b6892fc2589c64f1f86df5e7386bbbfd2ada268839e911b021c6b3123428aed7c6be3ac7940eee139556c75324c7e83 + languageName: node + linkType: hard + +"pino-abstract-transport@npm:^3.0.0": + version: 3.0.0 + resolution: "pino-abstract-transport@npm:3.0.0" + dependencies: + split2: "npm:^4.0.0" + checksum: 10/f42b85b2663c8520839124a55b27801e88c89c65e9569384b49bb4c81b022ae24860020c2375b92a03db699113969007cc155e1fb2dfe53754403920c1cbe18c + languageName: node + linkType: hard + +"pino-pretty@npm:^13.0.0": + version: 13.1.3 + resolution: "pino-pretty@npm:13.1.3" + dependencies: + colorette: "npm:^2.0.7" + dateformat: "npm:^4.6.3" + fast-copy: "npm:^4.0.0" + fast-safe-stringify: "npm:^2.1.1" + help-me: "npm:^5.0.0" + joycon: "npm:^3.1.1" + minimist: "npm:^1.2.6" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^3.0.0" + pump: "npm:^3.0.0" + secure-json-parse: "npm:^4.0.0" + sonic-boom: "npm:^4.0.1" + strip-json-comments: "npm:^5.0.2" + bin: + pino-pretty: bin.js + checksum: 10/4bb721e1ece378c1c9000457e4fe4a914ea5b8e036551608f5681ca58c8fbacc6b8a31807e93bc0c66d17fb5d96e74b3e4051fb53152955dc51ac58848428e27 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^7.0.0": + version: 7.1.0 + resolution: "pino-std-serializers@npm:7.1.0" + checksum: 10/6e27f6f885927b6df3b424ddb8a9e0e9854f3b59f4abd51afa74e1c2cf33436a505277b004bb00ce61884a962c8fdfd977391205c7baab885d6afb35fce7396a + languageName: node + linkType: hard + +"pino@npm:^9.7.0": + version: 9.14.0 + resolution: "pino@npm:9.14.0" + dependencies: + "@pinojs/redact": "npm:^0.4.0" + atomic-sleep: "npm:^1.0.0" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^2.0.0" + pino-std-serializers: "npm:^7.0.0" + process-warning: "npm:^5.0.0" + quick-format-unescaped: "npm:^4.0.3" + real-require: "npm:^0.2.0" + safe-stable-stringify: "npm:^2.3.1" + sonic-boom: "npm:^4.0.1" + thread-stream: "npm:^3.0.0" + bin: + pino: bin.js + checksum: 10/918e1fc764885150cb2b4fae8249a0ece53275020a7ca389f994fa2fbbb17b6353cd736c2db3a3794fbac0351f8e3d58411fabe127e875e24151a8fa4cd0b2b5 + languageName: node + linkType: hard + +"postcss@npm:^8.5.6": + version: 8.5.14 + resolution: "postcss@npm:8.5.14" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10/2e3f4dea69692918fe9df5402beb0e54df84499995a094f2fbf63d1a9e38bc1b7a42854df47f09e02593213e01a5eb0627b1d1bd6d1b0ea90767b2e072f7167c + languageName: node + linkType: hard + +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: 10/35610bdb0177d3ab5d35f8827a429fb1dc2518d9e639f2151ac9007f01a061c30e0c635a970c9b00c39102216160f6ec54b62377c92fac3b7bfc2ad4b98d195c + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10/1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf + languageName: node + linkType: hard + +"process-warning@npm:^5.0.0": + version: 5.0.0 + resolution: "process-warning@npm:5.0.0" + checksum: 10/10f3e00ac9fc1943ec4566ff41fff2b964e660f853c283e622257719839d340b4616e707d62a02d6aa0038761bb1fa7c56bc7308d602d51bd96f05f9cd305dcd + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 10/dbaa7e8d1d5cf375c36963ff43116772a989ef2bb47c9bdee20f38fd8fc061119cf38140631cf90c781aca4d3f0f0d2c834711952b728953f04fd7d238f59f5b + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10/96e1a82453c6c96eef53a37a1d6134c9f2482f94068f98a59145d0986ca4e497bf110a410adf73857e588165eab3899f0ebcf7b3890c1b3ce802abc0d65967d4 + languageName: node + linkType: hard + +"prop-types@npm:^15.7.2": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10/7d959caec002bc964c86cdc461ec93108b27337dabe6192fb97d69e16a0c799a03462713868b40749bfc1caf5f57ef80ac3e4ffad3effa636ee667582a75e2c0 + languageName: node + linkType: hard + +"propagate@npm:^2.0.0": + version: 2.0.1 + resolution: "propagate@npm:2.0.1" + checksum: 10/8c761c16e8232f82f6d015d3e01e8bd4109f47ad804f904d950f6fe319813b448ca112246b6bfdc182b400424b155b0b7c4525a9bb009e6fa950200157569c14 + languageName: node + linkType: hard + +"proper-lockfile@npm:^4.1.2": + version: 4.1.2 + resolution: "proper-lockfile@npm:4.1.2" + dependencies: + graceful-fs: "npm:^4.2.4" + retry: "npm:^0.12.0" + signal-exit: "npm:^3.0.2" + checksum: 10/000a4875f543f591872b36ca94531af8a6463ddb0174f41c0b004d19e231d7445268b422ff1ea595e43d238655c702250cd3d27f408e7b9d97b56f1533ba26bf + languageName: node + linkType: hard + +"properties-reader@npm:^2.3.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: "npm:^1.0.4" + checksum: 10/0b41eb4136dc278ae0d97968ccce8de2d48d321655b319192e31f2424f1c6e052182204671e65aa8967216360cb3e7cbd9129830062e058fe9d6a1d74964c29a + languageName: node + linkType: hard + +"protobufjs@npm:^7.2.5, protobufjs@npm:^7.3.2, protobufjs@npm:^7.5.5": + version: 7.6.0 + resolution: "protobufjs@npm:7.6.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.5" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.1" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.2" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.1" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.3.2" + checksum: 10/2becdf429fa148b2f3c9ee5e52c7b8249d2b775d158ce9e5bcf82d2f9d979bf95667818f5c70487636f775e5712aecf20775ac6e86a019e146fb95ed4063dfdc + languageName: node + linkType: hard + +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10/fbbaf4dab2a6231dc9e394903a5f66f20475e36b734335790b46feb9da07c37d6b32e2c02e3e2ea4d4b23774c53d8562e5b7cc73282cb43f4a597b7eacaee2ee + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.4 + resolution: "pump@npm:3.0.4" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10/d043c3e710c56ffd280711e98a94e863ab334f79ea43cee0fb70e1349b2355ffd2ff287c7522e4c960a247699d5b7825f00fa090b85d6179c973be13f78a6c49 + languageName: node + linkType: hard + +"pure-rand@npm:^6.1.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10/256aa4bcaf9297256f552914e03cbdb0039c8fe1db11fa1e6d3f80790e16e563eb0a859a1e61082a95e224fc0c608661839439f8ecc6a3db4e48d46d99216ee4 + languageName: node + linkType: hard + +"pure-rand@npm:^7.0.0": + version: 7.0.1 + resolution: "pure-rand@npm:7.0.1" + checksum: 10/c61a576fda5032ec9763ecb000da4a8f19263b9e2f9ae9aa2759c8fbd9dc6b192b2ce78391ebd41abb394a5fedb7bcc4b03c9e6141ac8ab20882dd5717698b80 + languageName: node + linkType: hard + +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 10/591eca457509a99368b623db05248c1193aa3cedafc9a077d7acab09495db1231017ba3ad1b5386e5633271edd0a03b312d8640a59ee585b8516a42e15438aa7 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10/5aa564a1cde7d391ac980bedee21202fc90bdea3b399952117f54fb71a932af1e5902020144fb354b4690b2414a0c7aafe798eb617b76a3d441d956db7726fdf languageName: node linkType: hard -"pathe@npm:^2.0.3": - version: 2.0.3 - resolution: "pathe@npm:2.0.3" - checksum: 10/01e9a69928f39087d96e1751ce7d6d50da8c39abf9a12e0ac2389c42c83bc76f78c45a475bd9026a02e6a6f79be63acc75667df855862fe567d99a00a540d23d +"readable-stream@npm:^2.0.5": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10/8500dd3a90e391d6c5d889256d50ec6026c059fadee98ae9aa9b86757d60ac46fff24fafb7a39fa41d54cb39d8be56cc77be202ebd4cd8ffcf4cb226cbaa40d4 languageName: node linkType: hard -"picocolors@npm:^1.1.1": - version: 1.1.1 - resolution: "picocolors@npm:1.1.1" - checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 +"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10/d9e3e53193adcdb79d8f10f2a1f6989bd4389f5936c6f8b870e77570853561c362bee69feca2bbb7b32368ce96a85504aa4cedf7cf80f36e6a9de30d64244048 languageName: node linkType: hard -"picomatch@npm:^4.0.3": - version: 4.0.4 - resolution: "picomatch@npm:4.0.4" - checksum: 10/f6ef80a3590827ce20378ae110ac78209cc4f74d39236370f1780f957b7ee41c12acde0e4651b90f39983506fd2f5e449994716f516db2e9752924aff8de93ce +"readable-stream@npm:^4.0.0": + version: 4.7.0 + resolution: "readable-stream@npm:4.7.0" + dependencies: + abort-controller: "npm:^3.0.0" + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + process: "npm:^0.11.10" + string_decoder: "npm:^1.3.0" + checksum: 10/bdf096c8ff59452ce5d08f13da9597f9fcfe400b4facfaa88e74ec057e5ad1fdfa140ffe28e5ed806cf4d2055f0b812806e962bca91dce31bc4cef08e53be3a4 languageName: node linkType: hard -"postcss@npm:^8.5.6": - version: 8.5.14 - resolution: "postcss@npm:8.5.14" +"readdir-glob@npm:^1.1.2": + version: 1.1.3 + resolution: "readdir-glob@npm:1.1.3" dependencies: - nanoid: "npm:^3.3.11" - picocolors: "npm:^1.1.1" - source-map-js: "npm:^1.2.1" - checksum: 10/2e3f4dea69692918fe9df5402beb0e54df84499995a094f2fbf63d1a9e38bc1b7a42854df47f09e02593213e01a5eb0627b1d1bd6d1b0ea90767b2e072f7167c + minimatch: "npm:^5.1.0" + checksum: 10/ca3a20aa1e715d671302d4ec785a32bf08e59d6d0dd25d5fc03e9e5a39f8c612cdf809ab3e638a79973db7ad6868492edf38504701e313328e767693671447d6 languageName: node linkType: hard -"proc-log@npm:^5.0.0": - version: 5.0.0 - resolution: "proc-log@npm:5.0.0" - checksum: 10/35610bdb0177d3ab5d35f8827a429fb1dc2518d9e639f2151ac9007f01a061c30e0c635a970c9b00c39102216160f6ec54b62377c92fac3b7bfc2ad4b98d195c +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: 10/ddf44ee76301c774e9c9f2826da8a3c5c9f8fc87310f4a364e803ef003aa1a43c378b4323051ced212097fff1af459070f4499338b36a7469df1d4f7e8c0ba4c languageName: node linkType: hard -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10/96e1a82453c6c96eef53a37a1d6134c9f2482f94068f98a59145d0986ca4e497bf110a410adf73857e588165eab3899f0ebcf7b3890c1b3ce802abc0d65967d4 +"rehackt@npm:^0.1.0": + version: 0.1.0 + resolution: "rehackt@npm:0.1.0" + peerDependencies: + "@types/react": "*" + react: "*" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 10/c81adead82c165dffc574cbf9e1de3605522782a56b48df48b68d53d45c4d8c9253df3790109335bf97072424e54ad2423bb9544ca3a985fa91995dda43452fc languageName: node linkType: hard -"pure-rand@npm:^7.0.0": - version: 7.0.1 - resolution: "pure-rand@npm:7.0.1" - checksum: 10/c61a576fda5032ec9763ecb000da4a8f19263b9e2f9ae9aa2759c8fbd9dc6b192b2ce78391ebd41abb394a5fedb7bcc4b03c9e6141ac8ab20882dd5717698b80 +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10/a72468e2589270d91f06c7d36ec97a88db53ae5d6fe3787fadc943f0b0276b10347f89b363b2a82285f650bdcc135ad4a257c61bdd4d00d6df1fa24875b0ddaf languageName: node linkType: hard @@ -1776,13 +4789,57 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3.0.0": +"rxjs@npm:^7.5.0, rxjs@npm:^7.8.1, rxjs@npm:^7.8.2": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a + languageName: node + linkType: hard + +"safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 languageName: node linkType: hard +"scale-ts@npm:^1.6.0": + version: 1.6.1 + resolution: "scale-ts@npm:1.6.1" + checksum: 10/f1f9bf1d9abfcfcaf8ae2ae326270beca5c2456cc72f6b6b8230aa175a30bdcd6387678746a4d873c834efbba9c8e015698d42ee67bd71b70f7adfe2e0ba1d39 + languageName: node + linkType: hard + +"secure-json-parse@npm:^4.0.0": + version: 4.1.0 + resolution: "secure-json-parse@npm:4.1.0" + checksum: 10/1025c6fd0b8fa0e8c6ac7225fc0b79ecc528b2e51a8446e4bb73bfc47a2450b9e9e9813b84bc9e6735ce30c947b52e5b9d90771521aa9bb2ec216afd24c2da4e + languageName: node + linkType: hard + "semver@npm:^7.3.5": version: 7.7.3 resolution: "semver@npm:7.7.3" @@ -1822,6 +4879,13 @@ __metadata: languageName: node linkType: hard +"signal-exit@npm:^3.0.2": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + "signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" @@ -1836,6 +4900,22 @@ __metadata: languageName: node linkType: hard +"smol-toml@npm:^1.3.4": + version: 1.6.1 + resolution: "smol-toml@npm:1.6.1" + checksum: 10/9a0d86cc7f8abef429c915b373b9a1f369fe57a87efbbec46b967fb41dc28af753a2fa62c9c4848907c3b47c282be15c8854aa4e2942ef1fa86ff95a76d13856 + languageName: node + linkType: hard + +"smoldot@npm:2.0.26": + version: 2.0.26 + resolution: "smoldot@npm:2.0.26" + dependencies: + ws: "npm:^8.8.1" + checksum: 10/b975c8ef16e2286b2eddc8c19c18080bd528f27e9abc0e2731304823e67ebe1fc71b01bed2c070d00da1f7e2f69e25c159c976d27eb1796de4a978362dae701e + languageName: node + linkType: hard + "socks-proxy-agent@npm:^8.0.3": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" @@ -1857,6 +4937,15 @@ __metadata: languageName: node linkType: hard +"sonic-boom@npm:^4.0.1": + version: 4.2.1 + resolution: "sonic-boom@npm:4.2.1" + dependencies: + atomic-sleep: "npm:^1.0.0" + checksum: 10/161af46b3e6debc4ad3865b0db47f37289741a0b3005b8cf056f93a4e0e1a347e24ca1a2d8ccc864f7f19caa6185a766797f8382cdbfd2f3d046a0323d73a542 + languageName: node + linkType: hard + "source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" @@ -1864,6 +4953,47 @@ __metadata: languageName: node linkType: hard +"split-ca@npm:^1.0.1": + version: 1.0.1 + resolution: "split-ca@npm:1.0.1" + checksum: 10/1e7409938a95ee843fe2593156a5735e6ee63772748ee448ea8477a5a3e3abde193c3325b3696e56a5aff07c7dcf6b1f6a2f2a036895b4f3afe96abb366d893f + languageName: node + linkType: hard + +"split2@npm:^4.0.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10/09bbefc11bcf03f044584c9764cd31a252d8e52cea29130950b26161287c11f519807c5e54bd9e5804c713b79c02cefe6a98f4688630993386be353e03f534ab + languageName: node + linkType: hard + +"ssh-remote-port-forward@npm:^1.0.4": + version: 1.0.4 + resolution: "ssh-remote-port-forward@npm:1.0.4" + dependencies: + "@types/ssh2": "npm:^0.5.48" + ssh2: "npm:^1.4.0" + checksum: 10/c6c04c5ddfde7cb06e9a8655a152bd28fe6771c6fe62ff0bc08be229491546c410f30b153c968b8d6817a57d38678a270c228f30143ec0fe1be546efc4f6b65a + languageName: node + linkType: hard + +"ssh2@npm:^1.15.0, ssh2@npm:^1.4.0": + version: 1.17.0 + resolution: "ssh2@npm:1.17.0" + dependencies: + asn1: "npm:^0.2.6" + bcrypt-pbkdf: "npm:^1.0.2" + cpu-features: "npm:~0.0.10" + nan: "npm:^2.23.0" + dependenciesMeta: + cpu-features: + optional: true + nan: + optional: true + checksum: 10/5a7e911f234f73c4332f2b436cc6131c164962d2eac71f463ab401b54c4b8627875d9c9be1c55e0bfd1a0eae108cfa33217bc73939287e4a5e81f34f532b1036 + languageName: node + linkType: hard + "ssri@npm:^12.0.0": version: 12.0.0 resolution: "ssri@npm:12.0.0" @@ -1894,7 +5024,18 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": +"streamx@npm:^2.12.5, streamx@npm:^2.15.0, streamx@npm:^2.25.0": + version: 2.25.0 + resolution: "streamx@npm:2.25.0" + dependencies: + events-universal: "npm:^1.0.0" + fast-fifo: "npm:^1.3.2" + text-decoder: "npm:^1.1.0" + checksum: 10/d00dd38a1b73e4dac5225344aee421eb12ba9dded3f0ee3427d358d663677af185bc2310f46cb85ff3da31e032a50514d6f66348ba756154fe8a89b845273a3c + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -1926,6 +5067,24 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10/54d23f4a6acae0e93f999a585e673be9e561b65cd4cca37714af1e893ab8cd8dfa52a9e4f58f48f87b4a44918d3a9254326cb80ed194bf2e4c226e2b21767e56 + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10/7c41c17ed4dea105231f6df208002ebddd732e8e9e2d619d133cecd8e0087ddfd9587d2feb3c8caf3213cbd841ada6d057f5142cae68a4e62d3540778d9819b4 + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -1944,6 +5103,83 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:^5.0.2": + version: 5.0.3 + resolution: "strip-json-comments@npm:5.0.3" + checksum: 10/3ccbf26f278220f785e4b71f8a719a6a063d72558cc63cb450924254af258a4f4c008b8c9b055373a680dc7bd525be9e543ad742c177f8a7667e0b726258e0e4 + languageName: node + linkType: hard + +"superjson@npm:^2.0.0": + version: 2.2.6 + resolution: "superjson@npm:2.2.6" + dependencies: + copy-anything: "npm:^4" + checksum: 10/7bb6446b70e8a37ec9aa2f2d08295ae4e7e8268b86c89d83a306b3798cd0cc60d89016c0c5fa83b558db23e8de8863c585a4cf52d18c4834c48bad7d2b6ee25b + languageName: node + linkType: hard + +"symbol-observable@npm:^4.0.0": + version: 4.0.0 + resolution: "symbol-observable@npm:4.0.0" + checksum: 10/983aef3912ad080fc834b9ad115d44bc2994074c57cea4fb008e9f7ab9bb4118b908c63d9edc861f51257bc0595025510bdf7263bb09d8953a6929f240165c24 + languageName: node + linkType: hard + +"tar-fs@npm:^2.1.4": + version: 2.1.4 + resolution: "tar-fs@npm:2.1.4" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.1.4" + checksum: 10/bdf7e3cb039522e39c6dae3084b1bca8d7bcc1de1906eae4a1caea6a2250d22d26dcc234118bf879b345d91ebf250a744b196e379334a4abcbb109a78db7d3be + languageName: node + linkType: hard + +"tar-fs@npm:^3.0.7": + version: 3.1.2 + resolution: "tar-fs@npm:3.1.2" + dependencies: + bare-fs: "npm:^4.0.1" + bare-path: "npm:^3.0.0" + pump: "npm:^3.0.0" + tar-stream: "npm:^3.1.5" + dependenciesMeta: + bare-fs: + optional: true + bare-path: + optional: true + checksum: 10/b358fb7061eebb42bfa6f122cf62d1bdd40dc619117863f3b59eeaa4f880dc03707014905bdb592e77176703d9045956d1ba27adda4458805f9f7cbf62015cbd + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: "npm:^4.0.3" + end-of-stream: "npm:^1.4.1" + fs-constants: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + checksum: 10/1a52a51d240c118cbcd30f7368ea5e5baef1eac3e6b793fb1a41e6cd7319296c79c0264ccc5859f5294aa80f8f00b9239d519e627b9aade80038de6f966fec6a + languageName: node + linkType: hard + +"tar-stream@npm:^3.0.0, tar-stream@npm:^3.1.5": + version: 3.2.0 + resolution: "tar-stream@npm:3.2.0" + dependencies: + b4a: "npm:^1.6.4" + bare-fs: "npm:^4.5.5" + fast-fifo: "npm:^1.2.0" + streamx: "npm:^2.15.0" + checksum: 10/ce57a81521de73ae7a3b7d55a08da50d6771427c249bfa89a208518e48faf5254c8fa7201a8f5419ab8bde9601a74e6dd512b31a13ec89774aec96178f99a8d3 + languageName: node + linkType: hard + "tar@npm:^7.4.3": version: 7.5.14 resolution: "tar@npm:7.5.14" @@ -1957,6 +5193,56 @@ __metadata: languageName: node linkType: hard +"teex@npm:^1.0.1": + version: 1.0.1 + resolution: "teex@npm:1.0.1" + dependencies: + streamx: "npm:^2.12.5" + checksum: 10/36bf7ce8bb5eb428ad7b14b695ee7fb0a02f09c1a9d8181cc42531208543a920b299d711bf78dad4ff9bcf36ac437ae8e138053734746076e3e0e7d6d76eef64 + languageName: node + linkType: hard + +"testcontainers@npm:^10.28.0": + version: 10.28.0 + resolution: "testcontainers@npm:10.28.0" + dependencies: + "@balena/dockerignore": "npm:^1.0.2" + "@types/dockerode": "npm:^3.3.35" + archiver: "npm:^7.0.1" + async-lock: "npm:^1.4.1" + byline: "npm:^5.0.0" + debug: "npm:^4.3.5" + docker-compose: "npm:^0.24.8" + dockerode: "npm:^4.0.5" + get-port: "npm:^7.1.0" + proper-lockfile: "npm:^4.1.2" + properties-reader: "npm:^2.3.0" + ssh-remote-port-forward: "npm:^1.0.4" + tar-fs: "npm:^3.0.7" + tmp: "npm:^0.2.3" + undici: "npm:^5.29.0" + checksum: 10/434d3677e10a114805420f2420831a8eae4091acdaf242787fb100a8755140af0e11eab3932cdb29267f0869af22d0b572532f72ee5450d60f63f3fed30d098c + languageName: node + linkType: hard + +"text-decoder@npm:^1.1.0": + version: 1.2.7 + resolution: "text-decoder@npm:1.2.7" + dependencies: + b4a: "npm:^1.6.4" + checksum: 10/151f89339a497353ad579b32536be94bf90a0785fd2aa2dc0a5ec8a4b71ed59998f4adb872201bdc536805425aa8c5cf8f4a936c449be614c1d3c4527688b3d0 + languageName: node + linkType: hard + +"thread-stream@npm:^3.0.0": + version: 3.1.0 + resolution: "thread-stream@npm:3.1.0" + dependencies: + real-require: "npm:^0.2.0" + checksum: 10/ea2d816c4f6077a7062fac5414a88e82977f807c82ee330938fb9691fe11883bb03f078551c0518bb649c239e47ba113d44014fcbb5db42c5abd5996f35e4213 + languageName: node + linkType: hard + "tinybench@npm:^2.9.0": version: 2.9.0 resolution: "tinybench@npm:2.9.0" @@ -1988,6 +5274,29 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.2.3": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10/dd4b78b32385eab4899d3ae296007b34482b035b6d73e1201c4a9aede40860e90997a1452c65a2d21aee73d53e93cd167d741c3db4015d90e63b6d568a93d7ec + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10/8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695 + languageName: node + linkType: hard + +"ts-invariant@npm:^0.10.3": + version: 0.10.3 + resolution: "ts-invariant@npm:0.10.3" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/bb07d56fe4aae69d8860e0301dfdee2d375281159054bc24bf1e49e513fb0835bf7f70a11351344d213a79199c5e695f37ebbf5a447188a377ce0cd81d91ddb5 + languageName: node + linkType: hard + "ts-node@npm:^10.9.2": version: 10.9.2 resolution: "ts-node@npm:10.9.2" @@ -2026,6 +5335,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.7.0, tslib@npm:^2.8.0, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 + languageName: node + linkType: hard + "turbo-darwin-64@npm:2.6.1": version: 2.6.1 resolution: "turbo-darwin-64@npm:2.6.1" @@ -2097,6 +5413,13 @@ __metadata: languageName: node linkType: hard +"tweetnacl@npm:^0.14.3": + version: 0.14.5 + resolution: "tweetnacl@npm:0.14.5" + checksum: 10/04ee27901cde46c1c0a64b9584e04c96c5fe45b38c0d74930710751ea991408b405747d01dfae72f80fc158137018aea94f9c38c651cb9c318f0861a310c3679 + languageName: node + linkType: hard + "typescript@npm:^5.8.2, typescript@npm:^5.9.3": version: 5.9.3 resolution: "typescript@npm:5.9.3" @@ -2117,6 +5440,20 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:>=7.24.0 <7.24.7": + version: 7.24.6 + resolution: "undici-types@npm:7.24.6" + checksum: 10/defc9538b952e3c15b8526596c591f7c1f0c7605ad27a2b7feddbea7ef2e3003f3eda2cdb051a3cb1a2185e3893100fd9cb925c799db99d48131ea63b5233d10 + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10/0097779d94bc0fd26f0418b3a05472410408877279141ded2bd449167be1aed7ea5b76f756562cb3586a07f251b90799bab22d9019ceba49c037c76445f7cddd + languageName: node + linkType: hard + "undici-types@npm:~7.16.0": version: 7.16.0 resolution: "undici-types@npm:7.16.0" @@ -2124,6 +5461,15 @@ __metadata: languageName: node linkType: hard +"undici@npm:^5.29.0": + version: 5.29.0 + resolution: "undici@npm:5.29.0" + dependencies: + "@fastify/busboy": "npm:^2.0.0" + checksum: 10/0ceca8924a32acdcc0cfb8dd2d368c217840970aa3f5e314fc169608474be6341c5b8e50cad7bd257dbe3b4e432bc5d0a0d000f83644b54fa11a48735ec52b93 + languageName: node + linkType: hard + "unique-filename@npm:^4.0.0": version: 4.0.0 resolution: "unique-filename@npm:4.0.0" @@ -2142,6 +5488,22 @@ __metadata: languageName: node linkType: hard +"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10/474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 + languageName: node + linkType: hard + +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10/35aa60614811a201ff90f8ca5e9ecb7076a75c3821e17f0f5ff72d44e36c2d35fcbc2ceee9c4ac7317f4cc41895da30e74f3885e30313bee48fda6338f250538 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -2263,6 +5625,37 @@ __metadata: languageName: node linkType: hard +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9 + languageName: node + linkType: hard + +"web-worker@npm:^1.5.0": + version: 1.5.0 + resolution: "web-worker@npm:1.5.0" + checksum: 10/1209461e2c731fe8e8297c95a8a324c6dd00fd9f3c489ed79d18a15592731324762b7b06c8b6bc404596259aa13cd413119e0153e12a80f47a7f374960461e0d + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10/b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10/f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07 + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -2297,7 +5690,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -2319,6 +5712,35 @@ __metadata: languageName: node linkType: hard +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10/159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 + languageName: node + linkType: hard + +"ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.18.0, ws@npm:^8.8.1": + version: 8.20.1 + resolution: "ws@npm:8.20.1" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/8c4d2b06dc65381b6bfab1f2e584275dabd30a99a5ce058b4dc76f3d03fad1921cef3a21d8f53127d30a808cfd1864aa2fe6890a5d43359f682457315baec873 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10/5f1b5f95e3775de4514edbb142398a2c37849ccfaf04a015be5d75521e9629d3be29bd4432d23c57f37e5b61ade592fb0197022e9993f81a06a5afbdcda9346d + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -2333,6 +5755,37 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.2": + version: 2.9.0 + resolution: "yaml@npm:2.9.0" + bin: + yaml: bin.mjs + checksum: 10/9a95e8e08651c3d292ab6a5befeb5f57b76801caa097c75bb45c9a70ce19c1b11f57e87a6ef84a579ea070ed2c2c8ac541c88c0ae684d544d5f42c7e77d11b7b + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10/9dc2c217ea3bf8d858041252d43e074f7166b53f3d010a8c711275e09cd3d62a002969a39858b92bbda2a6a63a585c7127014534a560b9c69ed2d923d113406e + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10/abb3e37678d6e38ea85485ed86ebe0d1e3464c640d7d9069805ea0da12f69d5a32df8e5625e370f9c96dd1c2dc088ab2d0a4dd32af18222ef3c4224a19471576 + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" @@ -2346,3 +5799,37 @@ __metadata: checksum: 10/6ee42d665a4cc161c7de3f015b2a65d6c65d2808bfe3b99e228bd2b1b784ef1e54d1907415c025fc12b400f26f372bfc1b71966c6c738d998325ca422eb39363 languageName: node linkType: hard + +"zen-observable-ts@npm:^1.1.0, zen-observable-ts@npm:^1.2.5": + version: 1.2.5 + resolution: "zen-observable-ts@npm:1.2.5" + dependencies: + zen-observable: "npm:0.8.15" + checksum: 10/2384cf92a60e39e7b9735a0696f119684fee0f8bcc81d71474c92d656eca1bc3e87b484a04e97546e56bd539f8756bf97cf21a28a933ff7a94b35a8d217848eb + languageName: node + linkType: hard + +"zen-observable@npm:0.8.15": + version: 0.8.15 + resolution: "zen-observable@npm:0.8.15" + checksum: 10/30eac3f4055d33f446b4cd075d3543da347c2c8e68fbc35c3f5a19fb43be67c6ed27ee136bc8f8933efa547be7ce04957809ad00ee7f1b00a964f199ae6fb514 + languageName: node + linkType: hard + +"zip-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "zip-stream@npm:6.0.1" + dependencies: + archiver-utils: "npm:^5.0.0" + compress-commons: "npm:^6.0.2" + readable-stream: "npm:^4.0.0" + checksum: 10/aa5abd6a89590eadeba040afbc375f53337f12637e5e98330012a12d9886cde7a3ccc28bd91aafab50576035bbb1de39a9a316eecf2411c8b9009c9f94f0db27 + languageName: node + linkType: hard + +"zod@npm:^3.23.8": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995 + languageName: node + linkType: hard From 7e30fa8a920e382152eed73c01b05c86ca3ab61a Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Mon, 18 May 2026 15:34:28 +0200 Subject: [PATCH 02/12] refactor(deploy): convert config and loaders to material classes - Replace the four loader functions with classes that own their loaded values: SigningKey, ConstructorArgs, InitialPrivateState, Artifact. Each exposes `static async load()` + readonly fields; pipeline reads `.hex` / `.values` / `?.value` at the call site. - Add shared LoaderContext + RefResolver helpers under loaders/ to absorb path resolution, readFile, dynamic-import error wrapping, and the { file } | { module, export } dispatch. - Promote loadConfig + LoadedConfig to a CompactConfig class with `network(name)` / `contract(name)` lookups that throw with the available set on miss. resolveTargets collapses; wallet/resolve.ts drops its redundant rootDir parameter (derived from config.rootDir). - Rename Zod-inferred CompactConfig -> CompactConfigData (internal); the public name is now the class. --- .../{load.test.ts => compact-config.test.ts} | 33 +++- packages/deploy/src/config/compact-config.ts | 152 +++++++++++++++ packages/deploy/src/config/load.ts | 86 --------- packages/deploy/src/config/schema.ts | 17 +- packages/deploy/src/index.ts | 11 +- packages/deploy/src/loaders/args.test.ts | 28 +-- packages/deploy/src/loaders/args.ts | 127 ++++++------- packages/deploy/src/loaders/artifact.ts | 175 ++++++++++-------- packages/deploy/src/loaders/context.ts | 59 ++++++ .../deploy/src/loaders/init-state.test.ts | 14 +- packages/deploy/src/loaders/init-state.ts | 109 +++++------ packages/deploy/src/loaders/ref-resolver.ts | 48 +++++ .../deploy/src/loaders/signing-key.test.ts | 12 +- packages/deploy/src/loaders/signing-key.ts | 50 ++--- packages/deploy/src/pipeline.ts | 70 +++---- packages/deploy/src/wallet/resolve.ts | 9 +- 16 files changed, 592 insertions(+), 408 deletions(-) rename packages/deploy/src/config/{load.test.ts => compact-config.test.ts} (64%) create mode 100644 packages/deploy/src/config/compact-config.ts delete mode 100644 packages/deploy/src/config/load.ts create mode 100644 packages/deploy/src/loaders/context.ts create mode 100644 packages/deploy/src/loaders/ref-resolver.ts diff --git a/packages/deploy/src/config/load.test.ts b/packages/deploy/src/config/compact-config.test.ts similarity index 64% rename from packages/deploy/src/config/load.test.ts rename to packages/deploy/src/config/compact-config.test.ts index e3856cf..e7123d3 100644 --- a/packages/deploy/src/config/load.test.ts +++ b/packages/deploy/src/config/compact-config.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { ConfigError } from '../errors.ts'; -import { loadConfig } from './load.ts'; +import { CompactConfig } from './compact-config.ts'; const MIN_VALID = ` [profile] @@ -28,19 +28,28 @@ function tmpRepo(toml: string): string { return dir; } -describe('loadConfig', () => { +describe('CompactConfig', () => { it('parses a minimal valid config', async () => { const dir = tmpRepo(MIN_VALID); - const { config, rootDir } = await loadConfig(undefined, dir); - expect(rootDir).toBe(dir); - expect(config.profile.default_network).toBe('local'); - expect(config.networks.local.network_id).toBe('undeployed'); - expect(config.contracts.Token.artifact).toBe('src/artifacts/Token/Token'); + const config = await CompactConfig.load(undefined, dir); + expect(config.rootDir).toBe(dir); + expect(config.defaultNetwork).toBe('local'); + expect(config.network('local').network_id).toBe('undeployed'); + expect(config.contract('Token').artifact).toBe('src/artifacts/Token/Token'); + }); + + it('lookup methods throw with the available set on miss', async () => { + const dir = tmpRepo(MIN_VALID); + const config = await CompactConfig.load(undefined, dir); + expect(() => config.network('ghost')).toThrow(/Available: local/); + expect(() => config.contract('Vault')).toThrow(/Available: Token/); }); it('rejects a config whose default_network does not exist', async () => { const dir = tmpRepo(`${MIN_VALID}\n[profile]\ndefault_network = "ghost"\n`); - await expect(loadConfig(undefined, dir)).rejects.toThrow(ConfigError); + await expect(CompactConfig.load(undefined, dir)).rejects.toThrow( + ConfigError, + ); }); it('rejects a contract missing signing_key_file', async () => { @@ -56,7 +65,9 @@ proof_server = "http://x" [contracts.Token] artifact = "x" `); - await expect(loadConfig(undefined, dir)).rejects.toThrow(ConfigError); + await expect(CompactConfig.load(undefined, dir)).rejects.toThrow( + ConfigError, + ); }); it('rejects when init_private_state is set but private_state_id is not', async () => { @@ -74,6 +85,8 @@ artifact = "x" signing_key_file = "x.sk" init_private_state = { file = "x.json" } `); - await expect(loadConfig(undefined, dir)).rejects.toThrow(ConfigError); + await expect(CompactConfig.load(undefined, dir)).rejects.toThrow( + ConfigError, + ); }); }); diff --git a/packages/deploy/src/config/compact-config.ts b/packages/deploy/src/config/compact-config.ts new file mode 100644 index 0000000..f7c1064 --- /dev/null +++ b/packages/deploy/src/config/compact-config.ts @@ -0,0 +1,152 @@ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { dirname, isAbsolute, resolve } from 'node:path'; +import { parse as parseToml } from 'smol-toml'; +import { ConfigError } from '../errors.ts'; +import { + type CompactConfigData, + type ContractConfig, + type NetworkConfig, + type WalletConfig, + configSchema, +} from './schema.ts'; + +/** + * A parsed and validated `compact.toml`, plus the resolved project root. + * + * Acts as the source of truth for the deploy pipeline — every loader and + * provider derives its paths and target lookups from a single + * `CompactConfig` instance. Lookup methods (`network`, `contract`) throw + * {@link ConfigError} with the available set on miss. + */ +export class CompactConfig { + readonly configPath: string; + readonly rootDir: string; + readonly #data: CompactConfigData; + + private constructor(data: CompactConfigData, configPath: string) { + this.#data = data; + this.configPath = configPath; + this.rootDir = dirname(configPath); + } + + /** + * Find, parse, and validate `compact.toml` against the schema. + * + * When `explicitPath` is omitted the loader walks the directory tree + * upward from `cwd` (Foundry-style) and the first match becomes the + * project root. Pass `--config ` to override. + */ + static async load( + explicitPath?: string, + cwd: string = process.cwd(), + ): Promise { + const configPath = explicitPath + ? resolveExplicit(explicitPath, cwd) + : findUpward(cwd); + if (!configPath) { + throw new ConfigError( + `compact.toml not found (searched upward from ${cwd}). Pass --config or create one at the repo root.`, + ); + } + + let raw: string; + try { + raw = await readFile(configPath, 'utf8'); + } catch (e) { + throw new ConfigError( + `Failed to read ${configPath}: ${(e as Error).message}`, + ); + } + + let parsed: unknown; + try { + parsed = parseToml(raw); + } catch (e) { + throw new ConfigError( + `Invalid TOML in ${configPath}: ${(e as Error).message}`, + ); + } + + const result = configSchema.safeParse(parsed); + if (!result.success) { + const issues = result.error.issues + .map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`) + .join('\n'); + throw new ConfigError(`compact.toml validation failed:\n${issues}`); + } + + return new CompactConfig(result.data, configPath); + } + + get defaultNetwork(): string | undefined { + return this.#data.profile.default_network; + } + + get artifactsDir(): string { + return this.#data.profile.artifacts_dir; + } + + get deploymentsDir(): string { + return this.#data.profile.deployments_dir; + } + + get wallet(): WalletConfig | undefined { + return this.#data.wallet; + } + + hasNetwork(name: string): boolean { + return Object.hasOwn(this.#data.networks, name); + } + + hasContract(name: string): boolean { + return Object.hasOwn(this.#data.contracts, name); + } + + listNetworks(): string[] { + return Object.keys(this.#data.networks); + } + + listContracts(): string[] { + return Object.keys(this.#data.contracts); + } + + network(name: string): NetworkConfig { + const n = this.#data.networks[name]; + if (!n) { + throw new ConfigError( + `Network "${name}" not defined. Available: ${this.listNetworks().join(', ')}`, + ); + } + return n; + } + + contract(name: string): ContractConfig { + const c = this.#data.contracts[name]; + if (!c) { + throw new ConfigError( + `Contract "${name}" not defined. Available: ${this.listContracts().join(', ')}`, + ); + } + return c; + } +} + +function resolveExplicit(p: string, cwd: string): string { + const abs = isAbsolute(p) ? p : resolve(cwd, p); + if (!existsSync(abs)) { + throw new ConfigError(`--config path does not exist: ${abs}`); + } + return abs; +} + +function findUpward(start: string): string | undefined { + let dir = resolve(start); + while (true) { + const candidate = resolve(dir, 'compact.toml'); + if (existsSync(candidate)) return candidate; + const parent = dirname(dir); + if (parent === dir) return undefined; + dir = parent; + } +} diff --git a/packages/deploy/src/config/load.ts b/packages/deploy/src/config/load.ts deleted file mode 100644 index 4e78cf0..0000000 --- a/packages/deploy/src/config/load.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { existsSync } from 'node:fs'; -import { readFile } from 'node:fs/promises'; -import { dirname, isAbsolute, resolve } from 'node:path'; -import { parse as parseToml } from 'smol-toml'; -import { ConfigError } from '../errors.ts'; -import { type CompactConfig, configSchema } from './schema.ts'; - -/** - * Find, parse, and validate `compact.toml` against {@link configSchema}. - * - * When `explicitPath` is omitted the loader walks the directory tree - * upward from `cwd` (Foundry-style) and the *first* `compact.toml` found - * becomes the project root. `rootDir` in the returned bundle is always the - * config file's directory — every other module resolves relative paths - * against it, so the same TOML works whether invoked from a subdir or root. - */ -export interface LoadedConfig { - config: CompactConfig; - configPath: string; - rootDir: string; -} - -export async function loadConfig( - explicitPath: string | undefined, - cwd = process.cwd(), -): Promise { - const configPath = explicitPath - ? resolveExplicit(explicitPath, cwd) - : findUpward(cwd); - if (!configPath) { - throw new ConfigError( - `compact.toml not found (searched upward from ${cwd}). Pass --config or create one at the repo root.`, - ); - } - - let raw: string; - try { - raw = await readFile(configPath, 'utf8'); - } catch (e) { - throw new ConfigError( - `Failed to read ${configPath}: ${(e as Error).message}`, - ); - } - - let parsed: unknown; - try { - parsed = parseToml(raw); - } catch (e) { - throw new ConfigError( - `Invalid TOML in ${configPath}: ${(e as Error).message}`, - ); - } - - const result = configSchema.safeParse(parsed); - if (!result.success) { - const issues = result.error.issues - .map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`) - .join('\n'); - throw new ConfigError(`compact.toml validation failed:\n${issues}`); - } - - return { - config: result.data, - configPath, - rootDir: dirname(configPath), - }; -} - -function resolveExplicit(p: string, cwd: string): string { - const abs = isAbsolute(p) ? p : resolve(cwd, p); - if (!existsSync(abs)) { - throw new ConfigError(`--config path does not exist: ${abs}`); - } - return abs; -} - -function findUpward(start: string): string | undefined { - let dir = resolve(start); - while (true) { - const candidate = resolve(dir, 'compact.toml'); - if (existsSync(candidate)) return candidate; - const parent = dirname(dir); - if (parent === dir) return undefined; - dir = parent; - } -} diff --git a/packages/deploy/src/config/schema.ts b/packages/deploy/src/config/schema.ts index f02ce6f..bc7a3b8 100644 --- a/packages/deploy/src/config/schema.ts +++ b/packages/deploy/src/config/schema.ts @@ -44,11 +44,10 @@ const networkSchema = z.object({ faucet_url: url.optional(), }); -const walletSchema = z - .object({ - keystore: z.string().optional(), - }) - .optional(); +const walletObjectSchema = z.object({ + keystore: z.string().optional(), +}); +const walletSchema = walletObjectSchema.optional(); const fileRefSchema = z.object({ file: z.string().min(1) }); const moduleRefSchema = z.object({ @@ -97,9 +96,15 @@ export const configSchema = z }, ); -export type CompactConfig = z.infer; +/** + * Zod-inferred shape of a validated `compact.toml`. Used internally by + * the {@link CompactConfig} class; not exported from the package barrel. + */ +export type CompactConfigData = z.infer; export type NetworkConfig = z.infer; export type ContractConfig = z.infer; +export type Profile = z.infer; +export type WalletConfig = z.infer; export type FileRef = z.infer; export type ModuleRef = z.infer; export type FileOrModuleRef = z.infer; diff --git a/packages/deploy/src/index.ts b/packages/deploy/src/index.ts index 505ec3f..2797eb6 100644 --- a/packages/deploy/src/index.ts +++ b/packages/deploy/src/index.ts @@ -6,11 +6,12 @@ * in `bin/` re-uses the same exports — it is just an opinionated shell. */ // biome-ignore-all lint/performance/noBarrelFile: this file is the programmatic API surface for consumers of @openzeppelin/compact-deploy -export { loadConfig } from './config/load.ts'; +export { CompactConfig } from './config/compact-config.ts'; export type { - CompactConfig, ContractConfig, NetworkConfig, + Profile, + WalletConfig, } from './config/schema.ts'; export { Deployments } from './deployments.ts'; export type { @@ -33,6 +34,12 @@ export { UnfundedWalletError, WalletError, } from './errors.ts'; +export { Artifact } from './loaders/artifact.ts'; +export type { LoadArtifactOptions } from './loaders/artifact.ts'; +export { ConstructorArgs } from './loaders/args.ts'; +export type { ArgsSource } from './loaders/args.ts'; +export { InitialPrivateState } from './loaders/init-state.ts'; +export { SigningKey } from './loaders/signing-key.ts'; export { Keystore } from './wallet/keystore.ts'; export type { MidnightKeystore } from './wallet/keystore.ts'; export { ProofServer } from './providers/proof-server.ts'; diff --git a/packages/deploy/src/loaders/args.test.ts b/packages/deploy/src/loaders/args.test.ts index 4f15106..11c18b4 100644 --- a/packages/deploy/src/loaders/args.test.ts +++ b/packages/deploy/src/loaders/args.test.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import type { ContractConfig } from '../config/schema.ts'; import { ConfigError } from '../errors.ts'; -import { loadConstructorArgs } from './args.ts'; +import { ConstructorArgs } from './args.ts'; const baseContract = (extra: Partial = {}): ContractConfig => ({ @@ -13,38 +13,42 @@ const baseContract = (extra: Partial = {}): ContractConfig => ...extra, }) as ContractConfig; -describe('loadConstructorArgs', () => { - it('returns [] when args is unset', async () => { - const args = await loadConstructorArgs(baseContract(), '/tmp'); - expect(args).toEqual([]); +describe('ConstructorArgs', () => { + it('returns empty values when args is unset', async () => { + const args = await ConstructorArgs.load(baseContract(), '/tmp'); + expect(args.values).toEqual([]); + expect(args.source).toBe('empty'); }); it('passes inline arrays through', async () => { - const args = await loadConstructorArgs( + const args = await ConstructorArgs.load( baseContract({ args: ['MyToken', 'MTK', 18] }), '/tmp', ); - expect(args).toEqual(['MyToken', 'MTK', 18]); + expect(args.values).toEqual(['MyToken', 'MTK', 18]); + expect(args.source).toBe('inline'); }); it('reads a JSON file ref and revives bigints', async () => { const dir = mkdtempSync(join(tmpdir(), 'args-test-')); writeFileSync(join(dir, 'a.json'), '["x", "100n"]'); - const args = await loadConstructorArgs( + const args = await ConstructorArgs.load( baseContract({ args: { file: 'a.json' } }), dir, ); - expect(args).toEqual(['x', 100n]); + expect(args.values).toEqual(['x', 100n]); + expect(args.source).toBe('file'); }); it('parses a --args override JSON string', async () => { - const args = await loadConstructorArgs(baseContract(), '/tmp', '[1,2,3]'); - expect(args).toEqual([1, 2, 3]); + const args = await ConstructorArgs.load(baseContract(), '/tmp', '[1,2,3]'); + expect(args.values).toEqual([1, 2, 3]); + expect(args.source).toBe('cli'); }); it('rejects a non-array --args override', async () => { await expect( - loadConstructorArgs(baseContract(), '/tmp', '{"x":1}'), + ConstructorArgs.load(baseContract(), '/tmp', '{"x":1}'), ).rejects.toThrow(ConfigError); }); }); diff --git a/packages/deploy/src/loaders/args.ts b/packages/deploy/src/loaders/args.ts index 52885f2..0bd129c 100644 --- a/packages/deploy/src/loaders/args.ts +++ b/packages/deploy/src/loaders/args.ts @@ -1,74 +1,71 @@ -import { readFile } from 'node:fs/promises'; -import { isAbsolute, resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import { - type ContractConfig, - isFileRef, - isModuleRef, -} from '../config/schema.ts'; +import { type ContractConfig, isFileRef } from '../config/schema.ts'; import { ConfigError } from '../errors.ts'; +import { LoaderContext } from './context.ts'; +import { RefResolver } from './ref-resolver.ts'; + +export type ArgsSource = 'cli' | 'inline' | 'file' | 'module' | 'empty'; /** - * Resolve a contract's constructor args from CLI / TOML / file / module. - * - * Precedence (highest first): - * 1. `override` (CLI `--args '[…]'`, parsed as JSON). - * 2. Inline `args = [...]` array in TOML. - * 3. `args = { file = "…" }` → JSON file (bigints encoded as `"123n"`). - * 4. `args = { module = "…", export = "…" }` → ES module export (value - * or zero-arg function returning an array). + * A contract's constructor argument list, hydrated from the highest-precedence + * source available in `compact.toml` / CLI flags. * - * Returns `[]` when no source supplies args. + * The `source` field records *where* the values came from — useful for + * debug logging without re-running the resolution logic. */ -export async function loadConstructorArgs( - contract: ContractConfig, - rootDir: string, - override?: string, -): Promise { - if (override !== undefined) { - return parseJsonArray(override, '--args'); - } - const raw = contract.args; - if (raw === undefined) return []; +export class ConstructorArgs { + readonly values: readonly unknown[]; + readonly source: ArgsSource; - if (Array.isArray(raw)) return raw; - - if (isFileRef(raw)) { - const path = abs(rootDir, raw.file); - const text = await safeRead(path, 'args file'); - return parseJsonArray(text, path); + private constructor(values: readonly unknown[], source: ArgsSource) { + this.values = values; + this.source = source; } - if (isModuleRef(raw)) { - const path = abs(rootDir, raw.module); - let mod: Record; - try { - mod = await import(pathToFileURL(path).href); - } catch (e) { - throw new ConfigError( - `args: failed to import ${path}: ${(e as Error).message}`, - ); - } - const exported = mod[raw.export]; - const resolved = - typeof exported === 'function' - ? await (exported as () => unknown)() - : exported; - if (!Array.isArray(resolved)) { - throw new ConfigError( - `args: module ${path} export "${raw.export}" must be an array`, - ); + /** + * Resolve args. Precedence (highest first): + * 1. `override` (CLI `--args '[…]'`, parsed as JSON). + * 2. Inline `args = [...]` array in TOML. + * 3. `args = { file = "…" }` → JSON file (bigints encoded as `"123n"`). + * 4. `args = { module = "…", export = "…" }` → ES module export (value + * or zero-arg function returning an array). + * + * Returns an instance with `values = []` and `source = 'empty'` when no + * source supplies args. + */ + static async load( + contract: ContractConfig, + rootDir: string, + override?: string, + ): Promise { + if (override !== undefined) { + return new ConstructorArgs(parseJsonArray(override, '--args'), 'cli'); } - return resolved; - } + const raw = contract.args; + if (raw === undefined) return new ConstructorArgs([], 'empty'); + if (Array.isArray(raw)) return new ConstructorArgs(raw, 'inline'); - throw new ConfigError( - 'args must be an inline array, { file }, or { module, export }', - ); -} + const resolver = new RefResolver( + new LoaderContext(rootDir), + 'args', + ); + const values = await resolver.resolve( + raw, + (text, path) => parseJsonArray(text, path), + (value, path, exp) => { + if (!Array.isArray(value)) { + throw new ConfigError( + `args: module ${path} export "${exp}" must be an array`, + ); + } + return value; + }, + ); + return new ConstructorArgs(values, isFileRef(raw) ? 'file' : 'module'); + } -function abs(rootDir: string, p: string): string { - return isAbsolute(p) ? p : resolve(rootDir, p); + get length(): number { + return this.values.length; + } } function parseJsonArray(text: string, label: string): unknown[] { @@ -87,13 +84,3 @@ function parseJsonArray(text: string, label: string): unknown[] { } return parsed; } - -async function safeRead(path: string, label: string): Promise { - try { - return await readFile(path, 'utf8'); - } catch (e) { - throw new ConfigError( - `Failed to read ${label} (${path}): ${(e as Error).message}`, - ); - } -} diff --git a/packages/deploy/src/loaders/artifact.ts b/packages/deploy/src/loaders/artifact.ts index b6fa29b..138cc6a 100644 --- a/packages/deploy/src/loaders/artifact.ts +++ b/packages/deploy/src/loaders/artifact.ts @@ -1,10 +1,6 @@ import { existsSync, readdirSync } from 'node:fs'; import { isAbsolute, resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import { - CompiledContract, - type Contract, -} from '@midnight-ntwrk/compact-js'; +import { CompiledContract, type Contract } from '@midnight-ntwrk/compact-js'; import type { Types } from 'effect'; import { isFileRef, @@ -12,9 +8,10 @@ import { type FileOrModuleRef, } from '../config/schema.ts'; import { ArtifactNotFoundError, ConfigError } from '../errors.ts'; +import { LoaderContext } from './context.ts'; /** - * Locate a compactc artifact bundle on disk and wrap it for the deploy + * A compactc artifact bundle, located on disk and wrapped for the deploy * pipeline. * * The bundle layout (produced by `compactc` / `compact-builder`) is: @@ -27,15 +24,11 @@ import { ArtifactNotFoundError, ConfigError } from '../errors.ts'; type AnyContract = Contract.Any; type AnyWitnesses = Contract.Witnesses; -type AnyCompiledContract = CompiledContract.CompiledContract; - -/** Output of {@link loadArtifact}; consumed by {@link buildProviders} and the pipeline. */ -export interface LoadedArtifact { - compiledContract: AnyCompiledContract; - zkConfigPath: string; - artifactPath: string; - circuitNames: string[]; -} +type AnyCompiledContract = CompiledContract.CompiledContract< + AnyContract, + unknown, + never +>; export interface LoadArtifactOptions { rootDir: string; @@ -45,72 +38,97 @@ export interface LoadArtifactOptions { witnesses?: FileOrModuleRef; } -/** - * Resolve, validate, and import a compactc artifact bundle. - * - * Throws {@link ArtifactNotFoundError} when the directory, `contract/index` - * entry, or `keys/`/`zkir/` subdirs are missing. The returned `circuitNames` - * is a sorted list scraped from `.bzkir` files — useful for diagnostics and - * for the JSON CLI output. - */ -export async function loadArtifact({ - rootDir, - artifactsDir, - artifact, - contractName, - witnesses, -}: LoadArtifactOptions): Promise { - const artifactPath = resolveUnderRoot(rootDir, artifact, artifactsDir); - - if (!existsSync(artifactPath)) { - throw new ArtifactNotFoundError(artifactPath); - } - - const contractDir = resolve(artifactPath, 'contract'); - const entry = findEntry(contractDir, artifactPath); - if (!entry) { - throw new ArtifactNotFoundError( - `${artifactPath} (no contract/index.{cjs,js} or index.{cjs,js} found)`, - ); +export class Artifact { + readonly compiledContract: AnyCompiledContract; + readonly artifactPath: string; + readonly zkConfigPath: string; + readonly circuitNames: readonly string[]; + + private constructor(input: { + compiledContract: AnyCompiledContract; + artifactPath: string; + zkConfigPath: string; + circuitNames: readonly string[]; + }) { + this.compiledContract = input.compiledContract; + this.artifactPath = input.artifactPath; + this.zkConfigPath = input.zkConfigPath; + this.circuitNames = input.circuitNames; } - const keysDir = resolve(artifactPath, 'keys'); - const zkirDir = resolve(artifactPath, 'zkir'); - if (!existsSync(keysDir) || !existsSync(zkirDir)) { - throw new ArtifactNotFoundError( - `${artifactPath} (missing keys/ or zkir/ subdirectory)`, - ); + /** + * Resolve, validate, and import a compactc artifact bundle. + * + * Throws {@link ArtifactNotFoundError} when the directory, `contract/index` + * entry, or `keys/`/`zkir/` subdirs are missing. The returned `circuitNames` + * is a sorted list scraped from `.bzkir` files — useful for diagnostics + * and for the JSON CLI output. + */ + static async load(opts: LoadArtifactOptions): Promise { + const { rootDir, artifactsDir, artifact, contractName, witnesses } = opts; + const ctx = new LoaderContext(rootDir); + const artifactPath = resolveUnderRoot(rootDir, artifact, artifactsDir); + + if (!existsSync(artifactPath)) { + throw new ArtifactNotFoundError(artifactPath); + } + + const contractDir = resolve(artifactPath, 'contract'); + const entry = findEntry(contractDir, artifactPath); + if (!entry) { + throw new ArtifactNotFoundError( + `${artifactPath} (no contract/index.{cjs,js} or index.{cjs,js} found)`, + ); + } + + const keysDir = resolve(artifactPath, 'keys'); + const zkirDir = resolve(artifactPath, 'zkir'); + if (!existsSync(keysDir) || !existsSync(zkirDir)) { + throw new ArtifactNotFoundError( + `${artifactPath} (missing keys/ or zkir/ subdirectory)`, + ); + } + + const circuitNames = collectCircuitNames(zkirDir); + const Ctor = await importContractCtor(ctx, entry); + const witnessImpls = witnesses + ? await importWitnesses(ctx, witnesses) + : undefined; + + const compiledContract = buildCompiledContract({ + contractName, + Ctor, + witnessImpls, + contractDir, + }); + + return new Artifact({ + compiledContract, + artifactPath, + zkConfigPath: artifactPath, + circuitNames, + }); } - - const circuitNames = collectCircuitNames(zkirDir); - - const Ctor = await importContractCtor(entry); - const witnessImpls = witnesses ? await importWitnesses(witnesses, rootDir) : undefined; - - const compiledContract = buildCompiledContract({ - contractName, - Ctor, - witnessImpls, - contractDir, - }); - - return { compiledContract, zkConfigPath: artifactPath, artifactPath, circuitNames }; } -async function importContractCtor(entry: string): Promise> { - const mod = (await import(pathToFileURL(entry).href)) as ArtifactModule; - const Ctor = mod.Contract ?? mod.default?.Contract; +async function importContractCtor( + ctx: LoaderContext, + entry: string, +): Promise> { + const { mod, path } = await ctx.importModule(entry, 'artifact'); + const m = mod as ArtifactModule; + const Ctor = m.Contract ?? m.default?.Contract; if (!Ctor) { throw new ConfigError( - `Artifact at ${entry} does not export a \`Contract\` class (got keys: ${Object.keys(mod).join(', ')})`, + `Artifact at ${path} does not export a \`Contract\` class (got keys: ${Object.keys(m).join(', ')})`, ); } return Ctor; } async function importWitnesses( + ctx: LoaderContext, ref: FileOrModuleRef, - rootDir: string, ): Promise { if (isFileRef(ref)) { throw new ConfigError( @@ -120,16 +138,12 @@ async function importWitnesses( if (!isModuleRef(ref)) { throw new ConfigError('witnesses must be { module, export }'); } - const path = isAbsolute(ref.module) ? ref.module : resolve(rootDir, ref.module); - let mod: Record; - try { - mod = await import(pathToFileURL(path).href); - } catch (e) { - throw new ConfigError(`witnesses: failed to import ${path}: ${(e as Error).message}`); - } + const { mod, path } = await ctx.importModule(ref.module, 'witnesses'); const exported = mod[ref.export]; const resolved = - typeof exported === 'function' ? await (exported as () => unknown)() : exported; + typeof exported === 'function' + ? await (exported as () => unknown)() + : exported; if (typeof resolved !== 'object' || resolved === null) { throw new ConfigError( `witnesses: module ${path} export "${ref.export}" must resolve to an object`, @@ -156,14 +170,21 @@ interface ArtifactModule { default?: { Contract?: Types.Ctor }; } -function resolveUnderRoot(rootDir: string, artifact: string, artifactsDir: string): string { +function resolveUnderRoot( + rootDir: string, + artifact: string, + artifactsDir: string, +): string { if (isAbsolute(artifact)) return artifact; const direct = resolve(rootDir, artifact); if (existsSync(direct)) return direct; return resolve(rootDir, artifactsDir, artifact); } -function findEntry(contractDir: string, artifactDir: string): string | undefined { +function findEntry( + contractDir: string, + artifactDir: string, +): string | undefined { const candidates = [ resolve(contractDir, 'index.cjs'), resolve(contractDir, 'index.js'), diff --git a/packages/deploy/src/loaders/context.ts b/packages/deploy/src/loaders/context.ts new file mode 100644 index 0000000..a17e663 --- /dev/null +++ b/packages/deploy/src/loaders/context.ts @@ -0,0 +1,59 @@ +import { readFile } from 'node:fs/promises'; +import { isAbsolute, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { ConfigError } from '../errors.ts'; + +/** + * Per-call helper bundle for loaders. + * + * Wraps `rootDir` and the three I/O primitives every loader needs — path + * resolution, UTF-8 file read, ES-module import — so the `try { … } catch + * (e) { throw new ConfigError(…) }` boilerplate lives in exactly one place. + */ +export class LoaderContext { + readonly rootDir: string; + + constructor(rootDir: string) { + this.rootDir = rootDir; + } + + /** Resolve `p` against `rootDir`, unless `p` is already absolute. */ + abs(p: string): string { + return isAbsolute(p) ? p : resolve(this.rootDir, p); + } + + /** Read a UTF-8 file; returns the text alongside the absolute path used. */ + async readText( + p: string, + label: string, + ): Promise<{ text: string; path: string }> { + const path = this.abs(p); + try { + const text = await readFile(path, 'utf8'); + return { text, path }; + } catch (e) { + throw new ConfigError( + `${label}: failed to read ${path}: ${(e as Error).message}`, + ); + } + } + + /** Dynamic-import an ES module by file path; returns module + absolute path. */ + async importModule( + p: string, + label: string, + ): Promise<{ mod: Record; path: string }> { + const path = this.abs(p); + try { + const mod = (await import(pathToFileURL(path).href)) as Record< + string, + unknown + >; + return { mod, path }; + } catch (e) { + throw new ConfigError( + `${label}: failed to import ${path}: ${(e as Error).message}`, + ); + } + } +} diff --git a/packages/deploy/src/loaders/init-state.test.ts b/packages/deploy/src/loaders/init-state.test.ts index 905cd3e..c5f938a 100644 --- a/packages/deploy/src/loaders/init-state.test.ts +++ b/packages/deploy/src/loaders/init-state.test.ts @@ -3,23 +3,23 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { ConfigError } from '../errors.ts'; -import { loadInitialPrivateState } from './init-state.ts'; +import { InitialPrivateState } from './init-state.ts'; -describe('loadInitialPrivateState', () => { +describe('InitialPrivateState', () => { it('returns undefined when ref is absent', async () => { - expect(await loadInitialPrivateState(undefined, '/tmp')).toBeUndefined(); + expect(await InitialPrivateState.load(undefined, '/tmp')).toBeUndefined(); }); it('parses a { file } JSON ref with bigint revival', async () => { const dir = mkdtempSync(join(tmpdir(), 'initstate-test-')); writeFileSync(join(dir, 's.json'), '{"counter":"100n","name":"x"}'); - const state = await loadInitialPrivateState({ file: 's.json' }, dir); - expect(state).toEqual({ counter: 100n, name: 'x' }); + const state = await InitialPrivateState.load({ file: 's.json' }, dir); + expect(state?.value).toEqual({ counter: 100n, name: 'x' }); }); it('throws ConfigError for missing files', async () => { await expect( - loadInitialPrivateState({ file: 'does-not-exist.json' }, '/tmp'), + InitialPrivateState.load({ file: 'does-not-exist.json' }, '/tmp'), ).rejects.toThrow(ConfigError); }); @@ -27,7 +27,7 @@ describe('loadInitialPrivateState', () => { const dir = mkdtempSync(join(tmpdir(), 'initstate-test-')); writeFileSync(join(dir, 'bad.json'), 'not json'); await expect( - loadInitialPrivateState({ file: 'bad.json' }, dir), + InitialPrivateState.load({ file: 'bad.json' }, dir), ).rejects.toThrow(ConfigError); }); }); diff --git a/packages/deploy/src/loaders/init-state.ts b/packages/deploy/src/loaders/init-state.ts index 62225a0..2c81427 100644 --- a/packages/deploy/src/loaders/init-state.ts +++ b/packages/deploy/src/loaders/init-state.ts @@ -1,73 +1,60 @@ -import { readFile } from 'node:fs/promises'; -import { isAbsolute, resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import { - type FileOrModuleRef, - isFileRef, - isModuleRef, -} from '../config/schema.ts'; +import { type FileOrModuleRef } from '../config/schema.ts'; import { ConfigError } from '../errors.ts'; +import { LoaderContext } from './context.ts'; +import { RefResolver } from './ref-resolver.ts'; /** - * Load the initial private state passed to a contract's constructor. + * The initial private-state value passed to a contract's constructor. * - * Source is either `{ file }` (JSON, with `"123n"` strings revived as - * bigints) or `{ module, export }` (TS/JS module, value or zero-arg function). - * Returns `undefined` when the config omits `init_private_state`. + * `load` returns `undefined` when `[contracts.X].init_private_state` is + * omitted — a contract either has private state or it doesn't, and we + * surface that distinction at the type level rather than via a sentinel + * `value`. */ -export async function loadInitialPrivateState( - ref: FileOrModuleRef | undefined, - rootDir: string, -): Promise { - if (!ref) return undefined; +export class InitialPrivateState { + readonly value: unknown; - if (isFileRef(ref)) { - const path = abs(rootDir, ref.file); - let raw: string; - try { - raw = await readFile(path, 'utf8'); - } catch (e) { - throw new ConfigError( - `init_private_state: failed to read ${path}: ${(e as Error).message}`, - ); - } - try { - return JSON.parse(raw, bigintReviver); - } catch (e) { - throw new ConfigError( - `init_private_state: invalid JSON at ${path}: ${(e as Error).message}`, - ); - } + private constructor(value: unknown) { + this.value = value; } - if (isModuleRef(ref)) { - const path = abs(rootDir, ref.module); - let mod: Record; - try { - mod = await import(pathToFileURL(path).href); - } catch (e) { - throw new ConfigError( - `init_private_state: failed to import ${path}: ${(e as Error).message}`, - ); - } - const exported = mod[ref.export]; - if (exported === undefined) { - throw new ConfigError( - `init_private_state: module ${path} has no export "${ref.export}"`, - ); - } - return typeof exported === 'function' - ? await (exported as () => unknown)() - : exported; - } - - throw new ConfigError( - 'init_private_state must be { file } or { module, export }', - ); -} + /** + * Source is either `{ file }` (JSON, with `"123n"` strings revived as + * bigints) or `{ module, export }` (TS/JS module, value or zero-arg + * function). + */ + static async load( + ref: FileOrModuleRef | undefined, + rootDir: string, + ): Promise { + if (!ref) return undefined; -function abs(rootDir: string, p: string): string { - return isAbsolute(p) ? p : resolve(rootDir, p); + const resolver = new RefResolver( + new LoaderContext(rootDir), + 'init_private_state', + ); + const value = await resolver.resolve( + ref, + (text, path) => { + try { + return JSON.parse(text, bigintReviver); + } catch (e) { + throw new ConfigError( + `init_private_state: invalid JSON at ${path}: ${(e as Error).message}`, + ); + } + }, + (v, path, exp) => { + if (v === undefined) { + throw new ConfigError( + `init_private_state: module ${path} has no export "${exp}"`, + ); + } + return v; + }, + ); + return new InitialPrivateState(value); + } } function bigintReviver(_key: string, value: unknown): unknown { diff --git a/packages/deploy/src/loaders/ref-resolver.ts b/packages/deploy/src/loaders/ref-resolver.ts new file mode 100644 index 0000000..766aea8 --- /dev/null +++ b/packages/deploy/src/loaders/ref-resolver.ts @@ -0,0 +1,48 @@ +import { type FileOrModuleRef, isFileRef, isModuleRef } from '../config/schema.ts'; +import { ConfigError } from '../errors.ts'; +import type { LoaderContext } from './context.ts'; + +/** + * Resolve a `{ file }` or `{ module, export }` reference to a typed value. + * + * Carries a {@link LoaderContext} plus a human label used in error messages. + * The caller supplies a `parseFile` callback for the JSON-file branch and a + * `validateExport` callback for the module-export branch — together those + * two cover every loader that consumes a `FileOrModuleRef` from `compact.toml` + * (today: args + initial private state). + */ +export class RefResolver { + readonly #ctx: LoaderContext; + readonly #label: string; + + constructor(ctx: LoaderContext, label: string) { + this.#ctx = ctx; + this.#label = label; + } + + async resolve( + ref: FileOrModuleRef, + parseFile: (text: string, path: string) => T, + validateExport: (value: unknown, path: string, exportName: string) => T, + ): Promise { + if (isFileRef(ref)) { + const { text, path } = await this.#ctx.readText(ref.file, this.#label); + return parseFile(text, path); + } + if (isModuleRef(ref)) { + const { mod, path } = await this.#ctx.importModule( + ref.module, + this.#label, + ); + const exported = mod[ref.export]; + const resolved = + typeof exported === 'function' + ? await (exported as () => unknown)() + : exported; + return validateExport(resolved, path, ref.export); + } + throw new ConfigError( + `${this.#label}: must be { file } or { module, export }`, + ); + } +} diff --git a/packages/deploy/src/loaders/signing-key.test.ts b/packages/deploy/src/loaders/signing-key.test.ts index 8193b80..8c86a90 100644 --- a/packages/deploy/src/loaders/signing-key.test.ts +++ b/packages/deploy/src/loaders/signing-key.test.ts @@ -3,31 +3,31 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { ConfigError } from '../errors.ts'; -import { loadSigningKey } from './signing-key.ts'; +import { SigningKey } from './signing-key.ts'; const VALID = 'a'.repeat(64); -describe('loadSigningKey', () => { +describe('SigningKey', () => { it('reads and lowercases a 32-byte hex key', async () => { const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); writeFileSync(join(dir, 'sk'), `${VALID.toUpperCase()}\n`); - expect(await loadSigningKey(dir, 'sk')).toBe(VALID); + expect((await SigningKey.load(dir, 'sk')).hex).toBe(VALID); }); it('strips an optional 0x prefix', async () => { const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); writeFileSync(join(dir, 'sk'), `0x${VALID}\n`); - expect(await loadSigningKey(dir, 'sk')).toBe(VALID); + expect((await SigningKey.load(dir, 'sk')).hex).toBe(VALID); }); it('rejects a wrong-length key', async () => { const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); writeFileSync(join(dir, 'sk'), 'abcd'); - await expect(loadSigningKey(dir, 'sk')).rejects.toThrow(ConfigError); + await expect(SigningKey.load(dir, 'sk')).rejects.toThrow(ConfigError); }); it('rejects a missing file', async () => { - await expect(loadSigningKey('/tmp', 'no-such-file')).rejects.toThrow( + await expect(SigningKey.load('/tmp', 'no-such-file')).rejects.toThrow( ConfigError, ); }); diff --git a/packages/deploy/src/loaders/signing-key.ts b/packages/deploy/src/loaders/signing-key.ts index d36708e..00d2983 100644 --- a/packages/deploy/src/loaders/signing-key.ts +++ b/packages/deploy/src/loaders/signing-key.ts @@ -1,34 +1,34 @@ -import { readFile } from 'node:fs/promises'; -import { isAbsolute, resolve } from 'node:path'; import { ConfigError } from '../errors.ts'; +import { LoaderContext } from './context.ts'; /** - * Read a 32-byte signing key from `[contracts.X].signing_key_file` and - * return it as lowercase hex (no `0x` prefix). + * A contract's maintenance-authority signing key, loaded from + * `[contracts.X].signing_key_file` in `compact.toml`. * - * The signing key is the contract's maintenance authority. We refuse fuzzy - * input formats — exactly 64 hex chars after stripping optional `0x` and - * trimming whitespace — to avoid the foot-gun where midnight-js silently + * Canonical form: 64 lowercase hex chars, no `0x` prefix. We refuse fuzzy + * input formats to avoid the foot-gun where midnight-js silently * auto-samples a key the user then can't recover. */ -export async function loadSigningKey( - rootDir: string, - path: string, -): Promise { - const abs = isAbsolute(path) ? path : resolve(rootDir, path); - let raw: string; - try { - raw = await readFile(abs, 'utf8'); - } catch (e) { - throw new ConfigError( - `signing_key_file: failed to read ${abs}: ${(e as Error).message}`, - ); +export class SigningKey { + readonly hex: string; + + private constructor(hex: string) { + this.hex = hex; } - const trimmed = raw.trim().replace(/^0x/i, ''); - if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) { - throw new ConfigError( - `signing_key_file ${abs}: expected 32 bytes hex-encoded (64 hex chars)`, - ); + + /** + * Read and validate a key file — exactly 64 hex chars after stripping + * optional `0x` and trimming whitespace. + */ + static async load(rootDir: string, path: string): Promise { + const ctx = new LoaderContext(rootDir); + const { text, path: abs } = await ctx.readText(path, 'signing_key_file'); + const trimmed = text.trim().replace(/^0x/i, ''); + if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) { + throw new ConfigError( + `signing_key_file ${abs}: expected 32 bytes hex-encoded (64 hex chars)`, + ); + } + return new SigningKey(trimmed.toLowerCase()); } - return trimmed.toLowerCase(); } diff --git a/packages/deploy/src/pipeline.ts b/packages/deploy/src/pipeline.ts index a8a3cb9..eb421a8 100644 --- a/packages/deploy/src/pipeline.ts +++ b/packages/deploy/src/pipeline.ts @@ -8,21 +8,17 @@ import { import { UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format'; import type { Logger } from 'pino'; import * as Rx from 'rxjs'; -import { loadConstructorArgs } from './loaders/args.ts'; -import { type LoadedArtifact, loadArtifact } from './loaders/artifact.ts'; -import { loadConfig } from './config/load.ts'; -import type { - CompactConfig, - ContractConfig, - NetworkConfig, -} from './config/schema.ts'; +import { ConstructorArgs } from './loaders/args.ts'; +import { Artifact } from './loaders/artifact.ts'; +import { CompactConfig } from './config/compact-config.ts'; +import type { ContractConfig, NetworkConfig } from './config/schema.ts'; import { ConfigError, DeployTxFailedError } from './errors.ts'; -import { loadInitialPrivateState } from './loaders/init-state.ts'; +import { InitialPrivateState } from './loaders/init-state.ts'; import { Deployments, type DeploymentRecord } from './deployments.ts'; import { buildProviders } from './providers/build.ts'; import { applyNetwork } from './providers/network.ts'; import { ProofServer } from './providers/proof-server.ts'; -import { loadSigningKey } from './loaders/signing-key.ts'; +import { SigningKey } from './loaders/signing-key.ts'; import { buildDeployerWallet } from './wallet/build-deployer.ts'; import { type SeedResolution, resolveSeed } from './wallet/resolve.ts'; @@ -87,13 +83,13 @@ export async function runPipeline( ): Promise { const { logger } = opts; - const { config, rootDir } = await loadConfig(opts.configPath); + const config = await CompactConfig.load(opts.configPath); + const { rootDir } = config; const { networkName, network, contract } = resolveTargets(opts, config); - const signingKey = await loadSigningKey(rootDir, contract.signing_key_file); + const signingKey = await SigningKey.load(rootDir, contract.signing_key_file); const seedResolution = await maybeResolveSeed(opts, { config, - rootDir, networkName, network, }); @@ -112,9 +108,9 @@ export async function runPipeline( `Network ID: ${env.networkId}; proof server: ${env.proofServer}`, ); - const artifact = await loadArtifact({ + const artifact = await Artifact.load({ rootDir, - artifactsDir: config.profile.artifacts_dir, + artifactsDir: config.artifactsDir, artifact: contract.artifact, contractName: opts.contract, witnesses: contract.witnesses, @@ -143,12 +139,12 @@ export async function runPipeline( zkConfigPath: artifact.zkConfigPath, }); - const args = await loadConstructorArgs( + const args = await ConstructorArgs.load( contract, rootDir, opts.argsOverride, ); - const initialPrivateState = await loadInitialPrivateState( + const initialPrivateState = await InitialPrivateState.load( contract.init_private_state, rootDir, ); @@ -168,7 +164,7 @@ export async function runPipeline( return dryRunResult({ contractName: opts.contract, networkName, - signingKey, + signingKey: signingKey.hex, deployer, artifact: contract.artifact, }); @@ -179,21 +175,21 @@ export async function runPipeline( contractName: opts.contract, contract, artifact, - signingKey, - args, - initialPrivateState, + signingKey: signingKey.hex, + args: args.values, + initialPrivateState: initialPrivateState?.value, }); const record = toDeploymentRecord({ deployTxData: txResult.deployTxData, - signingKey, + signingKey: signingKey.hex, deployer, artifact: contract.artifact, }); const deployments = new Deployments({ rootDir, - deploymentsDir: config.profile.deployments_dir, + deploymentsDir: config.deploymentsDir, network: networkName, }); const persistResult = await deployments.record(opts.contract, record); @@ -232,25 +228,17 @@ function resolveTargets( opts: PipelineOptions, config: CompactConfig, ): ResolvedTargets { - const networkName = opts.network ?? config.profile.default_network; + const networkName = opts.network ?? config.defaultNetwork; if (!networkName) { throw new ConfigError( 'No network selected. Pass --network or set [profile].default_network.', ); } - const network = config.networks[networkName]; - if (!network) { - throw new ConfigError( - `Network "${networkName}" not defined. Available: ${Object.keys(config.networks).join(', ')}`, - ); - } - const contract = config.contracts[opts.contract]; - if (!contract) { - throw new ConfigError( - `Contract "${opts.contract}" not defined. Available: ${Object.keys(config.contracts).join(', ')}`, - ); - } - return { networkName, network, contract }; + return { + networkName, + network: config.network(networkName), + contract: config.contract(opts.contract), + }; } /** @@ -263,7 +251,6 @@ async function maybeResolveSeed( opts: PipelineOptions, ctx: { config: CompactConfig; - rootDir: string; networkName: string; network: NetworkConfig; }, @@ -271,7 +258,6 @@ async function maybeResolveSeed( if (opts.walletProvider) return undefined; return resolveSeed({ config: ctx.config, - rootDir: ctx.rootDir, networkName: ctx.networkName, network: ctx.network, seedFile: opts.seedFile, @@ -337,9 +323,9 @@ interface ExecuteDeployArgs { providers: Parameters[0]; contractName: string; contract: ContractConfig; - artifact: LoadedArtifact; + artifact: Artifact; signingKey: string; - args: unknown[]; + args: readonly unknown[]; initialPrivateState: unknown; } @@ -417,7 +403,7 @@ function logDryRun( details: { contractName: string; networkName: string; - artifact: LoadedArtifact; + artifact: Artifact; argCount: number; hasPrivateState: boolean; faucet: boolean; diff --git a/packages/deploy/src/wallet/resolve.ts b/packages/deploy/src/wallet/resolve.ts index 04e959e..a5d46dd 100644 --- a/packages/deploy/src/wallet/resolve.ts +++ b/packages/deploy/src/wallet/resolve.ts @@ -1,7 +1,8 @@ import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import { isAbsolute, resolve } from 'node:path'; -import type { CompactConfig, NetworkConfig } from '../config/schema.ts'; +import type { CompactConfig } from '../config/compact-config.ts'; +import type { NetworkConfig } from '../config/schema.ts'; import { WalletError } from '../errors.ts'; import { Keystore } from './keystore.ts'; import { localPrefundedSeed } from './local-seeds.ts'; @@ -27,7 +28,6 @@ export interface SeedResolution { export interface ResolveOptions { config: CompactConfig; - rootDir: string; networkName: string; network: NetworkConfig; seedFile?: string; @@ -37,8 +37,9 @@ export interface ResolveOptions { export async function resolveSeed( opts: ResolveOptions, ): Promise { + const { rootDir } = opts.config; if (opts.seedFile) { - const path = absoluteUnder(opts.rootDir, opts.seedFile); + const path = absoluteUnder(rootDir, opts.seedFile); const raw = await safeRead(path, '--seed-file'); return { seed: classifySeed(raw), origin: 'cli' }; } @@ -50,7 +51,7 @@ export async function resolveSeed( const keystorePath = opts.config.wallet?.keystore; if (keystorePath) { - const path = absoluteUnder(opts.rootDir, keystorePath); + const path = absoluteUnder(rootDir, keystorePath); if (!existsSync(path)) { throw new WalletError(`Keystore file not found: ${path}`); } From bfeb490ae37656e985aafeb4fbde7e4ae12b851f Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:03:24 +0200 Subject: [PATCH 03/12] feat(deploy): add Symbol.asyncDispose to ProofServer Lets callers manage the proof-server container with `await using` and AsyncDisposableStack instead of explicit try/finally for teardown. Dispose errors are warn-logged rather than thrown so a failed teardown doesn't mask the deploy's primary failure. --- packages/deploy/src/providers/proof-server.ts | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/packages/deploy/src/providers/proof-server.ts b/packages/deploy/src/providers/proof-server.ts index ebd3ca2..b1b7baf 100644 --- a/packages/deploy/src/providers/proof-server.ts +++ b/packages/deploy/src/providers/proof-server.ts @@ -30,10 +30,16 @@ export class ProofServer { /** Resolved URL the proof provider POSTs to. */ readonly url: string; readonly #dispose: () => Promise; + readonly #logger: Logger; - private constructor(url: string, dispose: () => Promise) { + private constructor( + url: string, + dispose: () => Promise, + logger: Logger, + ) { this.url = url; this.#dispose = dispose; + this.#logger = logger; } /** @@ -53,7 +59,7 @@ export class ProofServer { if (explicit && explicit !== 'auto') { logger.debug(`Using configured proof server: ${explicit}`); - return ProofServer.fromStaticUrl(explicit); + return ProofServer.fromStaticUrl(explicit, logger); } if (explicit === 'auto') { @@ -63,7 +69,7 @@ export class ProofServer { undefined, network.network_id, ); - return new ProofServer(container.getUrl(), () => container.stop()); + return new ProofServer(container.getUrl(), () => container.stop(), logger); } const port = process.env.PROOF_SERVER_PORT; @@ -74,21 +80,40 @@ export class ProofServer { } logger.debug(`Using PROOF_SERVER_PORT=${parsed}`); const container = new StaticProofServerContainer(parsed); - return new ProofServer(container.getUrl(), () => container.stop()); + return new ProofServer(container.getUrl(), () => container.stop(), logger); } logger.debug('Falling back to default proof server at http://127.0.0.1:6300'); - return ProofServer.fromStaticUrl('http://127.0.0.1:6300'); + return ProofServer.fromStaticUrl('http://127.0.0.1:6300', logger); } - private static fromStaticUrl(url: string): ProofServer { - return new ProofServer(url, async () => { - /* no container to stop */ - }); + private static fromStaticUrl(url: string, logger: Logger): ProofServer { + return new ProofServer( + url, + async () => { + /* no container to stop */ + }, + logger, + ); } /** Release any underlying container. Idempotent for static-URL instances. */ async dispose(): Promise { return this.#dispose(); } + + /** + * AsyncDisposable hook for `await using` — swallows teardown errors with + * a `warn` log so dispose failures don't mask the deploy's real error. + */ + async [Symbol.asyncDispose](): Promise { + try { + await this.#dispose(); + } catch (e) { + this.#logger.warn( + { err: (e as Error).message }, + 'Proof server dispose failed', + ); + } + } } From 54417d843cb93c6f5128ce59836ec1e84e443233 Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:03:37 +0200 Subject: [PATCH 04/12] chore(deploy,cli): bump engines.node to >=24 AsyncDisposableStack landed in Node 24.0; Node 22 only ships Symbol.asyncDispose and `await using`. Realigns engines with the @tsconfig/node24 lib defs already in use across these packages. --- packages/cli/package.json | 2 +- packages/deploy/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 29ceca3..976b2bd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,7 +24,7 @@ "LICENSE" ], "engines": { - "node": ">=22" + "node": ">=24" }, "bin": { "compact-builder": "dist/runBuilder.js", diff --git a/packages/deploy/package.json b/packages/deploy/package.json index f46415b..377f609 100644 --- a/packages/deploy/package.json +++ b/packages/deploy/package.json @@ -24,7 +24,7 @@ "LICENSE" ], "engines": { - "node": ">=22" + "node": ">=24" }, "scripts": { "build": "tsc -p .", From 78ecdcc7b98758e881673908fcaad0d93a21b87b Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:05:13 +0200 Subject: [PATCH 05/12] refactor(deploy): replace runPipeline with Deployer + WalletHandler classes; consolidate seed helpers Reshapes the deploy entry-point around resource-managed classes: - `Deployer` (deployer.ts) replaces the procedural `runPipeline` function. `Deployer.prepare(opts)` loads config + signing key, starts the proof server, builds or adopts a wallet, and loads constructor args; the returned instance exposes `.deploy()` and `.dryRun()`. CLI flow becomes: await using deployer = await Deployer.prepare(opts); return dryRun ? await deployer.dryRun() : await deployer.deploy(); Owned resources are accumulated in an `AsyncDisposableStack` inside `prepare`, moved to the instance on success, and disposed in reverse order from `[Symbol.asyncDispose]`. Mid-prepare failures unwind cleanly via the local `await using` on the stack. - `WalletHandler` (wallet/handler.ts) replaces the `buildDeployerWallet` free function. It wraps a built `MidnightWalletProvider` and implements `[Symbol.asyncDispose]`, so the Deployer adds it to its stack with a single `stack.use(owned)` instead of `stack.defer(...)` with custom try/catch. Mirrors the `ProofServer` pattern. - `wallet/seeds.ts` merges the previous `local-seeds.ts`, `normalize.ts`, and `resolve.ts` into one module. None of those owned state or a lifecycle, so a class wrapper would be ceremony; a single file with three exports is the right unit. Test renamed `normalize.test.ts` -> `seeds.test.ts`. Public API: `runPipeline as deploy` and `PipelineOptions/PipelineResult` are gone; consumers now use `Deployer`, `DeployerOptions`, `DeployResult`. `buildDeployerWallet` replaced by `WalletHandler`. Integration harness and CLI updated. --- packages/cli/src/runDeploy.ts | 14 +- packages/deploy/src/deployer.ts | 540 ++++++++++++++++++ packages/deploy/src/index.ts | 15 +- packages/deploy/src/pipeline.ts | 496 ---------------- packages/deploy/src/wallet/build-deployer.ts | 70 --- packages/deploy/src/wallet/handler.ts | 114 ++++ packages/deploy/src/wallet/local-seeds.ts | 29 - packages/deploy/src/wallet/normalize.ts | 35 -- packages/deploy/src/wallet/resolve.ts | 93 --- .../{normalize.test.ts => seeds.test.ts} | 2 +- packages/deploy/src/wallet/seeds.ts | 173 ++++++ tests/integrations/_harness/deployer.ts | 25 +- tests/integrations/_harness/walletPool.ts | 19 +- tests/integrations/specs/errors.spec.ts | 16 +- 14 files changed, 874 insertions(+), 767 deletions(-) create mode 100644 packages/deploy/src/deployer.ts delete mode 100644 packages/deploy/src/pipeline.ts delete mode 100644 packages/deploy/src/wallet/build-deployer.ts create mode 100644 packages/deploy/src/wallet/handler.ts delete mode 100644 packages/deploy/src/wallet/local-seeds.ts delete mode 100644 packages/deploy/src/wallet/normalize.ts delete mode 100644 packages/deploy/src/wallet/resolve.ts rename packages/deploy/src/wallet/{normalize.test.ts => seeds.test.ts} (96%) create mode 100644 packages/deploy/src/wallet/seeds.ts diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index f18d9a0..685d5ad 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -1,14 +1,14 @@ #!/usr/bin/env node /** - * `compact-deploy` — opinionated CLI shell over the deploy pipeline. + * `compact-deploy` — opinionated CLI shell over the {@link Deployer} class. * * Responsibilities limited to: * - argv parsing (handwritten, no external CLI lib — keeps cold-start fast) * - constructing the logger / spinner / passphrase prompt - * - delegating to `runPipeline` and rendering its result + * - calling `Deployer.prepare` then `.deploy()` or `.dryRun()` * - mapping exceptions to typed exit codes via {@link DeployError.exitCode} * - * All deploy logic lives in `pipeline.ts` and its dependencies; this file + * All deploy logic lives in `deployer.ts` and its dependencies; this file * should never grow business logic. * * The `globalThis.WebSocket = ws` shim is required because midnight-js's @@ -19,7 +19,7 @@ import chalk from 'chalk'; import ora from 'ora'; import { WebSocket } from 'ws'; -import { deploy, DeployError } from '@openzeppelin/compact-deploy'; +import { Deployer, DeployError } from '@openzeppelin/compact-deploy'; import { createLogger } from './logger.ts'; import { promptPassphrase } from './prompt.ts'; @@ -141,14 +141,13 @@ async function main(): Promise { ).start(); try { - const result = await deploy({ + await using deployer = await Deployer.prepare({ contract: args.contract, network: args.network, configPath: args.configPath, seedFile: args.seedFile, proofServer: args.proofServer, skipFaucet: args.skipFaucet, - dryRun: args.dryRun, logger, promptPassphrase: async (path) => { if (spinner) spinner.stop(); @@ -157,6 +156,9 @@ async function main(): Promise { return pp; }, }); + const result = args.dryRun + ? await deployer.dryRun() + : await deployer.deploy(); if (args.json) { process.stdout.write(`${JSON.stringify(result)}\n`); diff --git a/packages/deploy/src/deployer.ts b/packages/deploy/src/deployer.ts new file mode 100644 index 0000000..16ade32 --- /dev/null +++ b/packages/deploy/src/deployer.ts @@ -0,0 +1,540 @@ +import { deployContract } from '@midnight-ntwrk/midnight-js-contracts'; +import { getNetworkId } from '@midnight-ntwrk/midnight-js-network-id'; +import { + type EnvironmentConfiguration, + FaucetClient, + type MidnightWalletProvider, +} from '@midnight-ntwrk/testkit-js'; +import { UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format'; +import type { Logger } from 'pino'; +import * as Rx from 'rxjs'; +import { CompactConfig } from './config/compact-config.ts'; +import type { ContractConfig, NetworkConfig } from './config/schema.ts'; +import { Deployments, type DeploymentRecord } from './deployments.ts'; +import { ConfigError, DeployTxFailedError } from './errors.ts'; +import { ConstructorArgs } from './loaders/args.ts'; +import { Artifact } from './loaders/artifact.ts'; +import { InitialPrivateState } from './loaders/init-state.ts'; +import { SigningKey } from './loaders/signing-key.ts'; +import { buildProviders } from './providers/build.ts'; +import { applyNetwork } from './providers/network.ts'; +import { ProofServer } from './providers/proof-server.ts'; +import { WalletHandler } from './wallet/handler.ts'; +import { type SeedResolution, resolveSeed } from './wallet/seeds.ts'; + +/** + * Inputs to {@link Deployer.prepare}. The CLI in `bin/compact-deploy.ts` + * is a thin shell that fills these out from argv + env; embedders can + * construct them directly to skip TOML lookup or to inject a shared + * wallet. + */ +export interface DeployerOptions { + contract: string; + network?: string; + configPath?: string; + seedFile?: string; + proofServer?: string; + skipFaucet?: boolean; + argsOverride?: string; + initPrivateStateOverride?: string; + logger: Logger; + promptPassphrase?: (path: string) => Promise; + /** + * Inject an already-built, already-started `MidnightWalletProvider`. + * When set, {@link Deployer.prepare} skips seed resolution, wallet + * build, faucet calls, `wallet.start()`, and `wallet.stop()` — the + * caller owns the wallet's lifecycle. + * + * Use this when running many deploys in a single Node process (e.g. + * integration test suites). Each `WalletHandler.build` rebuilds a + * wallet that syncs from the indexer; under rapid back-to-back + * deploys the indexer can lag and the new wallet sees an + * already-spent dust UTXO, producing a `DustDoubleSpend` rejection. + * Sharing one wallet across deploys keeps its UTXO view internally + * consistent. + */ + walletProvider?: MidnightWalletProvider; +} + +/** + * Final shape returned by {@link Deployer.deploy} and + * {@link Deployer.dryRun}. In dry-run mode the on-chain fields + * (`address`, `txHash`, `txId`, `blockHeight`, `deploymentsFile`) are + * empty and `dryRun: true`. + */ +export interface DeployResult { + contractName: string; + network: string; + address: string; + txHash: string; + txId: string; + blockHeight: number; + signingKey: string; + deployer: string; + artifact: string; + deploymentsFile: string; + dryRun: boolean; +} + +/** + * Internal bundle that {@link Deployer.prepare} produces and the + * action methods consume. Keeping the action methods pure transforms + * over this struct makes the deploy/dry-run paths read top-to-bottom. + */ +interface PreparedState { + opts: DeployerOptions; + logger: Logger; + config: CompactConfig; + networkName: string; + network: NetworkConfig; + contract: ContractConfig; + signingKey: SigningKey; + artifact: Artifact; + args: ConstructorArgs; + initialPrivateState: InitialPrivateState | undefined; + wallet: MidnightWalletProvider; + deployer: string; + env: EnvironmentConfiguration; + faucetUrl: string | undefined; + resources: AsyncDisposableStack; +} + +/** + * Stateful handle for a single contract's deploy lifecycle. + * + * `Deployer.prepare(opts)` loads config + artifact + signing key, + * starts the proof server, builds or adopts a wallet (started, optional + * faucet), and returns an instance ready to {@link deploy} or + * {@link dryRun}. + * + * Always acquired with `await using` — `[Symbol.asyncDispose]` stops + * the wallet (only if built here) and the proof-server container + * (only if `"auto"`). + * + * Resource handling: {@link prepare} accumulates owned resources into + * a local {@link AsyncDisposableStack}. On failure mid-prepare, + * `await using` disposes everything it acquired so far; on success, + * ownership transfers to the returned instance via `stack.move()` and + * `[Symbol.asyncDispose]` disposes it later. + */ +export class Deployer implements AsyncDisposable { + /** Contract name as specified in opts. */ + readonly contractName: string; + /** Resolved network name (`opts.network` or `[profile].default_network`). */ + readonly networkName: string; + /** Hex of the deployer's coin public key. */ + readonly deployer: string; + /** Loaded artifact: zk config path + compiled-contract handle. */ + readonly artifact: Artifact; + /** Per-contract signing key loaded from disk. */ + readonly signingKey: SigningKey; + + readonly #state: PreparedState; + + private constructor(state: PreparedState) { + this.#state = state; + this.contractName = state.opts.contract; + this.networkName = state.networkName; + this.deployer = state.deployer; + this.artifact = state.artifact; + this.signingKey = state.signingKey; + } + + /** + * Load + validate everything needed to deploy, in order: + * + * 1. Parse `compact.toml`, pick network + contract. + * 2. Load signing key from `contract.signing_key_file`. + * 3. Resolve seed (unless `opts.walletProvider` was injected). + * 4. Start the proof server (CLI > TOML URL > `"auto"` > env > default). + * 5. Load the artifact (compiled contract, zk config). + * 6. Build the wallet (or adopt the injected one), faucet + start + * when owned. + * 7. Load constructor args and initial private state. + * + * Throws typed errors ({@link ConfigError}, {@link WalletError}, etc.) + * that map to the CLI's exit codes via `DeployError.exitCode`. + */ + static async prepare(opts: DeployerOptions): Promise { + const { logger } = opts; + + const config = await CompactConfig.load(opts.configPath); + const { rootDir } = config; + const { networkName, network, contract } = resolveTargets(opts, config); + const signingKey = await SigningKey.load(rootDir, contract.signing_key_file); + + const seedResolution = opts.walletProvider + ? undefined + : await resolveSeed({ + config, + networkName, + network, + seedFile: opts.seedFile, + promptPassphrase: opts.promptPassphrase, + }); + if (seedResolution) { + logger.debug(`Resolved deployer seed from: ${seedResolution.origin}`); + } + + // Stack owns every resource acquired below. On any throw before + // the final `stack.move()`, `await using` disposes them in reverse + // order; on success, ownership transfers to the returned Deployer + // and the local `await using` becomes a no-op. + await using stack = new AsyncDisposableStack(); + + const proofServer = await ProofServer.start({ + cliOverride: opts.proofServer, + network, + logger, + }); + stack.use(proofServer); + + const { env, faucetUrl } = applyNetwork(network, proofServer.url); + logger.debug( + `Network ID: ${env.networkId}; proof server: ${env.proofServer}`, + ); + + const artifact = await Artifact.load({ + rootDir, + artifactsDir: config.artifactsDir, + artifact: contract.artifact, + contractName: opts.contract, + witnesses: contract.witnesses, + }); + logger.debug( + `Artifact: ${artifact.artifactPath} (${artifact.circuitNames.length} circuits)`, + ); + + let wallet: MidnightWalletProvider; + if (opts.walletProvider) { + wallet = opts.walletProvider; + } else { + if (!seedResolution) { + throw new Error('internal: seedResolution missing for owned wallet'); + } + const owned = await WalletHandler.build(logger, env, seedResolution.seed); + stack.use(owned); + wallet = owned.provider; + await maybeRequestFaucet(opts, wallet, env, network, logger); + await wallet.start(true); + } + + const args = await ConstructorArgs.load( + contract, + rootDir, + opts.argsOverride, + ); + const initialPrivateState = await InitialPrivateState.load( + contract.init_private_state, + rootDir, + ); + const deployer = wallet.getCoinPublicKey(); + + return new Deployer({ + opts, + logger, + config, + networkName, + network, + contract, + signingKey, + artifact, + args, + initialPrivateState, + wallet, + deployer, + env, + faucetUrl, + resources: stack.move(), + }); + } + + /** + * Submit the deploy transaction, persist the deployment record under + * `deployments/.json` (rotating any prior head into history), + * and return the success result. + */ + async deploy(): Promise { + const s = this.#state; + const providers = buildProviders({ + env: s.env, + wallet: s.wallet, + contractName: s.opts.contract, + contract: s.contract, + zkConfigPath: s.artifact.zkConfigPath, + }); + const txResult = await executeDeploy({ + providers, + contractName: s.opts.contract, + contract: s.contract, + artifact: s.artifact, + signingKey: s.signingKey.hex, + args: s.args.values, + initialPrivateState: s.initialPrivateState?.value, + }); + + const record = toDeploymentRecord({ + deployTxData: txResult.deployTxData, + signingKey: s.signingKey.hex, + deployer: s.deployer, + artifact: s.contract.artifact, + }); + + const deployments = new Deployments({ + rootDir: s.config.rootDir, + deploymentsDir: s.config.deploymentsDir, + network: s.networkName, + }); + const persisted = await deployments.record(s.opts.contract, record); + + return successResult({ + contractName: s.opts.contract, + networkName: s.networkName, + record, + deploymentsFile: persisted.head, + }); + } + + /** + * Log a structured "would deploy" event and return a synthetic + * result. No transaction is submitted and no file is written. + */ + async dryRun(): Promise { + const s = this.#state; + logDryRun(s.logger, { + contractName: s.opts.contract, + networkName: s.networkName, + artifact: s.artifact, + argCount: s.args.length, + hasPrivateState: s.initialPrivateState !== undefined, + faucet: !!s.network.faucet && !s.opts.skipFaucet, + faucetUrl: s.faucetUrl, + deployer: s.deployer, + }); + return dryRunResult({ + contractName: s.opts.contract, + networkName: s.networkName, + signingKey: s.signingKey.hex, + deployer: s.deployer, + artifact: s.contract.artifact, + }); + } + + /** + * Release every resource `prepare` acquired: proof-server container + * (if `"auto"`) and the wallet (if built here, not injected). + */ + async [Symbol.asyncDispose](): Promise { + await this.#state.resources.disposeAsync(); + } +} + +// --------------------------------------------------------------------------- +// Helpers — pure transforms used by `prepare` and the action methods. +// --------------------------------------------------------------------------- + +interface ResolvedTargets { + networkName: string; + network: NetworkConfig; + contract: ContractConfig; +} + +/** + * Pick the network and contract from `compact.toml`, defaulting the + * network to `[profile].default_network` when `opts.network` isn't + * passed. Throws {@link ConfigError} with the available set on each + * invalid lookup. + */ +function resolveTargets( + opts: DeployerOptions, + config: CompactConfig, +): ResolvedTargets { + const networkName = opts.network ?? config.defaultNetwork; + if (!networkName) { + throw new ConfigError( + 'No network selected. Pass --network or set [profile].default_network.', + ); + } + return { + networkName, + network: config.network(networkName), + contract: config.contract(opts.contract), + }; +} + +/** + * Hit the network's faucet for the deployer address when configured + * (`[networks.X].faucet = true`, not `--skip-faucet`, and the resolved + * `env.faucet` URL is present). Safe to call before `wallet.start()` — + * we read the unshielded address from the wallet's already-running + * state stream. + */ +async function maybeRequestFaucet( + opts: DeployerOptions, + wallet: MidnightWalletProvider, + env: EnvironmentConfiguration, + network: NetworkConfig, + logger: Logger, +): Promise { + if (!network.faucet || opts.skipFaucet || !env.faucet) return; + const initialUnshielded = await Rx.firstValueFrom( + wallet.wallet.unshielded.state, + ); + const address = UnshieldedAddress.codec + .encode(getNetworkId(), initialUnshielded.address) + .toString(); + logger.info(`Requesting faucet tokens for ${address}…`); + await new FaucetClient(env.faucet, logger).requestTokens(address); +} + +interface ExecuteDeployArgs { + providers: Parameters[0]; + contractName: string; + contract: ContractConfig; + artifact: Artifact; + signingKey: string; + args: readonly unknown[]; + initialPrivateState: unknown; +} + +/** + * Assemble the `deployContract` options (conditionally including the + * private-state pair) and submit. Wraps any failure in + * {@link DeployTxFailedError} so callers can branch on its `exitCode` + * without parsing midnight-js error shapes. + */ +async function executeDeploy({ + providers, + contractName, + contract, + artifact, + signingKey, + args, + initialPrivateState, +}: ExecuteDeployArgs): Promise>> { + const compiled = artifact.compiledContract as Parameters< + typeof deployContract + >[1]['compiledContract']; + const base = { + compiledContract: compiled, + signingKey, + args, + } as Parameters[1]; + const deployOptions = + contract.private_state_id !== undefined + ? { + ...base, + privateStateId: contract.private_state_id, + initialPrivateState, + } + : base; + + try { + return await deployContract(providers, deployOptions); + } catch (e) { + throw new DeployTxFailedError( + `Deploy of "${contractName}" failed: ${(e as Error).message}`, + { cause: e }, + ); + } +} + +type ContractDeployResult = Awaited>; + +/** Map the midnight-js deploy-tx result into the persisted record shape. */ +function toDeploymentRecord({ + deployTxData, + signingKey, + deployer, + artifact, +}: { + deployTxData: ContractDeployResult['deployTxData']; + signingKey: string; + deployer: string; + artifact: string; +}): DeploymentRecord { + return { + address: deployTxData.public.contractAddress, + txHash: deployTxData.public.txHash, + txId: deployTxData.public.txId, + blockHeight: deployTxData.public.blockHeight, + signingKey, + deployer, + artifact, + timestamp: new Date().toISOString(), + }; +} + +/** Emit the same structured `dry-run: would deploy` event the pipeline did. */ +function logDryRun( + logger: Logger, + details: { + contractName: string; + networkName: string; + artifact: Artifact; + argCount: number; + hasPrivateState: boolean; + faucet: boolean; + faucetUrl: string | undefined; + deployer: string; + }, +): void { + logger.info( + { + contract: details.contractName, + network: details.networkName, + artifact: details.artifact.artifactPath, + argCount: details.argCount, + hasPrivateState: details.hasPrivateState, + faucet: details.faucet, + faucetUrl: details.faucetUrl, + deployer: details.deployer, + }, + 'dry-run: would deploy', + ); +} + +/** Build the `DeployResult` returned from a dry run (no on-chain fields). */ +function dryRunResult(params: { + contractName: string; + networkName: string; + signingKey: string; + deployer: string; + artifact: string; +}): DeployResult { + return { + contractName: params.contractName, + network: params.networkName, + address: '', + txHash: '', + txId: '', + blockHeight: 0, + signingKey: params.signingKey, + deployer: params.deployer, + artifact: params.artifact, + deploymentsFile: '', + dryRun: true, + }; +} + +/** Build the `DeployResult` returned from a confirmed deploy. */ +function successResult(params: { + contractName: string; + networkName: string; + record: DeploymentRecord; + deploymentsFile: string; +}): DeployResult { + return { + contractName: params.contractName, + network: params.networkName, + address: params.record.address, + txHash: params.record.txHash, + txId: params.record.txId, + blockHeight: params.record.blockHeight, + signingKey: params.record.signingKey, + deployer: params.record.deployer, + artifact: params.record.artifact, + deploymentsFile: params.deploymentsFile, + dryRun: false, + }; +} diff --git a/packages/deploy/src/index.ts b/packages/deploy/src/index.ts index 2797eb6..cac0389 100644 --- a/packages/deploy/src/index.ts +++ b/packages/deploy/src/index.ts @@ -13,17 +13,14 @@ export type { Profile, WalletConfig, } from './config/schema.ts'; +export { Deployer } from './deployer.ts'; +export type { DeployerOptions, DeployResult } from './deployer.ts'; export { Deployments } from './deployments.ts'; export type { DeploymentRecord, DeploymentsFile, DeploymentsHistory, } from './deployments.ts'; -export type { - PipelineOptions as DeployOptions, - PipelineResult as DeployResult, -} from './pipeline.ts'; -export { runPipeline as deploy } from './pipeline.ts'; export { ArtifactNotFoundError, ConfigError, @@ -43,10 +40,10 @@ export { SigningKey } from './loaders/signing-key.ts'; export { Keystore } from './wallet/keystore.ts'; export type { MidnightKeystore } from './wallet/keystore.ts'; export { ProofServer } from './providers/proof-server.ts'; +export { WalletHandler } from './wallet/handler.ts'; export { LOCAL_PREFUNDED_SEEDS, + classifySeed, localPrefundedSeed, -} from './wallet/local-seeds.ts'; -export { buildDeployerWallet } from './wallet/build-deployer.ts'; -export { classifySeed } from './wallet/normalize.ts'; -export type { WalletSeed } from './wallet/normalize.ts'; +} from './wallet/seeds.ts'; +export type { WalletSeed } from './wallet/seeds.ts'; diff --git a/packages/deploy/src/pipeline.ts b/packages/deploy/src/pipeline.ts deleted file mode 100644 index eb421a8..0000000 --- a/packages/deploy/src/pipeline.ts +++ /dev/null @@ -1,496 +0,0 @@ -import { deployContract } from '@midnight-ntwrk/midnight-js-contracts'; -import { getNetworkId } from '@midnight-ntwrk/midnight-js-network-id'; -import { - type EnvironmentConfiguration, - FaucetClient, - type MidnightWalletProvider, -} from '@midnight-ntwrk/testkit-js'; -import { UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format'; -import type { Logger } from 'pino'; -import * as Rx from 'rxjs'; -import { ConstructorArgs } from './loaders/args.ts'; -import { Artifact } from './loaders/artifact.ts'; -import { CompactConfig } from './config/compact-config.ts'; -import type { ContractConfig, NetworkConfig } from './config/schema.ts'; -import { ConfigError, DeployTxFailedError } from './errors.ts'; -import { InitialPrivateState } from './loaders/init-state.ts'; -import { Deployments, type DeploymentRecord } from './deployments.ts'; -import { buildProviders } from './providers/build.ts'; -import { applyNetwork } from './providers/network.ts'; -import { ProofServer } from './providers/proof-server.ts'; -import { SigningKey } from './loaders/signing-key.ts'; -import { buildDeployerWallet } from './wallet/build-deployer.ts'; -import { type SeedResolution, resolveSeed } from './wallet/resolve.ts'; - -/** - * Inputs to {@link runPipeline}. The CLI in `bin/compact-deploy.ts` is a - * thin shell that fills these out from argv + env; embedders can construct - * them directly to skip TOML lookup or to inject a shared wallet. - */ -export interface PipelineOptions { - contract: string; - network?: string; - configPath?: string; - seedFile?: string; - proofServer?: string; - skipFaucet?: boolean; - dryRun?: boolean; - argsOverride?: string; - initPrivateStateOverride?: string; - logger: Logger; - promptPassphrase?: (path: string) => Promise; - /** - * Inject an already-built, already-started `MidnightWalletProvider`. When - * set, the pipeline skips seed resolution, wallet build, faucet calls, - * `wallet.start()`, and `wallet.stop()` — the caller owns the wallet's - * lifecycle. - * - * Use this when running many deploys in a single Node process (e.g. - * integration test suites). Each `buildDeployerWallet` rebuilds a wallet - * that syncs from the indexer; under rapid back-to-back deploys the - * indexer can lag and the new wallet sees an already-spent dust UTXO, - * producing a `DustDoubleSpend` rejection. Sharing one wallet across - * the deploys keeps its UTXO view internally consistent. - */ - walletProvider?: MidnightWalletProvider; -} - -/** Final shape returned by {@link runPipeline}; identical in dry-run mode except `dryRun: true` and the on-chain fields are empty. */ -export interface PipelineResult { - contractName: string; - network: string; - address: string; - txHash: string; - txId: string; - blockHeight: number; - signingKey: string; - deployer: string; - artifact: string; - deploymentsFile: string; - dryRun: boolean; -} - -/** - * End-to-end deploy: config → wallet → faucet → providers → submit → persist. - * - * Reads as a linear recipe — every step is a named helper below. Two - * resources need explicit cleanup (the proof-server container if `"auto"`, - * and an owned wallet); both are wrapped in `try/finally` here so the - * helpers can stay free of teardown logic. - */ -export async function runPipeline( - opts: PipelineOptions, -): Promise { - const { logger } = opts; - - const config = await CompactConfig.load(opts.configPath); - const { rootDir } = config; - const { networkName, network, contract } = resolveTargets(opts, config); - - const signingKey = await SigningKey.load(rootDir, contract.signing_key_file); - const seedResolution = await maybeResolveSeed(opts, { - config, - networkName, - network, - }); - if (seedResolution) { - logger.debug(`Resolved deployer seed from: ${seedResolution.origin}`); - } - - const proofServer = await ProofServer.start({ - cliOverride: opts.proofServer, - network, - logger, - }); - try { - const { env, faucetUrl } = applyNetwork(network, proofServer.url); - logger.debug( - `Network ID: ${env.networkId}; proof server: ${env.proofServer}`, - ); - - const artifact = await Artifact.load({ - rootDir, - artifactsDir: config.artifactsDir, - artifact: contract.artifact, - contractName: opts.contract, - witnesses: contract.witnesses, - }); - logger.debug( - `Artifact: ${artifact.artifactPath} (${artifact.circuitNames.length} circuits)`, - ); - - const { wallet, ownsWallet } = await acquireWallet( - opts, - env, - seedResolution, - logger, - ); - try { - if (ownsWallet) { - await maybeRequestFaucet(opts, wallet, env, network, logger); - await wallet.start(true); - } - - const providers = buildProviders({ - env, - wallet, - contractName: opts.contract, - contract, - zkConfigPath: artifact.zkConfigPath, - }); - - const args = await ConstructorArgs.load( - contract, - rootDir, - opts.argsOverride, - ); - const initialPrivateState = await InitialPrivateState.load( - contract.init_private_state, - rootDir, - ); - const deployer = wallet.getCoinPublicKey(); - - if (opts.dryRun) { - logDryRun(logger, { - contractName: opts.contract, - networkName, - artifact, - argCount: args.length, - hasPrivateState: initialPrivateState !== undefined, - faucet: !!network.faucet && !opts.skipFaucet, - faucetUrl, - deployer, - }); - return dryRunResult({ - contractName: opts.contract, - networkName, - signingKey: signingKey.hex, - deployer, - artifact: contract.artifact, - }); - } - - const txResult = await executeDeploy({ - providers, - contractName: opts.contract, - contract, - artifact, - signingKey: signingKey.hex, - args: args.values, - initialPrivateState: initialPrivateState?.value, - }); - - const record = toDeploymentRecord({ - deployTxData: txResult.deployTxData, - signingKey: signingKey.hex, - deployer, - artifact: contract.artifact, - }); - - const deployments = new Deployments({ - rootDir, - deploymentsDir: config.deploymentsDir, - network: networkName, - }); - const persistResult = await deployments.record(opts.contract, record); - - return successResult({ - contractName: opts.contract, - networkName, - record, - deploymentsFile: persistResult.head, - }); - } finally { - if (ownsWallet) await safeStopWallet(wallet, logger); - } - } finally { - await safeDisposeProofServer(proofServer, logger); - } -} - -// --------------------------------------------------------------------------- -// Helpers — every step of runPipeline lives in its own function. Order -// roughly follows the order each helper runs. -// --------------------------------------------------------------------------- - -interface ResolvedTargets { - networkName: string; - network: NetworkConfig; - contract: ContractConfig; -} - -/** - * Pick the network and contract from `compact.toml`, defaulting the network - * to `[profile].default_network` when `--network` isn't passed. Throws - * {@link ConfigError} with the available set on each invalid lookup. - */ -function resolveTargets( - opts: PipelineOptions, - config: CompactConfig, -): ResolvedTargets { - const networkName = opts.network ?? config.defaultNetwork; - if (!networkName) { - throw new ConfigError( - 'No network selected. Pass --network or set [profile].default_network.', - ); - } - return { - networkName, - network: config.network(networkName), - contract: config.contract(opts.contract), - }; -} - -/** - * Resolve a deployer seed unless the caller injected its own wallet - * (`opts.walletProvider` set). Returning `undefined` is the signal to - * {@link acquireWallet} that it should adopt the injected wallet instead of - * building one. - */ -async function maybeResolveSeed( - opts: PipelineOptions, - ctx: { - config: CompactConfig; - networkName: string; - network: NetworkConfig; - }, -): Promise { - if (opts.walletProvider) return undefined; - return resolveSeed({ - config: ctx.config, - networkName: ctx.networkName, - network: ctx.network, - seedFile: opts.seedFile, - promptPassphrase: opts.promptPassphrase, - }); -} - -interface AcquiredWallet { - wallet: MidnightWalletProvider; - /** True when the pipeline built the wallet itself and is therefore responsible for `start`/`stop`. */ - ownsWallet: boolean; -} - -/** - * Either return the caller-injected wallet (and skip the lifecycle) or - * build a fresh one from the resolved seed (and own its lifecycle). - */ -async function acquireWallet( - opts: PipelineOptions, - env: EnvironmentConfiguration, - seedResolution: SeedResolution | undefined, - logger: Logger, -): Promise { - if (opts.walletProvider) { - return { wallet: opts.walletProvider, ownsWallet: false }; - } - if (!seedResolution) { - // Should be unreachable — maybeResolveSeed returns a value whenever - // walletProvider is undefined — but the explicit check keeps the type - // narrowing local instead of relying on the caller. - throw new Error('internal: resolvedSeed missing for owned wallet'); - } - const wallet = await buildDeployerWallet(logger, env, seedResolution.seed); - return { wallet, ownsWallet: true }; -} - -/** - * Hit the network's faucet for the deployer address when configured - * (`[networks.X].faucet = true`, not `--skip-faucet`, and the resolved - * `env.faucet` URL is present). Safe to call before `wallet.start()` — we - * read the unshielded address from the wallet's already-running state - * stream. - */ -async function maybeRequestFaucet( - opts: PipelineOptions, - wallet: MidnightWalletProvider, - env: EnvironmentConfiguration, - network: NetworkConfig, - logger: Logger, -): Promise { - if (!network.faucet || opts.skipFaucet || !env.faucet) return; - const initialUnshielded = await Rx.firstValueFrom( - wallet.wallet.unshielded.state, - ); - const address = UnshieldedAddress.codec - .encode(getNetworkId(), initialUnshielded.address) - .toString(); - logger.info(`Requesting faucet tokens for ${address}…`); - await new FaucetClient(env.faucet, logger).requestTokens(address); -} - -interface ExecuteDeployArgs { - providers: Parameters[0]; - contractName: string; - contract: ContractConfig; - artifact: Artifact; - signingKey: string; - args: readonly unknown[]; - initialPrivateState: unknown; -} - -/** - * Assemble the `deployContract` options (conditionally including the - * private-state pair) and submit. Wraps any failure in - * {@link DeployTxFailedError} so callers can branch on its `exitCode` - * without parsing midnight-js error shapes. - */ -async function executeDeploy({ - providers, - contractName, - contract, - artifact, - signingKey, - args, - initialPrivateState, -}: ExecuteDeployArgs): Promise>> { - const compiled = artifact.compiledContract as Parameters< - typeof deployContract - >[1]['compiledContract']; - const base = { - compiledContract: compiled, - signingKey, - args, - } as Parameters[1]; - const deployOptions = - contract.private_state_id !== undefined - ? { - ...base, - privateStateId: contract.private_state_id, - initialPrivateState, - } - : base; - - try { - return await deployContract(providers, deployOptions); - } catch (e) { - throw new DeployTxFailedError( - `Deploy of "${contractName}" failed: ${(e as Error).message}`, - { cause: e }, - ); - } -} - -type DeployResult = Awaited>; - -/** Map the midnight-js deploy-tx result into the persisted record shape. */ -function toDeploymentRecord({ - deployTxData, - signingKey, - deployer, - artifact, -}: { - deployTxData: DeployResult['deployTxData']; - signingKey: string; - deployer: string; - artifact: string; -}): DeploymentRecord { - return { - address: deployTxData.public.contractAddress, - txHash: deployTxData.public.txHash, - txId: deployTxData.public.txId, - blockHeight: deployTxData.public.blockHeight, - signingKey, - deployer, - artifact, - timestamp: new Date().toISOString(), - }; -} - -/** Emit the same structured `dry-run: would deploy` event the old pipeline did. */ -function logDryRun( - logger: Logger, - details: { - contractName: string; - networkName: string; - artifact: Artifact; - argCount: number; - hasPrivateState: boolean; - faucet: boolean; - faucetUrl: string | undefined; - deployer: string; - }, -): void { - logger.info( - { - contract: details.contractName, - network: details.networkName, - artifact: details.artifact.artifactPath, - argCount: details.argCount, - hasPrivateState: details.hasPrivateState, - faucet: details.faucet, - faucetUrl: details.faucetUrl, - deployer: details.deployer, - }, - 'dry-run: would deploy', - ); -} - -/** Build the `PipelineResult` returned from a dry run (no on-chain fields). */ -function dryRunResult(params: { - contractName: string; - networkName: string; - signingKey: string; - deployer: string; - artifact: string; -}): PipelineResult { - return { - contractName: params.contractName, - network: params.networkName, - address: '', - txHash: '', - txId: '', - blockHeight: 0, - signingKey: params.signingKey, - deployer: params.deployer, - artifact: params.artifact, - deploymentsFile: '', - dryRun: true, - }; -} - -/** Build the `PipelineResult` returned from a confirmed deploy. */ -function successResult(params: { - contractName: string; - networkName: string; - record: DeploymentRecord; - deploymentsFile: string; -}): PipelineResult { - return { - contractName: params.contractName, - network: params.networkName, - address: params.record.address, - txHash: params.record.txHash, - txId: params.record.txId, - blockHeight: params.record.blockHeight, - signingKey: params.record.signingKey, - deployer: params.record.deployer, - artifact: params.record.artifact, - deploymentsFile: params.deploymentsFile, - dryRun: false, - }; -} - -/** Stop the wallet, swallowing teardown errors with a `warn` log. */ -async function safeStopWallet( - wallet: MidnightWalletProvider, - logger: Logger, -): Promise { - try { - await wallet.stop(); - } catch (e) { - logger.warn({ err: (e as Error).message }, 'Wallet stop failed'); - } -} - -/** Dispose the proof-server container, swallowing teardown errors with a `warn` log. */ -async function safeDisposeProofServer( - proofServer: ProofServer, - logger: Logger, -): Promise { - try { - await proofServer.dispose(); - } catch (e) { - logger.warn({ err: (e as Error).message }, 'Proof server dispose failed'); - } -} diff --git a/packages/deploy/src/wallet/build-deployer.ts b/packages/deploy/src/wallet/build-deployer.ts deleted file mode 100644 index 1b21e6b..0000000 --- a/packages/deploy/src/wallet/build-deployer.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { DustSecretKey, ZswapSecretKeys } from '@midnight-ntwrk/ledger-v8'; -import { - DEFAULT_DUST_OPTIONS, - type DustWalletOptions, - type EnvironmentConfiguration, - FluentWalletBuilder, - MidnightWalletProvider, -} from '@midnight-ntwrk/testkit-js'; -import type { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade'; -import type { Logger } from 'pino'; -import type { WalletSeed } from './normalize.ts'; - -/** - * Build a `MidnightWalletProvider` with dust options tuned for the target - * network. - * - * Two things this fixes vs. the bare `MidnightWalletProvider.build`: - * - * 1. **Dust overhead.** testkit-js' default `additionalFeeOverhead` is - * `1_000n`, which is too low for the dev-preset `undeployed` node — - * every deploy then fails with a generic `SubmissionError`. CMA's - * harness bumps to `5e17` for undeployed; we mirror that. - * - * 2. **Mnemonic-vs-hex routing.** `FluentWalletBuilder.withMnemonic` and - * `.withSeed(hex)` derive *different* wallets from the same input — - * `withMnemonic` runs the BIP39 → seed → wallet path expected by the - * genesis-funded test mnemonic (`TEST_MNEMONIC`), while a hex seed is - * interpreted as already-derived entropy. Keeping the seed's `kind` - * explicit lets us pick the right builder method. - * - * Caller still owns lifecycle: invoke `wallet.start(waitForFunds)` after - * (and any faucet hit) and `wallet.stop()` on teardown. - */ -export async function buildDeployerWallet( - logger: Logger, - env: EnvironmentConfiguration, - seed: WalletSeed, -): Promise { - const dustOptions: DustWalletOptions = { - ...DEFAULT_DUST_OPTIONS, - additionalFeeOverhead: - env.walletNetworkId === 'undeployed' - ? 500_000_000_000_000_000n - : DEFAULT_DUST_OPTIONS.additionalFeeOverhead, - }; - - const builder = FluentWalletBuilder.forEnvironment(env).withDustOptions( - dustOptions, - ); - const seeded = - seed.kind === 'mnemonic' - ? builder.withMnemonic(seed.value) - : builder.withSeed(seed.value); - - const build = await seeded.buildWithoutStarting(); - const { wallet, seeds, keystore } = build as unknown as { - wallet: WalletFacade; - seeds: { shielded: Uint8Array; dust: Uint8Array }; - keystore: Parameters[5]; - }; - - return MidnightWalletProvider.withWallet( - logger, - env, - wallet, - ZswapSecretKeys.fromSeed(seeds.shielded), - DustSecretKey.fromSeed(seeds.dust), - keystore, - ); -} diff --git a/packages/deploy/src/wallet/handler.ts b/packages/deploy/src/wallet/handler.ts new file mode 100644 index 0000000..e9cd32d --- /dev/null +++ b/packages/deploy/src/wallet/handler.ts @@ -0,0 +1,114 @@ +import { DustSecretKey, ZswapSecretKeys } from '@midnight-ntwrk/ledger-v8'; +import { + DEFAULT_DUST_OPTIONS, + type DustWalletOptions, + type EnvironmentConfiguration, + FluentWalletBuilder, + MidnightWalletProvider, +} from '@midnight-ntwrk/testkit-js'; +import type { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade'; +import type { Logger } from 'pino'; +import type { WalletSeed } from './seeds.ts'; + +/** + * Owned deployer wallet handle: a built `MidnightWalletProvider` paired + * with the lifecycle needed to release it. + * + * Always acquired via {@link WalletHandler.build} and handed to + * `AsyncDisposableStack.use()` (or `await using`) — the dispose hook + * stops the wallet and warn-logs any error so a failed teardown doesn't + * mask the deploy's primary failure. + * + * Mirrors the {@link ProofServer} pattern in `providers/proof-server.ts`. + * The underlying testkit provider is exposed via {@link provider}; pass + * that to anything that wants a plain `MidnightWalletProvider`. + */ +export class WalletHandler implements AsyncDisposable { + /** The underlying testkit-js wallet provider. */ + readonly provider: MidnightWalletProvider; + readonly #logger: Logger; + + private constructor(provider: MidnightWalletProvider, logger: Logger) { + this.provider = provider; + this.#logger = logger; + } + + /** + * Build a `MidnightWalletProvider` with dust options tuned for the + * target network, wrapped in a `WalletHandler` for safe teardown. + * + * Two things this fixes vs. the bare `MidnightWalletProvider.build`: + * + * 1. **Dust overhead.** testkit-js' default `additionalFeeOverhead` + * is `1_000n`, which is too low for the dev-preset `undeployed` + * node — every deploy then fails with a generic + * `SubmissionError`. CMA's harness bumps to `5e17` for + * undeployed; we mirror that. + * + * 2. **Mnemonic-vs-hex routing.** `FluentWalletBuilder.withMnemonic` + * and `.withSeed(hex)` derive *different* wallets from the same + * input — `withMnemonic` runs the BIP39 → seed → wallet path + * expected by the genesis-funded test mnemonic (`TEST_MNEMONIC`), + * while a hex seed is interpreted as already-derived entropy. + * Keeping the seed's `kind` explicit lets us pick the right + * builder method. + * + * Caller still drives `provider.start(waitForFunds)` (and any faucet + * hit); teardown is automatic via `await using` or + * `stack.use(wallet)`. + */ + static async build( + logger: Logger, + env: EnvironmentConfiguration, + seed: WalletSeed, + ): Promise { + const dustOptions: DustWalletOptions = { + ...DEFAULT_DUST_OPTIONS, + additionalFeeOverhead: + env.walletNetworkId === 'undeployed' + ? 500_000_000_000_000_000n + : DEFAULT_DUST_OPTIONS.additionalFeeOverhead, + }; + + const builder = FluentWalletBuilder.forEnvironment(env).withDustOptions( + dustOptions, + ); + const seeded = + seed.kind === 'mnemonic' + ? builder.withMnemonic(seed.value) + : builder.withSeed(seed.value); + + const build = await seeded.buildWithoutStarting(); + const { wallet, seeds, keystore } = build as unknown as { + wallet: WalletFacade; + seeds: { shielded: Uint8Array; dust: Uint8Array }; + keystore: Parameters[5]; + }; + + const provider = await MidnightWalletProvider.withWallet( + logger, + env, + wallet, + ZswapSecretKeys.fromSeed(seeds.shielded), + DustSecretKey.fromSeed(seeds.dust), + keystore, + ); + + return new WalletHandler(provider, logger); + } + + /** + * Stop the underlying wallet. Swallows the error with a `warn` log so + * a failed dispose doesn't mask the deploy's real error. + */ + async [Symbol.asyncDispose](): Promise { + try { + await this.provider.stop(); + } catch (e) { + this.#logger.warn( + { err: (e as Error).message }, + 'Wallet stop failed', + ); + } + } +} diff --git a/packages/deploy/src/wallet/local-seeds.ts b/packages/deploy/src/wallet/local-seeds.ts deleted file mode 100644 index a3f993b..0000000 --- a/packages/deploy/src/wallet/local-seeds.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TEST_MNEMONIC } from '@midnight-ntwrk/testkit-js'; - -/** - * Prefunded wallets on `midnight-node --preset=dev`. - * - * Slot 0 is the canonical testkit-js BIP39 mnemonic (`abandon × 23 diesel`), - * which the dev preset funds at genesis. Slots 1..4 are the additional hex - * seeds the standalone testkit exposes via `LocalTestEnvironment`. The - * mnemonic is normalised to its 128-char BIP39 hex seed inside - * `normalizeSeed`, so every entry here is the input we pass to - * `FluentWalletBuilder.withSeed(...)` after normalisation. - */ -export const LOCAL_PREFUNDED_SEEDS: readonly string[] = [ - TEST_MNEMONIC, - '0000000000000000000000000000000000000000000000000000000000000001', - '0000000000000000000000000000000000000000000000000000000000000002', - '0000000000000000000000000000000000000000000000000000000000000003', - '0000000000000000000000000000000000000000000000000000000000000004', -] as const; - -export function localPrefundedSeed(index: number): string { - const seed = LOCAL_PREFUNDED_SEEDS[index]; - if (!seed) { - throw new RangeError( - `local wallet index ${index} out of range (0..${LOCAL_PREFUNDED_SEEDS.length - 1})`, - ); - } - return seed; -} diff --git a/packages/deploy/src/wallet/normalize.ts b/packages/deploy/src/wallet/normalize.ts deleted file mode 100644 index d9f406d..0000000 --- a/packages/deploy/src/wallet/normalize.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { validateMnemonic } from '@scure/bip39'; -import { wordlist } from '@scure/bip39/wordlists/english'; -import { WalletError } from '../errors.ts'; - -/** - * Discriminated representation of a deployer wallet input. - * - * The wallet builder offers two paths — `.withSeed(hex)` and - * `.withMnemonic(phrase)` — that derive *different* wallets from the same - * underlying entropy. Keeping the kind explicit through the resolve chain - * lets the builder pick the matching method instead of force-converting a - * mnemonic to hex (which silently lands on the wrong wallet). - */ -export type WalletSeed = - | { kind: 'hex'; value: string } - | { kind: 'mnemonic'; value: string }; - -export function classifySeed(input: string): WalletSeed { - const trimmed = input.trim(); - if (!trimmed) { - throw new WalletError('Seed cannot be empty'); - } - if ( - /^[0-9a-fA-F]+$/.test(trimmed) && - (trimmed.length === 64 || trimmed.length === 128) - ) { - return { kind: 'hex', value: trimmed.toLowerCase() }; - } - if (validateMnemonic(trimmed, wordlist)) { - return { kind: 'mnemonic', value: trimmed }; - } - throw new WalletError( - 'Invalid seed: expected a 64/128-char hex string or a valid BIP39 mnemonic (12 or 24 words).', - ); -} diff --git a/packages/deploy/src/wallet/resolve.ts b/packages/deploy/src/wallet/resolve.ts deleted file mode 100644 index a5d46dd..0000000 --- a/packages/deploy/src/wallet/resolve.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { existsSync } from 'node:fs'; -import { readFile } from 'node:fs/promises'; -import { isAbsolute, resolve } from 'node:path'; -import type { CompactConfig } from '../config/compact-config.ts'; -import type { NetworkConfig } from '../config/schema.ts'; -import { WalletError } from '../errors.ts'; -import { Keystore } from './keystore.ts'; -import { localPrefundedSeed } from './local-seeds.ts'; -import { classifySeed, type WalletSeed } from './normalize.ts'; - -/** - * Resolve the deployer seed for a given network, with a documented - * precedence chain. - * - * Order (highest first): - * 1. `--seed-file ` (CLI) - * 2. `MN_DEPLOYER_SEED` (env) - * 3. `[wallet].keystore` (TOML; passphrase prompt required) - * 4. `[networks.local].wallet.source = "local"` (prefunded dev seed) - * - * Fails with {@link WalletError} when none match — with an actionable - * message that lists every path the user can take. - */ -export interface SeedResolution { - seed: WalletSeed; - origin: 'cli' | 'env' | 'keystore' | 'local'; -} - -export interface ResolveOptions { - config: CompactConfig; - networkName: string; - network: NetworkConfig; - seedFile?: string; - promptPassphrase?: (path: string) => Promise; -} - -export async function resolveSeed( - opts: ResolveOptions, -): Promise { - const { rootDir } = opts.config; - if (opts.seedFile) { - const path = absoluteUnder(rootDir, opts.seedFile); - const raw = await safeRead(path, '--seed-file'); - return { seed: classifySeed(raw), origin: 'cli' }; - } - - const envSeed = process.env.MN_DEPLOYER_SEED; - if (envSeed?.trim()) { - return { seed: classifySeed(envSeed), origin: 'env' }; - } - - const keystorePath = opts.config.wallet?.keystore; - if (keystorePath) { - const path = absoluteUnder(rootDir, keystorePath); - if (!existsSync(path)) { - throw new WalletError(`Keystore file not found: ${path}`); - } - if (!opts.promptPassphrase) { - throw new WalletError( - 'Keystore configured but no passphrase prompt provided', - ); - } - const ks = await Keystore.readFromFile(path); - const passphrase = await opts.promptPassphrase(path); - // Keystores store a raw 32-byte hex secret; classify ensures shape. - return { seed: classifySeed(ks.decrypt(passphrase)), origin: 'keystore' }; - } - - if (opts.networkName === 'local' && opts.network.wallet?.source === 'local') { - return { - seed: classifySeed(localPrefundedSeed(opts.network.wallet.index ?? 0)), - origin: 'local', - }; - } - - throw new WalletError( - `No deployer seed for network "${opts.networkName}". Provide --seed-file, set MN_DEPLOYER_SEED, or configure [wallet].keystore in compact.toml.`, - ); -} - -function absoluteUnder(root: string, p: string): string { - return isAbsolute(p) ? p : resolve(root, p); -} - -async function safeRead(path: string, label: string): Promise { - try { - return await readFile(path, 'utf8'); - } catch (e) { - throw new WalletError( - `Failed to read ${label} (${path}): ${(e as Error).message}`, - ); - } -} diff --git a/packages/deploy/src/wallet/normalize.test.ts b/packages/deploy/src/wallet/seeds.test.ts similarity index 96% rename from packages/deploy/src/wallet/normalize.test.ts rename to packages/deploy/src/wallet/seeds.test.ts index 3e08533..d89259e 100644 --- a/packages/deploy/src/wallet/normalize.test.ts +++ b/packages/deploy/src/wallet/seeds.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { WalletError } from '../errors.ts'; -import { classifySeed } from './normalize.ts'; +import { classifySeed } from './seeds.ts'; describe('classifySeed', () => { it('classifies a 64-char hex string as hex (lowercased)', () => { diff --git a/packages/deploy/src/wallet/seeds.ts b/packages/deploy/src/wallet/seeds.ts new file mode 100644 index 0000000..45b205e --- /dev/null +++ b/packages/deploy/src/wallet/seeds.ts @@ -0,0 +1,173 @@ +/** + * Seed input handling: prefunded local-dev seeds, hex-vs-mnemonic + * classification, and the precedence chain that picks a seed from CLI, + * env, keystore, or local pool. + * + * Kept as plain functions on purpose — none of these own state, hold a + * lifecycle, or share data; merging them into a class would just add + * ceremony. + */ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { isAbsolute, resolve } from 'node:path'; +import { TEST_MNEMONIC } from '@midnight-ntwrk/testkit-js'; +import { validateMnemonic } from '@scure/bip39'; +import { wordlist } from '@scure/bip39/wordlists/english'; +import type { CompactConfig } from '../config/compact-config.ts'; +import type { NetworkConfig } from '../config/schema.ts'; +import { WalletError } from '../errors.ts'; +import { Keystore } from './keystore.ts'; + +// --------------------------------------------------------------------------- +// Local prefunded seeds (dev-preset midnight-node). +// --------------------------------------------------------------------------- + +/** + * Prefunded wallets on `midnight-node --preset=dev`. + * + * Slot 0 is the canonical testkit-js BIP39 mnemonic + * (`abandon × 23 diesel`), which the dev preset funds at genesis. + * Slots 1..4 are the additional hex seeds the standalone testkit + * exposes via `LocalTestEnvironment`. + */ +export const LOCAL_PREFUNDED_SEEDS: readonly string[] = [ + TEST_MNEMONIC, + '0000000000000000000000000000000000000000000000000000000000000001', + '0000000000000000000000000000000000000000000000000000000000000002', + '0000000000000000000000000000000000000000000000000000000000000003', + '0000000000000000000000000000000000000000000000000000000000000004', +] as const; + +export function localPrefundedSeed(index: number): string { + const seed = LOCAL_PREFUNDED_SEEDS[index]; + if (!seed) { + throw new RangeError( + `local wallet index ${index} out of range (0..${LOCAL_PREFUNDED_SEEDS.length - 1})`, + ); + } + return seed; +} + +// --------------------------------------------------------------------------- +// Classify: raw string → discriminated WalletSeed. +// --------------------------------------------------------------------------- + +/** + * Discriminated representation of a deployer wallet input. + * + * The wallet builder offers two paths — `.withSeed(hex)` and + * `.withMnemonic(phrase)` — that derive *different* wallets from the + * same underlying entropy. Keeping the kind explicit through the + * resolve chain lets the builder pick the matching method instead of + * force-converting a mnemonic to hex (which silently lands on the + * wrong wallet). + */ +export type WalletSeed = + | { kind: 'hex'; value: string } + | { kind: 'mnemonic'; value: string }; + +export function classifySeed(input: string): WalletSeed { + const trimmed = input.trim(); + if (!trimmed) { + throw new WalletError('Seed cannot be empty'); + } + if ( + /^[0-9a-fA-F]+$/.test(trimmed) && + (trimmed.length === 64 || trimmed.length === 128) + ) { + return { kind: 'hex', value: trimmed.toLowerCase() }; + } + if (validateMnemonic(trimmed, wordlist)) { + return { kind: 'mnemonic', value: trimmed }; + } + throw new WalletError( + 'Invalid seed: expected a 64/128-char hex string or a valid BIP39 mnemonic (12 or 24 words).', + ); +} + +// --------------------------------------------------------------------------- +// Resolve: pick a seed from the precedence chain. +// --------------------------------------------------------------------------- + +/** + * Resolve the deployer seed for a given network, with a documented + * precedence chain. + * + * Order (highest first): + * 1. `--seed-file ` (CLI) + * 2. `MN_DEPLOYER_SEED` (env) + * 3. `[wallet].keystore` (TOML; passphrase prompt required) + * 4. `[networks.local].wallet.source = "local"` (prefunded dev seed) + * + * Fails with {@link WalletError} when none match — with an actionable + * message that lists every path the user can take. + */ +export interface SeedResolution { + seed: WalletSeed; + origin: 'cli' | 'env' | 'keystore' | 'local'; +} + +export interface ResolveOptions { + config: CompactConfig; + networkName: string; + network: NetworkConfig; + seedFile?: string; + promptPassphrase?: (path: string) => Promise; +} + +export async function resolveSeed( + opts: ResolveOptions, +): Promise { + const { rootDir } = opts.config; + if (opts.seedFile) { + const path = absoluteUnder(rootDir, opts.seedFile); + const raw = await safeRead(path, '--seed-file'); + return { seed: classifySeed(raw), origin: 'cli' }; + } + + const envSeed = process.env.MN_DEPLOYER_SEED; + if (envSeed?.trim()) { + return { seed: classifySeed(envSeed), origin: 'env' }; + } + + const keystorePath = opts.config.wallet?.keystore; + if (keystorePath) { + const path = absoluteUnder(rootDir, keystorePath); + if (!existsSync(path)) { + throw new WalletError(`Keystore file not found: ${path}`); + } + if (!opts.promptPassphrase) { + throw new WalletError( + 'Keystore configured but no passphrase prompt provided', + ); + } + const ks = await Keystore.readFromFile(path); + const passphrase = await opts.promptPassphrase(path); + return { seed: classifySeed(ks.decrypt(passphrase)), origin: 'keystore' }; + } + + if (opts.networkName === 'local' && opts.network.wallet?.source === 'local') { + return { + seed: classifySeed(localPrefundedSeed(opts.network.wallet.index ?? 0)), + origin: 'local', + }; + } + + throw new WalletError( + `No deployer seed for network "${opts.networkName}". Provide --seed-file, set MN_DEPLOYER_SEED, or configure [wallet].keystore in compact.toml.`, + ); +} + +function absoluteUnder(root: string, p: string): string { + return isAbsolute(p) ? p : resolve(root, p); +} + +async function safeRead(path: string, label: string): Promise { + try { + return await readFile(path, 'utf8'); + } catch (e) { + throw new WalletError( + `Failed to read ${label} (${path}): ${(e as Error).message}`, + ); + } +} diff --git a/tests/integrations/_harness/deployer.ts b/tests/integrations/_harness/deployer.ts index e6daad3..a33e2b4 100644 --- a/tests/integrations/_harness/deployer.ts +++ b/tests/integrations/_harness/deployer.ts @@ -1,4 +1,4 @@ -import { deploy, type DeployResult } from '@openzeppelin/compact-deploy'; +import { Deployer, type DeployResult } from '@openzeppelin/compact-deploy'; import { testLogger } from './logger.ts'; import { localNetworkConfig, setupLocalNetwork } from './network.ts'; import { CONFIG_PATH } from './paths.ts'; @@ -7,16 +7,16 @@ import { getSharedPool, type PoolAlias } from './walletPool.ts'; /** * Deploy `Counter` against the local stack using the wallet at `alias`. * - * Each spec is expected to call `deployFixture` with its own alias so the - * pipeline always reuses the same wallet for multiple deploys within that - * spec. Sharing one wallet across multiple `deploy` calls keeps its UTXO - * view internally consistent — a fresh `buildDeployerWallet` per deploy - * syncs from the indexer (which may lag) and can occasionally see an - * already-spent dust UTXO, producing a `DustDoubleSpend` rejection on - * submission. + * Each spec is expected to call `deployFixture` with its own alias so + * the Deployer always reuses the same wallet for multiple deploys + * within that spec. Sharing one wallet across multiple `deploy` calls + * keeps its UTXO view internally consistent — a fresh + * `WalletHandler.build` per deploy syncs from the indexer (which may + * lag) and can occasionally see an already-spent dust UTXO, producing + * a `DustDoubleSpend` rejection on submission. * - * Wallet lifecycle is owned by the shared pool: built and started on first - * use, stopped via `resetSharedPool()` once at end-of-suite. + * Wallet lifecycle is owned by the shared pool: built and started on + * first use, stopped via `resetSharedPool()` once at end-of-suite. */ export async function deployFixture( contract: 'Counter', @@ -25,12 +25,13 @@ export async function deployFixture( ): Promise { setupLocalNetwork(); const wallet = await getSharedPool(localNetworkConfig()).signerFor(alias); - return deploy({ + await using deployer = await Deployer.prepare({ contract, network: 'local', configPath: CONFIG_PATH, logger: testLogger(), walletProvider: wallet, - ...overrides, + proofServer: overrides.proofServer, }); + return overrides.dryRun ? deployer.dryRun() : deployer.deploy(); } diff --git a/tests/integrations/_harness/walletPool.ts b/tests/integrations/_harness/walletPool.ts index 1242074..e7d93b4 100644 --- a/tests/integrations/_harness/walletPool.ts +++ b/tests/integrations/_harness/walletPool.ts @@ -3,7 +3,7 @@ import { type MidnightWalletProvider, TEST_MNEMONIC, } from '@midnight-ntwrk/testkit-js'; -import { buildDeployerWallet, classifySeed } from '@openzeppelin/compact-deploy'; +import { WalletHandler, classifySeed } from '@openzeppelin/compact-deploy'; import { testLogger } from './logger.ts'; /** @@ -35,11 +35,15 @@ export type PoolAlias = keyof typeof PREFUNDED_SEEDS; * Specs that need wallet isolation can construct their own pool instance. */ export class WalletPool { - private cache = new Map>(); + private cache = new Map>(); constructor(private readonly env: EnvironmentConfiguration) {} - signerFor(alias: PoolAlias): Promise { + async signerFor(alias: PoolAlias): Promise { + return (await this.ownedFor(alias)).provider; + } + + private ownedFor(alias: PoolAlias): Promise { const seedString = PREFUNDED_SEEDS[alias]; if (seedString === undefined) { throw new Error( @@ -50,13 +54,13 @@ export class WalletPool { if (cached) return cached; const built = (async () => { - const wallet = await buildDeployerWallet( + const owned = await WalletHandler.build( testLogger(), this.env, classifySeed(seedString), ); - await wallet.start(true); - return wallet; + await owned.provider.start(true); + return owned; })(); this.cache.set(alias, built); return built; @@ -69,8 +73,7 @@ export class WalletPool { await Promise.all( entries.map(async (p) => { try { - const w = await p; - await w.stop(); + await (await p)[Symbol.asyncDispose](); } catch { /* ignore stop errors during teardown */ } diff --git a/tests/integrations/specs/errors.spec.ts b/tests/integrations/specs/errors.spec.ts index 4823352..8fe29ae 100644 --- a/tests/integrations/specs/errors.spec.ts +++ b/tests/integrations/specs/errors.spec.ts @@ -1,19 +1,19 @@ -import { deploy, ConfigError } from '@openzeppelin/compact-deploy'; +import { ConfigError, Deployer } from '@openzeppelin/compact-deploy'; import { describe, expect, it } from 'vitest'; import { testLogger } from '../_harness/logger.ts'; import { CONFIG_PATH, requireFixtureArtifact } from '../_harness/paths.ts'; /** - * Spec: pipeline surfaces typed `ConfigError`s for foreseeable user - * mistakes, with messages that name the offending key/value. These run - * against the live stack but never get past the config-validation phase, - * so they're fast. + * Spec: Deployer.prepare surfaces typed `ConfigError`s for foreseeable + * user mistakes, with messages that name the offending key/value. These + * run against the live stack but never get past the config-validation + * phase, so they're fast. */ describe('compact-deploy — config errors are typed and actionable', () => { it('rejects an unknown contract name', async () => { requireFixtureArtifact(); await expect( - deploy({ + Deployer.prepare({ contract: 'Nonexistent', network: 'local', configPath: CONFIG_PATH, @@ -25,7 +25,7 @@ describe('compact-deploy — config errors are typed and actionable', () => { it('rejects an unknown network name', async () => { requireFixtureArtifact(); await expect( - deploy({ + Deployer.prepare({ contract: 'Counter', network: 'unknown-network', configPath: CONFIG_PATH, @@ -36,7 +36,7 @@ describe('compact-deploy — config errors are typed and actionable', () => { it('rejects a missing compact.toml path', async () => { await expect( - deploy({ + Deployer.prepare({ contract: 'Counter', network: 'local', configPath: '/nonexistent/compact.toml', From 6ebce94ab853ca065a56a0944a12b9225b96f1f8 Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:05:32 +0200 Subject: [PATCH 06/12] test(deploy): add unit tests for Deployer and WalletHandler Mock-based unit tests covering orchestration semantics that integration tests can't isolate cheaply: - Deployer (6 tests): dryRun returns dryRun:true and never calls deployContract; deploy submits the tx and returns the populated success result; injected walletProvider is adopted without calling WalletHandler.build; missing walletProvider builds and starts a wallet; Symbol.asyncDispose stops owned wallets but leaves injected ones alone; deployContract failures are wrapped in DeployTxFailedError. - WalletHandler (7 tests): mnemonic seed routes through .withMnemonic; hex seed routes through .withSeed; additionalFeeOverhead bumps to 5e17 for the undeployed network and keeps the testkit default otherwise; .provider returns the wallet built by MidnightWalletProvider.withWallet; Symbol.asyncDispose stops the underlying wallet; dispose swallows stop() failures with a warn log. testkit-js + ledger-v8 + midnight-js are vi.mock'd; CompactConfig, SigningKey, Deployments run against real tmpdir fixtures. End-to-end network flow remains covered by tests/integrations/. --- packages/deploy/src/deployer.test.ts | 301 +++++++++++++++++++++ packages/deploy/src/wallet/handler.test.ts | 213 +++++++++++++++ 2 files changed, 514 insertions(+) create mode 100644 packages/deploy/src/deployer.test.ts create mode 100644 packages/deploy/src/wallet/handler.test.ts diff --git a/packages/deploy/src/deployer.test.ts b/packages/deploy/src/deployer.test.ts new file mode 100644 index 0000000..342a525 --- /dev/null +++ b/packages/deploy/src/deployer.test.ts @@ -0,0 +1,301 @@ +/** + * Unit tests for the `Deployer` orchestration class. + * + * Heavy collaborators (artifact import, proof-server container, wallet + * build, midnight-js `deployContract`, providers) are replaced via + * `vi.mock`. The remaining flow — config + signing key + deployments + * file — runs against real code with a tmpdir fixture. These tests + * exercise orchestration semantics only (wallet adoption vs build, + * dispose, dry-run, error wrapping); the end-to-end network path is + * covered by `tests/integrations/`. + * + * Test-only `as unknown as` casts at the mock boundary are intentional: + * `MidnightWalletProvider` and `WalletHandler` both have private + * fields and so cannot be produced structurally — duck-typing the + * small public surface `Deployer` actually touches is the cleanest + * substitute. + */ +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { deployContract } from '@midnight-ntwrk/midnight-js-contracts'; +import type { MidnightWalletProvider } from '@midnight-ntwrk/testkit-js'; +import pino from 'pino'; +import { + afterEach, + beforeEach, + describe, + expect, + it, + type Mock, + vi, +} from 'vitest'; +import { Deployer } from './deployer.ts'; +import { DeployTxFailedError } from './errors.ts'; +import { buildProviders } from './providers/build.ts'; +import { WalletHandler } from './wallet/handler.ts'; + +vi.mock('./loaders/artifact.ts', () => ({ + Artifact: { + load: vi.fn(async () => ({ + artifactPath: '/fake/artifact', + zkConfigPath: '/fake/artifact', + compiledContract: { fake: 'compiled' }, + circuitNames: ['increment'], + })), + }, +})); + +vi.mock('./providers/proof-server.ts', () => ({ + ProofServer: { + start: vi.fn(async () => ({ + url: 'http://localhost:6300', + [Symbol.asyncDispose]: async () => { + // no-op for static-URL stub + }, + })), + }, +})); + +vi.mock('./providers/build.ts', () => ({ + buildProviders: vi.fn(() => ({})), +})); + +vi.mock('./wallet/handler.ts', () => ({ + WalletHandler: { build: vi.fn() }, +})); + +vi.mock('@midnight-ntwrk/midnight-js-contracts', () => ({ + deployContract: vi.fn(), +})); + +const silentLogger = pino({ level: 'silent' }); + +/** + * Public surface of `MidnightWalletProvider` that `Deployer` actually + * calls. Cast to `MidnightWalletProvider` at the boundary where we + * hand it into the pipeline. + */ +interface FakeProvider { + getCoinPublicKey: () => string; + start: Mock; + stop: Mock; +} + +function fakeProvider(coinKey = '0xCOIN'): FakeProvider { + return { + getCoinPublicKey: () => coinKey, + start: vi.fn(async () => undefined), + stop: vi.fn(async () => undefined), + }; +} + +function asInjected(p: FakeProvider): MidnightWalletProvider { + return p as unknown as MidnightWalletProvider; +} + +interface FakeOwned { + owned: WalletHandler; + provider: FakeProvider; + dispose: Mock; +} + +/** + * Build a `WalletHandler`-shaped fake whose `[Symbol.asyncDispose]` + * spy mirrors the real class's contract: call `provider.stop()` then + * record the call. Tests can assert against the dispose spy, the stop + * spy, or both. + */ +function fakeOwnedWallet(coinKey = '0xCOIN'): FakeOwned { + const provider = fakeProvider(coinKey); + const dispose = vi.fn(async () => { + await provider.stop(); + }); + const owned = { + provider, + [Symbol.asyncDispose]: dispose, + } as unknown as WalletHandler; + return { owned, provider, dispose }; +} + +type DeployTxResult = Awaited>; +function fakeDeployTxResult(address = '0xCONTRACT'): DeployTxResult { + return { + deployTxData: { + public: { + contractAddress: address, + txHash: '0xHASH', + txId: '0xTX', + blockHeight: 1234, + }, + }, + } as unknown as DeployTxResult; +} + +interface Fixture { + rootDir: string; + configPath: string; + cleanup: () => void; +} + +function writeFixture(): Fixture { + const rootDir = mkdtempSync(join(tmpdir(), 'deployer-test-')); + const toml = ` +[profile] +artifacts_dir = "artifacts" +deployments_dir = "deployments" + +[networks.local] +network_id = "undeployed" +indexer = "http://localhost:8088/api/v1/graphql" +indexer_ws = "ws://localhost:8088/api/v1/graphql/ws" +node = "http://localhost:9944" +node_ws = "ws://localhost:9944" +proof_server = "http://localhost:6300" +wallet = { source = "local", index = 0 } + +[contracts.Counter] +artifact = "Counter" +signing_key_file = "signing-key.hex" +`; + writeFileSync(join(rootDir, 'compact.toml'), toml); + writeFileSync(join(rootDir, 'signing-key.hex'), `${'aa'.repeat(32)}\n`); + return { + rootDir, + configPath: join(rootDir, 'compact.toml'), + cleanup: () => rmSync(rootDir, { recursive: true, force: true }), + }; +} + +describe('Deployer', () => { + let fx: Fixture; + + beforeEach(() => { + fx = writeFixture(); + // Default owned-build returns a fresh fake; tests that need to + // introspect the built provider override with `mockResolvedValueOnce`. + vi.mocked(WalletHandler.build).mockImplementation( + async () => fakeOwnedWallet().owned, + ); + vi.mocked(deployContract).mockResolvedValue(fakeDeployTxResult()); + }); + + afterEach(() => { + fx.cleanup(); + vi.clearAllMocks(); + }); + + it('dryRun returns dryRun:true and never submits a tx', async () => { + const injected = fakeProvider('0xINJECTED'); + await using d = await Deployer.prepare({ + contract: 'Counter', + network: 'local', + configPath: fx.configPath, + logger: silentLogger, + walletProvider: asInjected(injected), + }); + const result = await d.dryRun(); + + expect(result.dryRun).toBe(true); + expect(result.address).toBe(''); + expect(result.txHash).toBe(''); + expect(result.deploymentsFile).toBe(''); + expect(result.contractName).toBe('Counter'); + expect(result.network).toBe('local'); + expect(result.deployer).toBe('0xINJECTED'); + expect(deployContract).not.toHaveBeenCalled(); + }); + + it('deploy submits the tx and returns the populated success result', async () => { + const injected = fakeProvider('0xDEPLOYER'); + await using d = await Deployer.prepare({ + contract: 'Counter', + network: 'local', + configPath: fx.configPath, + logger: silentLogger, + walletProvider: asInjected(injected), + }); + const result = await d.deploy(); + + expect(deployContract).toHaveBeenCalledTimes(1); + expect(buildProviders).toHaveBeenCalledTimes(1); + expect(result.dryRun).toBe(false); + expect(result.address).toBe('0xCONTRACT'); + expect(result.txHash).toBe('0xHASH'); + expect(result.txId).toBe('0xTX'); + expect(result.blockHeight).toBe(1234); + expect(result.deployer).toBe('0xDEPLOYER'); + expect(result.deploymentsFile).toContain('deployments'); + }); + + it('adopts an injected walletProvider without calling WalletHandler.build', async () => { + const injected = fakeProvider(); + await using d = await Deployer.prepare({ + contract: 'Counter', + network: 'local', + configPath: fx.configPath, + logger: silentLogger, + walletProvider: asInjected(injected), + }); + expect(d.contractName).toBe('Counter'); + expect(WalletHandler.build).not.toHaveBeenCalled(); + expect(injected.start).not.toHaveBeenCalled(); + }); + + it('builds + starts a wallet when none is injected', async () => { + const built = fakeOwnedWallet('0xBUILT'); + vi.mocked(WalletHandler.build).mockResolvedValueOnce(built.owned); + await using d = await Deployer.prepare({ + contract: 'Counter', + network: 'local', + configPath: fx.configPath, + logger: silentLogger, + }); + expect(d.deployer).toBe('0xBUILT'); + expect(WalletHandler.build).toHaveBeenCalledTimes(1); + expect(built.provider.start).toHaveBeenCalledWith(true); + }); + + it('disposes the owned wallet on asyncDispose but leaves an injected one alone', async () => { + const built = fakeOwnedWallet('0xOWNED'); + const injected = fakeProvider('0xINJ'); + vi.mocked(WalletHandler.build).mockResolvedValueOnce(built.owned); + { + await using owned = await Deployer.prepare({ + contract: 'Counter', + network: 'local', + configPath: fx.configPath, + logger: silentLogger, + }); + expect(owned.deployer).toBe('0xOWNED'); + } + { + await using adopted = await Deployer.prepare({ + contract: 'Counter', + network: 'local', + configPath: fx.configPath, + logger: silentLogger, + walletProvider: asInjected(injected), + }); + expect(adopted.deployer).toBe('0xINJ'); + } + expect(built.dispose).toHaveBeenCalledTimes(1); + expect(built.provider.stop).toHaveBeenCalledTimes(1); + expect(injected.stop).not.toHaveBeenCalled(); + }); + + it('wraps midnight-js deploy failures in DeployTxFailedError', async () => { + vi.mocked(deployContract).mockRejectedValueOnce( + new Error('chain rejected'), + ); + const injected = fakeProvider(); + await using d = await Deployer.prepare({ + contract: 'Counter', + network: 'local', + configPath: fx.configPath, + logger: silentLogger, + walletProvider: asInjected(injected), + }); + await expect(d.deploy()).rejects.toBeInstanceOf(DeployTxFailedError); + }); +}); diff --git a/packages/deploy/src/wallet/handler.test.ts b/packages/deploy/src/wallet/handler.test.ts new file mode 100644 index 0000000..b8e7f69 --- /dev/null +++ b/packages/deploy/src/wallet/handler.test.ts @@ -0,0 +1,213 @@ +/** + * Unit tests for the `WalletHandler` class. + * + * The whole `testkit-js` builder chain plus `ledger-v8`'s secret-key + * factories are replaced with `vi.mock` stubs — only the two pieces of + * business logic that justify this class's existence are exercised: + * + * - **Mnemonic vs hex routing.** `FluentWalletBuilder.withMnemonic` + * and `.withSeed(hex)` derive *different* wallets, so picking the + * wrong branch silently produces the wrong account. + * - **Dust overhead bump.** The `undeployed` dev preset needs + * `additionalFeeOverhead = 5e17` or every deploy fails with a + * generic `SubmissionError`; every other network keeps the + * testkit-js default. + * + * The remaining tests cover the disposable contract (provider stop on + * `[Symbol.asyncDispose]`, warn-log on stop failure). + */ +import type { + EnvironmentConfiguration, + MidnightWalletProvider, +} from '@midnight-ntwrk/testkit-js'; +import { + DEFAULT_DUST_OPTIONS, + FluentWalletBuilder, + MidnightWalletProvider as MidnightWalletProviderClass, +} from '@midnight-ntwrk/testkit-js'; +import type { Logger } from 'pino'; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; +import { WalletHandler } from './handler.ts'; + +vi.mock('@midnight-ntwrk/testkit-js', () => ({ + DEFAULT_DUST_OPTIONS: { additionalFeeOverhead: 1000n }, + FluentWalletBuilder: { forEnvironment: vi.fn() }, + MidnightWalletProvider: { withWallet: vi.fn() }, +})); + +vi.mock('@midnight-ntwrk/ledger-v8', () => ({ + ZswapSecretKeys: { fromSeed: vi.fn(() => ({ tag: 'zswap-keys' })) }, + DustSecretKey: { fromSeed: vi.fn(() => ({ tag: 'dust-key' })) }, +})); + +interface FakeProvider { + stop: Mock; +} + +function fakeProvider(opts: { failsOnStop?: boolean } = {}): FakeProvider { + return { + stop: vi.fn( + opts.failsOnStop + ? async () => { + throw new Error('boom'); + } + : async () => undefined, + ), + }; +} + +interface BuilderChain { + envBuilder: { withDustOptions: Mock }; + dustBuilder: { withMnemonic: Mock; withSeed: Mock }; + seededBuilder: { buildWithoutStarting: Mock }; +} + +/** + * Wire up the FluentWalletBuilder + withWallet mock chain so that + * `WalletHandler.build(...)` produces a handler whose `.provider` is + * the supplied fake. Returns each link in the chain so tests can + * assert which method was called. + */ +function wireTestkitChain(provider: FakeProvider): BuilderChain { + const seededBuilder = { + buildWithoutStarting: vi.fn(async () => ({ + wallet: { tag: 'wallet-facade' }, + seeds: { + shielded: new Uint8Array(32), + dust: new Uint8Array(32), + }, + keystore: { tag: 'keystore' }, + })), + }; + const dustBuilder = { + withMnemonic: vi.fn(() => seededBuilder), + withSeed: vi.fn(() => seededBuilder), + }; + const envBuilder = { + withDustOptions: vi.fn(() => dustBuilder), + }; + vi.mocked(FluentWalletBuilder.forEnvironment).mockReturnValue( + envBuilder as unknown as ReturnType, + ); + vi.mocked(MidnightWalletProviderClass.withWallet).mockResolvedValue( + provider as unknown as MidnightWalletProvider, + ); + return { envBuilder, dustBuilder, seededBuilder }; +} + +/** Pino-shaped logger whose methods are spies, freshly built per test. */ +function spyLogger(): Logger { + const logger: Record = { + trace: vi.fn(), + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + fatal: vi.fn(), + level: 'silent', + }; + logger.child = (): Logger => spyLogger(); + return logger as unknown as Logger; +} + +function fakeEnv( + walletNetworkId: EnvironmentConfiguration['walletNetworkId'] = 'testnet', +): EnvironmentConfiguration { + return { walletNetworkId } as unknown as EnvironmentConfiguration; +} + +describe('WalletHandler', () => { + let logger: Logger; + + beforeEach(() => { + logger = spyLogger(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('routes a mnemonic seed through .withMnemonic', async () => { + const chain = wireTestkitChain(fakeProvider()); + await WalletHandler.build(logger, fakeEnv(), { + kind: 'mnemonic', + value: 'abandon abandon abandon', + }); + expect(chain.dustBuilder.withMnemonic).toHaveBeenCalledWith( + 'abandon abandon abandon', + ); + expect(chain.dustBuilder.withSeed).not.toHaveBeenCalled(); + }); + + it('routes a hex seed through .withSeed', async () => { + const chain = wireTestkitChain(fakeProvider()); + await WalletHandler.build(logger, fakeEnv(), { + kind: 'hex', + value: 'aa'.repeat(32), + }); + expect(chain.dustBuilder.withSeed).toHaveBeenCalledWith('aa'.repeat(32)); + expect(chain.dustBuilder.withMnemonic).not.toHaveBeenCalled(); + }); + + it('bumps additionalFeeOverhead for the undeployed network', async () => { + const chain = wireTestkitChain(fakeProvider()); + await WalletHandler.build(logger, fakeEnv('undeployed'), { + kind: 'hex', + value: '00', + }); + expect(chain.envBuilder.withDustOptions).toHaveBeenCalledWith( + expect.objectContaining({ + additionalFeeOverhead: 500_000_000_000_000_000n, + }), + ); + }); + + it('keeps the testkit default additionalFeeOverhead for other networks', async () => { + const chain = wireTestkitChain(fakeProvider()); + await WalletHandler.build(logger, fakeEnv('testnet'), { + kind: 'hex', + value: '00', + }); + expect(chain.envBuilder.withDustOptions).toHaveBeenCalledWith( + expect.objectContaining({ + additionalFeeOverhead: DEFAULT_DUST_OPTIONS.additionalFeeOverhead, + }), + ); + }); + + it('.provider returns the wallet built by MidnightWalletProvider.withWallet', async () => { + const provider = fakeProvider(); + wireTestkitChain(provider); + const handler = await WalletHandler.build(logger, fakeEnv(), { + kind: 'hex', + value: '00', + }); + expect(handler.provider).toBe(provider); + }); + + it('Symbol.asyncDispose stops the underlying wallet', async () => { + const provider = fakeProvider(); + wireTestkitChain(provider); + const handler = await WalletHandler.build(logger, fakeEnv(), { + kind: 'hex', + value: '00', + }); + await handler[Symbol.asyncDispose](); + expect(provider.stop).toHaveBeenCalledTimes(1); + }); + + it('Symbol.asyncDispose swallows stop() failures with a warn log', async () => { + const provider = fakeProvider({ failsOnStop: true }); + wireTestkitChain(provider); + const handler = await WalletHandler.build(logger, fakeEnv(), { + kind: 'hex', + value: '00', + }); + await expect(handler[Symbol.asyncDispose]()).resolves.toBeUndefined(); + expect(provider.stop).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + expect.objectContaining({ err: 'boom' }), + 'Wallet stop failed', + ); + }); +}); From 0a6499ca29a6a49936fb67a9330419e886840fec Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:07:39 +0200 Subject: [PATCH 07/12] refactor(deploy): rename package to @openzeppelin/compact-deployer Renames the npm package from `@openzeppelin/compact-deploy` to `@openzeppelin/compact-deployer`. Updates workspace deps in root + compact-cli, all `from '@openzeppelin/compact-deploy'` imports across the CLI, integration harness and specs, the JSDoc/README references, and the regenerated yarn.lock. The `compact-deploy` binary name and the `packages/deploy/` directory layout are intentionally left unchanged. --- package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/src/runDeploy.ts | 2 +- packages/deploy/README.md | 4 ++-- packages/deploy/package.json | 2 +- packages/deploy/src/index.ts | 4 ++-- tests/integrations/README.md | 4 ++-- tests/integrations/_harness/deployer.ts | 2 +- tests/integrations/_harness/walletPool.ts | 2 +- tests/integrations/specs/errors.spec.ts | 2 +- yarn.lock | 8 ++++---- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index f69fce4..fa7bab6 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "@biomejs/biome": "2.3.8", - "@openzeppelin/compact-deploy": "workspace:^", + "@openzeppelin/compact-deployer": "workspace:^", "@types/node": "24.10.1", "pino": "^9.7.0", "ts-node": "^10.9.2", diff --git a/packages/cli/package.json b/packages/cli/package.json index 976b2bd..93e217f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -46,7 +46,7 @@ }, "dependencies": { "@openzeppelin/compact-builder": "workspace:^", - "@openzeppelin/compact-deploy": "workspace:^", + "@openzeppelin/compact-deployer": "workspace:^", "chalk": "^5.6.2", "ora": "^9.0.0", "pino": "^9.7.0", diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index 685d5ad..47c89ef 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -19,7 +19,7 @@ import chalk from 'chalk'; import ora from 'ora'; import { WebSocket } from 'ws'; -import { Deployer, DeployError } from '@openzeppelin/compact-deploy'; +import { Deployer, DeployError } from '@openzeppelin/compact-deployer'; import { createLogger } from './logger.ts'; import { promptPassphrase } from './prompt.ts'; diff --git a/packages/deploy/README.md b/packages/deploy/README.md index 2d89f8a..7800892 100644 --- a/packages/deploy/README.md +++ b/packages/deploy/README.md @@ -1,4 +1,4 @@ -# @openzeppelin/compact-deploy +# @openzeppelin/compact-deployer Forge-style deployer CLI for Midnight Compact contracts. @@ -118,7 +118,7 @@ signing_key_file = "./deploy/Vault.signingkey" ## Programmatic API ```ts -import { deploy } from "@openzeppelin/compact-deploy"; +import { deploy } from "@openzeppelin/compact-deployer"; const result = await deploy({ contract: "Token", diff --git a/packages/deploy/package.json b/packages/deploy/package.json index 377f609..dfeb3a6 100644 --- a/packages/deploy/package.json +++ b/packages/deploy/package.json @@ -1,5 +1,5 @@ { - "name": "@openzeppelin/compact-deploy", + "name": "@openzeppelin/compact-deployer", "description": "Forge-style deployer library for Midnight Compact contracts", "version": "0.0.1", "keywords": [ diff --git a/packages/deploy/src/index.ts b/packages/deploy/src/index.ts index cac0389..1150f64 100644 --- a/packages/deploy/src/index.ts +++ b/packages/deploy/src/index.ts @@ -1,11 +1,11 @@ /** - * Programmatic API surface for `@openzeppelin/compact-deploy`. + * Programmatic API surface for `@openzeppelin/compact-deployer`. * * Consumers that need to embed the deploy pipeline (CI runners, custom CLIs, * test harnesses) should import from this barrel. The `compact-deploy` binary * in `bin/` re-uses the same exports — it is just an opinionated shell. */ -// biome-ignore-all lint/performance/noBarrelFile: this file is the programmatic API surface for consumers of @openzeppelin/compact-deploy +// biome-ignore-all lint/performance/noBarrelFile: this file is the programmatic API surface for consumers of @openzeppelin/compact-deployer export { CompactConfig } from './config/compact-config.ts'; export type { ContractConfig, diff --git a/tests/integrations/README.md b/tests/integrations/README.md index 9199231..e7a3059 100644 --- a/tests/integrations/README.md +++ b/tests/integrations/README.md @@ -1,6 +1,6 @@ # compact-tools — integration tests -End-to-end tests for `@openzeppelin/compact-deploy` against a real local Midnight stack (proof-server + indexer + node, Docker). +End-to-end tests for `@openzeppelin/compact-deployer` against a real local Midnight stack (proof-server + indexer + node, Docker). ## Layout @@ -18,7 +18,7 @@ tests/integrations/ deploy.local.spec.ts # Specs: dry-run, deploy, history rotation ``` -This is **not** a workspace package. The root `package.json` adds `@openzeppelin/compact-deploy` as a dev dep (resolved via yarn workspaces), and the root `test:integration` script invokes vitest pointed at this folder. +This is **not** a workspace package. The root `package.json` adds `@openzeppelin/compact-deployer` as a dev dep (resolved via yarn workspaces), and the root `test:integration` script invokes vitest pointed at this folder. ## Run diff --git a/tests/integrations/_harness/deployer.ts b/tests/integrations/_harness/deployer.ts index a33e2b4..8d25040 100644 --- a/tests/integrations/_harness/deployer.ts +++ b/tests/integrations/_harness/deployer.ts @@ -1,4 +1,4 @@ -import { Deployer, type DeployResult } from '@openzeppelin/compact-deploy'; +import { Deployer, type DeployResult } from '@openzeppelin/compact-deployer'; import { testLogger } from './logger.ts'; import { localNetworkConfig, setupLocalNetwork } from './network.ts'; import { CONFIG_PATH } from './paths.ts'; diff --git a/tests/integrations/_harness/walletPool.ts b/tests/integrations/_harness/walletPool.ts index e7d93b4..6cc1992 100644 --- a/tests/integrations/_harness/walletPool.ts +++ b/tests/integrations/_harness/walletPool.ts @@ -3,7 +3,7 @@ import { type MidnightWalletProvider, TEST_MNEMONIC, } from '@midnight-ntwrk/testkit-js'; -import { WalletHandler, classifySeed } from '@openzeppelin/compact-deploy'; +import { WalletHandler, classifySeed } from '@openzeppelin/compact-deployer'; import { testLogger } from './logger.ts'; /** diff --git a/tests/integrations/specs/errors.spec.ts b/tests/integrations/specs/errors.spec.ts index 8fe29ae..6d71148 100644 --- a/tests/integrations/specs/errors.spec.ts +++ b/tests/integrations/specs/errors.spec.ts @@ -1,4 +1,4 @@ -import { ConfigError, Deployer } from '@openzeppelin/compact-deploy'; +import { ConfigError, Deployer } from '@openzeppelin/compact-deployer'; import { describe, expect, it } from 'vitest'; import { testLogger } from '../_harness/logger.ts'; import { CONFIG_PATH, requireFixtureArtifact } from '../_harness/paths.ts'; diff --git a/yarn.lock b/yarn.lock index e098311..a6050ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1048,7 +1048,7 @@ __metadata: resolution: "@openzeppelin/compact-cli@workspace:packages/cli" dependencies: "@openzeppelin/compact-builder": "workspace:^" - "@openzeppelin/compact-deploy": "workspace:^" + "@openzeppelin/compact-deployer": "workspace:^" "@tsconfig/node24": "npm:^24.0.3" "@types/node": "npm:24.10.1" "@types/ws": "npm:^8.5.10" @@ -1066,9 +1066,9 @@ __metadata: languageName: unknown linkType: soft -"@openzeppelin/compact-deploy@workspace:^, @openzeppelin/compact-deploy@workspace:packages/deploy": +"@openzeppelin/compact-deployer@workspace:^, @openzeppelin/compact-deployer@workspace:packages/deploy": version: 0.0.0-use.local - resolution: "@openzeppelin/compact-deploy@workspace:packages/deploy" + resolution: "@openzeppelin/compact-deployer@workspace:packages/deploy" dependencies: "@midnight-ntwrk/compact-js": "npm:2.5.0" "@midnight-ntwrk/compact-runtime": "npm:0.16.0" @@ -2799,7 +2799,7 @@ __metadata: resolution: "compact-tools-monorepo@workspace:." dependencies: "@biomejs/biome": "npm:2.3.8" - "@openzeppelin/compact-deploy": "workspace:^" + "@openzeppelin/compact-deployer": "workspace:^" "@types/node": "npm:24.10.1" pino: "npm:^9.7.0" ts-node: "npm:^10.9.2" From b1cb4df53b5cd6c705497bba2a2457d84dd46af5 Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:09:24 +0200 Subject: [PATCH 08/12] refactor(deployer): rename directory packages/deploy -> packages/deployer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns the workspace folder layout with the @openzeppelin/compact-deployer package name. Git tracks every file as a rename so blame is preserved. No path references needed updating — the yarn workspaces glob is `packages/*`, and no script or tsconfig hardcoded `packages/deploy`. --- packages/{deploy => deployer}/README.md | 0 packages/{deploy => deployer}/package.json | 0 .../{deploy => deployer}/src/config/compact-config.test.ts | 0 packages/{deploy => deployer}/src/config/compact-config.ts | 0 packages/{deploy => deployer}/src/config/schema.ts | 0 packages/{deploy => deployer}/src/deployer.test.ts | 0 packages/{deploy => deployer}/src/deployer.ts | 0 packages/{deploy => deployer}/src/deployments.test.ts | 0 packages/{deploy => deployer}/src/deployments.ts | 0 packages/{deploy => deployer}/src/errors.ts | 0 packages/{deploy => deployer}/src/index.ts | 0 packages/{deploy => deployer}/src/loaders/args.test.ts | 0 packages/{deploy => deployer}/src/loaders/args.ts | 0 packages/{deploy => deployer}/src/loaders/artifact.ts | 0 packages/{deploy => deployer}/src/loaders/context.ts | 0 packages/{deploy => deployer}/src/loaders/init-state.test.ts | 0 packages/{deploy => deployer}/src/loaders/init-state.ts | 0 packages/{deploy => deployer}/src/loaders/ref-resolver.ts | 0 packages/{deploy => deployer}/src/loaders/signing-key.test.ts | 0 packages/{deploy => deployer}/src/loaders/signing-key.ts | 0 packages/{deploy => deployer}/src/providers/build.ts | 0 packages/{deploy => deployer}/src/providers/network.ts | 0 .../src/providers/private-state-password.test.ts | 0 .../src/providers/private-state-password.ts | 0 packages/{deploy => deployer}/src/providers/proof-server.ts | 0 packages/{deploy => deployer}/src/wallet/handler.test.ts | 0 packages/{deploy => deployer}/src/wallet/handler.ts | 0 packages/{deploy => deployer}/src/wallet/keystore.test.ts | 0 packages/{deploy => deployer}/src/wallet/keystore.ts | 0 packages/{deploy => deployer}/src/wallet/seeds.test.ts | 0 packages/{deploy => deployer}/src/wallet/seeds.ts | 0 packages/{deploy => deployer}/tsconfig.json | 0 yarn.lock | 4 ++-- 33 files changed, 2 insertions(+), 2 deletions(-) rename packages/{deploy => deployer}/README.md (100%) rename packages/{deploy => deployer}/package.json (100%) rename packages/{deploy => deployer}/src/config/compact-config.test.ts (100%) rename packages/{deploy => deployer}/src/config/compact-config.ts (100%) rename packages/{deploy => deployer}/src/config/schema.ts (100%) rename packages/{deploy => deployer}/src/deployer.test.ts (100%) rename packages/{deploy => deployer}/src/deployer.ts (100%) rename packages/{deploy => deployer}/src/deployments.test.ts (100%) rename packages/{deploy => deployer}/src/deployments.ts (100%) rename packages/{deploy => deployer}/src/errors.ts (100%) rename packages/{deploy => deployer}/src/index.ts (100%) rename packages/{deploy => deployer}/src/loaders/args.test.ts (100%) rename packages/{deploy => deployer}/src/loaders/args.ts (100%) rename packages/{deploy => deployer}/src/loaders/artifact.ts (100%) rename packages/{deploy => deployer}/src/loaders/context.ts (100%) rename packages/{deploy => deployer}/src/loaders/init-state.test.ts (100%) rename packages/{deploy => deployer}/src/loaders/init-state.ts (100%) rename packages/{deploy => deployer}/src/loaders/ref-resolver.ts (100%) rename packages/{deploy => deployer}/src/loaders/signing-key.test.ts (100%) rename packages/{deploy => deployer}/src/loaders/signing-key.ts (100%) rename packages/{deploy => deployer}/src/providers/build.ts (100%) rename packages/{deploy => deployer}/src/providers/network.ts (100%) rename packages/{deploy => deployer}/src/providers/private-state-password.test.ts (100%) rename packages/{deploy => deployer}/src/providers/private-state-password.ts (100%) rename packages/{deploy => deployer}/src/providers/proof-server.ts (100%) rename packages/{deploy => deployer}/src/wallet/handler.test.ts (100%) rename packages/{deploy => deployer}/src/wallet/handler.ts (100%) rename packages/{deploy => deployer}/src/wallet/keystore.test.ts (100%) rename packages/{deploy => deployer}/src/wallet/keystore.ts (100%) rename packages/{deploy => deployer}/src/wallet/seeds.test.ts (100%) rename packages/{deploy => deployer}/src/wallet/seeds.ts (100%) rename packages/{deploy => deployer}/tsconfig.json (100%) diff --git a/packages/deploy/README.md b/packages/deployer/README.md similarity index 100% rename from packages/deploy/README.md rename to packages/deployer/README.md diff --git a/packages/deploy/package.json b/packages/deployer/package.json similarity index 100% rename from packages/deploy/package.json rename to packages/deployer/package.json diff --git a/packages/deploy/src/config/compact-config.test.ts b/packages/deployer/src/config/compact-config.test.ts similarity index 100% rename from packages/deploy/src/config/compact-config.test.ts rename to packages/deployer/src/config/compact-config.test.ts diff --git a/packages/deploy/src/config/compact-config.ts b/packages/deployer/src/config/compact-config.ts similarity index 100% rename from packages/deploy/src/config/compact-config.ts rename to packages/deployer/src/config/compact-config.ts diff --git a/packages/deploy/src/config/schema.ts b/packages/deployer/src/config/schema.ts similarity index 100% rename from packages/deploy/src/config/schema.ts rename to packages/deployer/src/config/schema.ts diff --git a/packages/deploy/src/deployer.test.ts b/packages/deployer/src/deployer.test.ts similarity index 100% rename from packages/deploy/src/deployer.test.ts rename to packages/deployer/src/deployer.test.ts diff --git a/packages/deploy/src/deployer.ts b/packages/deployer/src/deployer.ts similarity index 100% rename from packages/deploy/src/deployer.ts rename to packages/deployer/src/deployer.ts diff --git a/packages/deploy/src/deployments.test.ts b/packages/deployer/src/deployments.test.ts similarity index 100% rename from packages/deploy/src/deployments.test.ts rename to packages/deployer/src/deployments.test.ts diff --git a/packages/deploy/src/deployments.ts b/packages/deployer/src/deployments.ts similarity index 100% rename from packages/deploy/src/deployments.ts rename to packages/deployer/src/deployments.ts diff --git a/packages/deploy/src/errors.ts b/packages/deployer/src/errors.ts similarity index 100% rename from packages/deploy/src/errors.ts rename to packages/deployer/src/errors.ts diff --git a/packages/deploy/src/index.ts b/packages/deployer/src/index.ts similarity index 100% rename from packages/deploy/src/index.ts rename to packages/deployer/src/index.ts diff --git a/packages/deploy/src/loaders/args.test.ts b/packages/deployer/src/loaders/args.test.ts similarity index 100% rename from packages/deploy/src/loaders/args.test.ts rename to packages/deployer/src/loaders/args.test.ts diff --git a/packages/deploy/src/loaders/args.ts b/packages/deployer/src/loaders/args.ts similarity index 100% rename from packages/deploy/src/loaders/args.ts rename to packages/deployer/src/loaders/args.ts diff --git a/packages/deploy/src/loaders/artifact.ts b/packages/deployer/src/loaders/artifact.ts similarity index 100% rename from packages/deploy/src/loaders/artifact.ts rename to packages/deployer/src/loaders/artifact.ts diff --git a/packages/deploy/src/loaders/context.ts b/packages/deployer/src/loaders/context.ts similarity index 100% rename from packages/deploy/src/loaders/context.ts rename to packages/deployer/src/loaders/context.ts diff --git a/packages/deploy/src/loaders/init-state.test.ts b/packages/deployer/src/loaders/init-state.test.ts similarity index 100% rename from packages/deploy/src/loaders/init-state.test.ts rename to packages/deployer/src/loaders/init-state.test.ts diff --git a/packages/deploy/src/loaders/init-state.ts b/packages/deployer/src/loaders/init-state.ts similarity index 100% rename from packages/deploy/src/loaders/init-state.ts rename to packages/deployer/src/loaders/init-state.ts diff --git a/packages/deploy/src/loaders/ref-resolver.ts b/packages/deployer/src/loaders/ref-resolver.ts similarity index 100% rename from packages/deploy/src/loaders/ref-resolver.ts rename to packages/deployer/src/loaders/ref-resolver.ts diff --git a/packages/deploy/src/loaders/signing-key.test.ts b/packages/deployer/src/loaders/signing-key.test.ts similarity index 100% rename from packages/deploy/src/loaders/signing-key.test.ts rename to packages/deployer/src/loaders/signing-key.test.ts diff --git a/packages/deploy/src/loaders/signing-key.ts b/packages/deployer/src/loaders/signing-key.ts similarity index 100% rename from packages/deploy/src/loaders/signing-key.ts rename to packages/deployer/src/loaders/signing-key.ts diff --git a/packages/deploy/src/providers/build.ts b/packages/deployer/src/providers/build.ts similarity index 100% rename from packages/deploy/src/providers/build.ts rename to packages/deployer/src/providers/build.ts diff --git a/packages/deploy/src/providers/network.ts b/packages/deployer/src/providers/network.ts similarity index 100% rename from packages/deploy/src/providers/network.ts rename to packages/deployer/src/providers/network.ts diff --git a/packages/deploy/src/providers/private-state-password.test.ts b/packages/deployer/src/providers/private-state-password.test.ts similarity index 100% rename from packages/deploy/src/providers/private-state-password.test.ts rename to packages/deployer/src/providers/private-state-password.test.ts diff --git a/packages/deploy/src/providers/private-state-password.ts b/packages/deployer/src/providers/private-state-password.ts similarity index 100% rename from packages/deploy/src/providers/private-state-password.ts rename to packages/deployer/src/providers/private-state-password.ts diff --git a/packages/deploy/src/providers/proof-server.ts b/packages/deployer/src/providers/proof-server.ts similarity index 100% rename from packages/deploy/src/providers/proof-server.ts rename to packages/deployer/src/providers/proof-server.ts diff --git a/packages/deploy/src/wallet/handler.test.ts b/packages/deployer/src/wallet/handler.test.ts similarity index 100% rename from packages/deploy/src/wallet/handler.test.ts rename to packages/deployer/src/wallet/handler.test.ts diff --git a/packages/deploy/src/wallet/handler.ts b/packages/deployer/src/wallet/handler.ts similarity index 100% rename from packages/deploy/src/wallet/handler.ts rename to packages/deployer/src/wallet/handler.ts diff --git a/packages/deploy/src/wallet/keystore.test.ts b/packages/deployer/src/wallet/keystore.test.ts similarity index 100% rename from packages/deploy/src/wallet/keystore.test.ts rename to packages/deployer/src/wallet/keystore.test.ts diff --git a/packages/deploy/src/wallet/keystore.ts b/packages/deployer/src/wallet/keystore.ts similarity index 100% rename from packages/deploy/src/wallet/keystore.ts rename to packages/deployer/src/wallet/keystore.ts diff --git a/packages/deploy/src/wallet/seeds.test.ts b/packages/deployer/src/wallet/seeds.test.ts similarity index 100% rename from packages/deploy/src/wallet/seeds.test.ts rename to packages/deployer/src/wallet/seeds.test.ts diff --git a/packages/deploy/src/wallet/seeds.ts b/packages/deployer/src/wallet/seeds.ts similarity index 100% rename from packages/deploy/src/wallet/seeds.ts rename to packages/deployer/src/wallet/seeds.ts diff --git a/packages/deploy/tsconfig.json b/packages/deployer/tsconfig.json similarity index 100% rename from packages/deploy/tsconfig.json rename to packages/deployer/tsconfig.json diff --git a/yarn.lock b/yarn.lock index a6050ba..70cd521 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1066,9 +1066,9 @@ __metadata: languageName: unknown linkType: soft -"@openzeppelin/compact-deployer@workspace:^, @openzeppelin/compact-deployer@workspace:packages/deploy": +"@openzeppelin/compact-deployer@workspace:^, @openzeppelin/compact-deployer@workspace:packages/deployer": version: 0.0.0-use.local - resolution: "@openzeppelin/compact-deployer@workspace:packages/deploy" + resolution: "@openzeppelin/compact-deployer@workspace:packages/deployer" dependencies: "@midnight-ntwrk/compact-js": "npm:2.5.0" "@midnight-ntwrk/compact-runtime": "npm:0.16.0" From 74c73a4cd6371a257e10e6bde6967e5a20b1d059 Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:18:12 +0200 Subject: [PATCH 09/12] test(deployer): reword every test description to should.../should not... style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convention sweep across all unit + integration test files. No test behaviour changes — only the strings passed to `it(...)` (and the `it.each(...)` template in walletPool.spec.ts). All 50 unit tests still pass. --- .../deployer/src/config/compact-config.test.ts | 10 +++++----- packages/deployer/src/deployer.test.ts | 12 ++++++------ packages/deployer/src/deployments.test.ts | 8 ++++---- packages/deployer/src/loaders/args.test.ts | 10 +++++----- packages/deployer/src/loaders/init-state.test.ts | 8 ++++---- packages/deployer/src/loaders/signing-key.test.ts | 8 ++++---- .../src/providers/private-state-password.test.ts | 10 +++++----- packages/deployer/src/wallet/handler.test.ts | 14 +++++++------- packages/deployer/src/wallet/keystore.test.ts | 8 ++++---- packages/deployer/src/wallet/seeds.test.ts | 12 ++++++------ tests/integrations/specs/deploy.spec.ts | 4 ++-- tests/integrations/specs/dryRun.spec.ts | 4 ++-- tests/integrations/specs/errors.spec.ts | 6 +++--- tests/integrations/specs/historyRotation.spec.ts | 6 +++--- tests/integrations/specs/walletPool.spec.ts | 6 +++--- 15 files changed, 63 insertions(+), 63 deletions(-) diff --git a/packages/deployer/src/config/compact-config.test.ts b/packages/deployer/src/config/compact-config.test.ts index e7123d3..ddb58a9 100644 --- a/packages/deployer/src/config/compact-config.test.ts +++ b/packages/deployer/src/config/compact-config.test.ts @@ -29,7 +29,7 @@ function tmpRepo(toml: string): string { } describe('CompactConfig', () => { - it('parses a minimal valid config', async () => { + it('should parse a minimal valid config', async () => { const dir = tmpRepo(MIN_VALID); const config = await CompactConfig.load(undefined, dir); expect(config.rootDir).toBe(dir); @@ -38,21 +38,21 @@ describe('CompactConfig', () => { expect(config.contract('Token').artifact).toBe('src/artifacts/Token/Token'); }); - it('lookup methods throw with the available set on miss', async () => { + it('should throw with the available set when a lookup misses', async () => { const dir = tmpRepo(MIN_VALID); const config = await CompactConfig.load(undefined, dir); expect(() => config.network('ghost')).toThrow(/Available: local/); expect(() => config.contract('Vault')).toThrow(/Available: Token/); }); - it('rejects a config whose default_network does not exist', async () => { + it('should reject a config whose default_network does not exist', async () => { const dir = tmpRepo(`${MIN_VALID}\n[profile]\ndefault_network = "ghost"\n`); await expect(CompactConfig.load(undefined, dir)).rejects.toThrow( ConfigError, ); }); - it('rejects a contract missing signing_key_file', async () => { + it('should reject a contract missing signing_key_file', async () => { const dir = tmpRepo(` [networks.local] network_id = "undeployed" @@ -70,7 +70,7 @@ artifact = "x" ); }); - it('rejects when init_private_state is set but private_state_id is not', async () => { + it('should reject when init_private_state is set but private_state_id is not', async () => { const dir = tmpRepo(` [networks.local] network_id = "undeployed" diff --git a/packages/deployer/src/deployer.test.ts b/packages/deployer/src/deployer.test.ts index 342a525..4e1bdb8 100644 --- a/packages/deployer/src/deployer.test.ts +++ b/packages/deployer/src/deployer.test.ts @@ -185,7 +185,7 @@ describe('Deployer', () => { vi.clearAllMocks(); }); - it('dryRun returns dryRun:true and never submits a tx', async () => { + it('should return dryRun:true and not submit a tx on dryRun', async () => { const injected = fakeProvider('0xINJECTED'); await using d = await Deployer.prepare({ contract: 'Counter', @@ -206,7 +206,7 @@ describe('Deployer', () => { expect(deployContract).not.toHaveBeenCalled(); }); - it('deploy submits the tx and returns the populated success result', async () => { + it('should submit the tx and return the populated success result on deploy', async () => { const injected = fakeProvider('0xDEPLOYER'); await using d = await Deployer.prepare({ contract: 'Counter', @@ -228,7 +228,7 @@ describe('Deployer', () => { expect(result.deploymentsFile).toContain('deployments'); }); - it('adopts an injected walletProvider without calling WalletHandler.build', async () => { + it('should adopt an injected walletProvider and not call WalletHandler.build', async () => { const injected = fakeProvider(); await using d = await Deployer.prepare({ contract: 'Counter', @@ -242,7 +242,7 @@ describe('Deployer', () => { expect(injected.start).not.toHaveBeenCalled(); }); - it('builds + starts a wallet when none is injected', async () => { + it('should build and start a wallet when none is injected', async () => { const built = fakeOwnedWallet('0xBUILT'); vi.mocked(WalletHandler.build).mockResolvedValueOnce(built.owned); await using d = await Deployer.prepare({ @@ -256,7 +256,7 @@ describe('Deployer', () => { expect(built.provider.start).toHaveBeenCalledWith(true); }); - it('disposes the owned wallet on asyncDispose but leaves an injected one alone', async () => { + it('should dispose the owned wallet on asyncDispose but not the injected one', async () => { const built = fakeOwnedWallet('0xOWNED'); const injected = fakeProvider('0xINJ'); vi.mocked(WalletHandler.build).mockResolvedValueOnce(built.owned); @@ -284,7 +284,7 @@ describe('Deployer', () => { expect(injected.stop).not.toHaveBeenCalled(); }); - it('wraps midnight-js deploy failures in DeployTxFailedError', async () => { + it('should wrap midnight-js deploy failures in DeployTxFailedError', async () => { vi.mocked(deployContract).mockRejectedValueOnce( new Error('chain rejected'), ); diff --git a/packages/deployer/src/deployments.test.ts b/packages/deployer/src/deployments.test.ts index 3798a96..b229874 100644 --- a/packages/deployer/src/deployments.test.ts +++ b/packages/deployer/src/deployments.test.ts @@ -26,14 +26,14 @@ function make(root: string): Deployments { } describe('Deployments', () => { - it('writes a fresh deployments/.json', async () => { + it('should write a fresh deployments/.json', async () => { const root = mkdtempSync(join(tmpdir(), 'persist-test-')); const { head } = await make(root).record('Token', rec('0xaddr1')); const parsed = JSON.parse(readFileSync(head, 'utf8')); expect(parsed.Token.address).toBe('0xaddr1'); }); - it('rotates the previous head into history on overwrite', async () => { + it('should rotate the previous head into history on overwrite', async () => { const root = mkdtempSync(join(tmpdir(), 'persist-test-')); const d = make(root); await d.record('Token', rec('0xfirst')); @@ -47,7 +47,7 @@ describe('Deployments', () => { expect(historyJson.Token[0].address).toBe('0xfirst'); }); - it('preserves other contracts when one is updated', async () => { + it('should preserve other contracts when one is updated', async () => { const root = mkdtempSync(join(tmpdir(), 'persist-test-')); const d = make(root); await d.record('Token', rec('0xT1')); @@ -57,7 +57,7 @@ describe('Deployments', () => { expect(headJson.Vault.address).toBe('0xV1'); }); - it('getHead/getHistory/listContracts read what record wrote', async () => { + it('should let getHead/getHistory/listContracts read what record wrote', async () => { const root = mkdtempSync(join(tmpdir(), 'persist-test-')); const d = make(root); await d.record('Token', rec('0xT1')); diff --git a/packages/deployer/src/loaders/args.test.ts b/packages/deployer/src/loaders/args.test.ts index 11c18b4..87a2f5e 100644 --- a/packages/deployer/src/loaders/args.test.ts +++ b/packages/deployer/src/loaders/args.test.ts @@ -14,13 +14,13 @@ const baseContract = (extra: Partial = {}): ContractConfig => }) as ContractConfig; describe('ConstructorArgs', () => { - it('returns empty values when args is unset', async () => { + it('should return empty values when args is unset', async () => { const args = await ConstructorArgs.load(baseContract(), '/tmp'); expect(args.values).toEqual([]); expect(args.source).toBe('empty'); }); - it('passes inline arrays through', async () => { + it('should pass inline arrays through', async () => { const args = await ConstructorArgs.load( baseContract({ args: ['MyToken', 'MTK', 18] }), '/tmp', @@ -29,7 +29,7 @@ describe('ConstructorArgs', () => { expect(args.source).toBe('inline'); }); - it('reads a JSON file ref and revives bigints', async () => { + it('should read a JSON file ref and revive bigints', async () => { const dir = mkdtempSync(join(tmpdir(), 'args-test-')); writeFileSync(join(dir, 'a.json'), '["x", "100n"]'); const args = await ConstructorArgs.load( @@ -40,13 +40,13 @@ describe('ConstructorArgs', () => { expect(args.source).toBe('file'); }); - it('parses a --args override JSON string', async () => { + it('should parse a --args override JSON string', async () => { const args = await ConstructorArgs.load(baseContract(), '/tmp', '[1,2,3]'); expect(args.values).toEqual([1, 2, 3]); expect(args.source).toBe('cli'); }); - it('rejects a non-array --args override', async () => { + it('should reject a non-array --args override', async () => { await expect( ConstructorArgs.load(baseContract(), '/tmp', '{"x":1}'), ).rejects.toThrow(ConfigError); diff --git a/packages/deployer/src/loaders/init-state.test.ts b/packages/deployer/src/loaders/init-state.test.ts index c5f938a..29e5871 100644 --- a/packages/deployer/src/loaders/init-state.test.ts +++ b/packages/deployer/src/loaders/init-state.test.ts @@ -6,24 +6,24 @@ import { ConfigError } from '../errors.ts'; import { InitialPrivateState } from './init-state.ts'; describe('InitialPrivateState', () => { - it('returns undefined when ref is absent', async () => { + it('should return undefined when ref is absent', async () => { expect(await InitialPrivateState.load(undefined, '/tmp')).toBeUndefined(); }); - it('parses a { file } JSON ref with bigint revival', async () => { + it('should parse a { file } JSON ref with bigint revival', async () => { const dir = mkdtempSync(join(tmpdir(), 'initstate-test-')); writeFileSync(join(dir, 's.json'), '{"counter":"100n","name":"x"}'); const state = await InitialPrivateState.load({ file: 's.json' }, dir); expect(state?.value).toEqual({ counter: 100n, name: 'x' }); }); - it('throws ConfigError for missing files', async () => { + it('should throw ConfigError for missing files', async () => { await expect( InitialPrivateState.load({ file: 'does-not-exist.json' }, '/tmp'), ).rejects.toThrow(ConfigError); }); - it('throws ConfigError for invalid JSON', async () => { + it('should throw ConfigError for invalid JSON', async () => { const dir = mkdtempSync(join(tmpdir(), 'initstate-test-')); writeFileSync(join(dir, 'bad.json'), 'not json'); await expect( diff --git a/packages/deployer/src/loaders/signing-key.test.ts b/packages/deployer/src/loaders/signing-key.test.ts index 8c86a90..c434a39 100644 --- a/packages/deployer/src/loaders/signing-key.test.ts +++ b/packages/deployer/src/loaders/signing-key.test.ts @@ -8,25 +8,25 @@ import { SigningKey } from './signing-key.ts'; const VALID = 'a'.repeat(64); describe('SigningKey', () => { - it('reads and lowercases a 32-byte hex key', async () => { + it('should read and lowercase a 32-byte hex key', async () => { const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); writeFileSync(join(dir, 'sk'), `${VALID.toUpperCase()}\n`); expect((await SigningKey.load(dir, 'sk')).hex).toBe(VALID); }); - it('strips an optional 0x prefix', async () => { + it('should strip an optional 0x prefix', async () => { const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); writeFileSync(join(dir, 'sk'), `0x${VALID}\n`); expect((await SigningKey.load(dir, 'sk')).hex).toBe(VALID); }); - it('rejects a wrong-length key', async () => { + it('should reject a wrong-length key', async () => { const dir = mkdtempSync(join(tmpdir(), 'sk-test-')); writeFileSync(join(dir, 'sk'), 'abcd'); await expect(SigningKey.load(dir, 'sk')).rejects.toThrow(ConfigError); }); - it('rejects a missing file', async () => { + it('should reject a missing file', async () => { await expect(SigningKey.load('/tmp', 'no-such-file')).rejects.toThrow( ConfigError, ); diff --git a/packages/deployer/src/providers/private-state-password.test.ts b/packages/deployer/src/providers/private-state-password.test.ts index 5dc2838..eb70fe2 100644 --- a/packages/deployer/src/providers/private-state-password.test.ts +++ b/packages/deployer/src/providers/private-state-password.test.ts @@ -2,33 +2,33 @@ import { describe, expect, it } from 'vitest'; import { derivePrivateStatePassword } from './private-state-password.ts'; describe('derivePrivateStatePassword', () => { - it('is deterministic for the same input', () => { + it('should be deterministic for the same input', () => { const a = derivePrivateStatePassword('abcdef1234567890'); const b = derivePrivateStatePassword('abcdef1234567890'); expect(a).toBe(b); }); - it('differs for different inputs', () => { + it('should differ for different inputs', () => { const a = derivePrivateStatePassword('abcdef1234567890'); const b = derivePrivateStatePassword('abcdef1234567891'); expect(a).not.toBe(b); }); - it('never contains 4 identical chars in a row', () => { + it('should not contain 4 identical chars in a row', () => { for (let i = 0; i < 200; i++) { const pw = derivePrivateStatePassword(`pubkey-${i}`); expect(pw).not.toMatch(/(.)\1{3,}/); } }); - it('produces a password with mixed character classes (uppercase + digit + symbol)', () => { + it('should produce a password with mixed character classes (uppercase + digit + symbol)', () => { const pw = derivePrivateStatePassword('any input'); expect(pw).toMatch(/[A-Z]/); expect(pw).toMatch(/[0-9]/); expect(pw).toMatch(/[^A-Za-z0-9]/); }); - it('handles inputs that would have produced naïve-bad passwords', () => { + it('should handle inputs that would have produced naïve-bad passwords', () => { // A 64-zero hex (the kind of structured pubkey that breaks // `${encKey}A!`-style derivations) must still produce a valid password. const pw = derivePrivateStatePassword('0'.repeat(64)); diff --git a/packages/deployer/src/wallet/handler.test.ts b/packages/deployer/src/wallet/handler.test.ts index b8e7f69..78c3bb2 100644 --- a/packages/deployer/src/wallet/handler.test.ts +++ b/packages/deployer/src/wallet/handler.test.ts @@ -127,7 +127,7 @@ describe('WalletHandler', () => { vi.clearAllMocks(); }); - it('routes a mnemonic seed through .withMnemonic', async () => { + it('should route a mnemonic seed through .withMnemonic', async () => { const chain = wireTestkitChain(fakeProvider()); await WalletHandler.build(logger, fakeEnv(), { kind: 'mnemonic', @@ -139,7 +139,7 @@ describe('WalletHandler', () => { expect(chain.dustBuilder.withSeed).not.toHaveBeenCalled(); }); - it('routes a hex seed through .withSeed', async () => { + it('should route a hex seed through .withSeed', async () => { const chain = wireTestkitChain(fakeProvider()); await WalletHandler.build(logger, fakeEnv(), { kind: 'hex', @@ -149,7 +149,7 @@ describe('WalletHandler', () => { expect(chain.dustBuilder.withMnemonic).not.toHaveBeenCalled(); }); - it('bumps additionalFeeOverhead for the undeployed network', async () => { + it('should bump additionalFeeOverhead for the undeployed network', async () => { const chain = wireTestkitChain(fakeProvider()); await WalletHandler.build(logger, fakeEnv('undeployed'), { kind: 'hex', @@ -162,7 +162,7 @@ describe('WalletHandler', () => { ); }); - it('keeps the testkit default additionalFeeOverhead for other networks', async () => { + it('should keep the testkit default additionalFeeOverhead for other networks', async () => { const chain = wireTestkitChain(fakeProvider()); await WalletHandler.build(logger, fakeEnv('testnet'), { kind: 'hex', @@ -175,7 +175,7 @@ describe('WalletHandler', () => { ); }); - it('.provider returns the wallet built by MidnightWalletProvider.withWallet', async () => { + it('should expose the wallet built by MidnightWalletProvider.withWallet via .provider', async () => { const provider = fakeProvider(); wireTestkitChain(provider); const handler = await WalletHandler.build(logger, fakeEnv(), { @@ -185,7 +185,7 @@ describe('WalletHandler', () => { expect(handler.provider).toBe(provider); }); - it('Symbol.asyncDispose stops the underlying wallet', async () => { + it('should stop the underlying wallet on Symbol.asyncDispose', async () => { const provider = fakeProvider(); wireTestkitChain(provider); const handler = await WalletHandler.build(logger, fakeEnv(), { @@ -196,7 +196,7 @@ describe('WalletHandler', () => { expect(provider.stop).toHaveBeenCalledTimes(1); }); - it('Symbol.asyncDispose swallows stop() failures with a warn log', async () => { + it('should swallow stop() failures with a warn log on Symbol.asyncDispose', async () => { const provider = fakeProvider({ failsOnStop: true }); wireTestkitChain(provider); const handler = await WalletHandler.build(logger, fakeEnv(), { diff --git a/packages/deployer/src/wallet/keystore.test.ts b/packages/deployer/src/wallet/keystore.test.ts index 0b5221b..350f92a 100644 --- a/packages/deployer/src/wallet/keystore.test.ts +++ b/packages/deployer/src/wallet/keystore.test.ts @@ -6,7 +6,7 @@ const FAST_OPTS = { scryptN: 1024, scryptR: 8, scryptP: 1, dklen: 32 }; const SEED = 'deadbeef'.repeat(8); describe('Keystore', () => { - it('round-trips a seed through encrypt → decrypt', () => { + it('should round-trip a seed through encrypt → decrypt', () => { const ks = Keystore.encrypt(SEED, 'hunter2', FAST_OPTS); const json = ks.toJSON(); expect(json.version).toBe('midnight-1'); @@ -15,18 +15,18 @@ describe('Keystore', () => { expect(ks.decrypt('hunter2')).toBe(SEED); }); - it('rejects wrong passphrase with MAC mismatch', () => { + it('should reject a wrong passphrase with MAC mismatch', () => { const ks = Keystore.encrypt(SEED, 'hunter2', FAST_OPTS); expect(() => ks.decrypt('wrong')).toThrow(/MAC mismatch/); }); - it('rejects unsupported version at fromJSON', () => { + it('should reject an unsupported version at fromJSON', () => { const ks = Keystore.encrypt(SEED, 'hunter2', FAST_OPTS); const tampered = { ...ks.toJSON(), version: 'eth-3' } as unknown as MidnightKeystore; expect(() => Keystore.fromJSON(tampered)).toThrow(WalletError); }); - it('produces a different ciphertext on each encryption (random salt/iv)', () => { + it('should produce a different ciphertext on each encryption (random salt/iv)', () => { const a = Keystore.encrypt(SEED, 'pp', FAST_OPTS).toJSON(); const b = Keystore.encrypt(SEED, 'pp', FAST_OPTS).toJSON(); expect(a.crypto.ciphertext).not.toBe(b.crypto.ciphertext); diff --git a/packages/deployer/src/wallet/seeds.test.ts b/packages/deployer/src/wallet/seeds.test.ts index d89259e..df92f46 100644 --- a/packages/deployer/src/wallet/seeds.test.ts +++ b/packages/deployer/src/wallet/seeds.test.ts @@ -3,31 +3,31 @@ import { WalletError } from '../errors.ts'; import { classifySeed } from './seeds.ts'; describe('classifySeed', () => { - it('classifies a 64-char hex string as hex (lowercased)', () => { + it('should classify a 64-char hex string as hex (lowercased)', () => { const hex = 'A'.repeat(64); expect(classifySeed(hex)).toEqual({ kind: 'hex', value: 'a'.repeat(64) }); }); - it('classifies a 128-char hex string as hex', () => { + it('should classify a 128-char hex string as hex', () => { const hex = `${'0'.repeat(127)}1`; expect(classifySeed(hex)).toEqual({ kind: 'hex', value: hex }); }); - it('classifies a valid BIP39 mnemonic as mnemonic (no conversion)', () => { + it('should classify a valid BIP39 mnemonic as mnemonic (no conversion)', () => { const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; expect(classifySeed(mnemonic)).toEqual({ kind: 'mnemonic', value: mnemonic }); }); - it('rejects empty input', () => { + it('should reject empty input', () => { expect(() => classifySeed(' ')).toThrow(WalletError); }); - it('rejects an invalid hex length', () => { + it('should reject an invalid hex length', () => { expect(() => classifySeed('abc123')).toThrow(WalletError); }); - it('rejects gibberish that is neither hex nor BIP39', () => { + it('should reject gibberish that is neither hex nor BIP39', () => { expect(() => classifySeed('this is definitely not valid')).toThrow( WalletError, ); diff --git a/tests/integrations/specs/deploy.spec.ts b/tests/integrations/specs/deploy.spec.ts index 8e5e397..ff14da5 100644 --- a/tests/integrations/specs/deploy.spec.ts +++ b/tests/integrations/specs/deploy.spec.ts @@ -24,7 +24,7 @@ describe('compact-deploy — Counter deploys to local stack', () => { wipeDeployments(); }); - it('returns an address, txHash, signingKey, and block height', async () => { + it('should return an address, txHash, signingKey, and block height', async () => { const result = await deployFixture('Counter', 'DEPLOYER'); expect(result.dryRun).toBe(false); @@ -38,7 +38,7 @@ describe('compact-deploy — Counter deploys to local stack', () => { expect(result.deployer).toBeTruthy(); }); - it('persists the deployment record at deployments/compact/local.json', async () => { + it('should persist the deployment record at deployments/compact/local.json', async () => { const headPath = resolve(DEPLOYMENTS_DIR, 'local.json'); expect(existsSync(headPath)).toBe(true); diff --git a/tests/integrations/specs/dryRun.spec.ts b/tests/integrations/specs/dryRun.spec.ts index 452fd02..05dce0d 100644 --- a/tests/integrations/specs/dryRun.spec.ts +++ b/tests/integrations/specs/dryRun.spec.ts @@ -23,7 +23,7 @@ describe('compact-deploy — --dry-run validates without submitting', () => { wipeDeployments(); }); - it('returns dryRun=true and an empty address', async () => { + it('should return dryRun=true and an empty address', async () => { const result = await deployFixture('Counter', 'ALICE', { dryRun: true }); expect(result.dryRun).toBe(true); @@ -33,7 +33,7 @@ describe('compact-deploy — --dry-run validates without submitting', () => { expect(result.signingKey).toMatch(/^[0-9a-f]{64}$/); }); - it('does not write a deployments file', () => { + it('should not write a deployments file', () => { expect(existsSync(resolve(DEPLOYMENTS_DIR, 'local.json'))).toBe(false); expect(existsSync(resolve(DEPLOYMENTS_DIR, 'local.history.json'))).toBe( false, diff --git a/tests/integrations/specs/errors.spec.ts b/tests/integrations/specs/errors.spec.ts index 6d71148..5138b92 100644 --- a/tests/integrations/specs/errors.spec.ts +++ b/tests/integrations/specs/errors.spec.ts @@ -10,7 +10,7 @@ import { CONFIG_PATH, requireFixtureArtifact } from '../_harness/paths.ts'; * phase, so they're fast. */ describe('compact-deploy — config errors are typed and actionable', () => { - it('rejects an unknown contract name', async () => { + it('should reject an unknown contract name', async () => { requireFixtureArtifact(); await expect( Deployer.prepare({ @@ -22,7 +22,7 @@ describe('compact-deploy — config errors are typed and actionable', () => { ).rejects.toThrow(ConfigError); }); - it('rejects an unknown network name', async () => { + it('should reject an unknown network name', async () => { requireFixtureArtifact(); await expect( Deployer.prepare({ @@ -34,7 +34,7 @@ describe('compact-deploy — config errors are typed and actionable', () => { ).rejects.toThrow(ConfigError); }); - it('rejects a missing compact.toml path', async () => { + it('should reject a missing compact.toml path', async () => { await expect( Deployer.prepare({ contract: 'Counter', diff --git a/tests/integrations/specs/historyRotation.spec.ts b/tests/integrations/specs/historyRotation.spec.ts index f956006..6c76241 100644 --- a/tests/integrations/specs/historyRotation.spec.ts +++ b/tests/integrations/specs/historyRotation.spec.ts @@ -28,20 +28,20 @@ describe('compact-deploy — redeploy rotates head into history', () => { wipeDeployments(); }); - it('produces distinct addresses on each deploy', () => { + it('should produce distinct addresses on each deploy', () => { expect(firstAddress).not.toBe(secondAddress); expect(firstAddress).toMatch(/^[0-9a-f]+$/i); expect(secondAddress).toMatch(/^[0-9a-f]+$/i); }); - it('keeps the latest deployment at the head', async () => { + it('should keep the latest deployment at the head', async () => { const head = JSON.parse( await readFile(resolve(DEPLOYMENTS_DIR, 'local.json'), 'utf8'), ); expect(head.Counter.address).toBe(secondAddress); }); - it('moves the previous head into .history.json', async () => { + it('should move the previous head into .history.json', async () => { const history = JSON.parse( await readFile( resolve(DEPLOYMENTS_DIR, 'local.history.json'), diff --git a/tests/integrations/specs/walletPool.spec.ts b/tests/integrations/specs/walletPool.spec.ts index 2ad3246..d199b13 100644 --- a/tests/integrations/specs/walletPool.spec.ts +++ b/tests/integrations/specs/walletPool.spec.ts @@ -28,7 +28,7 @@ describe('compact-deploy — prefunded wallet pool', () => { const aliases = Object.keys(PREFUNDED_SEEDS) as PoolAlias[]; it.each(aliases)( - 'builds a synced, funded wallet for %s', + 'should build a synced, funded wallet for %s', async (alias) => { const pool = getSharedPool(localNetworkConfig()); const wallet = await pool.signerFor(alias); @@ -43,14 +43,14 @@ describe('compact-deploy — prefunded wallet pool', () => { 180_000, ); - it('returns the same wallet instance for repeated `signerFor` calls', async () => { + it('should return the same wallet instance for repeated `signerFor` calls', async () => { const pool = getSharedPool(localNetworkConfig()); const a = await pool.signerFor('ALICE'); const b = await pool.signerFor('ALICE'); expect(a).toBe(b); }); - it('produces distinct addresses for distinct aliases', async () => { + it('should produce distinct addresses for distinct aliases', async () => { const pool = getSharedPool(localNetworkConfig()); const alice = await pool.signerFor('ALICE'); const bob = await pool.signerFor('BOB'); From 26aa42bf09f7e9682c918ef8674632770e1d1dc8 Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:20:19 +0200 Subject: [PATCH 10/12] test(integration): group specs into deploy/, errors/, wallet/ subdirs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five specs are reorganised by feature theme to make the suite scale: specs/ deploy/ deploy.spec.ts dryRun.spec.ts historyRotation.spec.ts errors/ errors.spec.ts wallet/ walletPool.spec.ts No behaviour change — only relative-import depth bumps from `../_harness` to `../../_harness`. The existing `specs/**/*.spec.ts` glob in vitest.config already picks up nested directories. --- tests/integrations/specs/{ => deploy}/deploy.spec.ts | 4 ++-- tests/integrations/specs/{ => deploy}/dryRun.spec.ts | 4 ++-- tests/integrations/specs/{ => deploy}/historyRotation.spec.ts | 4 ++-- tests/integrations/specs/{ => errors}/errors.spec.ts | 4 ++-- tests/integrations/specs/{ => wallet}/walletPool.spec.ts | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename tests/integrations/specs/{ => deploy}/deploy.spec.ts (95%) rename tests/integrations/specs/{ => deploy}/dryRun.spec.ts (92%) rename tests/integrations/specs/{ => deploy}/historyRotation.spec.ts (94%) rename tests/integrations/specs/{ => errors}/errors.spec.ts (91%) rename tests/integrations/specs/{ => wallet}/walletPool.spec.ts (94%) diff --git a/tests/integrations/specs/deploy.spec.ts b/tests/integrations/specs/deploy/deploy.spec.ts similarity index 95% rename from tests/integrations/specs/deploy.spec.ts rename to tests/integrations/specs/deploy/deploy.spec.ts index ff14da5..e5864e6 100644 --- a/tests/integrations/specs/deploy.spec.ts +++ b/tests/integrations/specs/deploy/deploy.spec.ts @@ -2,12 +2,12 @@ import { readFile } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import { resolve } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { deployFixture } from '../_harness/deployer.ts'; +import { deployFixture } from '../../_harness/deployer.ts'; import { DEPLOYMENTS_DIR, requireFixtureArtifact, wipeDeployments, -} from '../_harness/paths.ts'; +} from '../../_harness/paths.ts'; /** * Spec: a fresh `compact-deploy` invocation puts Counter on the local diff --git a/tests/integrations/specs/dryRun.spec.ts b/tests/integrations/specs/deploy/dryRun.spec.ts similarity index 92% rename from tests/integrations/specs/dryRun.spec.ts rename to tests/integrations/specs/deploy/dryRun.spec.ts index 05dce0d..64c9158 100644 --- a/tests/integrations/specs/dryRun.spec.ts +++ b/tests/integrations/specs/deploy/dryRun.spec.ts @@ -1,12 +1,12 @@ import { existsSync } from 'node:fs'; import { resolve } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { deployFixture } from '../_harness/deployer.ts'; +import { deployFixture } from '../../_harness/deployer.ts'; import { DEPLOYMENTS_DIR, requireFixtureArtifact, wipeDeployments, -} from '../_harness/paths.ts'; +} from '../../_harness/paths.ts'; /** * Spec: `--dry-run` performs every validation step (config, artifact, diff --git a/tests/integrations/specs/historyRotation.spec.ts b/tests/integrations/specs/deploy/historyRotation.spec.ts similarity index 94% rename from tests/integrations/specs/historyRotation.spec.ts rename to tests/integrations/specs/deploy/historyRotation.spec.ts index 6c76241..052cd23 100644 --- a/tests/integrations/specs/historyRotation.spec.ts +++ b/tests/integrations/specs/deploy/historyRotation.spec.ts @@ -1,12 +1,12 @@ import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { deployFixture } from '../_harness/deployer.ts'; +import { deployFixture } from '../../_harness/deployer.ts'; import { DEPLOYMENTS_DIR, requireFixtureArtifact, wipeDeployments, -} from '../_harness/paths.ts'; +} from '../../_harness/paths.ts'; /** * Spec: redeploying the same contract rotates the previous head into diff --git a/tests/integrations/specs/errors.spec.ts b/tests/integrations/specs/errors/errors.spec.ts similarity index 91% rename from tests/integrations/specs/errors.spec.ts rename to tests/integrations/specs/errors/errors.spec.ts index 5138b92..1e29fc2 100644 --- a/tests/integrations/specs/errors.spec.ts +++ b/tests/integrations/specs/errors/errors.spec.ts @@ -1,7 +1,7 @@ import { ConfigError, Deployer } from '@openzeppelin/compact-deployer'; import { describe, expect, it } from 'vitest'; -import { testLogger } from '../_harness/logger.ts'; -import { CONFIG_PATH, requireFixtureArtifact } from '../_harness/paths.ts'; +import { testLogger } from '../../_harness/logger.ts'; +import { CONFIG_PATH, requireFixtureArtifact } from '../../_harness/paths.ts'; /** * Spec: Deployer.prepare surfaces typed `ConfigError`s for foreseeable diff --git a/tests/integrations/specs/walletPool.spec.ts b/tests/integrations/specs/wallet/walletPool.spec.ts similarity index 94% rename from tests/integrations/specs/walletPool.spec.ts rename to tests/integrations/specs/wallet/walletPool.spec.ts index d199b13..1bc1905 100644 --- a/tests/integrations/specs/walletPool.spec.ts +++ b/tests/integrations/specs/wallet/walletPool.spec.ts @@ -1,11 +1,11 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { localNetworkConfig, setupLocalNetwork } from '../_harness/network.ts'; +import { localNetworkConfig, setupLocalNetwork } from '../../_harness/network.ts'; import { getSharedPool, PREFUNDED_SEEDS, resetSharedPool, type PoolAlias, -} from '../_harness/walletPool.ts'; +} from '../../_harness/walletPool.ts'; /** * Spec: every alias in `PREFUNDED_SEEDS` (DEPLOYER via TEST_MNEMONIC, the From 95f7029e1b153b633eec580644a49bcafe20fdef Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:40:01 +0200 Subject: [PATCH 11/12] style(deployer): apply biome auto-fixes; relocate runDeploy.ts biome-ignore-all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Biome ci was failing with 23 errors + 1 warning across the deployer package and integration harness — mostly `useImportType` and `organizeImports` autofixes. `yarn lint:fix` resolved 19 of them; the remaining 4 were: - `packages/cli/src/runDeploy.ts` × 3 — `lint/suspicious/noConsole`. organizeImports had moved the `@openzeppelin/compact-deployer` import ahead of the `biome-ignore-all noConsole` directive, so the directive no longer applied to the file. Moved the directive to line 2 (right after the shebang, before all imports). - `packages/deployer/src/deployer.ts` — dead `SeedResolution` type import after the seeds-module merge consolidated the type alias. Lint:ci now clean; 50 unit tests still pass. --- packages/cli/src/runDeploy.ts | 5 +-- .../deployer/src/config/compact-config.ts | 2 +- packages/deployer/src/deployer.ts | 9 +++-- packages/deployer/src/deployments.test.ts | 6 ++-- packages/deployer/src/index.ts | 18 +++++----- packages/deployer/src/loaders/artifact.ts | 2 +- packages/deployer/src/loaders/init-state.ts | 2 +- packages/deployer/src/loaders/ref-resolver.ts | 6 +++- .../src/providers/private-state-password.ts | 4 ++- .../deployer/src/providers/proof-server.ts | 16 +++++++-- packages/deployer/src/wallet/handler.test.ts | 14 ++++++-- packages/deployer/src/wallet/handler.ts | 10 ++---- packages/deployer/src/wallet/keystore.test.ts | 5 ++- packages/deployer/src/wallet/keystore.ts | 5 ++- packages/deployer/src/wallet/seeds.test.ts | 5 ++- tests/integrations/_harness/paths.ts | 5 +-- tests/integrations/_harness/walletPool.ts | 2 +- .../integrations/specs/deploy/deploy.spec.ts | 2 +- .../specs/deploy/historyRotation.spec.ts | 5 +-- .../specs/wallet/walletPool.spec.ts | 35 ++++++++++--------- 20 files changed, 95 insertions(+), 63 deletions(-) diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index 47c89ef..2a19c4f 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -1,4 +1,6 @@ #!/usr/bin/env node +// biome-ignore-all lint/suspicious/noConsole: CLI writes user-facing diagnostics to stdout/stderr + /** * `compact-deploy` — opinionated CLI shell over the {@link Deployer} class. * @@ -15,11 +17,10 @@ * indexer client uses the browser WebSocket interface and Node only * provides it natively from v22. */ -// biome-ignore-all lint/suspicious/noConsole: CLI writes user-facing diagnostics to stdout/stderr +import { DeployError, Deployer } from '@openzeppelin/compact-deployer'; import chalk from 'chalk'; import ora from 'ora'; import { WebSocket } from 'ws'; -import { Deployer, DeployError } from '@openzeppelin/compact-deployer'; import { createLogger } from './logger.ts'; import { promptPassphrase } from './prompt.ts'; diff --git a/packages/deployer/src/config/compact-config.ts b/packages/deployer/src/config/compact-config.ts index f7c1064..b2356a8 100644 --- a/packages/deployer/src/config/compact-config.ts +++ b/packages/deployer/src/config/compact-config.ts @@ -6,9 +6,9 @@ import { ConfigError } from '../errors.ts'; import { type CompactConfigData, type ContractConfig, + configSchema, type NetworkConfig, type WalletConfig, - configSchema, } from './schema.ts'; /** diff --git a/packages/deployer/src/deployer.ts b/packages/deployer/src/deployer.ts index 16ade32..adb7a15 100644 --- a/packages/deployer/src/deployer.ts +++ b/packages/deployer/src/deployer.ts @@ -10,7 +10,7 @@ import type { Logger } from 'pino'; import * as Rx from 'rxjs'; import { CompactConfig } from './config/compact-config.ts'; import type { ContractConfig, NetworkConfig } from './config/schema.ts'; -import { Deployments, type DeploymentRecord } from './deployments.ts'; +import { type DeploymentRecord, Deployments } from './deployments.ts'; import { ConfigError, DeployTxFailedError } from './errors.ts'; import { ConstructorArgs } from './loaders/args.ts'; import { Artifact } from './loaders/artifact.ts'; @@ -20,7 +20,7 @@ import { buildProviders } from './providers/build.ts'; import { applyNetwork } from './providers/network.ts'; import { ProofServer } from './providers/proof-server.ts'; import { WalletHandler } from './wallet/handler.ts'; -import { type SeedResolution, resolveSeed } from './wallet/seeds.ts'; +import { resolveSeed } from './wallet/seeds.ts'; /** * Inputs to {@link Deployer.prepare}. The CLI in `bin/compact-deploy.ts` @@ -161,7 +161,10 @@ export class Deployer implements AsyncDisposable { const config = await CompactConfig.load(opts.configPath); const { rootDir } = config; const { networkName, network, contract } = resolveTargets(opts, config); - const signingKey = await SigningKey.load(rootDir, contract.signing_key_file); + const signingKey = await SigningKey.load( + rootDir, + contract.signing_key_file, + ); const seedResolution = opts.walletProvider ? undefined diff --git a/packages/deployer/src/deployments.test.ts b/packages/deployer/src/deployments.test.ts index b229874..f496b19 100644 --- a/packages/deployer/src/deployments.test.ts +++ b/packages/deployer/src/deployments.test.ts @@ -2,7 +2,7 @@ import { mkdtempSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import { Deployments, type DeploymentRecord } from './deployments.ts'; +import { type DeploymentRecord, Deployments } from './deployments.ts'; function rec(address: string): DeploymentRecord { return { @@ -66,7 +66,9 @@ describe('Deployments', () => { expect((await d.getHead('Token'))?.address).toBe('0xT2'); expect(await d.getHead('Missing')).toBeUndefined(); - expect((await d.getHistory('Token')).map((r) => r.address)).toEqual(['0xT1']); + expect((await d.getHistory('Token')).map((r) => r.address)).toEqual([ + '0xT1', + ]); expect(await d.getHistory('Vault')).toEqual([]); expect(await d.listContracts()).toEqual(['Token', 'Vault']); }); diff --git a/packages/deployer/src/index.ts b/packages/deployer/src/index.ts index 1150f64..b6d0bfd 100644 --- a/packages/deployer/src/index.ts +++ b/packages/deployer/src/index.ts @@ -13,14 +13,14 @@ export type { Profile, WalletConfig, } from './config/schema.ts'; -export { Deployer } from './deployer.ts'; export type { DeployerOptions, DeployResult } from './deployer.ts'; -export { Deployments } from './deployments.ts'; +export { Deployer } from './deployer.ts'; export type { DeploymentRecord, DeploymentsFile, DeploymentsHistory, } from './deployments.ts'; +export { Deployments } from './deployments.ts'; export { ArtifactNotFoundError, ConfigError, @@ -31,19 +31,19 @@ export { UnfundedWalletError, WalletError, } from './errors.ts'; -export { Artifact } from './loaders/artifact.ts'; -export type { LoadArtifactOptions } from './loaders/artifact.ts'; -export { ConstructorArgs } from './loaders/args.ts'; export type { ArgsSource } from './loaders/args.ts'; +export { ConstructorArgs } from './loaders/args.ts'; +export type { LoadArtifactOptions } from './loaders/artifact.ts'; +export { Artifact } from './loaders/artifact.ts'; export { InitialPrivateState } from './loaders/init-state.ts'; export { SigningKey } from './loaders/signing-key.ts'; -export { Keystore } from './wallet/keystore.ts'; -export type { MidnightKeystore } from './wallet/keystore.ts'; export { ProofServer } from './providers/proof-server.ts'; export { WalletHandler } from './wallet/handler.ts'; +export type { MidnightKeystore } from './wallet/keystore.ts'; +export { Keystore } from './wallet/keystore.ts'; +export type { WalletSeed } from './wallet/seeds.ts'; export { - LOCAL_PREFUNDED_SEEDS, classifySeed, + LOCAL_PREFUNDED_SEEDS, localPrefundedSeed, } from './wallet/seeds.ts'; -export type { WalletSeed } from './wallet/seeds.ts'; diff --git a/packages/deployer/src/loaders/artifact.ts b/packages/deployer/src/loaders/artifact.ts index 138cc6a..97bf2df 100644 --- a/packages/deployer/src/loaders/artifact.ts +++ b/packages/deployer/src/loaders/artifact.ts @@ -3,9 +3,9 @@ import { isAbsolute, resolve } from 'node:path'; import { CompiledContract, type Contract } from '@midnight-ntwrk/compact-js'; import type { Types } from 'effect'; import { + type FileOrModuleRef, isFileRef, isModuleRef, - type FileOrModuleRef, } from '../config/schema.ts'; import { ArtifactNotFoundError, ConfigError } from '../errors.ts'; import { LoaderContext } from './context.ts'; diff --git a/packages/deployer/src/loaders/init-state.ts b/packages/deployer/src/loaders/init-state.ts index 2c81427..9a1b0f3 100644 --- a/packages/deployer/src/loaders/init-state.ts +++ b/packages/deployer/src/loaders/init-state.ts @@ -1,4 +1,4 @@ -import { type FileOrModuleRef } from '../config/schema.ts'; +import type { FileOrModuleRef } from '../config/schema.ts'; import { ConfigError } from '../errors.ts'; import { LoaderContext } from './context.ts'; import { RefResolver } from './ref-resolver.ts'; diff --git a/packages/deployer/src/loaders/ref-resolver.ts b/packages/deployer/src/loaders/ref-resolver.ts index 766aea8..319c399 100644 --- a/packages/deployer/src/loaders/ref-resolver.ts +++ b/packages/deployer/src/loaders/ref-resolver.ts @@ -1,4 +1,8 @@ -import { type FileOrModuleRef, isFileRef, isModuleRef } from '../config/schema.ts'; +import { + type FileOrModuleRef, + isFileRef, + isModuleRef, +} from '../config/schema.ts'; import { ConfigError } from '../errors.ts'; import type { LoaderContext } from './context.ts'; diff --git a/packages/deployer/src/providers/private-state-password.ts b/packages/deployer/src/providers/private-state-password.ts index 398720f..99508eb 100644 --- a/packages/deployer/src/providers/private-state-password.ts +++ b/packages/deployer/src/providers/private-state-password.ts @@ -17,7 +17,9 @@ import { createHash } from 'node:crypto'; * input always produces the same output, so the local leveldb stays * decryptable across runs. */ -export function derivePrivateStatePassword(encryptionPublicKey: string): string { +export function derivePrivateStatePassword( + encryptionPublicKey: string, +): string { for (let counter = 0; counter < 1024; counter++) { const body = createHash('sha256') .update(`${encryptionPublicKey}:${counter}`) diff --git a/packages/deployer/src/providers/proof-server.ts b/packages/deployer/src/providers/proof-server.ts index b1b7baf..1f66435 100644 --- a/packages/deployer/src/providers/proof-server.ts +++ b/packages/deployer/src/providers/proof-server.ts @@ -69,7 +69,11 @@ export class ProofServer { undefined, network.network_id, ); - return new ProofServer(container.getUrl(), () => container.stop(), logger); + return new ProofServer( + container.getUrl(), + () => container.stop(), + logger, + ); } const port = process.env.PROOF_SERVER_PORT; @@ -80,10 +84,16 @@ export class ProofServer { } logger.debug(`Using PROOF_SERVER_PORT=${parsed}`); const container = new StaticProofServerContainer(parsed); - return new ProofServer(container.getUrl(), () => container.stop(), logger); + return new ProofServer( + container.getUrl(), + () => container.stop(), + logger, + ); } - logger.debug('Falling back to default proof server at http://127.0.0.1:6300'); + logger.debug( + 'Falling back to default proof server at http://127.0.0.1:6300', + ); return ProofServer.fromStaticUrl('http://127.0.0.1:6300', logger); } diff --git a/packages/deployer/src/wallet/handler.test.ts b/packages/deployer/src/wallet/handler.test.ts index 78c3bb2..95a4c87 100644 --- a/packages/deployer/src/wallet/handler.test.ts +++ b/packages/deployer/src/wallet/handler.test.ts @@ -26,7 +26,15 @@ import { MidnightWalletProvider as MidnightWalletProviderClass, } from '@midnight-ntwrk/testkit-js'; import type { Logger } from 'pino'; -import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; +import { + afterEach, + beforeEach, + describe, + expect, + it, + type Mock, + vi, +} from 'vitest'; import { WalletHandler } from './handler.ts'; vi.mock('@midnight-ntwrk/testkit-js', () => ({ @@ -87,7 +95,9 @@ function wireTestkitChain(provider: FakeProvider): BuilderChain { withDustOptions: vi.fn(() => dustBuilder), }; vi.mocked(FluentWalletBuilder.forEnvironment).mockReturnValue( - envBuilder as unknown as ReturnType, + envBuilder as unknown as ReturnType< + typeof FluentWalletBuilder.forEnvironment + >, ); vi.mocked(MidnightWalletProviderClass.withWallet).mockResolvedValue( provider as unknown as MidnightWalletProvider, diff --git a/packages/deployer/src/wallet/handler.ts b/packages/deployer/src/wallet/handler.ts index e9cd32d..96379ad 100644 --- a/packages/deployer/src/wallet/handler.ts +++ b/packages/deployer/src/wallet/handler.ts @@ -70,9 +70,8 @@ export class WalletHandler implements AsyncDisposable { : DEFAULT_DUST_OPTIONS.additionalFeeOverhead, }; - const builder = FluentWalletBuilder.forEnvironment(env).withDustOptions( - dustOptions, - ); + const builder = + FluentWalletBuilder.forEnvironment(env).withDustOptions(dustOptions); const seeded = seed.kind === 'mnemonic' ? builder.withMnemonic(seed.value) @@ -105,10 +104,7 @@ export class WalletHandler implements AsyncDisposable { try { await this.provider.stop(); } catch (e) { - this.#logger.warn( - { err: (e as Error).message }, - 'Wallet stop failed', - ); + this.#logger.warn({ err: (e as Error).message }, 'Wallet stop failed'); } } } diff --git a/packages/deployer/src/wallet/keystore.test.ts b/packages/deployer/src/wallet/keystore.test.ts index 350f92a..7121858 100644 --- a/packages/deployer/src/wallet/keystore.test.ts +++ b/packages/deployer/src/wallet/keystore.test.ts @@ -22,7 +22,10 @@ describe('Keystore', () => { it('should reject an unsupported version at fromJSON', () => { const ks = Keystore.encrypt(SEED, 'hunter2', FAST_OPTS); - const tampered = { ...ks.toJSON(), version: 'eth-3' } as unknown as MidnightKeystore; + const tampered = { + ...ks.toJSON(), + version: 'eth-3', + } as unknown as MidnightKeystore; expect(() => Keystore.fromJSON(tampered)).toThrow(WalletError); }); diff --git a/packages/deployer/src/wallet/keystore.ts b/packages/deployer/src/wallet/keystore.ts index c8dc3ca..8e507a4 100644 --- a/packages/deployer/src/wallet/keystore.ts +++ b/packages/deployer/src/wallet/keystore.ts @@ -201,7 +201,10 @@ export class Keystore { encKey, Buffer.from(cipherparams.iv, 'hex'), ); - const plain = Buffer.concat([decipher.update(cipherBytes), decipher.final()]); + const plain = Buffer.concat([ + decipher.update(cipherBytes), + decipher.final(), + ]); return plain.toString('hex'); } diff --git a/packages/deployer/src/wallet/seeds.test.ts b/packages/deployer/src/wallet/seeds.test.ts index df92f46..b997e5c 100644 --- a/packages/deployer/src/wallet/seeds.test.ts +++ b/packages/deployer/src/wallet/seeds.test.ts @@ -16,7 +16,10 @@ describe('classifySeed', () => { it('should classify a valid BIP39 mnemonic as mnemonic (no conversion)', () => { const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; - expect(classifySeed(mnemonic)).toEqual({ kind: 'mnemonic', value: mnemonic }); + expect(classifySeed(mnemonic)).toEqual({ + kind: 'mnemonic', + value: mnemonic, + }); }); it('should reject empty input', () => { diff --git a/tests/integrations/_harness/paths.ts b/tests/integrations/_harness/paths.ts index 1e97eb3..ee5fe65 100644 --- a/tests/integrations/_harness/paths.ts +++ b/tests/integrations/_harness/paths.ts @@ -10,10 +10,7 @@ export const ARTIFACT_DIR = resolve( INTEGRATION_DIR, 'fixtures/artifacts/Counter', ); -export const DEPLOYMENTS_DIR = resolve( - INTEGRATION_DIR, - 'deployments/compact', -); +export const DEPLOYMENTS_DIR = resolve(INTEGRATION_DIR, 'deployments/compact'); /** Throw with a helpful hint if the fixture hasn't been compiled yet. */ export function requireFixtureArtifact(): void { diff --git a/tests/integrations/_harness/walletPool.ts b/tests/integrations/_harness/walletPool.ts index 6cc1992..ca145f7 100644 --- a/tests/integrations/_harness/walletPool.ts +++ b/tests/integrations/_harness/walletPool.ts @@ -3,7 +3,7 @@ import { type MidnightWalletProvider, TEST_MNEMONIC, } from '@midnight-ntwrk/testkit-js'; -import { WalletHandler, classifySeed } from '@openzeppelin/compact-deployer'; +import { classifySeed, WalletHandler } from '@openzeppelin/compact-deployer'; import { testLogger } from './logger.ts'; /** diff --git a/tests/integrations/specs/deploy/deploy.spec.ts b/tests/integrations/specs/deploy/deploy.spec.ts index e5864e6..063dce9 100644 --- a/tests/integrations/specs/deploy/deploy.spec.ts +++ b/tests/integrations/specs/deploy/deploy.spec.ts @@ -1,5 +1,5 @@ -import { readFile } from 'node:fs/promises'; import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { deployFixture } from '../../_harness/deployer.ts'; diff --git a/tests/integrations/specs/deploy/historyRotation.spec.ts b/tests/integrations/specs/deploy/historyRotation.spec.ts index 052cd23..f52dd2c 100644 --- a/tests/integrations/specs/deploy/historyRotation.spec.ts +++ b/tests/integrations/specs/deploy/historyRotation.spec.ts @@ -43,10 +43,7 @@ describe('compact-deploy — redeploy rotates head into history', () => { it('should move the previous head into .history.json', async () => { const history = JSON.parse( - await readFile( - resolve(DEPLOYMENTS_DIR, 'local.history.json'), - 'utf8', - ), + await readFile(resolve(DEPLOYMENTS_DIR, 'local.history.json'), 'utf8'), ); expect(Array.isArray(history.Counter)).toBe(true); expect(history.Counter.length).toBeGreaterThanOrEqual(1); diff --git a/tests/integrations/specs/wallet/walletPool.spec.ts b/tests/integrations/specs/wallet/walletPool.spec.ts index 1bc1905..494233b 100644 --- a/tests/integrations/specs/wallet/walletPool.spec.ts +++ b/tests/integrations/specs/wallet/walletPool.spec.ts @@ -1,10 +1,13 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { localNetworkConfig, setupLocalNetwork } from '../../_harness/network.ts'; +import { + localNetworkConfig, + setupLocalNetwork, +} from '../../_harness/network.ts'; import { getSharedPool, + type PoolAlias, PREFUNDED_SEEDS, resetSharedPool, - type PoolAlias, } from '../../_harness/walletPool.ts'; /** @@ -27,21 +30,19 @@ describe('compact-deploy — prefunded wallet pool', () => { const aliases = Object.keys(PREFUNDED_SEEDS) as PoolAlias[]; - it.each(aliases)( - 'should build a synced, funded wallet for %s', - async (alias) => { - const pool = getSharedPool(localNetworkConfig()); - const wallet = await pool.signerFor(alias); - - const coinPublicKey = wallet.getCoinPublicKey(); - expect(typeof coinPublicKey).toBe('string'); - expect((coinPublicKey as unknown as string).length).toBeGreaterThan(0); - - const encryptionPublicKey = wallet.getEncryptionPublicKey(); - expect(typeof encryptionPublicKey).toBe('string'); - }, - 180_000, - ); + it.each( + aliases, + )('should build a synced, funded wallet for %s', async (alias) => { + const pool = getSharedPool(localNetworkConfig()); + const wallet = await pool.signerFor(alias); + + const coinPublicKey = wallet.getCoinPublicKey(); + expect(typeof coinPublicKey).toBe('string'); + expect((coinPublicKey as unknown as string).length).toBeGreaterThan(0); + + const encryptionPublicKey = wallet.getEncryptionPublicKey(); + expect(typeof encryptionPublicKey).toBe('string'); + }, 180_000); it('should return the same wallet instance for repeated `signerFor` calls', async () => { const pool = getSharedPool(localNetworkConfig()); From 6c4875702580c4d90e738c4ffcab32faf4c6e9ff Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 19 May 2026 14:44:12 +0200 Subject: [PATCH 12/12] chore(deps): force-upgrade vulnerable+EOL transitive deps via resolutions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the BoostSecurity findings on PR #86. Eight of the originally flagged packages auto-upgraded when yarn.lock regenerated post-rebase (brace-expansion, ip-address, minimatch, picomatch, postcss, rollup, tar, vite). The remaining three are forced via root resolutions: - undici ^6.24.0 (was 5.29.0 via testcontainers) Fixes CVE-2026-1525/1526/1527/22036/2229 — HTTP smuggling, WebSocket memory exhaustion, CRLF injection, decompression DoS. Major bump but testcontainers' usage stays within the v6 API. - glob ^11.0.0 (was 10.5.0 via archiver-utils, cacache) Replaces the EOL v10 line. - uuid ^13.0.0 (was 10.0.0 via dockerode) Replaces the EOL v10 line. Two findings left unaddressed: - @substrate/connect@0.8.11 (EOL warning). Pinned exactly by `@polkadot/rpc-provider@16.5.6` (constraint is `0.8.11`, not a range), so resolutions can't override it without breaking that transitive. Would need to wait for @polkadot to publish a release that drops the EOL dep. - node-domexception@1.0.0 (EOL warning). Pulled by `fetch-blob@3.2.0`. Modern Node has a native DOMException; the package only matters until fetch-blob/undici upstream drops the polyfill import. No safe override exists. Both are warning-level (EOL classification, no active CVE). Build + 50 unit tests + CLI typecheck still pass after resolutions. --- package.json | 5 +- yarn.lock | 191 +++++++++++++++++++-------------------------------- 2 files changed, 76 insertions(+), 120 deletions(-) diff --git a/package.json b/package.json index fa7bab6..5ec4136 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ "vitest": "^4.0.15" }, "resolutions": { - "@midnight-ntwrk/ledger-v8": "8.0.3" + "@midnight-ntwrk/ledger-v8": "8.0.3", + "undici": "^6.24.0", + "glob": "^11.0.0", + "uuid": "^13.0.0" } } diff --git a/yarn.lock b/yarn.lock index 70cd521..b1463ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -356,13 +356,6 @@ __metadata: languageName: node linkType: hard -"@fastify/busboy@npm:^2.0.0": - version: 2.1.1 - resolution: "@fastify/busboy@npm:2.1.1" - checksum: 10/2bb8a7eca8289ed14c9eb15239bc1019797454624e769b39a0b90ed204d032403adc0f8ed0d2aef8a18c772205fa7808cf5a1b91f21c7bfc7b6032150b1062c5 - languageName: node - linkType: hard - "@graphql-typed-document-node/core@npm:^3.1.1, @graphql-typed-document-node/core@npm:^3.2.0": version: 3.2.0 resolution: "@graphql-typed-document-node/core@npm:3.2.0" @@ -410,17 +403,10 @@ __metadata: languageName: node linkType: hard -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10/e9ed5fd27c3aec1095e3a16e0c0cf148d1fee55a38665c35f7b3f86a9b5d00d042ddaabc98e8a1cb7463b9378c15f22a94eb35e99469c201453eb8375191f243 +"@isaacs/cliui@npm:^9.0.0": + version: 9.0.0 + resolution: "@isaacs/cliui@npm:9.0.0" + checksum: 10/8ea3d1009fd29071419209bb91ede20cf27e6e2a1630c5e0702d8b3f47f9e1a3f1c5a587fa2cb96d22d18219790327df49db1bcced573346bbaf4577cf46b643 languageName: node linkType: hard @@ -1122,13 +1108,6 @@ __metadata: languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff - languageName: node - linkType: hard - "@polkadot-api/json-rpc-provider-proxy@npm:^0.1.0": version: 0.1.0 resolution: "@polkadot-api/json-rpc-provider-proxy@npm:0.1.0" @@ -2362,13 +2341,6 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.1.0": - version: 6.2.3 - resolution: "ansi-styles@npm:6.2.3" - checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 - languageName: node - linkType: hard - "archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2": version: 5.0.2 resolution: "archiver-utils@npm:5.0.2" @@ -2495,6 +2467,13 @@ __metadata: languageName: node linkType: hard +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10/fb07bb66a0959c2843fc055838047e2a95ccebb837c519614afb067ebfdf2fa967ca8d712c35ced07f2cd26fc6f07964230b094891315ad74f11eba3d53178a0 + languageName: node + linkType: hard + "bare-events@npm:^2.5.4, bare-events@npm:^2.7.0": version: 2.8.3 resolution: "bare-events@npm:2.8.3" @@ -2605,7 +2584,7 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^2.0.1, brace-expansion@npm:^2.0.2": +"brace-expansion@npm:^2.0.1": version: 2.1.0 resolution: "brace-expansion@npm:2.1.0" dependencies: @@ -2614,6 +2593,15 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:^5.0.5": + version: 5.0.6 + resolution: "brace-expansion@npm:5.0.6" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10/a7acf120fefa79e9d7c9c92898114f57c07596a3920197f3c5917e6a628b04220a5f7f9618c30bdd973a6576a32113b99f9c3f1c8245ccc399dd2a9a718d81d8 + languageName: node + linkType: hard + "browser-level@npm:^3.0.0": version: 3.0.0 resolution: "browser-level@npm:3.0.0" @@ -2989,13 +2977,6 @@ __metadata: languageName: node linkType: hard -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10/9b1d3e1baefeaf7d70799db8774149cef33b97183a6addceeba0cf6b85ba23ee2686f302f14482006df32df75d32b17c509c143a3689627929e4a8efaf483952 - languageName: node - linkType: hard - "effect@npm:^3.19.19, effect@npm:^3.20.0": version: 3.21.2 resolution: "effect@npm:3.21.2" @@ -3013,13 +2994,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10/915acf859cea7131dac1b2b5c9c8e35c4849e325a1d114c30adb8cd615970f6dca0e27f64f3a4949d7d6ed86ecd79a1c5c63f02e697513cddd7b5835c90948b8 - languageName: node - linkType: hard - "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -3328,7 +3302,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": +"foreground-child@npm:^3.3.1": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -3468,19 +3442,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2": - version: 10.5.0 - resolution: "glob@npm:10.5.0" +"glob@npm:^11.0.0": + version: 11.1.0 + resolution: "glob@npm:11.1.0" dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.1.1" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" + path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10/ab3bccfefcc0afaedbd1f480cd0c4a2c0e322eb3f0aa7ceaa31b3f00b825069f17cf0f1fc8b6f256795074b903f37c0ade37ddda6a176aa57f1c2bbfe7240653 + checksum: 10/da4501819633daff8822c007bb3f93d5c4d2cbc7b15a8e886660f4497dd251a1fb4f53a85fba1e760b31704eff7164aeb2c7a82db10f9f2c362d12c02fe52cf3 languageName: node linkType: hard @@ -3740,16 +3714,12 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" +"jackspeak@npm:^4.1.1": + version: 4.2.3 + resolution: "jackspeak@npm:4.2.3" dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10/96f8786eaab98e4bf5b2a5d6d9588ea46c4d06bbc4f2eb861fdd7b6b182b16f71d8a70e79820f335d52653b16d4843b29dd9cdcf38ae80406756db9199497cf3 + "@isaacs/cliui": "npm:^9.0.0" + checksum: 10/b88e3fe5fa04d34f0f939a15b7cef4a8589999b7a366ef89a3e0f2c45d2a7666066b67cbf46d57c3a4796a76d27b9d869b23d96a803dd834200d222c2a70de7e languageName: node linkType: hard @@ -3853,13 +3823,20 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": +"lru-cache@npm:^10.0.1": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.4.0 + resolution: "lru-cache@npm:11.4.0" + checksum: 10/c6bb5bb7cd1938c6a96ec70e8cae4b2181bca3852013b51b64c3a40dadb14271f1a3337d5f34350d03d9506970e73be5161eddcf7df524fdf4ad0e390e7d534c + languageName: node + linkType: hard + "magic-string@npm:^0.30.21": version: 0.30.21 resolution: "magic-string@npm:0.30.21" @@ -3932,6 +3909,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.1.1": + version: 10.2.5 + resolution: "minimatch@npm:10.2.5" + dependencies: + brace-expansion: "npm:^5.0.5" + checksum: 10/19e87a931aff60ee7b9d80f39f817b8bfc54f61f8356ee3549fbf636dbccacacfec8d803eac73293955c4527cd085247dfc064bce4a5e349f8f3b85e2bf5da0f + languageName: node + linkType: hard + "minimatch@npm:^5.1.0": version: 5.1.9 resolution: "minimatch@npm:5.1.9" @@ -3941,15 +3927,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": - version: 9.0.9 - resolution: "minimatch@npm:9.0.9" - dependencies: - brace-expansion: "npm:^2.0.2" - checksum: 10/b91fad937deaffb68a45a2cb731ff3cff1c3baf9b6469c879477ed16f15c8f4ce39d63a3f75c2455107c2fdff0f3ab597d97dc09e2e93b883aafcf926ef0c8f9 - languageName: node - linkType: hard - "minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" @@ -4017,7 +3994,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 @@ -4353,13 +4330,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" +"path-scurry@npm:^2.0.0": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10/2b4257422bcb870a4c2d205b3acdbb213a72f5e2250f61c80f79c9d014d010f82bdf8584441612c8e1fa4eb098678f5704a66fa8377d72646bad4be38e57a2c3 languageName: node linkType: hard @@ -5035,7 +5012,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -5046,17 +5023,6 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10/7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 - languageName: node - linkType: hard - "string-width@npm:^8.1.0": version: 8.1.0 resolution: "string-width@npm:8.1.0" @@ -5085,7 +5051,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -5094,7 +5060,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": +"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": version: 7.1.2 resolution: "strip-ansi@npm:7.1.2" dependencies: @@ -5461,12 +5427,10 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.29.0": - version: 5.29.0 - resolution: "undici@npm:5.29.0" - dependencies: - "@fastify/busboy": "npm:^2.0.0" - checksum: 10/0ceca8924a32acdcc0cfb8dd2d368c217840970aa3f5e314fc169608474be6341c5b8e50cad7bd257dbe3b4e432bc5d0a0d000f83644b54fa11a48735ec52b93 +"undici@npm:^6.24.0": + version: 6.25.0 + resolution: "undici@npm:6.25.0" + checksum: 10/a475e45da3e1d1073283bb70531666f09a432eabff2b857bd7063d469a1ee1486192ff61dc0dadbb526673ce1120fee14d66a59b6b17d1e0bd3a4d5f0a52d0a6 languageName: node linkType: hard @@ -5495,12 +5459,12 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^10.0.0": - version: 10.0.0 - resolution: "uuid@npm:10.0.0" +"uuid@npm:^13.0.0": + version: 13.0.2 + resolution: "uuid@npm:13.0.2" bin: - uuid: dist/bin/uuid - checksum: 10/35aa60614811a201ff90f8ca5e9ecb7076a75c3821e17f0f5ff72d44e36c2d35fcbc2ceee9c4ac7317f4cc41895da30e74f3885e30313bee48fda6338f250538 + uuid: dist-node/bin/uuid + checksum: 10/567dddca18a8520796dd3cd1e4513f4c7c522f25602c15381615395d60c7892f330366680fc21373f19fb83c991f3da8413f57dbd85bf976069cf0818aa6c61c languageName: node linkType: hard @@ -5690,7 +5654,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": +"wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -5701,17 +5665,6 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10/7b1e4b35e9bb2312d2ee9ee7dc95b8cb5f8b4b5a89f7dde5543fe66c1e3715663094defa50d75454ac900bd210f702d575f15f3f17fa9ec0291806d2578d1ddf - languageName: node - linkType: hard - "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2"