@mysten-incubation/devstack 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dashboard-ui/assets/{grpc-a4usE3Nk.js → grpc-CpkDu4SA.js} +1 -1
- package/dashboard-ui/assets/{index-CRYJ4pru.js → index-jLPRmjst.js} +39 -33
- package/dashboard-ui/index.html +1 -1
- package/dist/api/inference-network.d.mts +5 -5
- package/dist/api/inference-network.mjs +5 -5
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/run-stack-internal.mjs +2 -3
- package/dist/api/run-stack-internal.mjs.map +1 -1
- package/dist/build-integrations/dapp-kit/index.d.mts +12 -0
- package/dist/build-integrations/dapp-kit/index.mjs +14 -1
- package/dist/build-integrations/dapp-kit/index.mjs.map +1 -1
- package/dist/build-integrations/playwright/config.d.mts +2 -1
- package/dist/build-integrations/playwright/config.mjs +1 -1
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +1 -1
- package/dist/build-integrations/playwright/errors.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +2 -2
- package/dist/build-integrations/playwright/index.mjs +2 -2
- package/dist/build-integrations/playwright/wallet-context.d.mts +2 -1
- package/dist/build-integrations/playwright/wallet-context.mjs +141 -41
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
- package/dist/build-integrations/vite/index.d.mts +82 -40
- package/dist/build-integrations/vite/index.mjs +281 -112
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/build-integrations/vitest/config.mjs +3 -4
- package/dist/build-integrations/vitest/config.mjs.map +1 -1
- package/dist/build-integrations/vitest/global-setup.mjs +8 -8
- package/dist/build-integrations/vitest/global-setup.mjs.map +1 -1
- package/dist/cli/doctor-probes.mjs +7 -2
- package/dist/cli/doctor-probes.mjs.map +1 -1
- package/dist/cli/main.mjs +5 -4
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +0 -1
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/codegen.mjs +37 -38
- package/dist/cli/wirings/codegen.mjs.map +1 -1
- package/dist/cli/wirings/dump-deployment.mjs +141 -0
- package/dist/cli/wirings/dump-deployment.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +0 -1
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/cli/wirings/up.mjs +7 -0
- package/dist/cli/wirings/up.mjs.map +1 -1
- package/dist/contracts/codegenable.d.mts +22 -25
- package/dist/contracts/codegenable.mjs +35 -4
- package/dist/contracts/codegenable.mjs.map +1 -1
- package/dist/contracts/config-bindings.mjs +43 -18
- package/dist/contracts/config-bindings.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/orchestrators/boot.d.mts +0 -10
- package/dist/orchestrators/boot.mjs +24 -54
- package/dist/orchestrators/boot.mjs.map +1 -1
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/config-runtime.mjs +137 -112
- package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -1
- package/dist/orchestrators/codegen/deployment-network-file.mjs +130 -0
- package/dist/orchestrators/codegen/deployment-network-file.mjs.map +1 -0
- package/dist/orchestrators/codegen/deployment-strict.mjs +77 -0
- package/dist/orchestrators/codegen/deployment-strict.mjs.map +1 -0
- package/dist/orchestrators/codegen/deployment.d.mts +63 -0
- package/dist/orchestrators/codegen/deployment.mjs +153 -0
- package/dist/orchestrators/codegen/deployment.mjs.map +1 -0
- package/dist/orchestrators/codegen/errors.mjs +7 -9
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +20 -1
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/paths.mjs +1 -8
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +369 -141
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/layers.mjs +0 -1
- package/dist/orchestrators/layers.mjs.map +1 -1
- package/dist/orchestrators/network-options.d.mts +37 -35
- package/dist/orchestrators/network-options.mjs +13 -10
- package/dist/orchestrators/network-options.mjs.map +1 -1
- package/dist/plugins/account/codegen.d.mts +3 -4
- package/dist/plugins/account/codegen.mjs +5 -7
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/index.mjs +1 -3
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/coin/codegen.mjs +5 -5
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/dashboard/domain.mjs +18 -1
- package/dist/plugins/dashboard/domain.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/root.mjs +5 -3
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/types.mjs +15 -2
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
- package/dist/plugins/deepbook/codegen.mjs +4 -4
- package/dist/plugins/deepbook/codegen.mjs.map +1 -1
- package/dist/plugins/deepbook/index.d.mts +7 -3
- package/dist/plugins/deepbook/index.mjs +3 -3
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/internal/codegen-helpers.mjs +0 -1
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
- package/dist/plugins/package/codegen.d.mts +1 -12
- package/dist/plugins/package/codegen.mjs +43 -25
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +1 -1
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +14 -11
- package/dist/plugins/package/index.mjs +6 -7
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/seal/codegen.mjs +3 -3
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/sui/codegen.mjs +5 -4
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +10 -10
- package/dist/plugins/sui/index.mjs +1 -2
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/sui/move-summary-runner.mjs +29 -2
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +8 -12
- package/dist/plugins/wallet/codegen.mjs +22 -15
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +4 -3
- package/dist/plugins/wallet/index.mjs +1 -1
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +2 -2
- package/dist/plugins/wallet/service.mjs +2 -3
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.mjs +4 -4
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/index.mjs +1 -1
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/substrate/manifest.d.mts +17 -22
- package/dist/substrate/manifest.mjs +4 -6
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/plugin-ctx.d.mts +6 -6
- package/dist/substrate/plugin-ctx.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/index.mjs +1 -0
- package/dist/substrate/runtime/lifecycle/file-watcher.d.mts +1 -0
- package/dist/substrate/runtime/lifecycle/file-watcher.mjs +54 -0
- package/dist/substrate/runtime/lifecycle/file-watcher.mjs.map +1 -0
- package/dist/substrate/runtime/lifecycle/index.mjs +1 -0
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +76 -6
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +1 -1
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +2 -6
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
- package/dist/surfaces/cli/command-tree.mjs +6 -6
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +6 -4
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/cli/wirings/dump-ids.mjs +0 -86
- package/dist/cli/wirings/dump-ids.mjs.map +0 -1
- package/dist/orchestrators/codegen/id-config.d.mts +0 -1
- package/dist/orchestrators/codegen/id-config.mjs +0 -107
- package/dist/orchestrators/codegen/id-config.mjs.map +0 -1
- package/dist/orchestrators/codegen/output-location.mjs +0 -32
- package/dist/orchestrators/codegen/output-location.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-summary-runner.mjs","names":[],"sources":["../../../src/plugins/sui/move-summary-runner.ts"],"sourcesContent":["// Sui plugin — Move-summary runner Layer factories.\n//\n// The codegen orchestrator (L3) consumes an abstract\n// `MoveSummaryRunnerService`; it does NOT know which CLI image or\n// host binary produces the summary JSON. Both production\n// implementations live here because they encode Sui-specific\n// knowledge (the `sui` binary name, the Sui CLI image, the\n// `sui move summary` argv shape).\n//\n// Architecture: per \"Orchestrator boundaries — never names a\n// service\", the codegen orchestrator imports only the abstract\n// `MoveSummaryRunnerService` from its `bindings.ts`; the runtime\n// composer wires one of these sui-plugin layers in.\n//\n// Implementations:\n// - `layerSuiMoveSummaryRunnerDocker` — runs `sui move summary`\n// inside the Sui CLI container image; the default production\n// wiring (`orchestrators/boot.ts`).\n// - `layerSuiMoveSummaryRunnerHost` — runs the local `sui`\n// binary directly via `ChildProcessSpawner`. Useful for\n// embedders that already have a Sui CLI on PATH.\n\nimport { cp, mkdir, mkdtemp, rm } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { basename, dirname, join } from 'node:path';\n\nimport { Effect, Layer } from 'effect';\nimport { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';\n\nimport type { ContainerRuntime, ImageRef } from '../../contracts/container-runtime.ts';\nimport {\n\ttype MoveSummary,\n\ttype MoveSummaryInput,\n\tMoveSummaryRunnerService,\n} from '../../orchestrators/codegen/bindings.ts';\nimport { CodegenBindingsFailed } from '../../orchestrators/codegen/errors.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport { capture } from '../../substrate/runtime/observability/subprocess-capture.ts';\nimport { copyLocalMoveDeps, shellQuote, suiCliImageBuildContext } from './move/index.ts';\n\n// -----------------------------------------------------------------------------\n// Docker variant — `sui move summary` inside the Sui CLI container image.\n// -----------------------------------------------------------------------------\n\nexport const layerSuiMoveSummaryRunnerDocker: Layer.Layer<\n\tMoveSummaryRunnerService,\n\tnever,\n\tContainerRuntimeService\n> = Layer.effect(\n\tMoveSummaryRunnerService,\n\tEffect.gen(function* () {\n\t\tconst runtime: ContainerRuntime = yield* ContainerRuntimeService;\n\t\treturn MoveSummaryRunnerService.of({\n\t\t\trunSummary: (input) => runSummaryViaDocker(runtime, input),\n\t\t});\n\t}),\n);\n\n// -----------------------------------------------------------------------------\n// Host-binary variant — invoke a `sui` CLI on PATH directly.\n// -----------------------------------------------------------------------------\n\nexport const layerSuiMoveSummaryRunnerHost: Layer.Layer<\n\tMoveSummaryRunnerService,\n\tnever,\n\tChildProcessSpawner.ChildProcessSpawner\n> = Layer.effect(\n\tMoveSummaryRunnerService,\n\tEffect.gen(function* () {\n\t\tconst spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n\t\treturn MoveSummaryRunnerService.of({\n\t\t\trunSummary: ({ packageName, sourcePath }) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst scratchDir = yield* Effect.tryPromise({\n\t\t\t\t\t\ttry: () => mkdtemp(join(tmpdir(), 'devstack-move-summary-')),\n\t\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\tpackage: packageName,\n\t\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\thint: 'Unable to create a temporary directory for Move summary output.',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t});\n\t\t\t\t\tconst summaryPath = join(scratchDir, 'package');\n\t\t\t\t\t// Run summary inside a disposable copy of the package, never the\n\t\t\t\t\t// developer's real source — `sui move summary` rewrites Move.lock.\n\t\t\t\t\tconst stagedPkg = join(scratchDir, 'src');\n\t\t\t\t\tconst cleanupScratch = Effect.promise(() =>\n\t\t\t\t\t\trm(scratchDir, { recursive: true, force: true }),\n\t\t\t\t\t).pipe(Effect.ignore);\n\t\t\t\t\tconst result = yield* Effect.gen(function* () {\n\t\t\t\t\t\tyield* Effect.tryPromise({\n\t\t\t\t\t\t\ttry: async () => {\n\t\t\t\t\t\t\t\tawait mkdir(summaryPath, { recursive: true });\n\t\t\t\t\t\t\t\tawait cp(sourcePath, stagedPkg, { recursive: true });\n\t\t\t\t\t\t\t\t// Stage local relative deps so `sui move summary` resolves them.\n\t\t\t\t\t\t\t\tawait copyLocalMoveDeps(sourcePath, stagedPkg, dirname(stagedPkg));\n\t\t\t\t\t\t\t\t// Place the source manifest beside the emitted summary: it\n\t\t\t\t\t\t\t\t// is the dir we hand `@mysten/codegen`, which reads\n\t\t\t\t\t\t\t\t// `Move.toml` there for its `[addresses]` labels. Without\n\t\t\t\t\t\t\t\t// it the library logs \"Failed to read Move.toml for <dir>\"\n\t\t\t\t\t\t\t\t// and falls back to `packageName`; with it the read\n\t\t\t\t\t\t\t\t// succeeds (warning gone) and its native main-package\n\t\t\t\t\t\t\t\t// resolution works. Best-effort — a manifest-less source\n\t\t\t\t\t\t\t\t// just keeps the prior fallback.\n\t\t\t\t\t\t\t\tawait cp(join(sourcePath, 'Move.toml'), join(summaryPath, 'Move.toml')).catch(\n\t\t\t\t\t\t\t\t\t() => {},\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\t\tpackage: packageName,\n\t\t\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\t\thint: 'Unable to stage a disposable copy of the Move package for summary.',\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst cmd = ChildProcess.make(\n\t\t\t\t\t\t\t'sui',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'move',\n\t\t\t\t\t\t\t\t'summary',\n\t\t\t\t\t\t\t\t'--path',\n\t\t\t\t\t\t\t\t'.',\n\t\t\t\t\t\t\t\t'--install-dir',\n\t\t\t\t\t\t\t\tjoin(scratchDir, 'install'),\n\t\t\t\t\t\t\t\t'--output-directory',\n\t\t\t\t\t\t\t\tjoin(summaryPath, 'package_summaries'),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t{ cwd: stagedPkg },\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn yield* capture(spawner, cmd, {\n\t\t\t\t\t\t\top: `sui move summary (${sourcePath})`,\n\t\t\t\t\t\t\tnonZeroIsFailure: true,\n\t\t\t\t\t\t\tstdoutTruncate: Infinity,\n\t\t\t\t\t\t\tstderrTruncate: 4_000,\n\t\t\t\t\t\t}).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\t\t\tpackage: packageName,\n\t\t\t\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\t\t\thint: 'Install the Sui CLI and ensure this Move package can run `sui move summary`.',\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}).pipe(Effect.tapError(() => cleanupScratch));\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpackageName,\n\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\tsummaryPath,\n\t\t\t\t\t\tcleanupPath: scratchDir,\n\t\t\t\t\t\tsummaryJson: parseSummaryStdout(result.stdout),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t});\n\t}),\n);\n\n// -----------------------------------------------------------------------------\n// Docker-variant helpers — shared with `runSummaryViaDocker`.\n// -----------------------------------------------------------------------------\n\nconst runSummaryViaDocker = (\n\truntime: ContainerRuntime,\n\tinput: MoveSummaryInput,\n): Effect.Effect<MoveSummary, CodegenBindingsFailed> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tconst scratchDir = yield* makeSummaryScratch(input);\n\t\t\tconst summaryPath = join(scratchDir, 'package');\n\t\t\tconst cleanupScratch = Effect.promise(() =>\n\t\t\t\trm(scratchDir, { recursive: true, force: true }),\n\t\t\t).pipe(Effect.ignore);\n\t\t\tconst result = yield* Effect.gen(function* () {\n\t\t\t\tyield* prepareSummaryPackage(summaryPath, input);\n\t\t\t\tconst image = input.buildImage ?? (yield* resolveDefaultSummaryImage(runtime, input));\n\t\t\t\tconst moveHome = join(homedir(), '.move');\n\t\t\t\tyield* ensureMoveHome(moveHome, input);\n\t\t\t\tconst packageDir = basename(input.sourcePath);\n\t\t\t\t// Mount a disposable staged copy at /workspace, never the real source\n\t\t\t\t// tree — `sui move summary` rewrites Move.lock during resolution.\n\t\t\t\tconst stagedRoot = join(scratchDir, 'src');\n\t\t\t\tyield* stageSummarySource(input, join(stagedRoot, packageDir));\n\t\t\t\tconst hostUid = typeof process.getuid === 'function' ? process.getuid() : 0;\n\t\t\t\tconst hostGid = typeof process.getgid === 'function' ? process.getgid() : 0;\n\t\t\t\tconst command = [\n\t\t\t\t\t'set -e',\n\t\t\t\t\t'cleanup_summary() { status=$?; ' +\n\t\t\t\t\t\t'chmod -R a+rwX /summary /workspace 2>/dev/null || true; ' +\n\t\t\t\t\t\t`chown -R ${hostUid}:${hostGid} /summary /workspace 2>/dev/null || true; ` +\n\t\t\t\t\t\t'exit \"$status\"; }',\n\t\t\t\t\t'trap cleanup_summary EXIT',\n\t\t\t\t\t'mkdir -p /summary/package_summaries',\n\t\t\t\t\t`sui move summary --path /workspace/${shellQuote(packageDir)} ` +\n\t\t\t\t\t\t'--install-dir /tmp/devstack-move-summary-install ' +\n\t\t\t\t\t\t'--output-directory /summary/package_summaries',\n\t\t\t\t].join('; ');\n\t\t\t\tconst run = yield* runtime\n\t\t\t\t\t.runOneShot({\n\t\t\t\t\t\timage,\n\t\t\t\t\t\tentrypoint: 'sh',\n\t\t\t\t\t\targv: ['-c', command],\n\t\t\t\t\t\tmounts: [\n\t\t\t\t\t\t\t{ source: stagedRoot, target: '/workspace' },\n\t\t\t\t\t\t\t{ source: summaryPath, target: '/summary' },\n\t\t\t\t\t\t\t{ source: moveHome, target: '/root/.move' },\n\t\t\t\t\t\t],\n\t\t\t\t\t\ttimeoutMillis: 5 * 60_000,\n\t\t\t\t\t})\n\t\t\t\t\t.pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\t\tpackage: input.packageName,\n\t\t\t\t\t\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\t\thint: 'Docker runtime failed while running `sui move summary` for bindings codegen.',\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\tif (run.exitCode !== 0) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\tpackage: input.packageName,\n\t\t\t\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\thint:\n\t\t\t\t\t\t\t\t`sui move summary exited ${run.exitCode}. ` +\n\t\t\t\t\t\t\t\t`stderr: ${run.stderr || '(empty)'}; stdout tail: ${\n\t\t\t\t\t\t\t\t\trun.stdout.slice(-400) || '(empty)'\n\t\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn run;\n\t\t\t}).pipe(Effect.tapError(() => cleanupScratch));\n\t\t\treturn {\n\t\t\t\tpackageName: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\tsummaryPath,\n\t\t\t\tcleanupPath: scratchDir,\n\t\t\t\tsummaryJson: parseSummaryStdout(result.stdout),\n\t\t\t};\n\t\t}),\n\t);\n\nconst makeSummaryScratch = (\n\tinput: MoveSummaryInput,\n): Effect.Effect<string, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\ttry: () => mkdtemp(join(tmpdir(), 'devstack-move-summary-')),\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: 'Unable to create a temporary directory for Move summary output.',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst prepareSummaryPackage = (\n\tsummaryPath: string,\n\tinput: MoveSummaryInput,\n): Effect.Effect<void, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\t// Only create the OUTPUT mount dir; the package itself is mounted from a\n\t\t// disposable staged copy (see `stageSummarySource`), never the real source.\n\t\t// Also drop in the source manifest so the host-side `@mysten/codegen` reads\n\t\t// its `[addresses]` from this dir (see `layerSuiMoveSummaryRunnerHost`) —\n\t\t// silences the \"Failed to read Move.toml\" warning. Best-effort.\n\t\ttry: async () => {\n\t\t\tawait mkdir(summaryPath, { recursive: true });\n\t\t\tawait cp(join(input.sourcePath, 'Move.toml'), join(summaryPath, 'Move.toml')).catch(() => {});\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: 'Unable to prepare a temporary Move summary output directory.',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\n// Stage a disposable copy of the Move package. `sui move summary` rewrites\n// `Move.lock` during dependency resolution, so running it against the\n// developer's real checked-in tree would dirty their working copy. The copy\n// lives under the scoped scratch dir and is reaped with it.\nconst stageSummarySource = (\n\tinput: MoveSummaryInput,\n\tstagedPkg: string,\n): Effect.Effect<void, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tawait mkdir(dirname(stagedPkg), { recursive: true });\n\t\t\tawait cp(input.sourcePath, stagedPkg, { recursive: true });\n\t\t\t// Bring local `../` deps (`{ local = \"../token\" }`) into the staged\n\t\t\t// tree so `sui move summary` resolves them (mirrors the build path).\n\t\t\tawait copyLocalMoveDeps(input.sourcePath, stagedPkg, dirname(stagedPkg));\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: 'Unable to stage a disposable copy of the Move package for summary.',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst resolveDefaultSummaryImage = (\n\truntime: ContainerRuntime,\n\tinput: MoveSummaryInput,\n): Effect.Effect<ImageRef, CodegenBindingsFailed> =>\n\t// Shared CLI image — owned at the daemon-level _per-app_ pin (see\n\t// `chain-build-container.ts:PER_APP_SHARED_STACK`), not per-stack;\n\t// the build container that materialises it carries the labels, so\n\t// `ensureImage` itself is intentionally label-free here.\n\truntime.ensureImage(suiCliImageBuildContext()).pipe(\n\t\tEffect.mapError(\n\t\t\t(cause) =>\n\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\tpackage: input.packageName,\n\t\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\thint: 'Unable to resolve the Sui CLI container image for Move bindings codegen.',\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t),\n\t);\n\nconst ensureMoveHome = (\n\tmoveHome: string,\n\tinput: MoveSummaryInput,\n): Effect.Effect<void, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\ttry: () => mkdir(moveHome, { recursive: true }),\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: `Unable to create Move cache mount source \"${moveHome}\".`,\n\t\t\t\tcause,\n\t\t\t}),\n\t}).pipe(Effect.asVoid);\n\nconst parseSummaryStdout = (stdout: string): unknown => {\n\tconst trimmed = stdout.trim();\n\tif (trimmed.length === 0) return null;\n\ttry {\n\t\treturn JSON.parse(trimmed) as unknown;\n\t} catch {\n\t\treturn trimmed;\n\t}\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"move-summary-runner.mjs","names":[],"sources":["../../../src/plugins/sui/move-summary-runner.ts"],"sourcesContent":["// Sui plugin — Move-summary runner Layer factories.\n//\n// The codegen orchestrator (L3) consumes an abstract\n// `MoveSummaryRunnerService`; it does NOT know which CLI image or\n// host binary produces the summary JSON. Both production\n// implementations live here because they encode Sui-specific\n// knowledge (the `sui` binary name, the Sui CLI image, the\n// `sui move summary` argv shape).\n//\n// Architecture: per \"Orchestrator boundaries — never names a\n// service\", the codegen orchestrator imports only the abstract\n// `MoveSummaryRunnerService` from its `bindings.ts`; the runtime\n// composer wires one of these sui-plugin layers in.\n//\n// Implementations:\n// - `layerSuiMoveSummaryRunnerDocker` — runs `sui move summary`\n// inside the Sui CLI container image; the default production\n// wiring (`orchestrators/boot.ts`).\n// - `layerSuiMoveSummaryRunnerHost` — runs the local `sui`\n// binary directly via `ChildProcessSpawner`. Useful for\n// embedders that already have a Sui CLI on PATH.\n\nimport { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { basename, dirname, join } from 'node:path';\n\nimport { Effect, Layer } from 'effect';\nimport { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';\n\nimport type { ContainerRuntime, ImageRef } from '../../contracts/container-runtime.ts';\nimport {\n\ttype MoveSummary,\n\ttype MoveSummaryInput,\n\tMoveSummaryRunnerService,\n} from '../../orchestrators/codegen/bindings.ts';\nimport { CodegenBindingsFailed } from '../../orchestrators/codegen/errors.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport { capture } from '../../substrate/runtime/observability/subprocess-capture.ts';\nimport { copyLocalMoveDeps, shellQuote, suiCliImageBuildContext } from './move/index.ts';\n\n// -----------------------------------------------------------------------------\n// Docker variant — `sui move summary` inside the Sui CLI container image.\n// -----------------------------------------------------------------------------\n\nexport const layerSuiMoveSummaryRunnerDocker: Layer.Layer<\n\tMoveSummaryRunnerService,\n\tnever,\n\tContainerRuntimeService\n> = Layer.effect(\n\tMoveSummaryRunnerService,\n\tEffect.gen(function* () {\n\t\tconst runtime: ContainerRuntime = yield* ContainerRuntimeService;\n\t\treturn MoveSummaryRunnerService.of({\n\t\t\trunSummary: (input) => runSummaryViaDocker(runtime, input),\n\t\t});\n\t}),\n);\n\n// -----------------------------------------------------------------------------\n// Host-binary variant — invoke a `sui` CLI on PATH directly.\n// -----------------------------------------------------------------------------\n\n/** A minimal, DISPOSABLE sui client config. `sui move summary` initialises a\n * client config on first use and PROMPTS (\"No sui config found, create one\n * [Y/n]?\") when none exists — which blocks forever on a non-interactive stdin\n * (e.g. CI), hanging codegen. Pointing `SUI_CONFIG_DIR` at a pre-seeded config\n * inside the per-run scratch dir sidesteps the prompt without touching the\n * developer's real `~/.sui`. The summary is an OFFLINE build (framework deps\n * are embedded in the CLI), so we write the SMALLEST config the CLI parser\n * accepts: a keystore path and an EMPTY env list. No network env at all — the\n * `keystore` + `envs` fields are required by the parser, but no `rpc` is ever\n * contacted. */\nconst disposableSuiClientConfig = (configDir: string): string =>\n\t[\n\t\t'---',\n\t\t'keystore:',\n\t\t` File: ${join(configDir, 'sui.keystore')}`,\n\t\t'envs: []',\n\t\t'active_env: ~',\n\t\t'active_address: ~',\n\t\t'',\n\t].join('\\n');\n\nexport const layerSuiMoveSummaryRunnerHost: Layer.Layer<\n\tMoveSummaryRunnerService,\n\tnever,\n\tChildProcessSpawner.ChildProcessSpawner\n> = Layer.effect(\n\tMoveSummaryRunnerService,\n\tEffect.gen(function* () {\n\t\tconst spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n\t\treturn MoveSummaryRunnerService.of({\n\t\t\trunSummary: ({ packageName, sourcePath }) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst scratchDir = yield* Effect.tryPromise({\n\t\t\t\t\t\ttry: () => mkdtemp(join(tmpdir(), 'devstack-move-summary-')),\n\t\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\tpackage: packageName,\n\t\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\thint: 'Unable to create a temporary directory for Move summary output.',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t});\n\t\t\t\t\tconst summaryPath = join(scratchDir, 'package');\n\t\t\t\t\t// Run summary inside a disposable copy of the package, never the\n\t\t\t\t\t// developer's real source — `sui move summary` rewrites Move.lock.\n\t\t\t\t\tconst stagedPkg = join(scratchDir, 'src');\n\t\t\t\t\t// Disposable sui config dir so the CLI never prompts to create one\n\t\t\t\t\t// (see `disposableSuiClientConfig`). Passed via `SUI_CONFIG_DIR`.\n\t\t\t\t\tconst suiConfigDir = join(scratchDir, 'sui-config');\n\t\t\t\t\tconst cleanupScratch = Effect.promise(() =>\n\t\t\t\t\t\trm(scratchDir, { recursive: true, force: true }),\n\t\t\t\t\t).pipe(Effect.ignore);\n\t\t\t\t\tconst result = yield* Effect.gen(function* () {\n\t\t\t\t\t\tyield* Effect.tryPromise({\n\t\t\t\t\t\t\ttry: async () => {\n\t\t\t\t\t\t\t\tawait mkdir(summaryPath, { recursive: true });\n\t\t\t\t\t\t\t\tawait cp(sourcePath, stagedPkg, { recursive: true });\n\t\t\t\t\t\t\t\t// Stage local relative deps so `sui move summary` resolves them.\n\t\t\t\t\t\t\t\tawait copyLocalMoveDeps(sourcePath, stagedPkg, dirname(stagedPkg));\n\t\t\t\t\t\t\t\t// Place the source manifest beside the emitted summary: it\n\t\t\t\t\t\t\t\t// is the dir we hand `@mysten/codegen`, which reads\n\t\t\t\t\t\t\t\t// `Move.toml` there for its `[addresses]` labels. Without\n\t\t\t\t\t\t\t\t// it the library logs \"Failed to read Move.toml for <dir>\"\n\t\t\t\t\t\t\t\t// and falls back to `packageName`; with it the read\n\t\t\t\t\t\t\t\t// succeeds (warning gone) and its native main-package\n\t\t\t\t\t\t\t\t// resolution works. Best-effort — a manifest-less source\n\t\t\t\t\t\t\t\t// just keeps the prior fallback.\n\t\t\t\t\t\t\t\tawait cp(join(sourcePath, 'Move.toml'), join(summaryPath, 'Move.toml')).catch(\n\t\t\t\t\t\t\t\t\t() => {},\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t// Pre-seed the disposable sui config so the CLI never prompts.\n\t\t\t\t\t\t\t\tawait mkdir(suiConfigDir, { recursive: true });\n\t\t\t\t\t\t\t\tawait writeFile(join(suiConfigDir, 'sui.keystore'), '[]');\n\t\t\t\t\t\t\t\tawait writeFile(\n\t\t\t\t\t\t\t\t\tjoin(suiConfigDir, 'client.yaml'),\n\t\t\t\t\t\t\t\t\tdisposableSuiClientConfig(suiConfigDir),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\t\tpackage: packageName,\n\t\t\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\t\thint: 'Unable to stage a disposable copy of the Move package for summary.',\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst cmd = ChildProcess.make(\n\t\t\t\t\t\t\t'sui',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'move',\n\t\t\t\t\t\t\t\t'summary',\n\t\t\t\t\t\t\t\t'--path',\n\t\t\t\t\t\t\t\t'.',\n\t\t\t\t\t\t\t\t'--install-dir',\n\t\t\t\t\t\t\t\tjoin(scratchDir, 'install'),\n\t\t\t\t\t\t\t\t'--output-directory',\n\t\t\t\t\t\t\t\tjoin(summaryPath, 'package_summaries'),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t{ cwd: stagedPkg, env: { SUI_CONFIG_DIR: suiConfigDir }, extendEnv: true },\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn yield* capture(spawner, cmd, {\n\t\t\t\t\t\t\top: `sui move summary (${sourcePath})`,\n\t\t\t\t\t\t\tnonZeroIsFailure: true,\n\t\t\t\t\t\t\tstdoutTruncate: Infinity,\n\t\t\t\t\t\t\tstderrTruncate: 4_000,\n\t\t\t\t\t\t}).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\t\t\tpackage: packageName,\n\t\t\t\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\t\t\thint: 'Install the Sui CLI and ensure this Move package can run `sui move summary`.',\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}).pipe(Effect.tapError(() => cleanupScratch));\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpackageName,\n\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\tsummaryPath,\n\t\t\t\t\t\tcleanupPath: scratchDir,\n\t\t\t\t\t\tsummaryJson: parseSummaryStdout(result.stdout),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t});\n\t}),\n);\n\n// -----------------------------------------------------------------------------\n// Docker-variant helpers — shared with `runSummaryViaDocker`.\n// -----------------------------------------------------------------------------\n\nconst runSummaryViaDocker = (\n\truntime: ContainerRuntime,\n\tinput: MoveSummaryInput,\n): Effect.Effect<MoveSummary, CodegenBindingsFailed> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tconst scratchDir = yield* makeSummaryScratch(input);\n\t\t\tconst summaryPath = join(scratchDir, 'package');\n\t\t\tconst cleanupScratch = Effect.promise(() =>\n\t\t\t\trm(scratchDir, { recursive: true, force: true }),\n\t\t\t).pipe(Effect.ignore);\n\t\t\tconst result = yield* Effect.gen(function* () {\n\t\t\t\tyield* prepareSummaryPackage(summaryPath, input);\n\t\t\t\tconst image = input.buildImage ?? (yield* resolveDefaultSummaryImage(runtime, input));\n\t\t\t\tconst moveHome = join(homedir(), '.move');\n\t\t\t\tyield* ensureMoveHome(moveHome, input);\n\t\t\t\tconst packageDir = basename(input.sourcePath);\n\t\t\t\t// Mount a disposable staged copy at /workspace, never the real source\n\t\t\t\t// tree — `sui move summary` rewrites Move.lock during resolution.\n\t\t\t\tconst stagedRoot = join(scratchDir, 'src');\n\t\t\t\tyield* stageSummarySource(input, join(stagedRoot, packageDir));\n\t\t\t\tconst hostUid = typeof process.getuid === 'function' ? process.getuid() : 0;\n\t\t\t\tconst hostGid = typeof process.getgid === 'function' ? process.getgid() : 0;\n\t\t\t\tconst command = [\n\t\t\t\t\t'set -e',\n\t\t\t\t\t'cleanup_summary() { status=$?; ' +\n\t\t\t\t\t\t'chmod -R a+rwX /summary /workspace 2>/dev/null || true; ' +\n\t\t\t\t\t\t`chown -R ${hostUid}:${hostGid} /summary /workspace 2>/dev/null || true; ` +\n\t\t\t\t\t\t'exit \"$status\"; }',\n\t\t\t\t\t'trap cleanup_summary EXIT',\n\t\t\t\t\t'mkdir -p /summary/package_summaries',\n\t\t\t\t\t`sui move summary --path /workspace/${shellQuote(packageDir)} ` +\n\t\t\t\t\t\t'--install-dir /tmp/devstack-move-summary-install ' +\n\t\t\t\t\t\t'--output-directory /summary/package_summaries',\n\t\t\t\t].join('; ');\n\t\t\t\tconst run = yield* runtime\n\t\t\t\t\t.runOneShot({\n\t\t\t\t\t\timage,\n\t\t\t\t\t\tentrypoint: 'sh',\n\t\t\t\t\t\targv: ['-c', command],\n\t\t\t\t\t\tmounts: [\n\t\t\t\t\t\t\t{ source: stagedRoot, target: '/workspace' },\n\t\t\t\t\t\t\t{ source: summaryPath, target: '/summary' },\n\t\t\t\t\t\t\t{ source: moveHome, target: '/root/.move' },\n\t\t\t\t\t\t],\n\t\t\t\t\t\ttimeoutMillis: 5 * 60_000,\n\t\t\t\t\t})\n\t\t\t\t\t.pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\t\t\tpackage: input.packageName,\n\t\t\t\t\t\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\t\t\thint: 'Docker runtime failed while running `sui move summary` for bindings codegen.',\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\tif (run.exitCode !== 0) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\t\t\tpackage: input.packageName,\n\t\t\t\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\t\t\thint:\n\t\t\t\t\t\t\t\t`sui move summary exited ${run.exitCode}. ` +\n\t\t\t\t\t\t\t\t`stderr: ${run.stderr || '(empty)'}; stdout tail: ${\n\t\t\t\t\t\t\t\t\trun.stdout.slice(-400) || '(empty)'\n\t\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn run;\n\t\t\t}).pipe(Effect.tapError(() => cleanupScratch));\n\t\t\treturn {\n\t\t\t\tpackageName: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\tsummaryPath,\n\t\t\t\tcleanupPath: scratchDir,\n\t\t\t\tsummaryJson: parseSummaryStdout(result.stdout),\n\t\t\t};\n\t\t}),\n\t);\n\nconst makeSummaryScratch = (\n\tinput: MoveSummaryInput,\n): Effect.Effect<string, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\ttry: () => mkdtemp(join(tmpdir(), 'devstack-move-summary-')),\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: 'Unable to create a temporary directory for Move summary output.',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst prepareSummaryPackage = (\n\tsummaryPath: string,\n\tinput: MoveSummaryInput,\n): Effect.Effect<void, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\t// Only create the OUTPUT mount dir; the package itself is mounted from a\n\t\t// disposable staged copy (see `stageSummarySource`), never the real source.\n\t\t// Also drop in the source manifest so the host-side `@mysten/codegen` reads\n\t\t// its `[addresses]` from this dir (see `layerSuiMoveSummaryRunnerHost`) —\n\t\t// silences the \"Failed to read Move.toml\" warning. Best-effort.\n\t\ttry: async () => {\n\t\t\tawait mkdir(summaryPath, { recursive: true });\n\t\t\tawait cp(join(input.sourcePath, 'Move.toml'), join(summaryPath, 'Move.toml')).catch(() => {});\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: 'Unable to prepare a temporary Move summary output directory.',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\n// Stage a disposable copy of the Move package. `sui move summary` rewrites\n// `Move.lock` during dependency resolution, so running it against the\n// developer's real checked-in tree would dirty their working copy. The copy\n// lives under the scoped scratch dir and is reaped with it.\nconst stageSummarySource = (\n\tinput: MoveSummaryInput,\n\tstagedPkg: string,\n): Effect.Effect<void, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tawait mkdir(dirname(stagedPkg), { recursive: true });\n\t\t\tawait cp(input.sourcePath, stagedPkg, { recursive: true });\n\t\t\t// Bring local `../` deps (`{ local = \"../token\" }`) into the staged\n\t\t\t// tree so `sui move summary` resolves them (mirrors the build path).\n\t\t\tawait copyLocalMoveDeps(input.sourcePath, stagedPkg, dirname(stagedPkg));\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: 'Unable to stage a disposable copy of the Move package for summary.',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst resolveDefaultSummaryImage = (\n\truntime: ContainerRuntime,\n\tinput: MoveSummaryInput,\n): Effect.Effect<ImageRef, CodegenBindingsFailed> =>\n\t// Shared CLI image — owned at the daemon-level _per-app_ pin (see\n\t// `chain-build-container.ts:PER_APP_SHARED_STACK`), not per-stack;\n\t// the build container that materialises it carries the labels, so\n\t// `ensureImage` itself is intentionally label-free here.\n\truntime.ensureImage(suiCliImageBuildContext()).pipe(\n\t\tEffect.mapError(\n\t\t\t(cause) =>\n\t\t\t\tnew CodegenBindingsFailed({\n\t\t\t\t\tpackage: input.packageName,\n\t\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\t\treason: 'summary-failed',\n\t\t\t\t\thint: 'Unable to resolve the Sui CLI container image for Move bindings codegen.',\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t),\n\t);\n\nconst ensureMoveHome = (\n\tmoveHome: string,\n\tinput: MoveSummaryInput,\n): Effect.Effect<void, CodegenBindingsFailed> =>\n\tEffect.tryPromise({\n\t\ttry: () => mkdir(moveHome, { recursive: true }),\n\t\tcatch: (cause) =>\n\t\t\tnew CodegenBindingsFailed({\n\t\t\t\tpackage: input.packageName,\n\t\t\t\tsourcePath: input.sourcePath,\n\t\t\t\treason: 'summary-failed',\n\t\t\t\thint: `Unable to create Move cache mount source \"${moveHome}\".`,\n\t\t\t\tcause,\n\t\t\t}),\n\t}).pipe(Effect.asVoid);\n\nconst parseSummaryStdout = (stdout: string): unknown => {\n\tconst trimmed = stdout.trim();\n\tif (trimmed.length === 0) return null;\n\ttry {\n\t\treturn JSON.parse(trimmed) as unknown;\n\t} catch {\n\t\treturn trimmed;\n\t}\n};\n"],"mappings":"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"}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
//#region src/plugins/wallet/codegen.d.ts
|
|
2
|
-
/** The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
/** The NON-secret dev-wallet connection metadata the adapter needs to
|
|
3
|
+
* reach the in-process wallet HTTP server. Routed through the deployment
|
|
4
|
+
* envelope's `values['dev-wallet']` channel — NEVER a generated file, and
|
|
5
|
+
* NEVER the secret token (that stays in the `0o600` side-channel).
|
|
6
6
|
*
|
|
7
7
|
* - `walletUrl` : the wallet HTTP server's URL (router-fronted
|
|
8
8
|
* host form when available, direct-loopback
|
|
9
9
|
* fallback otherwise).
|
|
10
|
-
* - `pairUrl` : `walletUrl` + `/#token=<32-hex>` (single
|
|
11
|
-
* source of truth for the token).
|
|
12
|
-
* - `protocolPaths` : path constants the adapter reads. Mirrored
|
|
13
|
-
* here so the adapter doesn't depend on a
|
|
14
|
-
* separate import.
|
|
15
10
|
* - `network` : the network name the wallet's accounts are
|
|
16
11
|
* scoped to (e.g. `localnet`). The dev wallet
|
|
17
12
|
* derives the wallet-standard chain (`sui:<network>`)
|
|
18
13
|
* from it at the wallet-standard boundary; devstack
|
|
19
14
|
* itself never carries the `sui:`-prefixed form.
|
|
15
|
+
* - `protocolPaths` : path constants the adapter reads. Mirrored here
|
|
16
|
+
* so the adapter doesn't depend on a separate import.
|
|
20
17
|
*/
|
|
21
|
-
interface
|
|
18
|
+
interface DevWalletConnection {
|
|
22
19
|
readonly walletUrl: string;
|
|
23
|
-
readonly pairUrl: string;
|
|
24
20
|
readonly network: string;
|
|
25
21
|
readonly protocolPaths: {
|
|
26
22
|
readonly health: string;
|
|
@@ -30,5 +26,5 @@ interface DevWalletConfig {
|
|
|
30
26
|
};
|
|
31
27
|
}
|
|
32
28
|
//#endregion
|
|
33
|
-
export {
|
|
29
|
+
export { DevWalletConnection };
|
|
34
30
|
//# sourceMappingURL=codegen.d.mts.map
|
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import { defineSimpleConstExport } from "../internal/codegen-helpers.mjs";
|
|
2
2
|
//#region src/plugins/wallet/codegen.ts
|
|
3
|
+
/** The deployment-values namespace + key the dev-wallet connection rides.
|
|
4
|
+
* Shared between the producer (this decl's `idConfigValues`) and the
|
|
5
|
+
* Vite plugin's `optionalValue(dep, DEV_WALLET_VALUES_NAMESPACE,
|
|
6
|
+
* DEV_WALLET_VALUES_KEY)` reader so the two can never drift. */
|
|
7
|
+
const DEV_WALLET_VALUES_NAMESPACE = "dev-wallet";
|
|
8
|
+
const DEV_WALLET_VALUES_KEY = "connection";
|
|
3
9
|
/**
|
|
4
10
|
* Construct the Codegenable contribution.
|
|
5
11
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* The `resolved` arg is supplied AFTER acquire (the substrate's
|
|
12
|
-
* "resolve-once" memo). At factory time the barrel passes a
|
|
13
|
-
* placeholder so the type plumbing works; at codegen time the
|
|
14
|
-
* substrate re-evaluates with the resolved values.
|
|
12
|
+
* Values-only: emits NO standalone file. It carries the non-secret
|
|
13
|
+
* dev-wallet connection metadata through `aggregate.idConfigValues`, which
|
|
14
|
+
* boot's `assembleDeployment` folds into the deployment envelope's
|
|
15
|
+
* `values['dev-wallet'].connection`. The secret token is never routed here
|
|
16
|
+
* — it stays in the `0o600` side-channel file (`pairing.ts:tokenPath`).
|
|
15
17
|
*/
|
|
16
|
-
const makeWalletCodegen = (
|
|
17
|
-
emitterName: "
|
|
18
|
+
const makeWalletCodegen = (connection) => defineSimpleConstExport({
|
|
19
|
+
emitterName: "dev-wallet-connection",
|
|
18
20
|
outputPath: "dev-wallet.ts",
|
|
19
21
|
exportName: "devWallet",
|
|
20
|
-
value:
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
value: connection,
|
|
23
|
+
aggregateOnly: true,
|
|
24
|
+
aggregate: {
|
|
25
|
+
bucket: "dev-wallet.ts",
|
|
26
|
+
kind: "dev-wallet",
|
|
27
|
+
project: () => null,
|
|
28
|
+
idConfigValues: { [DEV_WALLET_VALUES_NAMESPACE]: { [DEV_WALLET_VALUES_KEY]: connection } }
|
|
29
|
+
}
|
|
23
30
|
});
|
|
24
31
|
//#endregion
|
|
25
|
-
export { makeWalletCodegen };
|
|
32
|
+
export { DEV_WALLET_VALUES_KEY, DEV_WALLET_VALUES_NAMESPACE, makeWalletCodegen };
|
|
26
33
|
|
|
27
34
|
//# sourceMappingURL=codegen.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/wallet/codegen.ts"],"sourcesContent":["// Wallet plugin — Codegenable contribution.\n//\n// Architecture (15-wallet.md §\"Capabilities PRODUCED\"
|
|
1
|
+
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/wallet/codegen.ts"],"sourcesContent":["// Wallet plugin — Codegenable contribution.\n//\n// Architecture (15-wallet.md §\"Capabilities PRODUCED\"):\n//\n// - The browser-side dev-wallet adapter is the cross-boundary\n// consumer. It reads the dev-wallet CONNECTION metadata (server\n// URL, network, protocol paths) at startup, constructs a\n// `DevstackSignerAdapter`, and registers it with `@mysten/dapp-kit`'s\n// wallet-standard surface (in user-app bundle code — devstack itself\n// NEVER imports dapp-kit).\n//\n// - The connection metadata rides the deployment ENVELOPE's generic\n// `values['dev-wallet']` channel (NOT a generated file): boot's\n// `assembleDeployment` folds this decl's `idConfigValues` into\n// `deployment.networks[net].values['dev-wallet']`, which the Vite\n// plugin reads (`optionalValue(dep, 'dev-wallet', …)`) to assemble\n// the dev-wallet injection. The decl emits NO standalone file — it\n// is a values-only contribution.\n//\n// SECRET TOKEN (manifest-vs-token threat surface):\n//\n// - The pairing token is NEVER routed through `values` (which lands in\n// the world-readable `deployment.json`). It stays in its `0o600`\n// side-channel file (see `pairing.ts:tokenPath`); the Vite `load`\n// hook runs in Node and reads it by path. Only the NON-secret\n// connection fields (`walletUrl`, `network`, `protocolPaths`) ride\n// `values`.\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\n\nimport { defineSimpleConstExport } from '../internal/codegen-helpers.ts';\n\n// ----------------------------------------------------------------------\n// Connection metadata shape\n// ----------------------------------------------------------------------\n\n/** The NON-secret dev-wallet connection metadata the adapter needs to\n * reach the in-process wallet HTTP server. Routed through the deployment\n * envelope's `values['dev-wallet']` channel — NEVER a generated file, and\n * NEVER the secret token (that stays in the `0o600` side-channel).\n *\n * - `walletUrl` : the wallet HTTP server's URL (router-fronted\n * host form when available, direct-loopback\n * fallback otherwise).\n * - `network` : the network name the wallet's accounts are\n * scoped to (e.g. `localnet`). The dev wallet\n * derives the wallet-standard chain (`sui:<network>`)\n * from it at the wallet-standard boundary; devstack\n * itself never carries the `sui:`-prefixed form.\n * - `protocolPaths` : path constants the adapter reads. Mirrored here\n * so the adapter doesn't depend on a separate import.\n */\nexport interface DevWalletConnection {\n\treadonly walletUrl: string;\n\treadonly network: string;\n\treadonly protocolPaths: {\n\t\treadonly health: string;\n\t\treadonly accounts: string;\n\t\treadonly signTransaction: string;\n\t\treadonly signPersonalMessage: string;\n\t};\n}\n\n/** The deployment-values namespace + key the dev-wallet connection rides.\n * Shared between the producer (this decl's `idConfigValues`) and the\n * Vite plugin's `optionalValue(dep, DEV_WALLET_VALUES_NAMESPACE,\n * DEV_WALLET_VALUES_KEY)` reader so the two can never drift. */\nexport const DEV_WALLET_VALUES_NAMESPACE = 'dev-wallet' as const;\nexport const DEV_WALLET_VALUES_KEY = 'connection' as const;\n\n// ----------------------------------------------------------------------\n// Decl construction\n// ----------------------------------------------------------------------\n\n/**\n * Construct the Codegenable contribution.\n *\n * Values-only: emits NO standalone file. It carries the non-secret\n * dev-wallet connection metadata through `aggregate.idConfigValues`, which\n * boot's `assembleDeployment` folds into the deployment envelope's\n * `values['dev-wallet'].connection`. The secret token is never routed here\n * — it stays in the `0o600` side-channel file (`pairing.ts:tokenPath`).\n */\nexport const makeWalletCodegen = (\n\tconnection: DevWalletConnection,\n): CodegenableDecl<'dev-wallet-connection'> =>\n\tdefineSimpleConstExport({\n\t\temitterName: 'dev-wallet-connection',\n\t\t// Never written: `aggregateOnly` skips the standalone file, and the\n\t\t// aggregate carries ONLY `idConfigValues` (no `bucket` file emit at\n\t\t// `assembleDeployment`, which special-cases `config.ts` / `accounts.ts`).\n\t\toutputPath: 'dev-wallet.ts',\n\t\texportName: 'devWallet',\n\t\tvalue: connection,\n\t\taggregateOnly: true,\n\t\taggregate: {\n\t\t\t// A neutral bucket name: `assembleDeployment` only PROJECTS the\n\t\t\t// `config.ts` / `accounts.ts` buckets into the typed deployment\n\t\t\t// fields; every other bucket contributes via `idConfigValues` only.\n\t\t\t// The committed-tree emit cycle never sees this decl (boot emits\n\t\t\t// only the STATIC id-free contributions + the deployment file).\n\t\t\tbucket: 'dev-wallet.ts',\n\t\t\tkind: 'dev-wallet',\n\t\t\t// Pass-through: the connection rides `idConfigValues`, so the\n\t\t\t// projection contributes nothing to a file bucket.\n\t\t\tproject: () => null,\n\t\t\t// The non-secret connection metadata folded into the deployment\n\t\t\t// envelope's `values['dev-wallet'].connection` by `assembleDeployment`.\n\t\t\tidConfigValues: {\n\t\t\t\t[DEV_WALLET_VALUES_NAMESPACE]: {\n\t\t\t\t\t[DEV_WALLET_VALUES_KEY]: connection,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t});\n"],"mappings":";;;;;;AAmEA,MAAa,8BAA8B;AAC3C,MAAa,wBAAwB;;;;;;;;;;AAerC,MAAa,qBACZ,eAEA,wBAAwB;CACvB,aAAa;CAIb,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,eAAe;CACf,WAAW;EAMV,QAAQ;EACR,MAAM;EAGN,eAAe;EAGf,gBAAgB,GACd,8BAA8B,GAC7B,wBAAwB,WAC1B,EACD;CACD;AACD,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { WALLET_AUTH_HEADER, WALLET_BEARER_PREFIX, WALLET_ENDPOINT_KEY, WALLET_E
|
|
|
2
2
|
import { Plugin, ResourceRef } from "../../substrate/plugin.mjs";
|
|
3
3
|
import { SuiOptions } from "../sui/mode/spec.mjs";
|
|
4
4
|
import { SuiClient } from "../sui/mode/shared.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { DevWalletConnection } from "./codegen.mjs";
|
|
6
6
|
import { WalletBootError, WalletBootPhase, WalletError, WalletRequestError, WalletRequestPhase } from "./errors.mjs";
|
|
7
7
|
import { PairingToken } from "./pairing.mjs";
|
|
8
8
|
import { WalletServerHandle } from "./server.mjs";
|
|
@@ -40,8 +40,9 @@ import { WALLET_ACCOUNTS_ALL, WalletAccountMember, WalletOptions, WalletValue }
|
|
|
40
40
|
*
|
|
41
41
|
* - Constant-time bearer compare on every request.
|
|
42
42
|
*
|
|
43
|
-
* - Token file `0o600`.
|
|
44
|
-
* `
|
|
43
|
+
* - Token file `0o600`. The connection metadata rides the deployment
|
|
44
|
+
* envelope's `values['dev-wallet']`; the token NEVER leaves the
|
|
45
|
+
* `0o600` side-channel file.
|
|
45
46
|
*/
|
|
46
47
|
declare function wallet<const Accounts extends ReadonlyArray<WalletAccountMember>>(opts: Omit<WalletOptions, 'accounts'> & {
|
|
47
48
|
readonly accounts: Accounts;
|
|
@@ -116,7 +116,7 @@ function makeWalletMember(opts, accounts) {
|
|
|
116
116
|
routedAppOrigin
|
|
117
117
|
});
|
|
118
118
|
ctx.snapshotExtra(makeWalletSnapshotable());
|
|
119
|
-
ctx.codegen(makeWalletCodegen(resolved.
|
|
119
|
+
ctx.codegen(makeWalletCodegen(resolved.connection));
|
|
120
120
|
ctx.endpoint(makeWalletRoutable({
|
|
121
121
|
app: identity.app,
|
|
122
122
|
stack: identity.stack,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/wallet/index.ts"],"sourcesContent":["// Wallet plugin — barrel + `wallet(opts?)` factory.\n//\n// Architecture (15-wallet.md):\n//\n// The wallet bridges devstack (the supervisor host) and the\n// browser-side dev-wallet adapter (in the separate `dev-wallet`\n// package). Two parts:\n//\n// 1. The IN-PROCESS HTTP server. Owned here. Boot at acquire,\n// serve `/api/v1/devstack/*` routes (health, accounts,\n// sign-transaction, sign-personal-message).\n//\n// 2. The BROWSER-SIDE ADAPTER. Owned by `dev-wallet`. Reads the\n// codegen-emitted `dapp-kit/config.ts`, constructs a\n// `DevstackSignerAdapter`, registers it with `@mysten/dapp-\n// kit`'s wallet-standard surface.\n//\n// The HTTP protocol is the ONE cross-boundary contract. THIS PACKAGE\n// NEVER IMPORTS `@mysten/dapp-kit*` OR `@mysten/wallet-standard`.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.snapshotExtra` — pairing token under `wallet/token`.\n// 2. `ctx.codegen` — `dapp-kit-config` bindings (the dev-wallet\n// adapter consumes this). Sensitive flag set — 0o600 + gitignore.\n// 3. `ctx.endpoint` — wallet UI URL on the stack-scoped router.\n\nimport { Effect } from 'effect';\n\nimport { definePlugin, resource } from '../../api/define-plugin.ts';\nimport { attachPluginExpander } from '../../contracts/plugin-expander.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tDEFAULT_PORT_WINDOW,\n\tPortBrokerService,\n} from '../../substrate/runtime/port-broker/index.ts';\nimport { renderUrl, routedHostname } from '../../substrate/runtime/routed-url.ts';\nimport { suiResource } from '../sui/index.ts';\nimport type { AccountResourceId } from '../account/index.ts';\nimport {\n\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\tHOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n} from '../host-service/index.ts';\n\nimport { makeWalletCodegen } from './codegen.ts';\nimport { walletBootError } from './errors.ts';\nimport { makeWalletRoutable, WALLET_ENTRYPOINT_PORT, WALLET_ROUTE_ROLE } from './routable.ts';\nimport { makeWalletSnapshotable } from './snapshot.ts';\nimport {\n\tacquireWallet,\n\tWALLET_ACCOUNTS_ALL,\n\ttype WalletAcquireContext,\n\ttype WalletAccountMember,\n\ttype WalletOptions,\n\ttype WalletValue,\n} from './service.ts';\nimport type { AnyPlugin } from '../../substrate/plugin.ts';\nimport { PluginContext } from '../../substrate/plugin-ctx.ts';\n\n/** Wallet's expander contributes through the substrate-owned\n * `PluginExpander` contract (`contracts/plugin-expander.ts`). The\n * composer dispatches every member's expander uniformly — wallet has\n * no special-case wiring in `api/define-devstack.ts`.\n *\n * Compose-time symmetry: any plugin needing the \"rewrite this\n * placeholder once the full member tuple is known\" rewrite uses the\n * same `attachPluginExpander(...)` seam wallet uses below. */\n\nconst ACCOUNT_RESOURCE_ID_PREFIX = 'account/' as const;\n\n/** Type-narrowing predicate on a plugin's resource id. The account\n * plugin's `AccountResourceId<Name>` template-literal type IS the\n * substrate-owned discriminator (12-account.md \"resource id flows into\n * the on-disk path, the manifest key, container labels, and generated\n * TypeScript exports\") — every account member's `id` reduces to\n * `account/${Name}`. Probing through this typed predicate (vs. a bare\n * `startsWith` whose narrowing returns `string`) prevents misclassifying\n * a future plugin whose id happens to start with `account/` but is NOT\n * the account-plugin shape, AND surfaces a compile error if the\n * account-id prefix convention ever changes. */\nconst isAccountResourceMember = (\n\tmember: AnyPlugin,\n): member is AnyPlugin & { readonly id: AccountResourceId<string> } =>\n\tmember.id.startsWith(ACCOUNT_RESOURCE_ID_PREFIX);\n\n// ----------------------------------------------------------------------\n// Resource identity\n// ----------------------------------------------------------------------\n\n/** The wallet plugin's resource identity. ONE per stack (15-wallet.md\n * \"singleton per stack\"). The id is `'wallet'` (singular). */\nconst walletResource = resource<'wallet', WalletValue>('wallet');\n\n// ----------------------------------------------------------------------\n// User-facing factory\n// ----------------------------------------------------------------------\n\n/**\n * Construct the wallet plugin.\n *\n * Two parts of the wallet:\n *\n * - HERE: HTTP server + token + pairing protocol + codegen.\n * - DEV-WALLET PACKAGE: `@mysten/dapp-kit`-shaped adapter the\n * user-app's frontend bundle imports.\n *\n * Distilled-doc invariant (15-wallet.md \"Always explicit\"): the\n * composer NEVER auto-mounts the wallet. The user calls `wallet()`,\n * `wallet({ accounts: 'all' })`, or `wallet({ accounts: [alice, bob] })`\n * and passes the result to `defineDevstack(...)`.\n *\n * ### Security defaults\n *\n * - `bindAddress: '0.0.0.0'`. The router runs in Docker, so on\n * native Linux it reaches this host process through the Docker\n * host-gateway address instead of host loopback. The published\n * wallet URL remains stack-scoped through the router.\n *\n * - Origin allowlist is the router-fronted dev-server origin for\n * this stack plus any explicit `allowedOrigins`. The wallet does\n * not auto-allowlist a bare `http://localhost:<vite-port>` form —\n * `localhost` is not stack-scoped, so a sibling stack on the same\n * port could pair with this wallet. See `origin-policy.ts`.\n *\n * - Pairing token in URL fragment only (`#token=<32-hex>`). Never\n * in query params (would land in access logs / referrers).\n *\n * - Constant-time bearer compare on every request.\n *\n * - Token file `0o600`. Codegen output (`dapp-kit/config.ts`)\n * `0o600` + gitignored via `sensitive: true`.\n */\nexport function wallet<const Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: Accounts },\n): ReturnType<typeof makeWalletMember<Accounts>>;\nexport function wallet(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: typeof WALLET_ACCOUNTS_ALL },\n): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(opts?: WalletOptions): AnyPlugin {\n\tconst resolvedOpts: WalletOptions =\n\t\topts ?? ({ accounts: WALLET_ACCOUNTS_ALL } satisfies WalletOptions);\n\tif (resolvedOpts.accounts === WALLET_ACCOUNTS_ALL) {\n\t\t// Deferred placeholder. `dependsOn` carries only `[suiResource]` —\n\t\t// the composer rewrites the member once it knows which account\n\t\t// members are in the stack (api-surface-design §4 D6). Without\n\t\t// composer expansion, the wallet would race account funding;\n\t\t// WITH composer expansion, every per-account dependency edge is\n\t\t// in place by the time the dep-graph builds.\n\t\t//\n\t\t// Type-level: the placeholder's `dependsOn` is `[suiResource]`. A\n\t\t// wider `ReadonlyArray<account/${string}>` would widen the\n\t\t// stack-level `MissingProviders` check to the template literal\n\t\t// (which never reduces to any concrete `account/<name>`), so\n\t\t// the placeholder MUST stay narrow.\n\t\t//\n\t\t// Runtime: the symbol-keyed expander is attached as a value-\n\t\t// only property; the factory's declared return type\n\t\t// intentionally does NOT surface it (a `unique symbol`-keyed\n\t\t// member field would leak into the user's inferred Stack type\n\t\t// and trigger TS2742 \"type cannot be named without a reference\n\t\t// to ./node_modules/.../plugins/wallet\" at every example's\n\t\t// default export).\n\t\tconst placeholder = makeWalletMember(resolvedOpts, [] as const);\n\t\tattachPluginExpander(placeholder, (members) => {\n\t\t\t// Filter the full composed member tuple to the per-account\n\t\t\t// resource members the wallet would otherwise have to receive\n\t\t\t// at factory call. `isAccountResourceMember` narrows on the\n\t\t\t// `AccountResourceId<Name>` template-literal type — the typed\n\t\t\t// discriminator the account plugin's barrel exposes via\n\t\t\t// `AccountResourceId<Name>` — so a future plugin whose id\n\t\t\t// accidentally starts with `account/` cannot masquerade.\n\t\t\tconst accountMembers: Array<WalletAccountMember> = [];\n\t\t\tfor (const m of members) {\n\t\t\t\tif (isAccountResourceMember(m)) {\n\t\t\t\t\taccountMembers.push(m as unknown as WalletAccountMember);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn makeWalletMember({ ...resolvedOpts, accounts: accountMembers }, accountMembers);\n\t\t});\n\t\treturn placeholder;\n\t}\n\treturn makeWalletMember(resolvedOpts, resolvedOpts.accounts);\n}\n\nfunction makeWalletMember<Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: WalletOptions,\n\taccounts: Accounts,\n) {\n\t// Dependencies MUST include every account ref (15-wallet.md\n\t// \"upstreamKeys MUST include suiResource.id + every account id\" — same\n\t// load-bearing invariant). The substrate's topological scheduler\n\t// uses `dependsOn` to drive build order; without including the\n\t// account refs here, the wallet would race account funding and the\n\t// first `signTransaction` would fail with `address-not-found`.\n\t//\n\tconst dependencies = [suiResource, ...accounts] as const;\n\n\t// The resolved-opts shape acquireWallet sees has accounts pinned to\n\t// the resolved tuple — `'all'` is purely a user-surface convenience\n\t// the composer never propagates to the supervisor.\n\tconst resolvedOpts: WalletOptions<Accounts> = { ...opts, accounts };\n\n\treturn definePlugin({\n\t\tid: walletResource.id,\n\t\tdependsOn: dependencies,\n\t\t// The HTTP server is a long-lived host process; per-request\n\t\t// handlers fork off the supervisor-context fiber but the server\n\t\t// itself lives for the stack's lifetime.\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\t// `deps` auto-infers from the resolved `dependsOn`: the hard Sui\n\t\t// ordering edge (discarded) followed by the resolved account\n\t\t// values. `ctx` arrives via the `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\t// Pull identity, the stack-paths bundle, and the port-\n\t\t\t\t// broker from the supervisor-provided substrate context.\n\t\t\t\t// `StackPathsService.stackRoot` is the on-disk root for\n\t\t\t\t// per-stack runtime artifacts (incl. `wallet/token`); the\n\t\t\t\t// port broker is per-stack (Layer-driven, one instance per\n\t\t\t\t// stack scope); the wallet's scope hangs off the\n\t\t\t\t// supervisor's `acquireScope`, so any release finalizer\n\t\t\t\t// installed by these primitives unwinds with the rest of\n\t\t\t\t// the plugin's resources on cycle / teardown.\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst paths = yield* StackPathsService;\n\t\t\t\tconst portBroker = yield* PortBrokerService;\n\n\t\t\t\t// The first dependency is the hard Sui ordering edge; the\n\t\t\t\t// remaining values mirror the explicit account tuple.\n\t\t\t\tconst [, ...resolvedAccounts] = deps;\n\t\t\t\tconst routerFrontedUrl = yield* routedHostname(identity, WALLET_ROUTE_ROLE).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: WALLET_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet router URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst routedAppOrigin = yield* routedHostname(\n\t\t\t\t\tidentity,\n\t\t\t\t\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: HOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet app-origin URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst acquireCtx: WalletAcquireContext = {\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\tstateRoot: paths.stackRoot,\n\t\t\t\t\tallocatePort: (preferred, probeHost) =>\n\t\t\t\t\t\tportBroker\n\t\t\t\t\t\t\t.allocate({\n\t\t\t\t\t\t\t\towner: 'wallet',\n\t\t\t\t\t\t\t\t// Pin the wallet's UX-meaningful range so the dev wallet\n\t\t\t\t\t\t\t\t// adapter's auto-connect-port heuristic keeps working even\n\t\t\t\t\t\t\t\t// after a fallback scan. The broker scan window is half-open\n\t\t\t\t\t\t\t\t// `[start, start + size)`, so `DEFAULT_PORT_WINDOW`\n\t\t\t\t\t\t\t\t// (start 39200, size 1000) covers 39200..40199 inclusive.\n\t\t\t\t\t\t\t\t// Reuse the broker's exported default rather than a local\n\t\t\t\t\t\t\t\t// literal so the two cannot silently drift (pinned by\n\t\t\t\t\t\t\t\t// `test/plugins/wallet/port-window-pin.test.ts`).\n\t\t\t\t\t\t\t\twindowHint: DEFAULT_PORT_WINDOW,\n\t\t\t\t\t\t\t\tpreferredPort: preferred,\n\t\t\t\t\t\t\t\t...(probeHost === undefined ? {} : { probeHost }),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.map((alloc) => alloc.port),\n\t\t\t\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\t\t\t\tphase: 'allocate-port',\n\t\t\t\t\t\t\t\t\t\tmessage: `port-broker allocate failed: ${err.detail}`,\n\t\t\t\t\t\t\t\t\t\thint:\n\t\t\t\t\t\t\t\t\t\t\terr.reason === 'preferred-busy'\n\t\t\t\t\t\t\t\t\t\t\t\t? 'another plugin in this stack is using your preferred port; omit `port` to let the broker pick.'\n\t\t\t\t\t\t\t\t\t\t\t\t: err.reason === 'no-free-port'\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'the wallet port window is exhausted; check for stray devstack supervisors holding ports.'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: 'bind-probe failed — likely a privileged port or jail restriction.',\n\t\t\t\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\tresolveAccounts: () => Effect.succeed(resolvedAccounts),\n\t\t\t\t\trouterFrontedUrl,\n\t\t\t\t\troutedAppOrigin,\n\t\t\t\t};\n\n\t\t\t\tconst resolved = yield* acquireWallet(resolvedOpts, acquireCtx);\n\n\t\t\t\t// Emit the wallet's contributions inline. `resolved` is the\n\t\t\t\t// just-acquired `WalletValue`; `runtime.identity.{app,stack}`\n\t\t\t\t// map to the `identity` already held here\n\t\t\t\t// (`yield* IdentityContext`). Emit each decl via its matching\n\t\t\t\t// buffered verb IN ORDER (snapshotable → `ctx.snapshotExtra`,\n\t\t\t\t// codegenable → `ctx.codegen`, routable → `ctx.endpoint`); decl\n\t\t\t\t// shapes are load-bearing. The verbs are void and buffer for the\n\t\t\t\t// supervisor's post-start replay.\n\t\t\t\tctx.snapshotExtra(makeWalletSnapshotable());\n\t\t\t\tctx.codegen(makeWalletCodegen(resolved.bindings));\n\t\t\t\tctx.endpoint(\n\t\t\t\t\tmakeWalletRoutable({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tport: resolved.localPort,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn resolved;\n\t\t\t}),\n\t});\n}\n\n// ----------------------------------------------------------------------\n// Re-exports\n// ----------------------------------------------------------------------\n\nexport type {\n\tWalletOptions,\n\tWalletValue,\n\tWalletAccountMember,\n\tWalletAccountsAll,\n} from './service.ts';\nexport { WALLET_ACCOUNTS_ALL } from './service.ts';\nexport type { DevWalletConfig } from './codegen.ts';\nexport {\n\tWalletHttpPath,\n\tWALLET_PROTOCOL_PREFIX,\n\tWALLET_AUTH_HEADER,\n\tWALLET_BEARER_PREFIX,\n\tWALLET_TOKEN_FRAGMENT_KEY,\n\tWALLET_TOKEN_HEX_LENGTH,\n\tSignRequestSchema,\n\tSignResponseSchema,\n\tHealthResponseSchema,\n\tAccountsResponseSchema,\n\tAccountSummarySchema,\n\tErrorResponseSchema,\n\tSuiAddressSchema,\n\tBase64Schema,\n\tSignatureSchemeSchema,\n\tAccountSourceSchema,\n\ttype WalletHttpPathValue,\n\ttype SignRequest,\n\ttype SignResponse,\n\ttype HealthResponse,\n\ttype AccountsResponse,\n\ttype AccountSummary,\n\ttype ErrorResponse,\n} from './protocol.ts';\nexport type {\n\tWalletError,\n\tWalletBootError,\n\tWalletBootPhase,\n\tWalletRequestError,\n\tWalletRequestPhase,\n} from './errors.ts';\nexport type { OriginPolicy, OriginPolicyInputs, OriginCheckResult } from './origin-policy.ts';\nexport { resolveOriginPolicy, checkOrigin, corsHeadersFor } from './origin-policy.ts';\nexport type { PairingToken } from './pairing.ts';\nexport {\n\tmintToken,\n\tacquirePairingToken,\n\ttokenPath,\n\tcomposePairUrl,\n\tparsePairUrl,\n\tparseBearerHeader,\n\tsafeBearerEquals,\n\tredactToken,\n} from './pairing.ts';\nexport { WALLET_ENDPOINT_NAME, WALLET_ENDPOINT_KEY, makeWalletRoutable } from './routable.ts';\nexport {\n\tdispatch,\n\tstartHttpServer,\n\tMAX_BODY_BYTES,\n\ttype WalletRequest,\n\ttype WalletResponse,\n\ttype WalletServerConfig,\n\ttype WalletServerHandle,\n} from './server.ts';\nexport { makeWalletCodegen } from './codegen.ts';\nexport { makeWalletSnapshotable } from './snapshot.ts';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAM,6BAA6B;;;;;;;;;;;AAYnC,MAAM,2BACL,WAEA,OAAO,GAAG,WAAW,0BAA0B;;;AAQhD,MAAM,iBAAiB,SAAgC,QAAQ;AAgD/D,SAAgB,OAAO,MAAiC;CACvD,MAAM,eACL,QAAS,EAAE,UAAA,MAA8B;CAC1C,IAAI,aAAa,aAAA,OAAkC;EAqBlD,MAAM,cAAc,iBAAiB,cAAc,CAAC,CAAU;EAC9D,qBAAqB,cAAc,YAAY;GAQ9C,MAAM,iBAA6C,CAAC;GACpD,KAAK,MAAM,KAAK,SACf,IAAI,wBAAwB,CAAC,GAC5B,eAAe,KAAK,CAAmC;GAGzD,OAAO,iBAAiB;IAAE,GAAG;IAAc,UAAU;GAAe,GAAG,cAAc;EACtF,CAAC;EACD,OAAO;CACR;CACA,OAAO,iBAAiB,cAAc,aAAa,QAAQ;AAC5D;AAEA,SAAS,iBACR,MACA,UACC;CAQD,MAAM,eAAe,CAAC,aAAa,GAAG,QAAQ;CAK9C,MAAM,eAAwC;EAAE,GAAG;EAAM;CAAS;CAElE,OAAO,aAAa;EACnB,IAAI,eAAe;EACnB,WAAW;EAIX,MAAM;EACN,SAAS;EAIT,QAAQ,SACP,OAAO,IAAI,aAAa;GACvB,MAAM,MAAM,OAAO;GAUnB,MAAM,WAAW,OAAO;GACxB,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAa,OAAO;GAI1B,MAAM,GAAG,GAAG,oBAAoB;GAChC,MAAM,mBAAmB,OAAO,eAAe,UAAA,KAA2B,CAAC,CAAC,KAC3E,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,0CAA0C,IAAI;IACvD,OAAO;GACR,CAAC,CACF,CACD;GACA,MAAM,kBAAkB,OAAO,eAC9B,UAAA,KAED,CAAC,CAAC,KACD,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,8CAA8C,IAAI;IAC3D,OAAO;GACR,CAAC,CACF,CACD;GA4CA,MAAM,WAAW,OAAO,cAAc,cAAc;IAzCnD,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,SAAS,SAAS;IAClB,WAAW,MAAM;IACjB,eAAe,WAAW,cACzB,WACE,SAAS;KACT,OAAO;KASP,YAAY;KACZ,eAAe;KACf,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;IAChD,CAAC,CAAC,CACD,KACA,OAAO,KAAK,UAAU,MAAM,IAAI,GAChC,OAAO,UAAU,QAChB,gBAAgB;KACf,OAAO;KACP,SAAS,gCAAgC,IAAI;KAC7C,MACC,IAAI,WAAW,mBACZ,mGACA,IAAI,WAAW,iBACd,6FACA;KACL,OAAO;IACR,CAAC,CACF,CACD;IACF,uBAAuB,OAAO,QAAQ,gBAAgB;IACtD;IACA;GAG4D,CAAC;GAU9D,IAAI,cAAc,uBAAuB,CAAC;GAC1C,IAAI,QAAQ,kBAAkB,SAAS,QAAQ,CAAC;GAChD,IAAI,SACH,mBAAmB;IAClB,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,MAAM,SAAS;GAChB,CAAC,CACF;GAEA,OAAO;EACR,CAAC;CACH,CAAC;AACF"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/wallet/index.ts"],"sourcesContent":["// Wallet plugin — barrel + `wallet(opts?)` factory.\n//\n// Architecture (15-wallet.md):\n//\n// The wallet bridges devstack (the supervisor host) and the\n// browser-side dev-wallet adapter (in the separate `dev-wallet`\n// package). Two parts:\n//\n// 1. The IN-PROCESS HTTP server. Owned here. Boot at acquire,\n// serve `/api/v1/devstack/*` routes (health, accounts,\n// sign-transaction, sign-personal-message).\n//\n// 2. The BROWSER-SIDE ADAPTER. Owned by `dev-wallet`. Reads the\n// dev-wallet connection metadata (off the deployment envelope's\n// `values['dev-wallet']`), constructs a `DevstackSignerAdapter`,\n// registers it with `@mysten/dapp-kit`'s wallet-standard surface.\n//\n// The HTTP protocol is the ONE cross-boundary contract. THIS PACKAGE\n// NEVER IMPORTS `@mysten/dapp-kit*` OR `@mysten/wallet-standard`.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.snapshotExtra` — pairing token under `wallet/token`.\n// 2. `ctx.codegen` — `dev-wallet-connection` values (the dev-wallet\n// adapter's NON-secret connection metadata, folded into the\n// deployment envelope's `values['dev-wallet']`; the secret token\n// stays in its 0o600 side-channel file).\n// 3. `ctx.endpoint` — wallet UI URL on the stack-scoped router.\n\nimport { Effect } from 'effect';\n\nimport { definePlugin, resource } from '../../api/define-plugin.ts';\nimport { attachPluginExpander } from '../../contracts/plugin-expander.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tDEFAULT_PORT_WINDOW,\n\tPortBrokerService,\n} from '../../substrate/runtime/port-broker/index.ts';\nimport { renderUrl, routedHostname } from '../../substrate/runtime/routed-url.ts';\nimport { suiResource } from '../sui/index.ts';\nimport type { AccountResourceId } from '../account/index.ts';\nimport {\n\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\tHOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n} from '../host-service/index.ts';\n\nimport { makeWalletCodegen } from './codegen.ts';\nimport { walletBootError } from './errors.ts';\nimport { makeWalletRoutable, WALLET_ENTRYPOINT_PORT, WALLET_ROUTE_ROLE } from './routable.ts';\nimport { makeWalletSnapshotable } from './snapshot.ts';\nimport {\n\tacquireWallet,\n\tWALLET_ACCOUNTS_ALL,\n\ttype WalletAcquireContext,\n\ttype WalletAccountMember,\n\ttype WalletOptions,\n\ttype WalletValue,\n} from './service.ts';\nimport type { AnyPlugin } from '../../substrate/plugin.ts';\nimport { PluginContext } from '../../substrate/plugin-ctx.ts';\n\n/** Wallet's expander contributes through the substrate-owned\n * `PluginExpander` contract (`contracts/plugin-expander.ts`). The\n * composer dispatches every member's expander uniformly — wallet has\n * no special-case wiring in `api/define-devstack.ts`.\n *\n * Compose-time symmetry: any plugin needing the \"rewrite this\n * placeholder once the full member tuple is known\" rewrite uses the\n * same `attachPluginExpander(...)` seam wallet uses below. */\n\nconst ACCOUNT_RESOURCE_ID_PREFIX = 'account/' as const;\n\n/** Type-narrowing predicate on a plugin's resource id. The account\n * plugin's `AccountResourceId<Name>` template-literal type IS the\n * substrate-owned discriminator (12-account.md \"resource id flows into\n * the on-disk path, the manifest key, container labels, and generated\n * TypeScript exports\") — every account member's `id` reduces to\n * `account/${Name}`. Probing through this typed predicate (vs. a bare\n * `startsWith` whose narrowing returns `string`) prevents misclassifying\n * a future plugin whose id happens to start with `account/` but is NOT\n * the account-plugin shape, AND surfaces a compile error if the\n * account-id prefix convention ever changes. */\nconst isAccountResourceMember = (\n\tmember: AnyPlugin,\n): member is AnyPlugin & { readonly id: AccountResourceId<string> } =>\n\tmember.id.startsWith(ACCOUNT_RESOURCE_ID_PREFIX);\n\n// ----------------------------------------------------------------------\n// Resource identity\n// ----------------------------------------------------------------------\n\n/** The wallet plugin's resource identity. ONE per stack (15-wallet.md\n * \"singleton per stack\"). The id is `'wallet'` (singular). */\nconst walletResource = resource<'wallet', WalletValue>('wallet');\n\n// ----------------------------------------------------------------------\n// User-facing factory\n// ----------------------------------------------------------------------\n\n/**\n * Construct the wallet plugin.\n *\n * Two parts of the wallet:\n *\n * - HERE: HTTP server + token + pairing protocol + codegen.\n * - DEV-WALLET PACKAGE: `@mysten/dapp-kit`-shaped adapter the\n * user-app's frontend bundle imports.\n *\n * Distilled-doc invariant (15-wallet.md \"Always explicit\"): the\n * composer NEVER auto-mounts the wallet. The user calls `wallet()`,\n * `wallet({ accounts: 'all' })`, or `wallet({ accounts: [alice, bob] })`\n * and passes the result to `defineDevstack(...)`.\n *\n * ### Security defaults\n *\n * - `bindAddress: '0.0.0.0'`. The router runs in Docker, so on\n * native Linux it reaches this host process through the Docker\n * host-gateway address instead of host loopback. The published\n * wallet URL remains stack-scoped through the router.\n *\n * - Origin allowlist is the router-fronted dev-server origin for\n * this stack plus any explicit `allowedOrigins`. The wallet does\n * not auto-allowlist a bare `http://localhost:<vite-port>` form —\n * `localhost` is not stack-scoped, so a sibling stack on the same\n * port could pair with this wallet. See `origin-policy.ts`.\n *\n * - Pairing token in URL fragment only (`#token=<32-hex>`). Never\n * in query params (would land in access logs / referrers).\n *\n * - Constant-time bearer compare on every request.\n *\n * - Token file `0o600`. The connection metadata rides the deployment\n * envelope's `values['dev-wallet']`; the token NEVER leaves the\n * `0o600` side-channel file.\n */\nexport function wallet<const Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: Accounts },\n): ReturnType<typeof makeWalletMember<Accounts>>;\nexport function wallet(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: typeof WALLET_ACCOUNTS_ALL },\n): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(opts?: WalletOptions): AnyPlugin {\n\tconst resolvedOpts: WalletOptions =\n\t\topts ?? ({ accounts: WALLET_ACCOUNTS_ALL } satisfies WalletOptions);\n\tif (resolvedOpts.accounts === WALLET_ACCOUNTS_ALL) {\n\t\t// Deferred placeholder. `dependsOn` carries only `[suiResource]` —\n\t\t// the composer rewrites the member once it knows which account\n\t\t// members are in the stack (api-surface-design §4 D6). Without\n\t\t// composer expansion, the wallet would race account funding;\n\t\t// WITH composer expansion, every per-account dependency edge is\n\t\t// in place by the time the dep-graph builds.\n\t\t//\n\t\t// Type-level: the placeholder's `dependsOn` is `[suiResource]`. A\n\t\t// wider `ReadonlyArray<account/${string}>` would widen the\n\t\t// stack-level `MissingProviders` check to the template literal\n\t\t// (which never reduces to any concrete `account/<name>`), so\n\t\t// the placeholder MUST stay narrow.\n\t\t//\n\t\t// Runtime: the symbol-keyed expander is attached as a value-\n\t\t// only property; the factory's declared return type\n\t\t// intentionally does NOT surface it (a `unique symbol`-keyed\n\t\t// member field would leak into the user's inferred Stack type\n\t\t// and trigger TS2742 \"type cannot be named without a reference\n\t\t// to ./node_modules/.../plugins/wallet\" at every example's\n\t\t// default export).\n\t\tconst placeholder = makeWalletMember(resolvedOpts, [] as const);\n\t\tattachPluginExpander(placeholder, (members) => {\n\t\t\t// Filter the full composed member tuple to the per-account\n\t\t\t// resource members the wallet would otherwise have to receive\n\t\t\t// at factory call. `isAccountResourceMember` narrows on the\n\t\t\t// `AccountResourceId<Name>` template-literal type — the typed\n\t\t\t// discriminator the account plugin's barrel exposes via\n\t\t\t// `AccountResourceId<Name>` — so a future plugin whose id\n\t\t\t// accidentally starts with `account/` cannot masquerade.\n\t\t\tconst accountMembers: Array<WalletAccountMember> = [];\n\t\t\tfor (const m of members) {\n\t\t\t\tif (isAccountResourceMember(m)) {\n\t\t\t\t\taccountMembers.push(m as unknown as WalletAccountMember);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn makeWalletMember({ ...resolvedOpts, accounts: accountMembers }, accountMembers);\n\t\t});\n\t\treturn placeholder;\n\t}\n\treturn makeWalletMember(resolvedOpts, resolvedOpts.accounts);\n}\n\nfunction makeWalletMember<Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: WalletOptions,\n\taccounts: Accounts,\n) {\n\t// Dependencies MUST include every account ref (15-wallet.md\n\t// \"upstreamKeys MUST include suiResource.id + every account id\" — same\n\t// load-bearing invariant). The substrate's topological scheduler\n\t// uses `dependsOn` to drive build order; without including the\n\t// account refs here, the wallet would race account funding and the\n\t// first `signTransaction` would fail with `address-not-found`.\n\t//\n\tconst dependencies = [suiResource, ...accounts] as const;\n\n\t// The resolved-opts shape acquireWallet sees has accounts pinned to\n\t// the resolved tuple — `'all'` is purely a user-surface convenience\n\t// the composer never propagates to the supervisor.\n\tconst resolvedOpts: WalletOptions<Accounts> = { ...opts, accounts };\n\n\treturn definePlugin({\n\t\tid: walletResource.id,\n\t\tdependsOn: dependencies,\n\t\t// The HTTP server is a long-lived host process; per-request\n\t\t// handlers fork off the supervisor-context fiber but the server\n\t\t// itself lives for the stack's lifetime.\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\t// `deps` auto-infers from the resolved `dependsOn`: the hard Sui\n\t\t// ordering edge (discarded) followed by the resolved account\n\t\t// values. `ctx` arrives via the `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\t// Pull identity, the stack-paths bundle, and the port-\n\t\t\t\t// broker from the supervisor-provided substrate context.\n\t\t\t\t// `StackPathsService.stackRoot` is the on-disk root for\n\t\t\t\t// per-stack runtime artifacts (incl. `wallet/token`); the\n\t\t\t\t// port broker is per-stack (Layer-driven, one instance per\n\t\t\t\t// stack scope); the wallet's scope hangs off the\n\t\t\t\t// supervisor's `acquireScope`, so any release finalizer\n\t\t\t\t// installed by these primitives unwinds with the rest of\n\t\t\t\t// the plugin's resources on cycle / teardown.\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst paths = yield* StackPathsService;\n\t\t\t\tconst portBroker = yield* PortBrokerService;\n\n\t\t\t\t// The first dependency is the hard Sui ordering edge; the\n\t\t\t\t// remaining values mirror the explicit account tuple.\n\t\t\t\tconst [, ...resolvedAccounts] = deps;\n\t\t\t\tconst routerFrontedUrl = yield* routedHostname(identity, WALLET_ROUTE_ROLE).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: WALLET_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet router URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst routedAppOrigin = yield* routedHostname(\n\t\t\t\t\tidentity,\n\t\t\t\t\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: HOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet app-origin URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst acquireCtx: WalletAcquireContext = {\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\tstateRoot: paths.stackRoot,\n\t\t\t\t\tallocatePort: (preferred, probeHost) =>\n\t\t\t\t\t\tportBroker\n\t\t\t\t\t\t\t.allocate({\n\t\t\t\t\t\t\t\towner: 'wallet',\n\t\t\t\t\t\t\t\t// Pin the wallet's UX-meaningful range so the dev wallet\n\t\t\t\t\t\t\t\t// adapter's auto-connect-port heuristic keeps working even\n\t\t\t\t\t\t\t\t// after a fallback scan. The broker scan window is half-open\n\t\t\t\t\t\t\t\t// `[start, start + size)`, so `DEFAULT_PORT_WINDOW`\n\t\t\t\t\t\t\t\t// (start 39200, size 1000) covers 39200..40199 inclusive.\n\t\t\t\t\t\t\t\t// Reuse the broker's exported default rather than a local\n\t\t\t\t\t\t\t\t// literal so the two cannot silently drift (pinned by\n\t\t\t\t\t\t\t\t// `test/plugins/wallet/port-window-pin.test.ts`).\n\t\t\t\t\t\t\t\twindowHint: DEFAULT_PORT_WINDOW,\n\t\t\t\t\t\t\t\tpreferredPort: preferred,\n\t\t\t\t\t\t\t\t...(probeHost === undefined ? {} : { probeHost }),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.map((alloc) => alloc.port),\n\t\t\t\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\t\t\t\tphase: 'allocate-port',\n\t\t\t\t\t\t\t\t\t\tmessage: `port-broker allocate failed: ${err.detail}`,\n\t\t\t\t\t\t\t\t\t\thint:\n\t\t\t\t\t\t\t\t\t\t\terr.reason === 'preferred-busy'\n\t\t\t\t\t\t\t\t\t\t\t\t? 'another plugin in this stack is using your preferred port; omit `port` to let the broker pick.'\n\t\t\t\t\t\t\t\t\t\t\t\t: err.reason === 'no-free-port'\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'the wallet port window is exhausted; check for stray devstack supervisors holding ports.'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: 'bind-probe failed — likely a privileged port or jail restriction.',\n\t\t\t\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\tresolveAccounts: () => Effect.succeed(resolvedAccounts),\n\t\t\t\t\trouterFrontedUrl,\n\t\t\t\t\troutedAppOrigin,\n\t\t\t\t};\n\n\t\t\t\tconst resolved = yield* acquireWallet(resolvedOpts, acquireCtx);\n\n\t\t\t\t// Emit the wallet's contributions inline. `resolved` is the\n\t\t\t\t// just-acquired `WalletValue`; `runtime.identity.{app,stack}`\n\t\t\t\t// map to the `identity` already held here\n\t\t\t\t// (`yield* IdentityContext`). Emit each decl via its matching\n\t\t\t\t// buffered verb IN ORDER (snapshotable → `ctx.snapshotExtra`,\n\t\t\t\t// codegenable → `ctx.codegen`, routable → `ctx.endpoint`); decl\n\t\t\t\t// shapes are load-bearing. The verbs are void and buffer for the\n\t\t\t\t// supervisor's post-start replay.\n\t\t\t\tctx.snapshotExtra(makeWalletSnapshotable());\n\t\t\t\tctx.codegen(makeWalletCodegen(resolved.connection));\n\t\t\t\tctx.endpoint(\n\t\t\t\t\tmakeWalletRoutable({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tport: resolved.localPort,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn resolved;\n\t\t\t}),\n\t});\n}\n\n// ----------------------------------------------------------------------\n// Re-exports\n// ----------------------------------------------------------------------\n\nexport type {\n\tWalletOptions,\n\tWalletValue,\n\tWalletAccountMember,\n\tWalletAccountsAll,\n} from './service.ts';\nexport { WALLET_ACCOUNTS_ALL } from './service.ts';\nexport type { DevWalletConnection } from './codegen.ts';\nexport { DEV_WALLET_VALUES_NAMESPACE, DEV_WALLET_VALUES_KEY } from './codegen.ts';\nexport {\n\tWalletHttpPath,\n\tWALLET_PROTOCOL_PREFIX,\n\tWALLET_AUTH_HEADER,\n\tWALLET_BEARER_PREFIX,\n\tWALLET_TOKEN_FRAGMENT_KEY,\n\tWALLET_TOKEN_HEX_LENGTH,\n\tSignRequestSchema,\n\tSignResponseSchema,\n\tHealthResponseSchema,\n\tAccountsResponseSchema,\n\tAccountSummarySchema,\n\tErrorResponseSchema,\n\tSuiAddressSchema,\n\tBase64Schema,\n\tSignatureSchemeSchema,\n\tAccountSourceSchema,\n\ttype WalletHttpPathValue,\n\ttype SignRequest,\n\ttype SignResponse,\n\ttype HealthResponse,\n\ttype AccountsResponse,\n\ttype AccountSummary,\n\ttype ErrorResponse,\n} from './protocol.ts';\nexport type {\n\tWalletError,\n\tWalletBootError,\n\tWalletBootPhase,\n\tWalletRequestError,\n\tWalletRequestPhase,\n} from './errors.ts';\nexport type { OriginPolicy, OriginPolicyInputs, OriginCheckResult } from './origin-policy.ts';\nexport { resolveOriginPolicy, checkOrigin, corsHeadersFor } from './origin-policy.ts';\nexport type { PairingToken } from './pairing.ts';\nexport {\n\tmintToken,\n\tacquirePairingToken,\n\ttokenPath,\n\tcomposePairUrl,\n\tparsePairUrl,\n\tparseBearerHeader,\n\tsafeBearerEquals,\n\tredactToken,\n} from './pairing.ts';\nexport { WALLET_ENDPOINT_NAME, WALLET_ENDPOINT_KEY, makeWalletRoutable } from './routable.ts';\nexport {\n\tdispatch,\n\tstartHttpServer,\n\tMAX_BODY_BYTES,\n\ttype WalletRequest,\n\ttype WalletResponse,\n\ttype WalletServerConfig,\n\ttype WalletServerHandle,\n} from './server.ts';\nexport { makeWalletCodegen } from './codegen.ts';\nexport { makeWalletSnapshotable } from './snapshot.ts';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,MAAM,6BAA6B;;;;;;;;;;;AAYnC,MAAM,2BACL,WAEA,OAAO,GAAG,WAAW,0BAA0B;;;AAQhD,MAAM,iBAAiB,SAAgC,QAAQ;AAiD/D,SAAgB,OAAO,MAAiC;CACvD,MAAM,eACL,QAAS,EAAE,UAAA,MAA8B;CAC1C,IAAI,aAAa,aAAA,OAAkC;EAqBlD,MAAM,cAAc,iBAAiB,cAAc,CAAC,CAAU;EAC9D,qBAAqB,cAAc,YAAY;GAQ9C,MAAM,iBAA6C,CAAC;GACpD,KAAK,MAAM,KAAK,SACf,IAAI,wBAAwB,CAAC,GAC5B,eAAe,KAAK,CAAmC;GAGzD,OAAO,iBAAiB;IAAE,GAAG;IAAc,UAAU;GAAe,GAAG,cAAc;EACtF,CAAC;EACD,OAAO;CACR;CACA,OAAO,iBAAiB,cAAc,aAAa,QAAQ;AAC5D;AAEA,SAAS,iBACR,MACA,UACC;CAQD,MAAM,eAAe,CAAC,aAAa,GAAG,QAAQ;CAK9C,MAAM,eAAwC;EAAE,GAAG;EAAM;CAAS;CAElE,OAAO,aAAa;EACnB,IAAI,eAAe;EACnB,WAAW;EAIX,MAAM;EACN,SAAS;EAIT,QAAQ,SACP,OAAO,IAAI,aAAa;GACvB,MAAM,MAAM,OAAO;GAUnB,MAAM,WAAW,OAAO;GACxB,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAa,OAAO;GAI1B,MAAM,GAAG,GAAG,oBAAoB;GAChC,MAAM,mBAAmB,OAAO,eAAe,UAAA,KAA2B,CAAC,CAAC,KAC3E,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,0CAA0C,IAAI;IACvD,OAAO;GACR,CAAC,CACF,CACD;GACA,MAAM,kBAAkB,OAAO,eAC9B,UAAA,KAED,CAAC,CAAC,KACD,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,8CAA8C,IAAI;IAC3D,OAAO;GACR,CAAC,CACF,CACD;GA4CA,MAAM,WAAW,OAAO,cAAc,cAAc;IAzCnD,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,SAAS,SAAS;IAClB,WAAW,MAAM;IACjB,eAAe,WAAW,cACzB,WACE,SAAS;KACT,OAAO;KASP,YAAY;KACZ,eAAe;KACf,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;IAChD,CAAC,CAAC,CACD,KACA,OAAO,KAAK,UAAU,MAAM,IAAI,GAChC,OAAO,UAAU,QAChB,gBAAgB;KACf,OAAO;KACP,SAAS,gCAAgC,IAAI;KAC7C,MACC,IAAI,WAAW,mBACZ,mGACA,IAAI,WAAW,iBACd,6FACA;KACL,OAAO;IACR,CAAC,CACF,CACD;IACF,uBAAuB,OAAO,QAAQ,gBAAgB;IACtD;IACA;GAG4D,CAAC;GAU9D,IAAI,cAAc,uBAAuB,CAAC;GAC1C,IAAI,QAAQ,kBAAkB,SAAS,UAAU,CAAC;GAClD,IAAI,SACH,mBAAmB;IAClB,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,MAAM,SAAS;GAChB,CAAC,CACF;GAEA,OAAO;EACR,CAAC;CACH,CAAC;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ResourceRef } from "../../substrate/plugin.mjs";
|
|
2
2
|
import { AccountValue } from "../account/service.mjs";
|
|
3
3
|
import { AccountResourceId } from "../account/index.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { DevWalletConnection } from "./codegen.mjs";
|
|
5
5
|
import { PairingToken } from "./pairing.mjs";
|
|
6
6
|
import { WalletServerHandle } from "./server.mjs";
|
|
7
7
|
import { Effect, FileSystem, Scope } from "effect";
|
|
@@ -51,7 +51,7 @@ interface WalletOptions<Accounts extends ReadonlyArray<WalletAccountMember> = Re
|
|
|
51
51
|
interface WalletValue {
|
|
52
52
|
readonly url: string;
|
|
53
53
|
readonly pairUrl: string;
|
|
54
|
-
readonly
|
|
54
|
+
readonly connection: DevWalletConnection;
|
|
55
55
|
readonly localPort: number;
|
|
56
56
|
readonly token: PairingToken;
|
|
57
57
|
/** Server handle — substrate's scope finalizer chain invokes
|
|
@@ -60,9 +60,8 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
|
|
|
60
60
|
});
|
|
61
61
|
const walletUrl = ctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;
|
|
62
62
|
const pairUrl = composePairUrl(walletUrl, token);
|
|
63
|
-
const
|
|
63
|
+
const connection = {
|
|
64
64
|
walletUrl,
|
|
65
|
-
pairUrl,
|
|
66
65
|
network: ctx.network,
|
|
67
66
|
protocolPaths: {
|
|
68
67
|
health: WalletHttpPath.HEALTH,
|
|
@@ -78,7 +77,7 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
|
|
|
78
77
|
return {
|
|
79
78
|
url: walletUrl,
|
|
80
79
|
pairUrl,
|
|
81
|
-
|
|
80
|
+
connection,
|
|
82
81
|
localPort: port,
|
|
83
82
|
token,
|
|
84
83
|
server
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":[],"sources":["../../../src/plugins/wallet/service.ts"],"sourcesContent":["// Wallet plugin — main acquire Effect.\n//\n// What this file does (15-wallet.md §Lifecycle, Startup ordered):\n//\n// 1. Resolve each consumed account tag and key the AccountValues by\n// address (load-bearing: sign endpoints look up by address).\n// 2. Allocate a port via the substrate's `PortBrokerService`.\n// 3. Mint or rehydrate the pairing token (read-existing-or-mint at\n// `<stateRoot>/wallet/token`, mode 0o600) via the substrate's\n// `atomicWriteFile`.\n// 4. Resolve the stack-scoped origin allowlist via\n// `origin-policy.ts`.\n// 5. Start the in-process HTTP server.\n// 6. Compose the wallet URL + pair URL + return the resolved\n// `WalletValue`.\n//\n// The substrate handles:\n//\n// - Scope finalizers (port release, server close) — declared by the\n// substrate primitives we call into.\n// - Endpoint registry publication (we surface the URL/pairUrl via\n// our resolved tag value; the substrate's endpoint-registry\n// primitive consumes the `RoutableDecl` from `routable.ts`).\n// - Manifest emit — the substrate projects from registry. We never\n// write `.devstack/manifest.json` directly.\n\nimport { Effect } from 'effect';\nimport type { FileSystem, Scope } from 'effect';\n\nimport type { AccountResourceId, AccountValue } from '../account/index.ts';\nimport type { DevWalletConfig } from './codegen.ts';\nimport { walletBootError, type WalletBootError } from './errors.ts';\nimport { resolveOriginPolicy } from './origin-policy.ts';\nimport { acquirePairingToken, composePairUrl, tokenPath, type PairingToken } from './pairing.ts';\nimport { startHttpServer, type WalletServerConfig, type WalletServerHandle } from './server.ts';\nimport { WalletHttpPath } from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// User-facing options\n// ----------------------------------------------------------------------\n\nimport type { ResourceRef } from '../../api/define-plugin.ts';\n\n/** Literal sentinel for `WalletOptions.accounts: 'all'` — every account\n * member in the stack. Expanded by the composer at `defineDevstack`\n * call time (api-surface-design §4 D6). Kept as an exported constant\n * so the wallet factory + composer share one source of truth. */\nexport const WALLET_ACCOUNTS_ALL = 'all' as const;\nexport type WalletAccountsAll = typeof WALLET_ACCOUNTS_ALL;\n\n/** A user-supplied account ref. The user passes the result of\n * `account('alice')` — NOT a bare string. Generic over the literal\n * account name so the wallet's dependency tuple preserves each\n * per-account resource id (`account/alice`, `account/bob`, ...). */\nexport type WalletAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\nexport interface WalletOptions<\n\tAccounts extends ReadonlyArray<WalletAccountMember> = ReadonlyArray<WalletAccountMember>,\n> {\n\t/** Accounts the wallet binds. Each is yielded for ordering AND its\n\t * resolved value is keyed by address into the sign-handler map.\n\t *\n\t * Two shapes:\n\t *\n\t * - Explicit tuple — each entry is the plugin/resource ref returned\n\t * by `account('name')`. Pins the bound set at the wallet's call\n\t * site; preserves each literal `account/${Name}` so stack\n\t * composition can validate and recursively expand the refs.\n\t *\n\t * - The literal `'all'` — shorthand for \"every account member in\n\t * the stack\". The composer expands this against the final\n\t * member tuple at `defineDevstack(...)` time (api-surface-design\n\t * §4 D6). The wallet member returned by the factory carries an\n\t * expander hook keyed off `WALLET_ACCOUNTS_ALL` that the\n\t * composer invokes once the account-providing members are\n\t * known. */\n\treadonly accounts: Accounts | typeof WALLET_ACCOUNTS_ALL;\n\t/** Extra origins merged on top of the router-fronted dev-server\n\t * origin. Useful for headless test runners and custom dev hosts. */\n\treadonly allowedOrigins?: ReadonlyArray<string>;\n\t/** Preferred host port. Substrate's port broker forward-scans if\n\t * this is taken. Default: substrate-picked (no preference). */\n\treadonly port?: number;\n\t/** NIC the HTTP server binds. Defaults to `'0.0.0.0'` because the\n\t * router runs in Docker and must reach the host process through the\n\t * host-gateway address on native Linux. The public wallet URL remains\n\t * router-fronted and stack-scoped. */\n\treadonly bindAddress?: string;\n}\n\n// ----------------------------------------------------------------------\n// Resolved value (what the wallet plugin publishes)\n// ----------------------------------------------------------------------\n\nexport interface WalletValue {\n\treadonly url: string; // router-fronted URL when available, loopback otherwise\n\treadonly pairUrl: string;\n\treadonly bindings: DevWalletConfig;\n\treadonly localPort: number;\n\treadonly token: PairingToken;\n\t/** Server handle — substrate's scope finalizer chain invokes\n\t * `.close()`; callers don't reach in. Exposed here so tests can\n\t * drive teardown explicitly. */\n\treadonly server: WalletServerHandle;\n}\n\nconst WALLET_DEFAULT_BIND_ADDRESS = '0.0.0.0' as const;\nconst WALLET_DIRECT_URL_HOST = '127.0.0.1' as const;\ntype WalletPortProbeHost = '127.0.0.1' | '0.0.0.0';\n\nconst portProbeHostForBindAddress = (bindAddress: string): WalletPortProbeHost =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? '0.0.0.0' : '127.0.0.1';\n\nconst directUrlHostForBindAddress = (bindAddress: string): string =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? WALLET_DIRECT_URL_HOST : bindAddress;\n\n// ----------------------------------------------------------------------\n// Per-acquire context — supplied by the barrel\n// ----------------------------------------------------------------------\n\n/** Inputs from the substrate. The barrel fills these from the\n * BuildContext; tests can construct directly. */\nexport interface WalletAcquireContext {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\t/** State root where `wallet/token` lives. Convention:\n\t * `<appDir>/.devstack/stacks/<stack>/runtime`. */\n\treadonly stateRoot: string;\n\t/** Port broker seam — returns the allocated port + a scope-\n\t * finalizer-installed release. The barrel adapts the substrate's\n\t * `PortBrokerService.allocate` to this signature so tests can pin\n\t * the port without yielding from a substrate Layer. */\n\treadonly allocatePort: (\n\t\tpreferred?: number,\n\t\tprobeHost?: WalletPortProbeHost,\n\t) => Effect.Effect<number, WalletBootError, Scope.Scope>;\n\t/** Account value resolver — the barrel hands this in keyed off the\n\t * BuildContext so the service body stays substrate-agnostic. */\n\treadonly resolveAccounts: () => Effect.Effect<ReadonlyArray<AccountValue>, WalletBootError>;\n\t/** Stable router-fronted base URL for this wallet on the stack-scoped\n\t * hostname (e.g. `http://wallet.<app>.localhost:<router-port>`). Null\n\t * only in tests that bypass the router derivation. */\n\treadonly routerFrontedUrl: string | null;\n\t/** Stable router-fronted dev-server origin for this stack. Added to\n\t * the wallet allowlist so app+wallet stacks work without repeating\n\t * router origins in user configs. */\n\treadonly routedAppOrigin: string | null;\n}\n\n// ----------------------------------------------------------------------\n// Acquire\n// ----------------------------------------------------------------------\n\n/**\n * Acquire the wallet service.\n *\n * Distilled-doc invariants honored:\n *\n * - C12 (mandatory Origin + bearer): both gates are wired in\n * `server.ts:dispatch`.\n * - Token comparison constant-time: `pairing.ts:safeBearerEquals`.\n * - Token file 0o600: `pairing.ts:acquirePairingToken`.\n * - Token in URL fragment only: `pairing.ts:composePairUrl`.\n * - Token NEVER in log lines: handlers log only `bearerValid:\n * boolean`.\n * - Default bindAddress `'0.0.0.0'`: required for the Docker router to\n * reach host-loopback services on native Linux.\n * - Stack-scoped origin allowlist (no cross-stack pairing risk):\n * `origin-policy.ts:resolveOriginPolicy`.\n */\nexport const acquireWallet = (\n\topts: WalletOptions,\n\tctx: WalletAcquireContext,\n): Effect.Effect<WalletValue, WalletBootError, Scope.Scope | FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\t// 1. Resolve the dependency account values. The barrel sets up\n\t\t// `resolveAccounts` to walk the BuildContext via resolved dependencies in\n\t\t// `opts.accounts`; we just project to the address-keyed map.\n\t\tconst accounts = yield* ctx.resolveAccounts();\n\t\tif (accounts.length === 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'no-accounts',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.\",\n\t\t\t\t\thint: \"`wallet()` and `wallet({ accounts: 'all' })` require at least one account member in the final stack.\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst accountsByAddress = new Map<string, AccountValue>();\n\t\tfor (const acct of accounts) {\n\t\t\t// Two accounts resolving to the same address would silently\n\t\t\t// last-write-wins the sign-route map, so a sign request for\n\t\t\t// that address binds to a non-deterministic account. Fail at\n\t\t\t// boot with the colliding address named.\n\t\t\tif (accountsByAddress.has(acct.address)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\twalletBootError({\n\t\t\t\t\t\tphase: 'bind-account',\n\t\t\t\t\t\tmessage: `wallet resolved two accounts at the same address ${acct.address}; each account must own a distinct address.`,\n\t\t\t\t\t\thint: 'Remove the duplicate account member, or give the colliding accounts distinct keypairs.',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\taccountsByAddress.set(acct.address, acct);\n\t\t}\n\n\t\t// 2. Port allocation. Probe the same host family the real\n\t\t// listener uses, otherwise a process bound on a non-loopback\n\t\t// interface could race the wallet's all-interface listen.\n\t\tconst bindAddress = opts.bindAddress ?? WALLET_DEFAULT_BIND_ADDRESS;\n\t\tconst port = yield* ctx.allocatePort(opts.port, portProbeHostForBindAddress(bindAddress));\n\n\t\t// 3. Token mint or rehydrate. Lives at the stack-scoped state root\n\t\t// so warm-start + snapshot-restore preserve the existing\n\t\t// pairing.\n\t\tconst token = yield* acquirePairingToken(tokenPath(ctx.stateRoot));\n\n\t\t// 4. Origin policy. Allowlist is the router-fronted dev-server\n\t\t// origin for this stack plus any explicit `allowedOrigins`.\n\t\tconst policy = yield* resolveOriginPolicy({\n\t\t\tapp: ctx.app,\n\t\t\tstack: ctx.stack,\n\t\t\troutedAppOrigin: ctx.routedAppOrigin,\n\t\t\textraOrigins: opts.allowedOrigins ?? [],\n\t\t});\n\n\t\t// 5. Start the HTTP server. The dispatcher in `server.ts` owns\n\t\t// route matching + the constant-time bearer compare + the\n\t\t// JSON envelope contract.\n\t\tconst serverConfig: WalletServerConfig = {\n\t\t\tbindAddress,\n\t\t\tport,\n\t\t\ttoken,\n\t\t\tpolicy,\n\t\t\taccountsByAddress,\n\t\t};\n\t\tconst server = yield* startHttpServer(serverConfig);\n\n\t\t// 6. Compose URLs. Router-fronted form when available, loopback\n\t\t// fallback otherwise. The token rides ONLY the fragment — never\n\t\t// a query param — per C13.\n\t\tconst walletUrl =\n\t\t\tctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;\n\t\tconst pairUrl = composePairUrl(walletUrl, token);\n\n\t\tconst bindings: DevWalletConfig = {\n\t\t\twalletUrl,\n\t\t\tpairUrl,\n\t\t\tnetwork: ctx.network,\n\t\t\tprotocolPaths: {\n\t\t\t\thealth: WalletHttpPath.HEALTH,\n\t\t\t\taccounts: WalletHttpPath.ACCOUNTS,\n\t\t\t\tsignTransaction: WalletHttpPath.SIGN_TRANSACTION,\n\t\t\t\tsignPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE,\n\t\t\t},\n\t\t};\n\n\t\t// Defensive: NEVER log `pairUrl` directly. It carries the token.\n\t\tyield* Effect.logInfo('wallet ready').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t'wallet.url': walletUrl,\n\t\t\t\t'wallet.token': 'redacted-fragment',\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: walletUrl,\n\t\t\tpairUrl,\n\t\t\tbindings,\n\t\t\tlocalPort: port,\n\t\t\ttoken,\n\t\t\tserver,\n\t\t} satisfies WalletValue;\n\t});\n"],"mappings":";;;;;;;;AA6GA,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AAG/B,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,YAAY;AAE3D,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,yBAAyB;;;;;;;;;;;;;;;;;;AAyDxE,MAAa,iBACZ,MACA,QAEA,OAAO,IAAI,aAAa;CAIvB,MAAM,WAAW,OAAO,IAAI,gBAAgB;CAC5C,IAAI,SAAS,WAAW,GACvB,OAAO,OAAO,OAAO,KACpB,gBAAgB;EACf,OAAO;EACP,SACC;EACD,MAAM;CACP,CAAC,CACF;CAED,MAAM,oCAAoB,IAAI,IAA0B;CACxD,KAAK,MAAM,QAAQ,UAAU;EAK5B,IAAI,kBAAkB,IAAI,KAAK,OAAO,GACrC,OAAO,OAAO,OAAO,KACpB,gBAAgB;GACf,OAAO;GACP,SAAS,oDAAoD,KAAK,QAAQ;GAC1E,MAAM;EACP,CAAC,CACF;EAED,kBAAkB,IAAI,KAAK,SAAS,IAAI;CACzC;CAKA,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,MAAM,4BAA4B,WAAW,CAAC;CAKxF,MAAM,QAAQ,OAAO,oBAAoB,UAAU,IAAI,SAAS,CAAC;CAqBjE,MAAM,SAAS,OAAO,gBAAgB;EANrC;EACA;EACA;EACA,QAAA,OAdqB,oBAAoB;GACzC,KAAK,IAAI;GACT,OAAO,IAAI;GACX,iBAAiB,IAAI;GACrB,cAAc,KAAK,kBAAkB,CAAC;EACvC,CAAC;EAUA;CAEgD,CAAC;CAKlD,MAAM,YACL,IAAI,oBAAoB,UAAU,4BAA4B,WAAW,EAAE,GAAG;CAC/E,MAAM,UAAU,eAAe,WAAW,KAAK;CAE/C,MAAM,WAA4B;EACjC;EACA;EACA,SAAS,IAAI;EACb,eAAe;GACd,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;EACrC;CACD;CAGA,OAAO,OAAO,QAAQ,cAAc,CAAC,CAAC,KACrC,OAAO,aAAa;EACnB,cAAc;EACd,gBAAgB;CACjB,CAAC,CACF;CAEA,OAAO;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX;EACA;CACD;AACD,CAAC"}
|
|
1
|
+
{"version":3,"file":"service.mjs","names":[],"sources":["../../../src/plugins/wallet/service.ts"],"sourcesContent":["// Wallet plugin — main acquire Effect.\n//\n// What this file does (15-wallet.md §Lifecycle, Startup ordered):\n//\n// 1. Resolve each consumed account tag and key the AccountValues by\n// address (load-bearing: sign endpoints look up by address).\n// 2. Allocate a port via the substrate's `PortBrokerService`.\n// 3. Mint or rehydrate the pairing token (read-existing-or-mint at\n// `<stateRoot>/wallet/token`, mode 0o600) via the substrate's\n// `atomicWriteFile`.\n// 4. Resolve the stack-scoped origin allowlist via\n// `origin-policy.ts`.\n// 5. Start the in-process HTTP server.\n// 6. Compose the wallet URL + pair URL + return the resolved\n// `WalletValue`.\n//\n// The substrate handles:\n//\n// - Scope finalizers (port release, server close) — declared by the\n// substrate primitives we call into.\n// - Endpoint registry publication (we surface the URL/pairUrl via\n// our resolved tag value; the substrate's endpoint-registry\n// primitive consumes the `RoutableDecl` from `routable.ts`).\n// - Manifest emit — the substrate projects from registry. We never\n// write `.devstack/manifest.json` directly.\n\nimport { Effect } from 'effect';\nimport type { FileSystem, Scope } from 'effect';\n\nimport type { AccountResourceId, AccountValue } from '../account/index.ts';\nimport type { DevWalletConnection } from './codegen.ts';\nimport { walletBootError, type WalletBootError } from './errors.ts';\nimport { resolveOriginPolicy } from './origin-policy.ts';\nimport { acquirePairingToken, composePairUrl, tokenPath, type PairingToken } from './pairing.ts';\nimport { startHttpServer, type WalletServerConfig, type WalletServerHandle } from './server.ts';\nimport { WalletHttpPath } from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// User-facing options\n// ----------------------------------------------------------------------\n\nimport type { ResourceRef } from '../../api/define-plugin.ts';\n\n/** Literal sentinel for `WalletOptions.accounts: 'all'` — every account\n * member in the stack. Expanded by the composer at `defineDevstack`\n * call time (api-surface-design §4 D6). Kept as an exported constant\n * so the wallet factory + composer share one source of truth. */\nexport const WALLET_ACCOUNTS_ALL = 'all' as const;\nexport type WalletAccountsAll = typeof WALLET_ACCOUNTS_ALL;\n\n/** A user-supplied account ref. The user passes the result of\n * `account('alice')` — NOT a bare string. Generic over the literal\n * account name so the wallet's dependency tuple preserves each\n * per-account resource id (`account/alice`, `account/bob`, ...). */\nexport type WalletAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\nexport interface WalletOptions<\n\tAccounts extends ReadonlyArray<WalletAccountMember> = ReadonlyArray<WalletAccountMember>,\n> {\n\t/** Accounts the wallet binds. Each is yielded for ordering AND its\n\t * resolved value is keyed by address into the sign-handler map.\n\t *\n\t * Two shapes:\n\t *\n\t * - Explicit tuple — each entry is the plugin/resource ref returned\n\t * by `account('name')`. Pins the bound set at the wallet's call\n\t * site; preserves each literal `account/${Name}` so stack\n\t * composition can validate and recursively expand the refs.\n\t *\n\t * - The literal `'all'` — shorthand for \"every account member in\n\t * the stack\". The composer expands this against the final\n\t * member tuple at `defineDevstack(...)` time (api-surface-design\n\t * §4 D6). The wallet member returned by the factory carries an\n\t * expander hook keyed off `WALLET_ACCOUNTS_ALL` that the\n\t * composer invokes once the account-providing members are\n\t * known. */\n\treadonly accounts: Accounts | typeof WALLET_ACCOUNTS_ALL;\n\t/** Extra origins merged on top of the router-fronted dev-server\n\t * origin. Useful for headless test runners and custom dev hosts. */\n\treadonly allowedOrigins?: ReadonlyArray<string>;\n\t/** Preferred host port. Substrate's port broker forward-scans if\n\t * this is taken. Default: substrate-picked (no preference). */\n\treadonly port?: number;\n\t/** NIC the HTTP server binds. Defaults to `'0.0.0.0'` because the\n\t * router runs in Docker and must reach the host process through the\n\t * host-gateway address on native Linux. The public wallet URL remains\n\t * router-fronted and stack-scoped. */\n\treadonly bindAddress?: string;\n}\n\n// ----------------------------------------------------------------------\n// Resolved value (what the wallet plugin publishes)\n// ----------------------------------------------------------------------\n\nexport interface WalletValue {\n\treadonly url: string; // router-fronted URL when available, loopback otherwise\n\treadonly pairUrl: string;\n\treadonly connection: DevWalletConnection;\n\treadonly localPort: number;\n\treadonly token: PairingToken;\n\t/** Server handle — substrate's scope finalizer chain invokes\n\t * `.close()`; callers don't reach in. Exposed here so tests can\n\t * drive teardown explicitly. */\n\treadonly server: WalletServerHandle;\n}\n\nconst WALLET_DEFAULT_BIND_ADDRESS = '0.0.0.0' as const;\nconst WALLET_DIRECT_URL_HOST = '127.0.0.1' as const;\ntype WalletPortProbeHost = '127.0.0.1' | '0.0.0.0';\n\nconst portProbeHostForBindAddress = (bindAddress: string): WalletPortProbeHost =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? '0.0.0.0' : '127.0.0.1';\n\nconst directUrlHostForBindAddress = (bindAddress: string): string =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? WALLET_DIRECT_URL_HOST : bindAddress;\n\n// ----------------------------------------------------------------------\n// Per-acquire context — supplied by the barrel\n// ----------------------------------------------------------------------\n\n/** Inputs from the substrate. The barrel fills these from the\n * BuildContext; tests can construct directly. */\nexport interface WalletAcquireContext {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\t/** State root where `wallet/token` lives. Convention:\n\t * `<appDir>/.devstack/stacks/<stack>/runtime`. */\n\treadonly stateRoot: string;\n\t/** Port broker seam — returns the allocated port + a scope-\n\t * finalizer-installed release. The barrel adapts the substrate's\n\t * `PortBrokerService.allocate` to this signature so tests can pin\n\t * the port without yielding from a substrate Layer. */\n\treadonly allocatePort: (\n\t\tpreferred?: number,\n\t\tprobeHost?: WalletPortProbeHost,\n\t) => Effect.Effect<number, WalletBootError, Scope.Scope>;\n\t/** Account value resolver — the barrel hands this in keyed off the\n\t * BuildContext so the service body stays substrate-agnostic. */\n\treadonly resolveAccounts: () => Effect.Effect<ReadonlyArray<AccountValue>, WalletBootError>;\n\t/** Stable router-fronted base URL for this wallet on the stack-scoped\n\t * hostname (e.g. `http://wallet.<app>.localhost:<router-port>`). Null\n\t * only in tests that bypass the router derivation. */\n\treadonly routerFrontedUrl: string | null;\n\t/** Stable router-fronted dev-server origin for this stack. Added to\n\t * the wallet allowlist so app+wallet stacks work without repeating\n\t * router origins in user configs. */\n\treadonly routedAppOrigin: string | null;\n}\n\n// ----------------------------------------------------------------------\n// Acquire\n// ----------------------------------------------------------------------\n\n/**\n * Acquire the wallet service.\n *\n * Distilled-doc invariants honored:\n *\n * - C12 (mandatory Origin + bearer): both gates are wired in\n * `server.ts:dispatch`.\n * - Token comparison constant-time: `pairing.ts:safeBearerEquals`.\n * - Token file 0o600: `pairing.ts:acquirePairingToken`.\n * - Token in URL fragment only: `pairing.ts:composePairUrl`.\n * - Token NEVER in log lines: handlers log only `bearerValid:\n * boolean`.\n * - Default bindAddress `'0.0.0.0'`: required for the Docker router to\n * reach host-loopback services on native Linux.\n * - Stack-scoped origin allowlist (no cross-stack pairing risk):\n * `origin-policy.ts:resolveOriginPolicy`.\n */\nexport const acquireWallet = (\n\topts: WalletOptions,\n\tctx: WalletAcquireContext,\n): Effect.Effect<WalletValue, WalletBootError, Scope.Scope | FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\t// 1. Resolve the dependency account values. The barrel sets up\n\t\t// `resolveAccounts` to walk the BuildContext via resolved dependencies in\n\t\t// `opts.accounts`; we just project to the address-keyed map.\n\t\tconst accounts = yield* ctx.resolveAccounts();\n\t\tif (accounts.length === 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'no-accounts',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.\",\n\t\t\t\t\thint: \"`wallet()` and `wallet({ accounts: 'all' })` require at least one account member in the final stack.\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst accountsByAddress = new Map<string, AccountValue>();\n\t\tfor (const acct of accounts) {\n\t\t\t// Two accounts resolving to the same address would silently\n\t\t\t// last-write-wins the sign-route map, so a sign request for\n\t\t\t// that address binds to a non-deterministic account. Fail at\n\t\t\t// boot with the colliding address named.\n\t\t\tif (accountsByAddress.has(acct.address)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\twalletBootError({\n\t\t\t\t\t\tphase: 'bind-account',\n\t\t\t\t\t\tmessage: `wallet resolved two accounts at the same address ${acct.address}; each account must own a distinct address.`,\n\t\t\t\t\t\thint: 'Remove the duplicate account member, or give the colliding accounts distinct keypairs.',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\taccountsByAddress.set(acct.address, acct);\n\t\t}\n\n\t\t// 2. Port allocation. Probe the same host family the real\n\t\t// listener uses, otherwise a process bound on a non-loopback\n\t\t// interface could race the wallet's all-interface listen.\n\t\tconst bindAddress = opts.bindAddress ?? WALLET_DEFAULT_BIND_ADDRESS;\n\t\tconst port = yield* ctx.allocatePort(opts.port, portProbeHostForBindAddress(bindAddress));\n\n\t\t// 3. Token mint or rehydrate. Lives at the stack-scoped state root\n\t\t// so warm-start + snapshot-restore preserve the existing\n\t\t// pairing.\n\t\tconst token = yield* acquirePairingToken(tokenPath(ctx.stateRoot));\n\n\t\t// 4. Origin policy. Allowlist is the router-fronted dev-server\n\t\t// origin for this stack plus any explicit `allowedOrigins`.\n\t\tconst policy = yield* resolveOriginPolicy({\n\t\t\tapp: ctx.app,\n\t\t\tstack: ctx.stack,\n\t\t\troutedAppOrigin: ctx.routedAppOrigin,\n\t\t\textraOrigins: opts.allowedOrigins ?? [],\n\t\t});\n\n\t\t// 5. Start the HTTP server. The dispatcher in `server.ts` owns\n\t\t// route matching + the constant-time bearer compare + the\n\t\t// JSON envelope contract.\n\t\tconst serverConfig: WalletServerConfig = {\n\t\t\tbindAddress,\n\t\t\tport,\n\t\t\ttoken,\n\t\t\tpolicy,\n\t\t\taccountsByAddress,\n\t\t};\n\t\tconst server = yield* startHttpServer(serverConfig);\n\n\t\t// 6. Compose URLs. Router-fronted form when available, loopback\n\t\t// fallback otherwise. The token rides ONLY the fragment — never\n\t\t// a query param — per C13.\n\t\tconst walletUrl =\n\t\t\tctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;\n\t\tconst pairUrl = composePairUrl(walletUrl, token);\n\n\t\t// NON-secret connection metadata only — the token never rides this\n\t\t// (it stays in the `0o600` side-channel file the Vite plugin reads by\n\t\t// path; `pairUrl`/`token` remain on the resolved value for tests and the\n\t\t// snapshot subtree, but are NOT routed through `deployment.json`).\n\t\tconst connection: DevWalletConnection = {\n\t\t\twalletUrl,\n\t\t\tnetwork: ctx.network,\n\t\t\tprotocolPaths: {\n\t\t\t\thealth: WalletHttpPath.HEALTH,\n\t\t\t\taccounts: WalletHttpPath.ACCOUNTS,\n\t\t\t\tsignTransaction: WalletHttpPath.SIGN_TRANSACTION,\n\t\t\t\tsignPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE,\n\t\t\t},\n\t\t};\n\n\t\t// Defensive: NEVER log `pairUrl` directly. It carries the token.\n\t\tyield* Effect.logInfo('wallet ready').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t'wallet.url': walletUrl,\n\t\t\t\t'wallet.token': 'redacted-fragment',\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: walletUrl,\n\t\t\tpairUrl,\n\t\t\tconnection,\n\t\t\tlocalPort: port,\n\t\t\ttoken,\n\t\t\tserver,\n\t\t} satisfies WalletValue;\n\t});\n"],"mappings":";;;;;;;;AA6GA,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AAG/B,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,YAAY;AAE3D,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,yBAAyB;;;;;;;;;;;;;;;;;;AAyDxE,MAAa,iBACZ,MACA,QAEA,OAAO,IAAI,aAAa;CAIvB,MAAM,WAAW,OAAO,IAAI,gBAAgB;CAC5C,IAAI,SAAS,WAAW,GACvB,OAAO,OAAO,OAAO,KACpB,gBAAgB;EACf,OAAO;EACP,SACC;EACD,MAAM;CACP,CAAC,CACF;CAED,MAAM,oCAAoB,IAAI,IAA0B;CACxD,KAAK,MAAM,QAAQ,UAAU;EAK5B,IAAI,kBAAkB,IAAI,KAAK,OAAO,GACrC,OAAO,OAAO,OAAO,KACpB,gBAAgB;GACf,OAAO;GACP,SAAS,oDAAoD,KAAK,QAAQ;GAC1E,MAAM;EACP,CAAC,CACF;EAED,kBAAkB,IAAI,KAAK,SAAS,IAAI;CACzC;CAKA,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,MAAM,4BAA4B,WAAW,CAAC;CAKxF,MAAM,QAAQ,OAAO,oBAAoB,UAAU,IAAI,SAAS,CAAC;CAqBjE,MAAM,SAAS,OAAO,gBAAgB;EANrC;EACA;EACA;EACA,QAAA,OAdqB,oBAAoB;GACzC,KAAK,IAAI;GACT,OAAO,IAAI;GACX,iBAAiB,IAAI;GACrB,cAAc,KAAK,kBAAkB,CAAC;EACvC,CAAC;EAUA;CAEgD,CAAC;CAKlD,MAAM,YACL,IAAI,oBAAoB,UAAU,4BAA4B,WAAW,EAAE,GAAG;CAC/E,MAAM,UAAU,eAAe,WAAW,KAAK;CAM/C,MAAM,aAAkC;EACvC;EACA,SAAS,IAAI;EACb,eAAe;GACd,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;EACrC;CACD;CAGA,OAAO,OAAO,QAAQ,cAAc,CAAC,CAAC,KACrC,OAAO,aAAa;EACnB,cAAc;EACd,gBAAgB;CACjB,CAAC,CACF;CAEA,OAAO;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX;EACA;CACD;AACD,CAAC"}
|
|
@@ -2,14 +2,14 @@ import { configCodegenable } from "../../contracts/config-bindings.mjs";
|
|
|
2
2
|
//#region src/plugins/walrus/codegen.ts
|
|
3
3
|
const NAMESPACE = "walrus";
|
|
4
4
|
/** TS source-type strings for the resolved walrus fields — keeps the committed
|
|
5
|
-
* `walrus.ts` typed as `WalrusBindings` declares (the generic `
|
|
5
|
+
* `walrus.ts` typed as `WalrusBindings` declares (the generic `requireValue`
|
|
6
6
|
* channel would otherwise return `unknown`). Composite blobs inline their
|
|
7
7
|
* structural literal types so no emitted type-import is needed. */
|
|
8
8
|
const PACKAGE_CONFIG_TS_TYPE = "{ readonly systemObjectId: string; readonly stakingPoolId: string; readonly exchangeIds?: ReadonlyArray<string> }";
|
|
9
9
|
const NODES_TS_TYPE = "ReadonlyArray<{ readonly nodeIndex: number; readonly publicHostname: string; readonly rpcUrl: string }>";
|
|
10
10
|
/** The walrus config bindings, declared ONCE. `mode` / `network` are
|
|
11
11
|
* structural literals; every id / coin type / URL / committee value is a
|
|
12
|
-
* RESOLVED binding on the generic `
|
|
12
|
+
* RESOLVED binding on the generic `requireValue(dep, 'walrus', '<key>')` channel.
|
|
13
13
|
* Both the live boot decl and the static committed-tree decl derive from it. */
|
|
14
14
|
const walrusConfigBindings = (structural) => {
|
|
15
15
|
const known = structural.known;
|
|
@@ -80,7 +80,7 @@ const walrusConfigBindings = (structural) => {
|
|
|
80
80
|
};
|
|
81
81
|
};
|
|
82
82
|
/** Construct the LIVE Codegenable contribution. Bakes the resolved ids /
|
|
83
|
-
* URLs into the ephemeral tree + feeds the generic
|
|
83
|
+
* URLs into the ephemeral tree + feeds the generic deployment `values`
|
|
84
84
|
* channel. */
|
|
85
85
|
const makeCodegenable = (inputs) => configCodegenable(walrusConfigBindings({
|
|
86
86
|
mode: inputs.mode,
|
|
@@ -90,7 +90,7 @@ const makeCodegenable = (inputs) => configCodegenable(walrusConfigBindings({
|
|
|
90
90
|
state: inputs
|
|
91
91
|
});
|
|
92
92
|
/** Construct the STATIC (stack-free) Codegenable contribution. Emits
|
|
93
|
-
* `
|
|
93
|
+
* `requireValue(dep, 'walrus', '<key>')` for the runtime fields; the committed
|
|
94
94
|
* `walrus.ts` carries no baked object id / endpoint URL. */
|
|
95
95
|
const makeWalrusStaticCodegen = (config) => configCodegenable(walrusConfigBindings(config), "static");
|
|
96
96
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/walrus/codegen.ts"],"sourcesContent":["// Walrus plugin — Codegenable contribution, via the UNIFIED config-binding\n// declaration.\n//\n// Walrus's contribution is the SDK-ready `packageConfig` shape that the\n// `@mysten/walrus` SDK consumes — `{systemObjectId, stakingPoolId,\n// exchangeIds}` — plus the proxy / aggregator / publisher URLs for HTTP\n// consumers. Walrus is single-instance per stack, so it exports `walrus`\n// directly (a FLAT bucket, not name-keyed like coin/seal).\n//\n// ONE declaration, TWO derivations (see `contracts/config-bindings.ts`):\n// - LIVE (boot): bakes the resolved ids / URLs into the ephemeral tree AND\n// feeds the generic
|
|
1
|
+
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/walrus/codegen.ts"],"sourcesContent":["// Walrus plugin — Codegenable contribution, via the UNIFIED config-binding\n// declaration.\n//\n// Walrus's contribution is the SDK-ready `packageConfig` shape that the\n// `@mysten/walrus` SDK consumes — `{systemObjectId, stakingPoolId,\n// exchangeIds}` — plus the proxy / aggregator / publisher URLs for HTTP\n// consumers. Walrus is single-instance per stack, so it exports `walrus`\n// directly (a FLAT bucket, not name-keyed like coin/seal).\n//\n// ONE declaration, TWO derivations (see `contracts/config-bindings.ts`):\n// - LIVE (boot): bakes the resolved ids / URLs into the ephemeral tree AND\n// feeds the generic deployment `values` channel.\n// - STATIC (committed tree): emits `requireValue(dep, 'walrus', '<key>')` so the\n// committed `walrus.ts` carries NO baked object id / endpoint URL.\n//\n// STRUCTURAL fields (`mode`, `network`) stay literals; the on-chain ids,\n// coin type, endpoint URLs, and the storage-node committee are RUNTIME\n// (loaded config data). The composite `packageConfig` / `nodes` values are\n// resolved as whole-value blobs (the array/optional shapes don't split into\n// the flat config-path model).\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport {\n\tconfigCodegenable,\n\ttype ConfigBinding,\n\ttype ConfigBindingSet,\n} from '../../contracts/config-bindings.ts';\nimport type { JsonValue } from '../../orchestrators/codegen/deployment.ts';\n\n/** Per-node descriptor. */\nexport interface WalrusNodeBinding {\n\treadonly nodeIndex: number;\n\treadonly publicHostname: string;\n\treadonly rpcUrl: string;\n}\n\n/** The typed shape the emitted file exports. */\nexport interface WalrusBindings {\n\treadonly mode: 'local' | 'known';\n\treadonly network: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\treadonly walCoinType: string | null;\n\treadonly packageConfig: {\n\t\treadonly systemObjectId: string;\n\t\treadonly stakingPoolId: string;\n\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t};\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly nodes: ReadonlyArray<WalrusNodeBinding>;\n}\n\n/** Inputs to the LIVE codegen contribution — supplied at acquire-time. */\nexport interface MakeCodegenableInputs {\n\treadonly mode: 'local' | 'known';\n\treadonly network: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\treadonly walCoinType: string | null;\n\treadonly systemObjectId: string;\n\treadonly stakingPoolId: string;\n\treadonly exchangeIds: ReadonlyArray<string>;\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly nodes: ReadonlyArray<WalrusNodeBinding>;\n}\n\n/** User-declared known-deployment ids / URLs, available at factory time. A\n * KNOWN deployment's values are DECLARED config (not loaded-at-runtime data),\n * so the committed `walrus.ts` bakes them as LITERALS. Absent for a `local`\n * (dev-deployed) cluster whose ids/URLs are dynamic. */\nexport interface WalrusKnownConfig {\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\treadonly walCoinType: string | null;\n\treadonly packageConfig: WalrusBindings['packageConfig'];\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly nodes: ReadonlyArray<WalrusNodeBinding>;\n}\n\n/** Static-config shape — what walrus knows BEFORE acquire. When `known` is\n * present (known mode), its declared ids/URLs are baked as literals;\n * otherwise (local mode) every id/URL resolves at app build/dev time. */\nexport interface WalrusStaticConfig {\n\treadonly mode: 'local' | 'known';\n\treadonly network: string;\n\treadonly known?: WalrusKnownConfig;\n}\n\nconst NAMESPACE = 'walrus';\n\n/** TS source-type strings for the resolved walrus fields — keeps the committed\n * `walrus.ts` typed as `WalrusBindings` declares (the generic `requireValue`\n * channel would otherwise return `unknown`). Composite blobs inline their\n * structural literal types so no emitted type-import is needed. */\nconst PACKAGE_CONFIG_TS_TYPE =\n\t'{ readonly systemObjectId: string; readonly stakingPoolId: string; readonly exchangeIds?: ReadonlyArray<string> }';\nconst NODES_TS_TYPE =\n\t'ReadonlyArray<{ readonly nodeIndex: number; readonly publicHostname: string; readonly rpcUrl: string }>';\n\n/** The walrus config bindings, declared ONCE. `mode` / `network` are\n * structural literals; every id / coin type / URL / committee value is a\n * RESOLVED binding on the generic `requireValue(dep, 'walrus', '<key>')` channel.\n * Both the live boot decl and the static committed-tree decl derive from it. */\nconst walrusConfigBindings = (\n\tstructural: WalrusStaticConfig,\n): ConfigBindingSet<MakeCodegenableInputs> => {\n\tconst known = structural.known;\n\t// A known deployment's declared ids/URLs are config (literal); a local\n\t// cluster's are dynamically deployed (resolved at app build/dev time).\n\tconst field = (\n\t\tkey:\n\t\t\t| 'walrusPackageId'\n\t\t\t| 'walPackageId'\n\t\t\t| 'walCoinType'\n\t\t\t| 'proxyUrl'\n\t\t\t| 'aggregatorUrl'\n\t\t\t| 'publisherUrl',\n\t\tlive: (i: MakeCodegenableInputs) => JsonValue,\n\t): ConfigBinding<MakeCodegenableInputs> =>\n\t\tknown !== undefined\n\t\t\t? { variant: 'literal', configPath: [key], value: known[key] }\n\t\t\t: {\n\t\t\t\t\tvariant: 'resolved',\n\t\t\t\t\tconfigPath: [key],\n\t\t\t\t\tnamespace: NAMESPACE,\n\t\t\t\t\tkey,\n\t\t\t\t\ttsType: 'string | null',\n\t\t\t\t\tlive,\n\t\t\t\t};\n\tconst packageConfigBinding: ConfigBinding<MakeCodegenableInputs> =\n\t\tknown !== undefined\n\t\t\t? {\n\t\t\t\t\tvariant: 'literal',\n\t\t\t\t\tconfigPath: ['packageConfig'],\n\t\t\t\t\tvalue: known.packageConfig as JsonValue,\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tvariant: 'resolved',\n\t\t\t\t\tconfigPath: ['packageConfig'],\n\t\t\t\t\tnamespace: NAMESPACE,\n\t\t\t\t\tkey: 'packageConfig',\n\t\t\t\t\ttsType: PACKAGE_CONFIG_TS_TYPE,\n\t\t\t\t\tlive: (i) =>\n\t\t\t\t\t\t({\n\t\t\t\t\t\t\tsystemObjectId: i.systemObjectId,\n\t\t\t\t\t\t\tstakingPoolId: i.stakingPoolId,\n\t\t\t\t\t\t\t...(i.exchangeIds.length > 0 ? { exchangeIds: [...i.exchangeIds] } : {}),\n\t\t\t\t\t\t}) as JsonValue,\n\t\t\t\t};\n\tconst nodesBinding: ConfigBinding<MakeCodegenableInputs> =\n\t\tknown !== undefined\n\t\t\t? { variant: 'literal', configPath: ['nodes'], value: known.nodes as unknown as JsonValue }\n\t\t\t: {\n\t\t\t\t\tvariant: 'resolved',\n\t\t\t\t\tconfigPath: ['nodes'],\n\t\t\t\t\tnamespace: NAMESPACE,\n\t\t\t\t\tkey: 'nodes',\n\t\t\t\t\ttsType: NODES_TS_TYPE,\n\t\t\t\t\tlive: (i) => i.nodes as unknown as JsonValue,\n\t\t\t\t};\n\tconst bindings: ReadonlyArray<ConfigBinding<MakeCodegenableInputs>> = [\n\t\t{ variant: 'literal', configPath: ['mode'], value: structural.mode },\n\t\t{ variant: 'literal', configPath: ['network'], value: structural.network },\n\t\tfield('walrusPackageId', (i) => i.walrusPackageId),\n\t\tfield('walPackageId', (i) => i.walPackageId),\n\t\tfield('walCoinType', (i) => i.walCoinType),\n\t\tpackageConfigBinding,\n\t\tfield('proxyUrl', (i) => i.proxyUrl),\n\t\tfield('aggregatorUrl', (i) => i.aggregatorUrl),\n\t\tfield('publisherUrl', (i) => i.publisherUrl),\n\t\tnodesBinding,\n\t];\n\treturn {\n\t\tbucket: 'walrus.ts',\n\t\tkind: 'walrus',\n\t\temitterName: 'walrus-network',\n\t\tbindings,\n\t};\n};\n\n/** Construct the LIVE Codegenable contribution. Bakes the resolved ids /\n * URLs into the ephemeral tree + feeds the generic deployment `values`\n * channel. */\nexport const makeCodegenable = (inputs: MakeCodegenableInputs): CodegenableDecl =>\n\tconfigCodegenable(walrusConfigBindings({ mode: inputs.mode, network: inputs.network }), {\n\t\tmode: 'live',\n\t\tstate: inputs,\n\t});\n\n/** Construct the STATIC (stack-free) Codegenable contribution. Emits\n * `requireValue(dep, 'walrus', '<key>')` for the runtime fields; the committed\n * `walrus.ts` carries no baked object id / endpoint URL. */\nexport const makeWalrusStaticCodegen = (config: WalrusStaticConfig): CodegenableDecl =>\n\tconfigCodegenable(walrusConfigBindings(config), 'static');\n"],"mappings":";;AA8FA,MAAM,YAAY;;;;;AAMlB,MAAM,yBACL;AACD,MAAM,gBACL;;;;;AAMD,MAAM,wBACL,eAC6C;CAC7C,MAAM,QAAQ,WAAW;CAGzB,MAAM,SACL,KAOA,SAEA,UAAU,KAAA,IACP;EAAE,SAAS;EAAW,YAAY,CAAC,GAAG;EAAG,OAAO,MAAM;CAAK,IAC3D;EACA,SAAS;EACT,YAAY,CAAC,GAAG;EAChB,WAAW;EACX;EACA,QAAQ;EACR;CACD;CACH,MAAM,uBACL,UAAU,KAAA,IACP;EACA,SAAS;EACT,YAAY,CAAC,eAAe;EAC5B,OAAO,MAAM;CACd,IACC;EACA,SAAS;EACT,YAAY,CAAC,eAAe;EAC5B,WAAW;EACX,KAAK;EACL,QAAQ;EACR,OAAO,OACL;GACA,gBAAgB,EAAE;GAClB,eAAe,EAAE;GACjB,GAAI,EAAE,YAAY,SAAS,IAAI,EAAE,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC;EACvE;CACF;CACH,MAAM,eACL,UAAU,KAAA,IACP;EAAE,SAAS;EAAW,YAAY,CAAC,OAAO;EAAG,OAAO,MAAM;CAA8B,IACxF;EACA,SAAS;EACT,YAAY,CAAC,OAAO;EACpB,WAAW;EACX,KAAK;EACL,QAAQ;EACR,OAAO,MAAM,EAAE;CAChB;CAaH,OAAO;EACN,QAAQ;EACR,MAAM;EACN,aAAa;EACb,UAAA;GAfA;IAAE,SAAS;IAAW,YAAY,CAAC,MAAM;IAAG,OAAO,WAAW;GAAK;GACnE;IAAE,SAAS;IAAW,YAAY,CAAC,SAAS;IAAG,OAAO,WAAW;GAAQ;GACzE,MAAM,oBAAoB,MAAM,EAAE,eAAe;GACjD,MAAM,iBAAiB,MAAM,EAAE,YAAY;GAC3C,MAAM,gBAAgB,MAAM,EAAE,WAAW;GACzC;GACA,MAAM,aAAa,MAAM,EAAE,QAAQ;GACnC,MAAM,kBAAkB,MAAM,EAAE,aAAa;GAC7C,MAAM,iBAAiB,MAAM,EAAE,YAAY;GAC3C;EAMO;CACR;AACD;;;;AAKA,MAAa,mBAAmB,WAC/B,kBAAkB,qBAAqB;CAAE,MAAM,OAAO;CAAM,SAAS,OAAO;AAAQ,CAAC,GAAG;CACvF,MAAM;CACN,OAAO;AACR,CAAC;;;;AAKF,MAAa,2BAA2B,WACvC,kBAAkB,qBAAqB,MAAM,GAAG,QAAQ"}
|
|
@@ -7,10 +7,10 @@ import { ContainerRuntimeService } from "../../runtime/docker/service.mjs";
|
|
|
7
7
|
import { PluginContext, emitContributions } from "../../substrate/plugin-ctx.mjs";
|
|
8
8
|
import { chainProbeFor } from "../../substrate/runtime/strategy-registry/chain-probe-for.mjs";
|
|
9
9
|
import "../../substrate/runtime/strategy-registry/index.mjs";
|
|
10
|
+
import { LOCAL_NETWORK_NAME } from "../../api/inference-network.mjs";
|
|
10
11
|
import { walrusPluginError } from "./errors.mjs";
|
|
11
12
|
import { buildWalrusNetworkName } from "./storage-nodes.mjs";
|
|
12
13
|
import { makeLocalRoutables } from "./routable.mjs";
|
|
13
|
-
import { LOCAL_NETWORK_NAME } from "../../api/inference-network.mjs";
|
|
14
14
|
import { defineModeNamespace } from "../../api/mode-narrowed-factory.mjs";
|
|
15
15
|
import { suiResource } from "../sui/index.mjs";
|
|
16
16
|
import { coinResourceId } from "../coin/index.mjs";
|