@mysten-incubation/devstack 0.3.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-Clz0oOtc.js → grpc-CpkDu4SA.js} +1 -1
- package/dashboard-ui/assets/{index-Cmqv9kiU.js → index-jLPRmjst.js} +2 -2
- 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 +1 -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/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 +54 -42
- package/dist/build-integrations/vite/index.mjs +239 -106
- package/dist/build-integrations/vite/index.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/main.mjs +3 -2
- 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 +2 -3
- 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/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 +19 -59
- 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 +354 -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/schema/root.mjs +3 -1
- package/dist/plugins/dashboard/schema/root.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.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.mjs +1 -2
- package/dist/plugins/sui/index.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/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +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":"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";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/walrus/index.ts"],"sourcesContent":["// Walrus plugin — barrel + factories.\n//\n// Architecture: Walrus is a service plugin that owns a local cluster\n// or describes a known deployment. The factory at this file folds the\n// four modes behind:\n//\n// - `walrus(opts?)` — local-cluster shorthand. No env\n// defaulting; use `walrusFor(...)`\n// for known deployments.\n// - `walrusFor(network)` — mode-narrowed factory namespace\n// (architecture Tension 11). Returns\n// `{ local: …, known: … }` narrowed\n// to the network's mode. Crucially,\n// the fork branch exposes ONLY\n// `.known` — `.local` is a compile\n// error on a fork-mode network.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs, per mode):\n//\n// Local mode (full local cluster):\n// 1. `ctx.snapshotExtra` — runtime/walrus/<name>/deploy/ subtree\n// + storage-node managed containers.\n// 2. `ctx.codegen` — `walrus-network` bindings.\n// 3. `ctx.endpoint` × (N+2) — per-node + aggregator + publisher.\n// 4. `ctx.provides` walrus-state-registry — local entry.\n// 5. `ctx.provides` endpoint-registry — N+2 entries.\n// 6. `ctx.provides` package-registry — `walrus.<name>`.\n// 7. `ctx.provides` coinType:<WAL fullCoinType> — WAL faucet\n// strategy\n// (when exchange exists).\n//\n// Known mode (read-only deployment):\n// 1. `ctx.snapshotExtra` — identity-guard only; no subtrees.\n// 2. `ctx.codegen` — `walrus-network` bindings (mode='known').\n// 3. `ctx.provides` walrus-state-registry — known entry.\n//\n// Resource id: `'walrus'` (singular). The plugin's substrate-level\n// plugin key is the same string.\n\nimport { Effect, Path } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, type ResourceRef } from '../../api/define-plugin.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport type { RoutableDecl } from '../../contracts/routable.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { deriveSubnetPrefix, withSubnetAddressing } from '../../substrate/runtime/subnet-broker.ts';\nimport type { AccountFundingCoinValue } from '../account/index.ts';\nimport { coinResourceId, type CoinResourceId } from '../coin/index.ts';\nimport { suiResource, type SuiProbeKey } from '../sui/index.ts';\n\nimport { chainProbeFor } from '../../substrate/runtime/strategy-registry/index.ts';\n\nimport { makeCodegenable, makeWalrusStaticCodegen } from './codegen.ts';\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport { walrusPluginKey } from './plugin-key.ts';\nimport { walrusPluginError, type WalrusPluginError } from './errors.ts';\nimport { makeWalFaucetContribution, type WalFaucetStrategy } from './faucet-strategy.ts';\nimport { bootWalrusService, type WalrusMode } from './service.ts';\nimport {\n\tresolveLocalClusterOptions,\n\ttype WalrusLocalClusterOptions,\n} from './mode/local-cluster.ts';\nimport {\n\tresolveKnownDeploymentOptions,\n\ttype WalrusKnownDeploymentOptions,\n} from './mode/known-deploy.ts';\nimport { makeSnapshotable, type WalrusSnapshotMode } from './snapshot.ts';\nimport { makeLocalRoutables } from './routable.ts';\nimport { WALRUS_STATE_REGISTRY_KEY, type WalrusStateEntry } from './registry-publish.ts';\nimport { buildWalrusNetworkName, type WalrusStorageNode } from './storage-nodes.ts';\n\n// ---------------------------------------------------------------------------\n// Resource — the resolved value all consumers read\n// ---------------------------------------------------------------------------\n\n/** The Walrus resolved value carried by the resource. */\nexport interface WalrusResolved {\n\treadonly mode: 'local' | 'known';\n\t/** Network name the walrus deployment targets (`localnet`/`testnet`/…). */\n\treadonly network: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\t/** SDK-ready `packageConfig` — structurally compatible with\n\t * `@mysten/walrus`'s `WalrusPackageConfig`. */\n\treadonly packageConfig: {\n\t\treadonly systemObjectId: string;\n\t\treadonly stakingPoolId: string;\n\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t};\n\treadonly nodes: ReadonlyArray<WalrusStorageNode>;\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly walFaucetStrategy: WalFaucetStrategy | null;\n\treadonly walCoinType: string | null;\n}\n\n/** Walrus plugin resource. */\nexport const walrusResource = resource<'walrus', WalrusResolved>('walrus');\n\nexport interface WalrusNetworkIdentity {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly walrusName: string;\n}\n\n/** Walrus deploy records storage-node listening IPs under this /24.\n * Docker network create requests the matching subnet explicitly, with\n * the prefix derived from the Walrus network identity so parallel\n * stacks don't all claim the same Docker IPAM range. Walrus claims\n * the `10.64.*` – `10.127.*` band; see substrate/runtime/subnet-broker.ts\n * for the algorithm and band coordination with seal. */\nexport const deriveWalrusSubnetPrefix = (identity: WalrusNetworkIdentity): string =>\n\tderiveSubnetPrefix(`${identity.app}\\0${identity.stack}\\0${identity.walrusName}`, 64);\n\nconst withWalrusNetworkAddressing = (\n\truntime: ContainerRuntime,\n\twalrusNetworkName: string,\n\tsubnetPrefix: string,\n): ContainerRuntime => ({\n\t...runtime,\n\tensureNetwork: (spec) =>\n\t\truntime.ensureNetwork(\n\t\t\tspec.name === walrusNetworkName ? withSubnetAddressing(spec, subnetPrefix) : spec,\n\t\t),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by walrus() + walrusFor())\n// ---------------------------------------------------------------------------\n\nconst buildLocalPlugin = (opts: WalrusLocalClusterOptions) => {\n\t// Synchronous factory-time validation (distilled-doc invariants\n\t// 11 — `nodeCount >= 1` + `shards >= nodeCount`).\n\tconst resolved = resolveLocalClusterOptions(opts);\n\n\tconst walrusKey = walrusPluginKey(resolved.name);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tpluginKey: walrusKey,\n\t\t// Stack-free codegen: a local walrus cluster's deploy ids / endpoint\n\t\t// URLs are LOADED CONFIG DATA -- the committed `walrus.ts` stub emits\n\t\t// `resolveValue('walrus', '<key>')`, never a baked object id / URL.\n\t\tstaticCodegen: () => [makeWalrusStaticCodegen({ mode: 'local', network: LOCAL_NETWORK_NAME })],\n\t\t// `deps` auto-infers the resolved `[sui]` tuple from the\n\t\t// `[suiResource] as const` dependency. `ctx` arrives via the\n\t\t// `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst [sui] = deps;\n\n\t\t\t\t// Substrate-context primitives:\n\t\t\t\t// - `ContainerRuntimeService` + `IdentityContext` arrive\n\t\t\t\t// via the supervisor's plugin runtime context.\n\t\t\t\t// - `ArtifactPublisher` is the substrate-level\n\t\t\t\t// publisher (cache → verify → produce → register cycle).\n\t\t\t\t// - `ChainProbe<SuiProbeKey>` is looked up via the\n\t\t\t\t// StrategyRegistry under `chain-probe:<chainId>`;\n\t\t\t\t// Sui registered itself there at its own acquire.\n\t\t\t\t// - `StackPathsService` resolves the per-stack on-disk\n\t\t\t\t// root so the deploy-output bind-mount source is a\n\t\t\t\t// real path (not the `<runtime>/...` template).\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\t\tconst path = yield* Path.Path;\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\n\t\t\t\t// Resolve the deploy-output bind-mount source from the\n\t\t\t\t// per-stack paths bundle. The deploy one-shot owns preparing\n\t\t\t\t// the directory immediately before its Docker bind mount.\n\t\t\t\tconst deployHostMountPath = path.join(\n\t\t\t\t\tstackPaths.stackRoot,\n\t\t\t\t\t'walrus',\n\t\t\t\t\tresolved.name,\n\t\t\t\t\t'deploy',\n\t\t\t\t);\n\n\t\t\t\t// Cross-container DNS: walrus containers (deploy one-shot\n\t\t\t\t// + N storage nodes) dial sui RPC + faucet via\n\t\t\t\t// `host.docker.internal`. The sui plugin binds brokered\n\t\t\t\t// host ports — no shared docker network needed.\n\t\t\t\t//\n\t\t\t\t// Architectural decision (B5): walrus owns its OWN docker\n\t\t\t\t// network for storage-node ↔ deploy connectivity; sui-side\n\t\t\t\t// hops go through the host gateway.\n\t\t\t\t// On Linux this requires Docker Desktop or the\n\t\t\t\t// `host.docker.internal:host-gateway` runtime hint (the\n\t\t\t\t// established devstack convention — see plugins/deepbook\n\t\t\t\t// which uses the same pattern).\n\t\t\t\tconst walrusNetworkName = buildWalrusNetworkName(\n\t\t\t\t\tidentity.app,\n\t\t\t\t\tidentity.stack,\n\t\t\t\t\tresolved.name,\n\t\t\t\t);\n\t\t\t\tconst walrusSubnetPrefix = deriveWalrusSubnetPrefix({\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t});\n\t\t\t\tconst walrusRuntime = withWalrusNetworkAddressing(\n\t\t\t\t\truntime,\n\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\twalrusSubnetPrefix,\n\t\t\t\t);\n\t\t\t\tconst suiRpcUrlInNetwork = sui.hostGateway.rpcUrl;\n\t\t\t\t// sui-faucet v2 endpoint — `/v2/gas` is the supported path\n\t\t\t\t// on devnet-v1.71.0+.\n\t\t\t\tconst suiFaucetUrlInNetwork = sui.hostGateway.faucetUrl;\n\t\t\t\tif (suiFaucetUrlInNetwork === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t\t'walrus local-cluster requires a Sui faucet URL for deploy funding.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst walrusFaucetUrlInNetwork = `${suiFaucetUrlInNetwork}/v2/gas`;\n\n\t\t\t\tconst mode: WalrusMode = { mode: 'local', opts: resolved };\n\t\t\t\tconst boot = yield* bootWalrusService(\n\t\t\t\t\t{\n\t\t\t\t\t\truntime: walrusRuntime,\n\t\t\t\t\t\tpublisher,\n\t\t\t\t\t\tprobe,\n\t\t\t\t\t\tsuiSdk: sui.sdk,\n\t\t\t\t\t\tsuiChainId: sui.chainId,\n\t\t\t\t\t\tsuiRpcUrlInNetwork,\n\t\t\t\t\t\twalrusFaucetUrlInNetwork,\n\t\t\t\t\t\twaitForFundsReady: sui.waitForTransactionsReady.wait,\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tsubnetPrefix: walrusSubnetPrefix,\n\t\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\t\t// Walrus has no `sui-net` to attach to (sui binds\n\t\t\t\t\t\t// host ports). Reuse the walrus network so the\n\t\t\t\t\t\t// deploy one-shot + storage nodes land on a single\n\t\t\t\t\t\t// network and reach sui via `host.docker.internal`.\n\t\t\t\t\t\tsuiNetworkName: walrusNetworkName,\n\t\t\t\t\t\tdeployHostMountPath,\n\t\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\t},\n\t\t\t\t\tmode,\n\t\t\t\t);\n\n\t\t\t\tif (boot.mode !== 'local') {\n\t\t\t\t\t// Should be unreachable — dispatch is by mode. Defense.\n\t\t\t\t\treturn yield* Effect.die('walrus: mode mismatch in local plugin');\n\t\t\t\t}\n\n\t\t\t\tconst resolvedValue: WalrusResolved = {\n\t\t\t\t\tmode: 'local',\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\twalrusPackageId: boot.walrusPackageId,\n\t\t\t\t\twalPackageId: boot.walPackageId,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: boot.deploy.systemObject,\n\t\t\t\t\t\tstakingPoolId: boot.deploy.stakingObject,\n\t\t\t\t\t\texchangeIds: boot.exchangeObjectId ? [boot.exchangeObjectId] : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnodes: boot.nodes,\n\t\t\t\t\tproxyUrl: boot.proxyUrl,\n\t\t\t\t\taggregatorUrl: boot.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: boot.publisherUrl,\n\t\t\t\t\twalFaucetStrategy: boot.walFaucetStrategy,\n\t\t\t\t\twalCoinType: boot.walCoinType,\n\t\t\t\t};\n\t\t\t\t// Emit the resolved contributions inline: snapshot → codegen →\n\t\t\t\t// state-registry → (optional WAL faucet) → routables. `identity`\n\t\t\t\t// (from `IdentityContext`) stamps the snapshot + routable\n\t\t\t\t// container names. ID-stability surfaces (deploy/blob ids via\n\t\t\t\t// ArtifactPublisher) live OUTSIDE this emission and are untouched.\n\t\t\t\tconst walFaucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t\t\tresolvedValue.walFaucetStrategy === null || resolvedValue.walCoinType === null\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\tmakeWalFaucetContribution(\n\t\t\t\t\t\t\t\t\tresolvedValue.walFaucetStrategy,\n\t\t\t\t\t\t\t\t\tresolvedValue.walCoinType,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t];\n\t\t\t\temitContributions(ctx, [\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'local' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\tresolved.name,\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t\tresolved.nodeCount,\n\t\t\t\t\t),\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'local',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\tstrategy: { noteName: resolved.name },\n\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\ttypeof WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\t{ readonly noteName: string }\n\t\t\t\t\t>,\n\t\t\t\t\t...walFaucetContribution,\n\t\t\t\t\t...(makeLocalRoutables({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t\t\tserviceKey: String(walrusKey),\n\t\t\t\t\t\tnodeCount: resolved.nodeCount,\n\t\t\t\t\t\tcontainerApiPort: resolved.containerApiPort,\n\t\t\t\t\t}) as readonly RoutableDecl[]),\n\t\t\t\t]);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nconst buildKnownPlugin = (opts: WalrusKnownDeploymentOptions) => {\n\t// Synchronous factory-time validation for required deployment ids.\n\tconst resolved = resolveKnownDeploymentOptions(opts);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\t// Known deployment is a pure value-producer — no containers,\n\t\t// no long-running children.\n\t\trole: 'task',\n\t\tsection: 'service',\n\t\t// Stack-free codegen: a known deployment's ids / URLs are DECLARED\n\t\t// config (not loaded-at-runtime data) — bake them as literals in the\n\t\t// committed `walrus.ts` (mirrors `knownPackage`). `walrusPackageId` /\n\t\t// `walPackageId` / `walCoinType` are null for a known deployment.\n\t\tstaticCodegen: () => [\n\t\t\tmakeWalrusStaticCodegen({\n\t\t\t\tmode: 'known',\n\t\t\t\tnetwork: resolved.network,\n\t\t\t\tknown: {\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\t...(resolved.exchangeIds.length > 0 ? { exchangeIds: [...resolved.exchangeIds] } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t\t// Known mode is a pure value-producer — `sui` is unused (the value\n\t\t// reads `resolved.network` from the deployment options) but the\n\t\t// `dependsOn` edge still orders boot, so `start` is zero-arg.\n\t\t// `ctx` arrives via the `PluginContext` service.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst resolvedValue = {\n\t\t\t\t\tmode: 'known',\n\t\t\t\t\tnetwork: resolved.network,\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolved.exchangeIds.length > 0 ? resolved.exchangeIds : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\twalFaucetStrategy: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t} satisfies WalrusResolved;\n\t\t\t\t// Emit inline: snapshot → codegen → state-registry. Known mode\n\t\t\t\t// emits no routable. `identity` (from `IdentityContext`) stamps\n\t\t\t\t// the snapshot scoping.\n\t\t\t\tctx.snapshotExtra(\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'known' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\t'walrusKnownDeployment',\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tctx.codegen(\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'known',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tctx.provides({\n\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\tstrategy: {\n\t\t\t\t\t\tname: 'walrusKnownDeployment',\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingObjectId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t},\n\t\t\t\t\tautoMounted: true,\n\t\t\t\t} satisfies StrategyContributorDecl<typeof WALRUS_STATE_REGISTRY_KEY, WalrusStateEntry>);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nexport interface WalCoinValue extends AccountFundingCoinValue {\n\treadonly symbol: 'WAL';\n\treadonly fullCoinType: `${string}::wal::WAL`;\n\treadonly decimals: 9;\n\treadonly source: 'walrus';\n}\n\n/** Resolve the local Walrus deployment's WAL coin as an account-funding\n * coin ref. Accounts that need WAL should use:\n *\n * funding: [{ coin: walCoin(localWalrus), amount }]\n *\n * The funding strategy itself is contributed by the Walrus service\n * once its local exchange exists. */\nexport const walCoin = (walrusMember: ResourceRef<'walrus', WalrusResolved>) => {\n\tconst coinRef = resource<CoinResourceId<'wal'>, WalCoinValue>(coinResourceId('wal'));\n\n\treturn definePlugin({\n\t\tid: coinRef.id,\n\t\tdependsOn: walrusMember,\n\t\trole: 'task',\n\t\tsection: 'action',\n\t\tstart: (resolved): Effect.Effect<WalCoinValue, WalrusPluginError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tif (resolved.walCoinType === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'exchange',\n\t\t\t\t\t\t\t'walCoin(...) requires a local Walrus deployment with a WAL package.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tsymbol: 'WAL',\n\t\t\t\t\tfullCoinType: resolved.walCoinType as `${string}::wal::WAL`,\n\t\t\t\t\tdecimals: 9,\n\t\t\t\t\tsource: 'walrus',\n\t\t\t\t} satisfies WalCoinValue;\n\t\t\t}),\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local-cluster shorthand. Known deployments are selected through\n * `walrusFor(network).known(...)` so network choice stays explicit. */\nexport const walrus = (opts?: { readonly local?: WalrusLocalClusterOptions }) => {\n\treturn buildLocalPlugin(opts?.local ?? {});\n};\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local', network: 'localnet' } as const;\n * walrusFor(network).local({...}) // OK\n * walrusFor(network).known({...}) // type error: 'known' not in 'local' branch\n *\n * Critically, the fork branch exposes ONLY `.known` — calling\n * `.local` on a fork-mode network is a **compile error** at the\n * call site (distilled-doc invariant 12 — type-level refusal). */\nexport const walrusFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: (opts: WalrusLocalClusterOptions = {}) => buildLocalPlugin(opts),\n\t\tknown: (opts: WalrusKnownDeploymentOptions) => buildKnownPlugin(opts),\n\t},\n\tlive: {\n\t\tknown: (opts: WalrusKnownDeploymentOptions) => buildKnownPlugin(opts),\n\t},\n\tfork: {\n\t\t// `.local` is intentionally absent — calling\n\t\t// `walrusFor(forkNetwork).local(...)` is a compile error.\n\t\tknown: (opts: WalrusKnownDeploymentOptions) => buildKnownPlugin(opts),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers\n// ---------------------------------------------------------------------------\n\nexport type { WalrusLocalClusterOptions } from './mode/local-cluster.ts';\nexport type { WalrusKnownDeploymentOptions, WalrusKnownNetwork } from './mode/known-deploy.ts';\nexport type { WalrusStorageNode } from './storage-nodes.ts';\nexport type { WalrusBindings, WalrusNodeBinding } from './codegen.ts';\nexport type { WalrusError, WalrusPluginError, WalrusConfigError, WalrusPhase } from './errors.ts';\nexport { walCoinType, walFaucetStrategyKey, type WalFaucetStrategy } from './faucet-strategy.ts';\nexport {\n\tWALRUS_STATE_REGISTRY_KEY,\n\ttype WalrusStateEntry,\n\ttype WalrusLocalStateEntry,\n\ttype WalrusKnownStateEntry,\n} from './registry-publish.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/walrus/index.ts"],"sourcesContent":["// Walrus plugin — barrel + factories.\n//\n// Architecture: Walrus is a service plugin that owns a local cluster\n// or describes a known deployment. The factory at this file folds the\n// four modes behind:\n//\n// - `walrus(opts?)` — local-cluster shorthand. No env\n// defaulting; use `walrusFor(...)`\n// for known deployments.\n// - `walrusFor(network)` — mode-narrowed factory namespace\n// (architecture Tension 11). Returns\n// `{ local: …, known: … }` narrowed\n// to the network's mode. Crucially,\n// the fork branch exposes ONLY\n// `.known` — `.local` is a compile\n// error on a fork-mode network.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs, per mode):\n//\n// Local mode (full local cluster):\n// 1. `ctx.snapshotExtra` — runtime/walrus/<name>/deploy/ subtree\n// + storage-node managed containers.\n// 2. `ctx.codegen` — `walrus-network` bindings.\n// 3. `ctx.endpoint` × (N+2) — per-node + aggregator + publisher.\n// 4. `ctx.provides` walrus-state-registry — local entry.\n// 5. `ctx.provides` endpoint-registry — N+2 entries.\n// 6. `ctx.provides` package-registry — `walrus.<name>`.\n// 7. `ctx.provides` coinType:<WAL fullCoinType> — WAL faucet\n// strategy\n// (when exchange exists).\n//\n// Known mode (read-only deployment):\n// 1. `ctx.snapshotExtra` — identity-guard only; no subtrees.\n// 2. `ctx.codegen` — `walrus-network` bindings (mode='known').\n// 3. `ctx.provides` walrus-state-registry — known entry.\n//\n// Resource id: `'walrus'` (singular). The plugin's substrate-level\n// plugin key is the same string.\n\nimport { Effect, Path } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, type ResourceRef } from '../../api/define-plugin.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport type { RoutableDecl } from '../../contracts/routable.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { deriveSubnetPrefix, withSubnetAddressing } from '../../substrate/runtime/subnet-broker.ts';\nimport type { AccountFundingCoinValue } from '../account/index.ts';\nimport { coinResourceId, type CoinResourceId } from '../coin/index.ts';\nimport { suiResource, type SuiProbeKey } from '../sui/index.ts';\n\nimport { chainProbeFor } from '../../substrate/runtime/strategy-registry/index.ts';\n\nimport { makeCodegenable, makeWalrusStaticCodegen } from './codegen.ts';\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport { walrusPluginKey } from './plugin-key.ts';\nimport { walrusPluginError, type WalrusPluginError } from './errors.ts';\nimport { makeWalFaucetContribution, type WalFaucetStrategy } from './faucet-strategy.ts';\nimport { bootWalrusService, type WalrusMode } from './service.ts';\nimport {\n\tresolveLocalClusterOptions,\n\ttype WalrusLocalClusterOptions,\n} from './mode/local-cluster.ts';\nimport {\n\tresolveKnownDeploymentOptions,\n\ttype WalrusKnownDeploymentOptions,\n} from './mode/known-deploy.ts';\nimport { makeSnapshotable, type WalrusSnapshotMode } from './snapshot.ts';\nimport { makeLocalRoutables } from './routable.ts';\nimport { WALRUS_STATE_REGISTRY_KEY, type WalrusStateEntry } from './registry-publish.ts';\nimport { buildWalrusNetworkName, type WalrusStorageNode } from './storage-nodes.ts';\n\n// ---------------------------------------------------------------------------\n// Resource — the resolved value all consumers read\n// ---------------------------------------------------------------------------\n\n/** The Walrus resolved value carried by the resource. */\nexport interface WalrusResolved {\n\treadonly mode: 'local' | 'known';\n\t/** Network name the walrus deployment targets (`localnet`/`testnet`/…). */\n\treadonly network: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\t/** SDK-ready `packageConfig` — structurally compatible with\n\t * `@mysten/walrus`'s `WalrusPackageConfig`. */\n\treadonly packageConfig: {\n\t\treadonly systemObjectId: string;\n\t\treadonly stakingPoolId: string;\n\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t};\n\treadonly nodes: ReadonlyArray<WalrusStorageNode>;\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly walFaucetStrategy: WalFaucetStrategy | null;\n\treadonly walCoinType: string | null;\n}\n\n/** Walrus plugin resource. */\nexport const walrusResource = resource<'walrus', WalrusResolved>('walrus');\n\nexport interface WalrusNetworkIdentity {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly walrusName: string;\n}\n\n/** Walrus deploy records storage-node listening IPs under this /24.\n * Docker network create requests the matching subnet explicitly, with\n * the prefix derived from the Walrus network identity so parallel\n * stacks don't all claim the same Docker IPAM range. Walrus claims\n * the `10.64.*` – `10.127.*` band; see substrate/runtime/subnet-broker.ts\n * for the algorithm and band coordination with seal. */\nexport const deriveWalrusSubnetPrefix = (identity: WalrusNetworkIdentity): string =>\n\tderiveSubnetPrefix(`${identity.app}\\0${identity.stack}\\0${identity.walrusName}`, 64);\n\nconst withWalrusNetworkAddressing = (\n\truntime: ContainerRuntime,\n\twalrusNetworkName: string,\n\tsubnetPrefix: string,\n): ContainerRuntime => ({\n\t...runtime,\n\tensureNetwork: (spec) =>\n\t\truntime.ensureNetwork(\n\t\t\tspec.name === walrusNetworkName ? withSubnetAddressing(spec, subnetPrefix) : spec,\n\t\t),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by walrus() + walrusFor())\n// ---------------------------------------------------------------------------\n\nconst buildLocalPlugin = (opts: WalrusLocalClusterOptions) => {\n\t// Synchronous factory-time validation (distilled-doc invariants\n\t// 11 — `nodeCount >= 1` + `shards >= nodeCount`).\n\tconst resolved = resolveLocalClusterOptions(opts);\n\n\tconst walrusKey = walrusPluginKey(resolved.name);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tpluginKey: walrusKey,\n\t\t// Stack-free codegen: a local walrus cluster's deploy ids / endpoint\n\t\t// URLs are LOADED CONFIG DATA -- the committed `walrus.ts` stub emits\n\t\t// `requireValue(dep, 'walrus', '<key>')`, never a baked object id / URL.\n\t\tstaticCodegen: () => [makeWalrusStaticCodegen({ mode: 'local', network: LOCAL_NETWORK_NAME })],\n\t\t// `deps` auto-infers the resolved `[sui]` tuple from the\n\t\t// `[suiResource] as const` dependency. `ctx` arrives via the\n\t\t// `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst [sui] = deps;\n\n\t\t\t\t// Substrate-context primitives:\n\t\t\t\t// - `ContainerRuntimeService` + `IdentityContext` arrive\n\t\t\t\t// via the supervisor's plugin runtime context.\n\t\t\t\t// - `ArtifactPublisher` is the substrate-level\n\t\t\t\t// publisher (cache → verify → produce → register cycle).\n\t\t\t\t// - `ChainProbe<SuiProbeKey>` is looked up via the\n\t\t\t\t// StrategyRegistry under `chain-probe:<chainId>`;\n\t\t\t\t// Sui registered itself there at its own acquire.\n\t\t\t\t// - `StackPathsService` resolves the per-stack on-disk\n\t\t\t\t// root so the deploy-output bind-mount source is a\n\t\t\t\t// real path (not the `<runtime>/...` template).\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\t\tconst path = yield* Path.Path;\n\t\t\t\tconst publisher = yield* CacheService;\n\t\t\t\tconst probe = yield* chainProbeFor<SuiProbeKey>(sui.chainId);\n\n\t\t\t\t// Resolve the deploy-output bind-mount source from the\n\t\t\t\t// per-stack paths bundle. The deploy one-shot owns preparing\n\t\t\t\t// the directory immediately before its Docker bind mount.\n\t\t\t\tconst deployHostMountPath = path.join(\n\t\t\t\t\tstackPaths.stackRoot,\n\t\t\t\t\t'walrus',\n\t\t\t\t\tresolved.name,\n\t\t\t\t\t'deploy',\n\t\t\t\t);\n\n\t\t\t\t// Cross-container DNS: walrus containers (deploy one-shot\n\t\t\t\t// + N storage nodes) dial sui RPC + faucet via\n\t\t\t\t// `host.docker.internal`. The sui plugin binds brokered\n\t\t\t\t// host ports — no shared docker network needed.\n\t\t\t\t//\n\t\t\t\t// Architectural decision (B5): walrus owns its OWN docker\n\t\t\t\t// network for storage-node ↔ deploy connectivity; sui-side\n\t\t\t\t// hops go through the host gateway.\n\t\t\t\t// On Linux this requires Docker Desktop or the\n\t\t\t\t// `host.docker.internal:host-gateway` runtime hint (the\n\t\t\t\t// established devstack convention — see plugins/deepbook\n\t\t\t\t// which uses the same pattern).\n\t\t\t\tconst walrusNetworkName = buildWalrusNetworkName(\n\t\t\t\t\tidentity.app,\n\t\t\t\t\tidentity.stack,\n\t\t\t\t\tresolved.name,\n\t\t\t\t);\n\t\t\t\tconst walrusSubnetPrefix = deriveWalrusSubnetPrefix({\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t});\n\t\t\t\tconst walrusRuntime = withWalrusNetworkAddressing(\n\t\t\t\t\truntime,\n\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\twalrusSubnetPrefix,\n\t\t\t\t);\n\t\t\t\tconst suiRpcUrlInNetwork = sui.hostGateway.rpcUrl;\n\t\t\t\t// sui-faucet v2 endpoint — `/v2/gas` is the supported path\n\t\t\t\t// on devnet-v1.71.0+.\n\t\t\t\tconst suiFaucetUrlInNetwork = sui.hostGateway.faucetUrl;\n\t\t\t\tif (suiFaucetUrlInNetwork === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'deploy',\n\t\t\t\t\t\t\t'walrus local-cluster requires a Sui faucet URL for deploy funding.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst walrusFaucetUrlInNetwork = `${suiFaucetUrlInNetwork}/v2/gas`;\n\n\t\t\t\tconst mode: WalrusMode = { mode: 'local', opts: resolved };\n\t\t\t\tconst boot = yield* bootWalrusService(\n\t\t\t\t\t{\n\t\t\t\t\t\truntime: walrusRuntime,\n\t\t\t\t\t\tpublisher,\n\t\t\t\t\t\tprobe,\n\t\t\t\t\t\tsuiSdk: sui.sdk,\n\t\t\t\t\t\tsuiChainId: sui.chainId,\n\t\t\t\t\t\tsuiRpcUrlInNetwork,\n\t\t\t\t\t\twalrusFaucetUrlInNetwork,\n\t\t\t\t\t\twaitForFundsReady: sui.waitForTransactionsReady.wait,\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tsubnetPrefix: walrusSubnetPrefix,\n\t\t\t\t\t\twalrusNetworkName,\n\t\t\t\t\t\t// Walrus has no `sui-net` to attach to (sui binds\n\t\t\t\t\t\t// host ports). Reuse the walrus network so the\n\t\t\t\t\t\t// deploy one-shot + storage nodes land on a single\n\t\t\t\t\t\t// network and reach sui via `host.docker.internal`.\n\t\t\t\t\t\tsuiNetworkName: walrusNetworkName,\n\t\t\t\t\t\tdeployHostMountPath,\n\t\t\t\t\t\tstackRoot: stackPaths.stackRoot,\n\t\t\t\t\t},\n\t\t\t\t\tmode,\n\t\t\t\t);\n\n\t\t\t\tif (boot.mode !== 'local') {\n\t\t\t\t\t// Should be unreachable — dispatch is by mode. Defense.\n\t\t\t\t\treturn yield* Effect.die('walrus: mode mismatch in local plugin');\n\t\t\t\t}\n\n\t\t\t\tconst resolvedValue: WalrusResolved = {\n\t\t\t\t\tmode: 'local',\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\twalrusPackageId: boot.walrusPackageId,\n\t\t\t\t\twalPackageId: boot.walPackageId,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: boot.deploy.systemObject,\n\t\t\t\t\t\tstakingPoolId: boot.deploy.stakingObject,\n\t\t\t\t\t\texchangeIds: boot.exchangeObjectId ? [boot.exchangeObjectId] : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnodes: boot.nodes,\n\t\t\t\t\tproxyUrl: boot.proxyUrl,\n\t\t\t\t\taggregatorUrl: boot.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: boot.publisherUrl,\n\t\t\t\t\twalFaucetStrategy: boot.walFaucetStrategy,\n\t\t\t\t\twalCoinType: boot.walCoinType,\n\t\t\t\t};\n\t\t\t\t// Emit the resolved contributions inline: snapshot → codegen →\n\t\t\t\t// state-registry → (optional WAL faucet) → routables. `identity`\n\t\t\t\t// (from `IdentityContext`) stamps the snapshot + routable\n\t\t\t\t// container names. ID-stability surfaces (deploy/blob ids via\n\t\t\t\t// ArtifactPublisher) live OUTSIDE this emission and are untouched.\n\t\t\t\tconst walFaucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t\t\tresolvedValue.walFaucetStrategy === null || resolvedValue.walCoinType === null\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\tmakeWalFaucetContribution(\n\t\t\t\t\t\t\t\t\tresolvedValue.walFaucetStrategy,\n\t\t\t\t\t\t\t\t\tresolvedValue.walCoinType,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t];\n\t\t\t\temitContributions(ctx, [\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'local' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\tresolved.name,\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t\tresolved.nodeCount,\n\t\t\t\t\t),\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'local',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\tstrategy: { noteName: resolved.name },\n\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\ttypeof WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\t\t{ readonly noteName: string }\n\t\t\t\t\t>,\n\t\t\t\t\t...walFaucetContribution,\n\t\t\t\t\t...(makeLocalRoutables({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\twalrusName: resolved.name,\n\t\t\t\t\t\tserviceKey: String(walrusKey),\n\t\t\t\t\t\tnodeCount: resolved.nodeCount,\n\t\t\t\t\t\tcontainerApiPort: resolved.containerApiPort,\n\t\t\t\t\t}) as readonly RoutableDecl[]),\n\t\t\t\t]);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nconst buildKnownPlugin = (opts: WalrusKnownDeploymentOptions) => {\n\t// Synchronous factory-time validation for required deployment ids.\n\tconst resolved = resolveKnownDeploymentOptions(opts);\n\n\treturn definePlugin({\n\t\tid: walrusResource.id,\n\t\tdependsOn: [suiResource] as const,\n\t\t// Known deployment is a pure value-producer — no containers,\n\t\t// no long-running children.\n\t\trole: 'task',\n\t\tsection: 'service',\n\t\t// Stack-free codegen: a known deployment's ids / URLs are DECLARED\n\t\t// config (not loaded-at-runtime data) — bake them as literals in the\n\t\t// committed `walrus.ts` (mirrors `knownPackage`). `walrusPackageId` /\n\t\t// `walPackageId` / `walCoinType` are null for a known deployment.\n\t\tstaticCodegen: () => [\n\t\t\tmakeWalrusStaticCodegen({\n\t\t\t\tmode: 'known',\n\t\t\t\tnetwork: resolved.network,\n\t\t\t\tknown: {\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\t...(resolved.exchangeIds.length > 0 ? { exchangeIds: [...resolved.exchangeIds] } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t\t// Known mode is a pure value-producer — `sui` is unused (the value\n\t\t// reads `resolved.network` from the deployment options) but the\n\t\t// `dependsOn` edge still orders boot, so `start` is zero-arg.\n\t\t// `ctx` arrives via the `PluginContext` service.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst resolvedValue = {\n\t\t\t\t\tmode: 'known',\n\t\t\t\t\tnetwork: resolved.network,\n\t\t\t\t\twalrusPackageId: null,\n\t\t\t\t\twalPackageId: null,\n\t\t\t\t\tpackageConfig: {\n\t\t\t\t\t\tsystemObjectId: resolved.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolved.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolved.exchangeIds.length > 0 ? resolved.exchangeIds : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnodes: resolved.nodes,\n\t\t\t\t\tproxyUrl: resolved.proxyUrl,\n\t\t\t\t\taggregatorUrl: resolved.aggregatorUrl,\n\t\t\t\t\tpublisherUrl: resolved.publisherUrl,\n\t\t\t\t\twalFaucetStrategy: null,\n\t\t\t\t\twalCoinType: null,\n\t\t\t\t} satisfies WalrusResolved;\n\t\t\t\t// Emit inline: snapshot → codegen → state-registry. Known mode\n\t\t\t\t// emits no routable. `identity` (from `IdentityContext`) stamps\n\t\t\t\t// the snapshot scoping.\n\t\t\t\tctx.snapshotExtra(\n\t\t\t\t\tmakeSnapshotable(\n\t\t\t\t\t\t'known' satisfies WalrusSnapshotMode,\n\t\t\t\t\t\tidentity.app,\n\t\t\t\t\t\tidentity.stack,\n\t\t\t\t\t\t'walrusKnownDeployment',\n\t\t\t\t\t\tresolvedValue.network,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tctx.codegen(\n\t\t\t\t\tmakeCodegenable({\n\t\t\t\t\t\tmode: 'known',\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t\twalrusPackageId: resolvedValue.walrusPackageId,\n\t\t\t\t\t\twalPackageId: resolvedValue.walPackageId,\n\t\t\t\t\t\twalCoinType: resolvedValue.walCoinType,\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingPoolId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\texchangeIds: resolvedValue.packageConfig.exchangeIds\n\t\t\t\t\t\t\t? [...resolvedValue.packageConfig.exchangeIds]\n\t\t\t\t\t\t\t: [],\n\t\t\t\t\t\tproxyUrl: resolvedValue.proxyUrl,\n\t\t\t\t\t\taggregatorUrl: resolvedValue.aggregatorUrl,\n\t\t\t\t\t\tpublisherUrl: resolvedValue.publisherUrl,\n\t\t\t\t\t\tnodes: resolvedValue.nodes,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tctx.provides({\n\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\tcapabilityKey: WALRUS_STATE_REGISTRY_KEY,\n\t\t\t\t\tstrategy: {\n\t\t\t\t\t\tname: 'walrusKnownDeployment',\n\t\t\t\t\t\tsystemObjectId: resolvedValue.packageConfig.systemObjectId,\n\t\t\t\t\t\tstakingObjectId: resolvedValue.packageConfig.stakingPoolId,\n\t\t\t\t\t\tnetwork: resolvedValue.network,\n\t\t\t\t\t},\n\t\t\t\t\tautoMounted: true,\n\t\t\t\t} satisfies StrategyContributorDecl<typeof WALRUS_STATE_REGISTRY_KEY, WalrusStateEntry>);\n\t\t\t\treturn resolvedValue;\n\t\t\t}),\n\t});\n};\n\nexport interface WalCoinValue extends AccountFundingCoinValue {\n\treadonly symbol: 'WAL';\n\treadonly fullCoinType: `${string}::wal::WAL`;\n\treadonly decimals: 9;\n\treadonly source: 'walrus';\n}\n\n/** Resolve the local Walrus deployment's WAL coin as an account-funding\n * coin ref. Accounts that need WAL should use:\n *\n * funding: [{ coin: walCoin(localWalrus), amount }]\n *\n * The funding strategy itself is contributed by the Walrus service\n * once its local exchange exists. */\nexport const walCoin = (walrusMember: ResourceRef<'walrus', WalrusResolved>) => {\n\tconst coinRef = resource<CoinResourceId<'wal'>, WalCoinValue>(coinResourceId('wal'));\n\n\treturn definePlugin({\n\t\tid: coinRef.id,\n\t\tdependsOn: walrusMember,\n\t\trole: 'task',\n\t\tsection: 'action',\n\t\tstart: (resolved): Effect.Effect<WalCoinValue, WalrusPluginError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tif (resolved.walCoinType === null) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t\t'exchange',\n\t\t\t\t\t\t\t'walCoin(...) requires a local Walrus deployment with a WAL package.',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tsymbol: 'WAL',\n\t\t\t\t\tfullCoinType: resolved.walCoinType as `${string}::wal::WAL`,\n\t\t\t\t\tdecimals: 9,\n\t\t\t\t\tsource: 'walrus',\n\t\t\t\t} satisfies WalCoinValue;\n\t\t\t}),\n\t});\n};\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local-cluster shorthand. Known deployments are selected through\n * `walrusFor(network).known(...)` so network choice stays explicit. */\nexport const walrus = (opts?: { readonly local?: WalrusLocalClusterOptions }) => {\n\treturn buildLocalPlugin(opts?.local ?? {});\n};\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local', network: 'localnet' } as const;\n * walrusFor(network).local({...}) // OK\n * walrusFor(network).known({...}) // type error: 'known' not in 'local' branch\n *\n * Critically, the fork branch exposes ONLY `.known` — calling\n * `.local` on a fork-mode network is a **compile error** at the\n * call site (distilled-doc invariant 12 — type-level refusal). */\nexport const walrusFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: (opts: WalrusLocalClusterOptions = {}) => buildLocalPlugin(opts),\n\t\tknown: (opts: WalrusKnownDeploymentOptions) => buildKnownPlugin(opts),\n\t},\n\tlive: {\n\t\tknown: (opts: WalrusKnownDeploymentOptions) => buildKnownPlugin(opts),\n\t},\n\tfork: {\n\t\t// `.local` is intentionally absent — calling\n\t\t// `walrusFor(forkNetwork).local(...)` is a compile error.\n\t\tknown: (opts: WalrusKnownDeploymentOptions) => buildKnownPlugin(opts),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers\n// ---------------------------------------------------------------------------\n\nexport type { WalrusLocalClusterOptions } from './mode/local-cluster.ts';\nexport type { WalrusKnownDeploymentOptions, WalrusKnownNetwork } from './mode/known-deploy.ts';\nexport type { WalrusStorageNode } from './storage-nodes.ts';\nexport type { WalrusBindings, WalrusNodeBinding } from './codegen.ts';\nexport type { WalrusError, WalrusPluginError, WalrusConfigError, WalrusPhase } from './errors.ts';\nexport { walCoinType, walFaucetStrategyKey, type WalFaucetStrategy } from './faucet-strategy.ts';\nexport {\n\tWALRUS_STATE_REGISTRY_KEY,\n\ttype WalrusStateEntry,\n\ttype WalrusLocalStateEntry,\n\ttype WalrusKnownStateEntry,\n} from './registry-publish.ts';\n"],"mappings":"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"}
|
|
@@ -23,27 +23,23 @@ declare const ManifestExtrasLookupError_base: Schema.Class<ManifestExtrasLookupE
|
|
|
23
23
|
* classify via `catchTag('ManifestExtrasLookupError', ...)` rather
|
|
24
24
|
* than die-cause inspection. */
|
|
25
25
|
declare class ManifestExtrasLookupError extends ManifestExtrasLookupError_base {}
|
|
26
|
-
/** Codegen metadata recorded per stack. The supervisor writes the
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
26
|
+
/** Codegen metadata recorded per stack. The supervisor writes the live
|
|
27
|
+
* `deploymentFile` (the gitignored `deployment.json` injected as
|
|
28
|
+
* `__DEVSTACK_DEPLOYMENT__`) here at manifest-flush time so the read-side
|
|
29
|
+
* build integrations (the Vite plugin) consult the exact location the
|
|
30
|
+
* boot wrote — read and write share one decision. The dev-wallet
|
|
31
|
+
* connection + dev accounts ride the deployment envelope itself
|
|
32
|
+
* (`values['dev-wallet']` / `accounts`), so no separate dev tree is
|
|
33
|
+
* recorded. Bindings are NOT recorded either: the `@generated` alias
|
|
34
|
+
* always resolves to the committed `src/generated` tree written by the
|
|
35
|
+
* stack-free `codegen` verb. Optional + additive: manifests written
|
|
36
|
+
* before this field existed still decode; consumers fall back to their
|
|
37
|
+
* cold-start path. */
|
|
36
38
|
interface ManifestCodegen {
|
|
37
|
-
/** Absolute path to the
|
|
38
|
-
* for this stack (`<appRoot>/.devstack/stacks/<stack>/generated-extras`).
|
|
39
|
-
* The `@devstack-dev` Vite alias resolves here. Optional +
|
|
40
|
-
* additive — older manifests omit it; the reader falls back to
|
|
41
|
-
* `.devstack/stacks/<stack>/generated-extras`. */
|
|
42
|
-
readonly extrasDir?: string;
|
|
43
|
-
/** Absolute path to the gitignored `devstack-ids.json` the boot wrote
|
|
39
|
+
/** Absolute path to the gitignored `deployment.json` the boot wrote
|
|
44
40
|
* for this stack (the live on-chain ids). The Vite plugin reads it to
|
|
45
|
-
* inject `
|
|
46
|
-
readonly
|
|
41
|
+
* inject `__DEVSTACK_DEPLOYMENT__` in dev. Optional + additive. */
|
|
42
|
+
readonly deploymentFile?: string;
|
|
47
43
|
}
|
|
48
44
|
/** Manifest envelope. */
|
|
49
45
|
interface ManifestEnvelope {
|
|
@@ -98,10 +94,9 @@ declare const ManifestEnvelopeSchema: Schema.Struct<{
|
|
|
98
94
|
}>>;
|
|
99
95
|
readonly extras: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
100
96
|
readonly codegen: Schema.optional<Schema.Struct<{
|
|
101
|
-
|
|
102
|
-
/** Absolute path to the gitignored `devstack-ids.json` the boot
|
|
97
|
+
/** Absolute path to the gitignored `deployment.json` the boot
|
|
103
98
|
* wrote for this stack. The Vite plugin reads it in dev. */
|
|
104
|
-
readonly
|
|
99
|
+
readonly deploymentFile: Schema.optional<Schema.String>;
|
|
105
100
|
}>>;
|
|
106
101
|
}>;
|
|
107
102
|
//#endregion
|
|
@@ -54,12 +54,10 @@ const ManifestEnvelopeSchema = Schema.Struct({
|
|
|
54
54
|
endpointKey: Schema.String
|
|
55
55
|
})),
|
|
56
56
|
extras: Schema.Record(Schema.String, Schema.Unknown),
|
|
57
|
-
codegen: Schema.optional(Schema.Struct({
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
idsFile: Schema.optional(Schema.String)
|
|
62
|
-
}))
|
|
57
|
+
codegen: Schema.optional(Schema.Struct({
|
|
58
|
+
/** Absolute path to the gitignored `deployment.json` the boot
|
|
59
|
+
* wrote for this stack. The Vite plugin reads it in dev. */
|
|
60
|
+
deploymentFile: Schema.optional(Schema.String) }))
|
|
63
61
|
});
|
|
64
62
|
//#endregion
|
|
65
63
|
export { ManifestEnvelopeSchema, ManifestExtrasInvalid, ManifestExtrasLookupError, resolveManifestExtras };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.mjs","names":[],"sources":["../../src/substrate/manifest.ts"],"sourcesContent":["// Manifest envelope schema.\n//\n// Architecture § Manifest data model: envelope shape is fixed at L0,\n// per-service projection is each plugin's responsibility (lives in\n// the Codegenable contribution). Build integrations read the\n// envelope; codegen output carries the typed per-service slice.\n//\n// L0 owns:\n// - the envelope (identity tuple, manifestVersion, endpoints lookup,\n// opaque extras),\n// - the endpoint-declaration shape (decl emitted by Routable; the\n// manifest writer at L3 walks them).\n\nimport { Effect, Schema } from 'effect';\n\n/** Tagged failure when a plugin's manifest-extras contribution does\n * not resolve to a plain record. STYLE_GUIDE §2.2 — substrate (L0)\n * errors use `Schema.TaggedErrorClass`; downstream classifiers\n * `catchTag('ManifestExtrasInvalid', ...)` instead of sniffing the\n * message. */\nexport class ManifestExtrasInvalid extends Schema.TaggedErrorClass<ManifestExtrasInvalid>()(\n\t'ManifestExtrasInvalid',\n\t{\n\t\tdetail: Schema.String,\n\t},\n) {}\n\n/** Tagged failure when a plugin's `extras` factory references a\n * resource the host cannot resolve — either the resource id is not\n * registered with the supervisor, or it has not produced a value\n * yet at the point the factory ran. Thrown synchronously from the\n * user-supplied `ctx.value(...)` closure; `resolveManifestExtras`\n * catches the throw and surfaces it as a typed failure so callers\n * classify via `catchTag('ManifestExtrasLookupError', ...)` rather\n * than die-cause inspection. */\nexport class ManifestExtrasLookupError extends Schema.TaggedErrorClass<ManifestExtrasLookupError>()(\n\t'ManifestExtrasLookupError',\n\t{\n\t\tkind: Schema.Literals(['unknown-resource', 'unresolved-resource']),\n\t\tresourceId: Schema.String,\n\t},\n) {}\n\n/** Codegen metadata recorded per stack. The supervisor writes the
|
|
1
|
+
{"version":3,"file":"manifest.mjs","names":[],"sources":["../../src/substrate/manifest.ts"],"sourcesContent":["// Manifest envelope schema.\n//\n// Architecture § Manifest data model: envelope shape is fixed at L0,\n// per-service projection is each plugin's responsibility (lives in\n// the Codegenable contribution). Build integrations read the\n// envelope; codegen output carries the typed per-service slice.\n//\n// L0 owns:\n// - the envelope (identity tuple, manifestVersion, endpoints lookup,\n// opaque extras),\n// - the endpoint-declaration shape (decl emitted by Routable; the\n// manifest writer at L3 walks them).\n\nimport { Effect, Schema } from 'effect';\n\n/** Tagged failure when a plugin's manifest-extras contribution does\n * not resolve to a plain record. STYLE_GUIDE §2.2 — substrate (L0)\n * errors use `Schema.TaggedErrorClass`; downstream classifiers\n * `catchTag('ManifestExtrasInvalid', ...)` instead of sniffing the\n * message. */\nexport class ManifestExtrasInvalid extends Schema.TaggedErrorClass<ManifestExtrasInvalid>()(\n\t'ManifestExtrasInvalid',\n\t{\n\t\tdetail: Schema.String,\n\t},\n) {}\n\n/** Tagged failure when a plugin's `extras` factory references a\n * resource the host cannot resolve — either the resource id is not\n * registered with the supervisor, or it has not produced a value\n * yet at the point the factory ran. Thrown synchronously from the\n * user-supplied `ctx.value(...)` closure; `resolveManifestExtras`\n * catches the throw and surfaces it as a typed failure so callers\n * classify via `catchTag('ManifestExtrasLookupError', ...)` rather\n * than die-cause inspection. */\nexport class ManifestExtrasLookupError extends Schema.TaggedErrorClass<ManifestExtrasLookupError>()(\n\t'ManifestExtrasLookupError',\n\t{\n\t\tkind: Schema.Literals(['unknown-resource', 'unresolved-resource']),\n\t\tresourceId: Schema.String,\n\t},\n) {}\n\n/** Codegen metadata recorded per stack. The supervisor writes the live\n * `deploymentFile` (the gitignored `deployment.json` injected as\n * `__DEVSTACK_DEPLOYMENT__`) here at manifest-flush time so the read-side\n * build integrations (the Vite plugin) consult the exact location the\n * boot wrote — read and write share one decision. The dev-wallet\n * connection + dev accounts ride the deployment envelope itself\n * (`values['dev-wallet']` / `accounts`), so no separate dev tree is\n * recorded. Bindings are NOT recorded either: the `@generated` alias\n * always resolves to the committed `src/generated` tree written by the\n * stack-free `codegen` verb. Optional + additive: manifests written\n * before this field existed still decode; consumers fall back to their\n * cold-start path. */\nexport interface ManifestCodegen {\n\t/** Absolute path to the gitignored `deployment.json` the boot wrote\n\t * for this stack (the live on-chain ids). The Vite plugin reads it to\n\t * inject `__DEVSTACK_DEPLOYMENT__` in dev. Optional + additive. */\n\treadonly deploymentFile?: string;\n}\n\n/** Manifest envelope. */\nexport interface ManifestEnvelope {\n\treadonly identity: {\n\t\treadonly app: string;\n\t\treadonly stack: string;\n\t\treadonly network: string;\n\t};\n\treadonly manifestVersion: number;\n\t/** Optional per-plugin service slot. The read-side build-integration API\n\t * surfaces `{}` when absent. */\n\treadonly services?: Readonly<Record<string, unknown>>;\n\treadonly endpoints: Readonly<Record<string, EndpointEntry>>;\n\treadonly extras: Readonly<Record<string, unknown>>;\n\t/** Per-stack codegen metadata. When absent, readers use `src/generated/`. */\n\treadonly codegen?: ManifestCodegen;\n}\n\nexport type ManifestExtras = Readonly<Record<string, unknown>>;\n\nexport interface ManifestExtrasContext {\n\treadonly value: (resource: { readonly id: string }) => unknown;\n}\n\nexport type ManifestExtrasInput =\n\t| ManifestExtras\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t| Effect.Effect<ManifestExtras, any, never>\n\t| ((\n\t\t\tctx: ManifestExtrasContext,\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t ) => ManifestExtras | Effect.Effect<ManifestExtras, any, never>);\n\nconst isRecord = (value: unknown): value is ManifestExtras =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport const resolveManifestExtras = (\n\tinput: ManifestExtrasInput | undefined,\n\tctx: ManifestExtrasContext,\n): Effect.Effect<ManifestExtras, ManifestExtrasInvalid | ManifestExtrasLookupError, never> =>\n\tEffect.gen(function* () {\n\t\tif (input === undefined) return {};\n\t\t// The user-supplied `extras` factory invokes `ctx.value(...)`\n\t\t// synchronously; a missing/unresolved resource throws a\n\t\t// `ManifestExtrasLookupError`. `Effect.try` with a typed\n\t\t// `catch` mapper promotes that throw into the typed failure\n\t\t// channel so callers `catchTag` it instead of having to inspect\n\t\t// the die-cause. Any non-tagged throw is rethrown to preserve\n\t\t// the existing defect semantics for genuine programmer errors\n\t\t// inside the factory body.\n\t\tconst resolvedEffect = Effect.isEffect(input)\n\t\t\t? input\n\t\t\t: typeof input === 'function'\n\t\t\t\t? Effect.try({\n\t\t\t\t\t\ttry: () => input(ctx),\n\t\t\t\t\t\tcatch: (cause) => {\n\t\t\t\t\t\t\tif (cause instanceof ManifestExtrasLookupError) return cause;\n\t\t\t\t\t\t\tthrow cause;\n\t\t\t\t\t\t},\n\t\t\t\t\t}).pipe(\n\t\t\t\t\t\tEffect.flatMap((value) => (Effect.isEffect(value) ? value : Effect.succeed(value))),\n\t\t\t\t\t)\n\t\t\t\t: Effect.succeed(input);\n\t\tconst resolved = yield* resolvedEffect;\n\t\tif (!isRecord(resolved)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ManifestExtrasInvalid({\n\t\t\t\t\tdetail: 'manifest extras must resolve to a plain record',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn resolved;\n\t});\n\n/** Flat endpoint entry — the manifest's load-bearing surface for\n * build integrations. */\nexport interface EndpointEntry {\n\treadonly name: string;\n\treadonly url: string;\n\treadonly displayUrl: string | null;\n\treadonly wireProtocol: 'http' | 'h2c' | 'tcp';\n\treadonly pluginKey: string;\n\treadonly endpointKey: string;\n}\n\n/** Schema for runtime validation of an on-disk manifest. Build\n * integrations decode through this on read. */\nexport const ManifestEnvelopeSchema = Schema.Struct({\n\tidentity: Schema.Struct({\n\t\tapp: Schema.String,\n\t\tstack: Schema.String,\n\t\tnetwork: Schema.String,\n\t}),\n\tmanifestVersion: Schema.Number,\n\t// Optional service map for read-side integrations.\n\tservices: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),\n\tendpoints: Schema.Record(\n\t\tSchema.String,\n\t\tSchema.Struct({\n\t\t\tname: Schema.String,\n\t\t\turl: Schema.String,\n\t\t\tdisplayUrl: Schema.NullOr(Schema.String),\n\t\t\twireProtocol: Schema.Literals(['http', 'h2c', 'tcp']),\n\t\t\tpluginKey: Schema.String,\n\t\t\tendpointKey: Schema.String,\n\t\t}),\n\t),\n\textras: Schema.Record(Schema.String, Schema.Unknown),\n\t// Optional codegen metadata. The Vite plugin reads\n\t// `codegen.deploymentFile` to inject the live on-chain ids via\n\t// `__DEVSTACK_DEPLOYMENT__`; on a miss it injects `null`. Bindings are\n\t// not recorded here — `@generated` always resolves to the committed\n\t// `src/generated` tree.\n\tcodegen: Schema.optional(\n\t\tSchema.Struct({\n\t\t\t/** Absolute path to the gitignored `deployment.json` the boot\n\t\t\t * wrote for this stack. The Vite plugin reads it in dev. */\n\t\t\tdeploymentFile: Schema.optional(Schema.String),\n\t\t}),\n\t),\n});\n"],"mappings":";;;;;;;AAoBA,IAAa,wBAAb,cAA2C,OAAO,iBAAwC,CAAC,CAC1F,yBACA,EACC,QAAQ,OAAO,OAChB,CACD,CAAC,CAAC,CAAC;;;;;;;;;AAUH,IAAa,4BAAb,cAA+C,OAAO,iBAA4C,CAAC,CAClG,6BACA;CACC,MAAM,OAAO,SAAS,CAAC,oBAAoB,qBAAqB,CAAC;CACjE,YAAY,OAAO;AACpB,CACD,CAAC,CAAC,CAAC;AAqDH,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAEpE,MAAa,yBACZ,OACA,QAEA,OAAO,IAAI,aAAa;CACvB,IAAI,UAAU,KAAA,GAAW,OAAO,CAAC;CAsBjC,MAAM,WAAW,OAbM,OAAO,SAAS,KAAK,IACzC,QACA,OAAO,UAAU,aAChB,OAAO,IAAI;EACX,WAAW,MAAM,GAAG;EACpB,QAAQ,UAAU;GACjB,IAAI,iBAAiB,2BAA2B,OAAO;GACvD,MAAM;EACP;CACD,CAAC,CAAC,CAAC,KACF,OAAO,SAAS,UAAW,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,QAAQ,KAAK,CAAE,CACnF,IACC,OAAO,QAAQ,KAAK;CAExB,IAAI,CAAC,SAAS,QAAQ,GACrB,OAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB,EACzB,QAAQ,iDACT,CAAC,CACF;CAED,OAAO;AACR,CAAC;;;AAeF,MAAa,yBAAyB,OAAO,OAAO;CACnD,UAAU,OAAO,OAAO;EACvB,KAAK,OAAO;EACZ,OAAO,OAAO;EACd,SAAS,OAAO;CACjB,CAAC;CACD,iBAAiB,OAAO;CAExB,UAAU,OAAO,SAAS,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC;CACtE,WAAW,OAAO,OACjB,OAAO,QACP,OAAO,OAAO;EACb,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,YAAY,OAAO,OAAO,OAAO,MAAM;EACvC,cAAc,OAAO,SAAS;GAAC;GAAQ;GAAO;EAAK,CAAC;EACpD,WAAW,OAAO;EAClB,aAAa,OAAO;CACrB,CAAC,CACF;CACA,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO;CAMnD,SAAS,OAAO,SACf,OAAO,OAAO;;;AAGb,gBAAgB,OAAO,SAAS,OAAO,MAAM,EAC9C,CAAC,CACF;AACD,CAAC"}
|