@mysten-incubation/devstack 0.3.0 → 0.5.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 +10 -11
- 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.d.mts +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.d.mts +36 -6
- package/dist/plugins/deepbook/index.mjs +52 -16
- 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.d.mts +5 -0
- package/dist/plugins/seal/codegen.mjs +55 -12
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +64 -19
- package/dist/plugins/seal/index.mjs +26 -36
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.d.mts +0 -2
- package/dist/plugins/seal/mode/fork-known.mjs +11 -22
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.d.mts +36 -13
- package/dist/plugins/seal/mode/live.mjs +104 -43
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +2 -1
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.d.mts +15 -3
- package/dist/plugins/seal/registry-publish.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.d.mts +137 -8
- package/dist/plugins/walrus/index.mjs +46 -9
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.d.mts +5 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +13 -10
- package/dist/plugins/walrus/mode/known-deploy.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 +2 -2
- 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
- package/dist/plugins/seal/service.mjs +0 -17
- package/dist/plugins/seal/service.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/sui/index.ts"],"sourcesContent":["// Sui plugin — barrel + factories.\n//\n// Architecture: Sui is the most-depended-on plugin in the stack.\n// Every consumer (Account/Coin/Wallet/Faucet/Package; Walrus/Seal/\n// Deepbook fork variants) reads its resolved `SuiClient` via the\n// `suiResource`. The factory at this file folds the four modes behind:\n//\n// - `sui(opts?)` — local shorthand. Defaults to an in-stack\n// local validator; pass a typed `opts`\n// record to select a different mode.\n// - `suiFor(network)` — mode-narrowed factory namespace (per\n// architecture Tension 11). Returns\n// `{ local: …, live: …, fork: … }`\n// narrowed to the network's mode.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.provides` — `chain-probe:<chainId>` strategy contributor,\n// the schema-validated read surface (`makeSuiChainProbe`).\n// 2. `ctx.snapshotExtra` — mode-aware container + bind-mount capture.\n// 3. `ctx.codegen` — `sui-network` bindings (chain id, rpc, etc.).\n// 4. `ctx.provides` — faucet strategy contributor, local-coin\n// dispensing for the mode's chain id.\n//\n// `ctx.endpoint` contributions are MODE-DEPENDENT (local + fork yes;\n// local-rpc + live no — the caller fronts their own RPC). They land\n// in the per-mode builder under `mode/*.ts`; this barrel emits them\n// alongside the rest during `start`.\n\nimport { Effect, type Scope } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, staticInputIdentity } from '../../api/define-plugin.ts';\nimport {\n\tbootPostgresSidecar,\n\tcredentialedUrl,\n\twithDatabase,\n} from '../internal/postgres-sidecar/index.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\n\nimport { chainProbeCapabilityKey } from '../../contracts/chain-probe.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport type {\n\tContainerRuntime,\n\tContainerRuntimeError,\n\tImageRef,\n} from '../../contracts/container-runtime.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { sanitizeAlias } from '../../substrate/runtime/managed-container.ts';\nimport {\n\tLeaseBrokerService,\n\ttype LeaseBroker,\n} from '../../substrate/runtime/lease-broker/index.ts';\nimport { PortBrokerService } from '../../substrate/runtime/port-broker/index.ts';\nimport { makeCodegenable, makeStaticCodegen } from './codegen.ts';\nimport type { SuiProbeKey } from './chain-probe.ts';\nimport { makeSnapshotable } from './snapshot.ts';\nimport { bootSuiService } from './service.ts';\nimport { suiPluginError, type SuiPluginError } from './errors.ts';\nimport { makeSuiForkRoutables, makeSuiLocalRoutables } from './routable.ts';\nimport { faucetCapabilityKey, type FaucetStrategy } from '../faucet/index.ts';\nimport { resolveNetworkOptions } from '../../orchestrators/network-options.ts';\nimport { suiLocalStrategy } from './local-faucet-strategy.ts';\nimport { suiForkFaucetStrategy } from './fork-faucet-strategy.ts';\nimport { selectSufficientForkCoin } from './fork-transaction.ts';\nimport { FORK_FAUCET_WHALE_MIN_COIN_MIST, resolveForkWhale } from './mode/fork.ts';\nimport { resolveImage, SUI_INDEXER_DB_ROLE, type LocalIndexer } from './mode/local.ts';\nimport type { SuiClient } from './mode/shared.ts';\nimport type {\n\tSuiForkOptions,\n\tSuiLiveOptions,\n\tSuiLocalRpcOptions,\n\tSuiLocalOptions,\n\tSuiOptions,\n} from './mode/spec.ts';\n\n// ---------------------------------------------------------------------------\n// Resource identity\n// ---------------------------------------------------------------------------\n\ntype SuiResolved = SuiClient & {\n\treadonly mode: SuiOptions['mode'];\n};\n\n/** Internal extension of `SuiResolved` carrying the pre-built funding\n * faucet strategy. `start` owns the `LeaseBrokerService` instance for\n * serialization; building the strategy at start-time (where the broker\n * is reachable) lets the capabilities factory consume a flat value\n * without threading the broker through a side channel. Local/live wrap\n * the HTTP faucet; fork impersonates a whale; `null` on networks with\n * no faucet (live-mainnet, external-rpc-without-faucet, faucet-disabled\n * fork). */\ntype SuiResolvedRuntime = SuiResolved & {\n\treadonly fundingFaucetStrategy: FaucetStrategy | null;\n};\n\n/** The Sui plugin's resource identity. The id is `'sui'` (singular). */\nexport const suiResource = resource<'sui', SuiResolved>('sui');\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by sui() + suiFor())\n// ---------------------------------------------------------------------------\n\n/** Build the funds-ready faucet strategy for the resolved network, or\n * `null` when the network has none. Local/live wrap the HTTP faucet;\n * fork mode impersonates a whale (resolved + validated here). */\nconst resolveFundingFaucetStrategy = (\n\topts: SuiOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> => {\n\tif (opts.mode === 'fork') {\n\t\treturn resolveForkFaucetStrategy(opts, client, broker);\n\t}\n\tif (client.fundingFaucetUrl === null) {\n\t\treturn Effect.succeed(null);\n\t}\n\treturn Effect.succeed(\n\t\tsuiLocalStrategy({\n\t\t\tfaucetUrl: client.fundingFaucetUrl,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-faucet:${client.chainId}`,\n\t\t\t},\n\t\t}),\n\t);\n};\n\n/** Fork-mode faucet: resolve the whale, validate it holds a large enough\n * SUI coin, then build the impersonation strategy. A whale the user set\n * explicitly that fails validation hard-fails the boot; a per-upstream\n * default whale only warns and disables the faucet so the fork still\n * comes up (account funding then surfaces the \"no faucet strategy\"\n * error only if something actually needs SUI). */\nconst resolveForkFaucetStrategy = (\n\topts: SuiForkOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> =>\n\tEffect.gen(function* () {\n\t\tconst resolved = resolveForkWhale(opts);\n\t\tconst fork = client.fork;\n\t\tif (resolved === null || fork === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst strategy = suiForkFaucetStrategy({\n\t\t\twhale: resolved.whale,\n\t\t\tfork,\n\t\t\tsdk: client.sdk,\n\t\t\tperRequestCapMist: resolved.perRequestCapMist,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-fork-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-fork-faucet:${client.chainId}`,\n\t\t\t},\n\t\t});\n\t\treturn yield* selectSufficientForkCoin(\n\t\t\tclient.sdk.core,\n\t\t\tresolved.whale,\n\t\t\tFORK_FAUCET_WHALE_MIN_COIN_MIST,\n\t\t).pipe(\n\t\t\tEffect.as(strategy),\n\t\t\tEffect.catchTag('SuiPluginError', (cause) =>\n\t\t\t\tresolved.explicit\n\t\t\t\t\t? Effect.fail(cause)\n\t\t\t\t\t: Effect.logWarning(\n\t\t\t\t\t\t\t`sui fork mode: default faucet whale ${resolved.whale} is unusable ` +\n\t\t\t\t\t\t\t\t`(${cause.message}); disabling the fork faucet. Set faucet.whale to override.`,\n\t\t\t\t\t\t).pipe(Effect.as(null)),\n\t\t\t),\n\t\t);\n\t});\n\n/** Default name for sui's GraphQL-indexer database. */\nconst DEFAULT_INDEXER_DATABASE = 'sui_indexer';\n\n/** In-network DNS alias siblings dial the indexer-db sidecar by. */\nconst SUI_INDEXER_DB_ALIAS = 'sui-indexer-db';\n\nconst suiInputIdentity = (opts: SuiOptions): unknown => {\n\tconst { readyTimeout: _readyTimeout, ...authored } = opts;\n\tif (authored.mode !== 'local') return { plugin: 'sui', ...authored };\n\treturn {\n\t\tplugin: 'sui',\n\t\t...authored,\n\t\tindexer: authored.indexer !== false,\n\t\tindexerDb:\n\t\t\tauthored.indexerDb === undefined\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\t...authored.indexerDb,\n\t\t\t\t\t\tdatabase: authored.indexerDb.database ?? DEFAULT_INDEXER_DATABASE,\n\t\t\t\t\t},\n\t};\n};\n\n/** Compose the indexer-db sidecar's stable `configHash` from validator\n * inputs known before local mode starts. A validator image change recreates\n * the validator, so the sidecar's mount-less PGDATA must reset with it.\n *\n * Validator data-layer resets that are not config values (absent validator\n * or last exit `137`) are handled by `validatorNeedsIndexerReset`, which\n * removes the sidecar before this hash is applied. */\nexport const composeIndexerConfigHash = (imageRef: string): string =>\n\t['indexer-db', `validator-img=${imageRef}`].join('|');\n\n/** Append a database segment to a BYO DSN only when it has no path (so a\n * caller-supplied `.../mydb` is respected). Rebuilds via the URL object\n * so the db slots into `pathname` BEFORE `?search` — a naive string\n * append on a `host:5432?sslmode=require` DSN (path-less but with a\n * query) would wrongly yield `...?sslmode=require/sui_indexer`. The\n * catch fallback (non-URL-parseable DSN) splits the query off first. */\nconst appendDatabaseIfMissing = (url: string, database: string): string => {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tconst hasPath = parsed.pathname !== '' && parsed.pathname !== '/';\n\t\tif (hasPath) return url;\n\t\tparsed.pathname = `/${database}`;\n\t\treturn parsed.toString();\n\t} catch {\n\t\tconst queryAt = url.indexOf('?');\n\t\tconst base = queryAt === -1 ? url : url.slice(0, queryAt);\n\t\tconst query = queryAt === -1 ? '' : url.slice(queryAt);\n\t\tconst afterScheme = base.replace(/^postgres(ql)?:\\/\\//, '');\n\t\tif (/\\/[^/]+$/.test(afterScheme)) return url;\n\t\treturn `${base.replace(/\\/$/, '')}/${encodeURIComponent(database)}${query}`;\n\t}\n};\n\n/** Whether the Sui-owned indexer DB must be deleted before boot.\n *\n * The validator's chain identity lives in its writable layer. If the\n * validator container is absent, the next boot creates a new layer and a\n * new chain. If it last exited `137`, the runtime's `on-failure` policy\n * also recreates it. Both cases make any existing indexer DB stale. */\nexport const validatorNeedsIndexerReset = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n): Effect.Effect<boolean, ContainerRuntimeError> =>\n\truntime\n\t\t.inspectByLabels({\n\t\t\tapp: identity.app,\n\t\t\tstack: identity.stack,\n\t\t\tplugin: 'sui',\n\t\t\trole: 'validator',\n\t\t})\n\t\t.pipe(\n\t\t\tEffect.map((handles) => {\n\t\t\t\tconst validator = handles[0];\n\t\t\t\treturn validator === undefined || validator.lastExitCode === 137;\n\t\t\t}),\n\t\t);\n\n/** Provision the GraphQL-indexer DB wiring for local mode.\n *\n * Default: sui OWNS a postgres sidecar (labelled under sui) — boot it on\n * a per-stack network and compose the DSN from its in-network alias (NOT\n * the per-stack container DNS host, which isn't parallel-stack-portable).\n * The sidecar resets on the same validator inputs that can make its rows\n * stale: absent/exited-`137` validator state removes the sidecar before\n * boot, and validator image identity is folded into its `configHash`.\n *\n * `validatorImage` is the SAME `ImageRef` the barrel hands `bootLocalMode`\n * for the validator container, so the image the sidecar hashed and the image\n * the validator runs cannot drift.\n *\n * BYO: when `indexerDb` is set, no sidecar — pass the caller's DSN +\n * network straight through (appending the default db iff the DSN has no\n * path). The caller owns that DB's lifecycle. */\nexport const provisionLocalIndexer = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n\topts: SuiLocalOptions,\n\tvalidatorImage: ImageRef,\n): Effect.Effect<LocalIndexer, SuiPluginError, Scope.Scope> => {\n\tconst database = opts.indexerDb?.database ?? DEFAULT_INDEXER_DATABASE;\n\tif (opts.indexerDb !== undefined) {\n\t\treturn Effect.succeed({\n\t\t\turl: appendDatabaseIfMissing(opts.indexerDb.url, database),\n\t\t\tnetwork: opts.indexerDb.network,\n\t\t});\n\t}\n\tconst network = sanitizeAlias(`devstack-${identity.app}-${identity.stack}-sui-indexer`);\n\treturn Effect.gen(function* () {\n\t\tconst resetExistingSidecar = yield* validatorNeedsIndexerReset(runtime, identity);\n\t\tif (resetExistingSidecar) {\n\t\t\tyield* runtime.removeManagedContainers({\n\t\t\t\tapp: identity.app,\n\t\t\t\tstack: identity.stack,\n\t\t\t\tplugin: 'sui',\n\t\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\t});\n\t\t}\n\t\tconst imageRef = validatorImage.tag ?? validatorImage.digest;\n\t\tconst { handle } = yield* bootPostgresSidecar(runtime, identity, {\n\t\t\tnetwork,\n\t\t\talias: SUI_INDEXER_DB_ALIAS,\n\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\tdatabase,\n\t\t\tconfigHash: composeIndexerConfigHash(imageRef),\n\t\t});\n\t\treturn {\n\t\t\turl: withDatabase(\n\t\t\t\tcredentialedUrl({\n\t\t\t\t\tuser: handle.user,\n\t\t\t\t\tpassword: handle.password,\n\t\t\t\t\thost: handle.networkAlias,\n\t\t\t\t\tport: handle.port,\n\t\t\t\t}),\n\t\t\t\tdatabase,\n\t\t\t),\n\t\t\tnetwork: handle.containerNetwork,\n\t\t} satisfies LocalIndexer;\n\t}).pipe(\n\t\t// Postgres / runtime-domain failures wrap into the sui error channel\n\t\t// — the sidecar is a sui implementation detail. Nothing in the gen\n\t\t// body produces a `SuiPluginError` directly, so we wrap every cause.\n\t\tEffect.mapError((cause) =>\n\t\t\tsuiPluginError(\n\t\t\t\t'container-start',\n\t\t\t\t`sui local mode: failed to provision the GraphQL indexer postgres sidecar (${cause._tag})`,\n\t\t\t\tcause,\n\t\t\t),\n\t\t),\n\t);\n};\n\n/** Shared boot + inline contribution emission, parameterised by the\n * resolved external-indexer wiring (`undefined` = no GraphQL, the\n * zero-config / non-local case) and, for local mode, the validator image\n * the barrel pre-resolved (so the sidecar's `configHash` and the validator\n * container share one `ImageRef`; `undefined` on the no-sidecar paths,\n * where `bootLocalMode` resolves it inline). */\nconst bootAndEmit = (\n\topts: SuiOptions,\n\tindexer: LocalIndexer | undefined,\n\tprebuiltImage?: ImageRef,\n) =>\n\tEffect.gen(function* () {\n\t\tconst ctx = yield* PluginContext;\n\t\t// The substrate threads `ContainerRuntime` + `IdentityContext`\n\t\t// via the plugin runtime context; the supervisor provides\n\t\t// these before this body runs.\n\t\tconst runtime = yield* ContainerRuntimeService;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst paths = yield* StackPathsService;\n\t\tconst portBroker = yield* PortBrokerService;\n\t\tconst fundingFaucetLeaseBroker = yield* LeaseBrokerService;\n\t\tconst { client } = yield* bootSuiService(\n\t\t\truntime,\n\t\t\tidentity,\n\t\t\tportBroker,\n\t\t\tpaths,\n\t\t\topts,\n\t\t\tindexer,\n\t\t\tprebuiltImage,\n\t\t);\n\n\t\tconst fundingFaucetStrategy = yield* resolveFundingFaucetStrategy(\n\t\t\topts,\n\t\t\tclient,\n\t\t\tfundingFaucetLeaseBroker,\n\t\t);\n\t\tconst value = {\n\t\t\t...client,\n\t\t\tmode: opts.mode,\n\t\t\tfundingFaucetStrategy,\n\t\t} satisfies SuiResolvedRuntime;\n\t\t// Emit the resolved contributions inline, top-to-bottom: the\n\t\t// decls stamp REAL chain ids / rpc URLs / container names\n\t\t// (the genesis-digest chain id, not the network name).\n\t\t// (`value` is the just-resolved runtime; `identity` from\n\t\t// `IdentityContext`, NOT re-fetched). The shared\n\t\t// `emitContributions` routes each by `kind`. Faucet (conditional\n\t\t// on a resolved strategy) and routables (mode-dependent) are the\n\t\t// only optional members; order is load-bearing.\n\t\tconst realChainId = value.chainId;\n\t\t// Per-network faucet gate (per-network options: ON for every network\n\t\t// EXCEPT live `mainnet`, where the funding faucet must NEVER run).\n\t\t// `identity.network` is the resolved network name. The gate honours\n\t\t// ONLY the POLICY DEFAULT, NOT any author per-network override: the\n\t\t// override RECORD (`networkOptions`) is an orchestrator-level concern\n\t\t// that the name-blind substrate does not forward into plugins, and\n\t\t// this plugin only receives `IdentityContext`. So a per-network\n\t\t// `{ <network>: { faucet: false } }` override is deliberately NOT\n\t\t// applied here (unlike `devWallet`/`autoApproveSigning`, which the\n\t\t// orchestrator resolves against the forwarded `networkOptions`); see\n\t\t// `orchestrators/network-options.ts`. The policy default already\n\t\t// carries the load-bearing mainnet hard-clamp, so a resolved strategy\n\t\t// on `mainnet` (none of the live modes build one today, but a future\n\t\t// faucet-bearing mainnet config could) is suppressed and the strategy\n\t\t// registry never exposes `faucet:request:<mainnet-chain-id>`; account\n\t\t// funding then surfaces the actionable \"no faucet strategy\" error\n\t\t// rather than silently faucet-funding against a production network.\n\t\tconst faucetEnabled = resolveNetworkOptions(identity.network).faucet;\n\t\tconst faucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t!faucetEnabled || value.fundingFaucetStrategy === null\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\t\tcapabilityKey: faucetCapabilityKey(realChainId),\n\t\t\t\t\t\t\tstrategy: value.fundingFaucetStrategy,\n\t\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\t\t`faucet:request:${string}`,\n\t\t\t\t\t\t\tReturnType<typeof suiLocalStrategy>\n\t\t\t\t\t\t>,\n\t\t\t\t\t];\n\t\tconst localRoutables =\n\t\t\topts.mode === 'local'\n\t\t\t\t? makeSuiLocalRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-validator`,\n\t\t\t\t\t\t// GraphQL routes only when the external indexer is wired.\n\t\t\t\t\t\tincludeGraphql: indexer !== undefined,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\tconst forkRoutables =\n\t\t\topts.mode === 'fork'\n\t\t\t\t? makeSuiForkRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-fork`,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\temitContributions(ctx, [\n\t\t\t// `hasIndexer` (local only) folds the sui-owned indexer-db\n\t\t\t// sidecar into the captured containers; `indexer !== undefined`\n\t\t\t// is the same gate `bootAndEmit`'s caller resolved GraphQL on.\n\t\t\tmakeSnapshotable(opts.mode, identity.app, identity.stack, realChainId, indexer !== undefined),\n\t\t\tmakeCodegenable({\n\t\t\t\tmode: opts.mode,\n\t\t\t\tchainId: realChainId,\n\t\t\t\trpc: value.rpcUrl,\n\t\t\t\tsource: 'default',\n\t\t\t\t...(value.faucetUrl !== null ? { faucet: value.faucetUrl } : {}),\n\t\t\t\t...(value.graphqlUrl !== null ? { graphql: value.graphqlUrl } : {}),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\tcapabilityKey: chainProbeCapabilityKey(realChainId),\n\t\t\t\tstrategy: value.chainProbe,\n\t\t\t\tautoMounted: true,\n\t\t\t} satisfies StrategyContributorDecl<`chain-probe:${string}`, ChainProbe<SuiProbeKey>>,\n\t\t\t...faucetContribution,\n\t\t\t...localRoutables,\n\t\t\t...forkRoutables,\n\t\t]);\n\t\treturn value;\n\t});\n\n/** The single sui plugin builder. No sibling `dependsOn`: in local mode\n * with the indexer on (the default), `start` provisions the GraphQL\n * indexer DB itself — a sui-owned postgres sidecar — inside the boot\n * scope BEFORE the boot-time validator (so the validator can join its\n * network), then boots + emits. Resolution order:\n * - `indexerDb` present → BYO DB (no sidecar)\n * - `indexer === false` → opt out (RPC + faucet only, GraphQL off)\n * - else (local default) → sui-owned sidecar\n * Non-local modes never touch the indexer (GraphQL off). */\nconst buildSuiPlugin = (opts: SuiOptions) =>\n\tdefinePlugin({\n\t\tid: suiResource.id,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tinputIdentity: staticInputIdentity(suiInputIdentity(opts)),\n\t\t// Stack-free codegen: the `codegen` verb derives the committed\n\t\t// `config.ts`'s `network`/`networks` from this hook. Both are\n\t\t// environment/live data (dynamic local rpc port; a real deployment\n\t\t// names a different network), so the committed tree carries\n\t\t// `resolveNetwork()`/`resolveNetworks()` raw expressions that resolve\n\t\t// at app build/dev time via the injected `__DEVSTACK_IDS__` global —\n\t\t// never literal values. No id-resolver input needed.\n\t\tstaticCodegen: makeStaticCodegen(),\n\t\t// Zero-arg `start` (no `dependsOn`); the substrate supplies the\n\t\t// container runtime + identity via the plugin runtime context.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Resolution order: BYO `indexerDb` wins; then an explicit\n\t\t\t\t// `indexer: false` opt-out; then the sui-owned sidecar default.\n\t\t\t\t// Non-local modes never wire the indexer.\n\t\t\t\tif (opts.mode !== 'local' || (opts.indexerDb === undefined && opts.indexer === false)) {\n\t\t\t\t\treturn yield* bootAndEmit(opts, undefined);\n\t\t\t\t}\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\t// Resolve the validator image ONCE, before the sidecar — single\n\t\t\t\t// source for both the sidecar's configHash (it folds the resolved\n\t\t\t\t// image ref so an image bump resets the indexer DB) and the\n\t\t\t\t// validator container `bootAndEmit` boots below. `resolveImage`\n\t\t\t\t// is deterministic (content-addressed build/pull) so this is the\n\t\t\t\t// SAME ref the validator runs; resolving it here, not twice,\n\t\t\t\t// avoids a redundant build AND any drift from `decideRunAction`.\n\t\t\t\tconst validatorImage = yield* resolveImage(runtime, identity, opts);\n\t\t\t\t// The sidecar boots first; if the validator boot below fails, the\n\t\t\t\t// sidecar's finalizer lingers on the plugin acquire scope until\n\t\t\t\t// teardown/retry. Benign and intentional — the stable name +\n\t\t\t\t// labels mean a retry ADOPTS the existing sidecar container\n\t\t\t\t// (no scope-threading machinery needed).\n\t\t\t\tconst indexer = yield* provisionLocalIndexer(runtime, identity, opts, validatorImage);\n\t\t\t\treturn yield* bootAndEmit(opts, indexer, validatorImage);\n\t\t\t}),\n\t});\n\nconst buildPlugin = <O extends SuiOptions>(opts: O) => buildSuiPlugin(opts);\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local Sui shorthand. Network/env selection belongs to the CLI or\n * `defineDevstackWith(...)`; plain `sui()` always means an in-stack\n * local validator (GraphQL/indexer/Postgres on by default via a\n * sui-owned sidecar). */\nexport const sui = <const O extends SuiOptions = { mode: 'local' }>(\n\topts: O = { mode: 'local' } as O,\n) => buildPlugin(opts);\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local' } as const;\n * suiFor(network).local({...}) // OK\n * suiFor(network).fork({...}) // type error: 'fork' not in 'local' branch\n *\n * The namespace MIRRORS the four mode option records: `local`,\n * `localRpc` (mapped onto the substrate `'local'` branch),\n * `live`, `fork`. */\nexport const suiFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: <const O extends Omit<SuiLocalOptions, 'mode'>>(opts: O = {} as O) =>\n\t\t\tbuildPlugin({ mode: 'local', ...opts }),\n\t\tlocalRpc: (opts: Omit<SuiLocalRpcOptions, 'mode'>) =>\n\t\t\tbuildPlugin({ mode: 'local-rpc', ...opts }),\n\t},\n\tlive: {\n\t\ttestnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'testnet', ...opts }),\n\t\tmainnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'mainnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'devnet', ...opts }),\n\t\tcustom: (opts: Omit<SuiLiveOptions, 'mode' | 'network'>) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'custom', ...opts }),\n\t},\n\tfork: {\n\t\tmainnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'mainnet', ...opts }),\n\t\ttestnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'testnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'devnet', ...opts }),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers (Account/Coin/Wallet/etc.) and for\n// the sibling plugins (Walrus/Seal/Deepbook fork variants).\n// ---------------------------------------------------------------------------\n\nexport type { SuiClient, ForkAdminSurface, WaitForTransactionsReady } from './mode/shared.ts';\nexport type { ResolvedSuiNetwork } from './network-resolver.ts';\nexport type {\n\tNetworkConfig,\n\tNetworkMode,\n\tDevstackNetworkModeRegistry,\n\tDefaultNetwork,\n} from './network-config.ts';\nexport type {\n\tSuiOptions,\n\tSuiLocalOptions,\n\tSuiLocalRpcOptions,\n\tSuiLiveOptions,\n\tSuiForkOptions,\n\tSuiPluginMode,\n} from './mode/spec.ts';\nexport type { SuiNetworkConfigEntry } from './codegen.ts';\nexport type {\n\tSuiError,\n\tSuiPluginError,\n\tSuiCliError,\n\tSuiConfigError,\n\tForkUnsupportedError,\n} from './errors.ts';\n\n// Cross-plugin seams (consumed by Walrus/Seal/Deepbook fork variants\n// and by Account/Coin/Wallet/Package).\nexport {\n\tchainProbeCapabilityKey,\n\ttype ChainProbe,\n\ttype ChainProbeError,\n\ttype ChainProbeMode,\n} from '../../contracts/chain-probe.ts';\nexport {\n\tFORK_UNSUPPORTED_SURFACES,\n\twrapWithForkGuard,\n\ttype ForkLockHolder,\n} from './fork-orchestration.ts';\nexport type { SuiProbeKey, SuiSdkShim } from './chain-probe.ts';\nexport { SuiLogAttr } from './log-attrs.ts';\n\n/** The shape `Transaction.build({ client })` and every `sdk.core.*` call\n * accepts. Re-exported from `@mysten/sui/client` so callers cast\n * `sui.sdk.client as ClientWithCoreApi` without each having to know the\n * SDK subpath. */\nexport type { ClientWithCoreApi } from '@mysten/sui/client';\n// Cross-plugin seams: fork impersonation + chain-build container.\n// Consumed by `action` (Move-call execution against fork) and\n// `package` (publish-to-fork + Move-build orchestration). Consumer\n// plugins import these barrel entries rather than internal modules.\nexport {\n\tbuildForkImpersonationTransactionBytes,\n\tprepareForkImpersonationTransaction,\n\tverifyForkImpersonationSender,\n\tselectSufficientForkCoin,\n\tFORK_IMPERSONATION_GAS_BUDGET,\n\tFORK_IMPERSONATION_GAS_PRICE,\n\ttype ForkGasCoin,\n\ttype ForkImpersonationGasClient,\n} from './fork-transaction.ts';\nexport {\n\tsuiForkFaucetStrategy,\n\ttype SuiForkFaucetStrategyOptions,\n\ttype SuiForkFaucetSerialization,\n} from './fork-faucet-strategy.ts';\nexport {\n\tacquireChainBuildContainer,\n\tcontainerNameForApp,\n\tmoveBuildLockPathFor,\n\tMOVE_BUILD_LOCK_TIMEOUT_MS,\n\ttype ChainBuildContainer,\n\ttype ChainBuildContainerSpec,\n} from './chain-build-container.ts';\n\n// Cross-plugin seams: hoisted exec / move / ledger helpers.\n// Consumed by Account/Action/Coin/Deepbook/Package/Seal/Walrus. These\n// barrel entries route those consumers through the sibling plugin's\n// barrel per the `plugin-boundary` invariant, rather than reaching into\n// the deep `sui/exec`, `sui/move`, and `sui/ledger` internal modules.\nexport {\n\textractExecuteDigest,\n\tformatExecutedFailure,\n\texecuteSuiTx,\n\tisSuiStaleObjectVersionError,\n\ttype ResolvedSigner,\n\ttype ExecutedFailure,\n\ttype ExecutedReceipt,\n\ttype TransactionSignerScope,\n} from './exec/index.ts';\nexport { signAndDispatch } from './exec/sign-and-dispatch.ts';\nexport {\n\thashMoveSources,\n\trunMoveBuild,\n\tscrubLocksHost,\n\twithMoveBuildLock,\n\ttype BuildOutput,\n\ttype MoveBuildContainer,\n\ttype MoveBuildError,\n} from './move/index.ts';\nexport { currentLedgerObjectRef } from './ledger/object-ref.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/sui/index.ts"],"sourcesContent":["// Sui plugin — barrel + factories.\n//\n// Architecture: Sui is the most-depended-on plugin in the stack.\n// Every consumer (Account/Coin/Wallet/Faucet/Package; Walrus/Seal/\n// Deepbook fork variants) reads its resolved `SuiClient` via the\n// `suiResource`. The factory at this file folds the four modes behind:\n//\n// - `sui(opts?)` — local shorthand. Defaults to an in-stack\n// local validator; pass a typed `opts`\n// record to select a different mode.\n// - `suiFor(network)` — mode-narrowed factory namespace (per\n// architecture Tension 11). Returns\n// `{ local: …, live: …, fork: … }`\n// narrowed to the network's mode.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.provides` — `chain-probe:<chainId>` strategy contributor,\n// the schema-validated read surface (`makeSuiChainProbe`).\n// 2. `ctx.snapshotExtra` — mode-aware container + bind-mount capture.\n// 3. `ctx.codegen` — `sui-network` bindings (chain id, rpc, etc.).\n// 4. `ctx.provides` — faucet strategy contributor, local-coin\n// dispensing for the mode's chain id.\n//\n// `ctx.endpoint` contributions are MODE-DEPENDENT (local + fork yes;\n// local-rpc + live no — the caller fronts their own RPC). They land\n// in the per-mode builder under `mode/*.ts`; this barrel emits them\n// alongside the rest during `start`.\n\nimport { Effect, type Scope } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, staticInputIdentity } from '../../api/define-plugin.ts';\nimport {\n\tbootPostgresSidecar,\n\tcredentialedUrl,\n\twithDatabase,\n} from '../internal/postgres-sidecar/index.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\n\nimport { chainProbeCapabilityKey } from '../../contracts/chain-probe.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport type {\n\tContainerRuntime,\n\tContainerRuntimeError,\n\tImageRef,\n} from '../../contracts/container-runtime.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { sanitizeAlias } from '../../substrate/runtime/managed-container.ts';\nimport {\n\tLeaseBrokerService,\n\ttype LeaseBroker,\n} from '../../substrate/runtime/lease-broker/index.ts';\nimport { PortBrokerService } from '../../substrate/runtime/port-broker/index.ts';\nimport { makeCodegenable, makeStaticCodegen } from './codegen.ts';\nimport type { SuiProbeKey } from './chain-probe.ts';\nimport { makeSnapshotable } from './snapshot.ts';\nimport { bootSuiService } from './service.ts';\nimport { suiPluginError, type SuiPluginError } from './errors.ts';\nimport { makeSuiForkRoutables, makeSuiLocalRoutables } from './routable.ts';\nimport { faucetCapabilityKey, type FaucetStrategy } from '../faucet/index.ts';\nimport { suiLocalStrategy } from './local-faucet-strategy.ts';\nimport { suiForkFaucetStrategy } from './fork-faucet-strategy.ts';\nimport { selectSufficientForkCoin } from './fork-transaction.ts';\nimport { FORK_FAUCET_WHALE_MIN_COIN_MIST, resolveForkWhale } from './mode/fork.ts';\nimport { resolveImage, SUI_INDEXER_DB_ROLE, type LocalIndexer } from './mode/local.ts';\nimport type { SuiClient } from './mode/shared.ts';\nimport type {\n\tSuiForkOptions,\n\tSuiLiveOptions,\n\tSuiLocalRpcOptions,\n\tSuiLocalOptions,\n\tSuiOptions,\n} from './mode/spec.ts';\n\n// ---------------------------------------------------------------------------\n// Resource identity\n// ---------------------------------------------------------------------------\n\ntype SuiResolved = SuiClient & {\n\treadonly mode: SuiOptions['mode'];\n};\n\n/** Internal extension of `SuiResolved` carrying the pre-built funding\n * faucet strategy. `start` owns the `LeaseBrokerService` instance for\n * serialization; building the strategy at start-time (where the broker\n * is reachable) lets the capabilities factory consume a flat value\n * without threading the broker through a side channel. Local/live wrap\n * the HTTP faucet; fork impersonates a whale; `null` on networks with\n * no faucet (live-mainnet, external-rpc-without-faucet, faucet-disabled\n * fork). */\ntype SuiResolvedRuntime = SuiResolved & {\n\treadonly fundingFaucetStrategy: FaucetStrategy | null;\n};\n\n/** The Sui plugin's resource identity. The id is `'sui'` (singular). */\nexport const suiResource = resource<'sui', SuiResolved>('sui');\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by sui() + suiFor())\n// ---------------------------------------------------------------------------\n\n/** Build the funds-ready faucet strategy for the resolved network, or\n * `null` when the network has none. Local/live wrap the HTTP faucet;\n * fork mode impersonates a whale (resolved + validated here). */\nconst resolveFundingFaucetStrategy = (\n\topts: SuiOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> => {\n\tif (opts.mode === 'fork') {\n\t\treturn resolveForkFaucetStrategy(opts, client, broker);\n\t}\n\tif (client.fundingFaucetUrl === null) {\n\t\treturn Effect.succeed(null);\n\t}\n\treturn Effect.succeed(\n\t\tsuiLocalStrategy({\n\t\t\tfaucetUrl: client.fundingFaucetUrl,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-faucet:${client.chainId}`,\n\t\t\t},\n\t\t}),\n\t);\n};\n\n/** Fork-mode faucet: resolve the whale, validate it holds a large enough\n * SUI coin, then build the impersonation strategy. A whale the user set\n * explicitly that fails validation hard-fails the boot; a per-upstream\n * default whale only warns and disables the faucet so the fork still\n * comes up (account funding then surfaces the \"no faucet strategy\"\n * error only if something actually needs SUI). */\nconst resolveForkFaucetStrategy = (\n\topts: SuiForkOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> =>\n\tEffect.gen(function* () {\n\t\tconst resolved = resolveForkWhale(opts);\n\t\tconst fork = client.fork;\n\t\tif (resolved === null || fork === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst strategy = suiForkFaucetStrategy({\n\t\t\twhale: resolved.whale,\n\t\t\tfork,\n\t\t\tsdk: client.sdk,\n\t\t\tperRequestCapMist: resolved.perRequestCapMist,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-fork-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-fork-faucet:${client.chainId}`,\n\t\t\t},\n\t\t});\n\t\treturn yield* selectSufficientForkCoin(\n\t\t\tclient.sdk.core,\n\t\t\tresolved.whale,\n\t\t\tFORK_FAUCET_WHALE_MIN_COIN_MIST,\n\t\t).pipe(\n\t\t\tEffect.as(strategy),\n\t\t\tEffect.catchTag('SuiPluginError', (cause) =>\n\t\t\t\tresolved.explicit\n\t\t\t\t\t? Effect.fail(cause)\n\t\t\t\t\t: Effect.logWarning(\n\t\t\t\t\t\t\t`sui fork mode: default faucet whale ${resolved.whale} is unusable ` +\n\t\t\t\t\t\t\t\t`(${cause.message}); disabling the fork faucet. Set faucet.whale to override.`,\n\t\t\t\t\t\t).pipe(Effect.as(null)),\n\t\t\t),\n\t\t);\n\t});\n\n/** Default name for sui's GraphQL-indexer database. */\nconst DEFAULT_INDEXER_DATABASE = 'sui_indexer';\n\n/** In-network DNS alias siblings dial the indexer-db sidecar by. */\nconst SUI_INDEXER_DB_ALIAS = 'sui-indexer-db';\n\nconst suiInputIdentity = (opts: SuiOptions): unknown => {\n\tconst { readyTimeout: _readyTimeout, ...authored } = opts;\n\tif (authored.mode !== 'local') return { plugin: 'sui', ...authored };\n\treturn {\n\t\tplugin: 'sui',\n\t\t...authored,\n\t\tindexer: authored.indexer !== false,\n\t\tindexerDb:\n\t\t\tauthored.indexerDb === undefined\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\t...authored.indexerDb,\n\t\t\t\t\t\tdatabase: authored.indexerDb.database ?? DEFAULT_INDEXER_DATABASE,\n\t\t\t\t\t},\n\t};\n};\n\n/** Compose the indexer-db sidecar's stable `configHash` from validator\n * inputs known before local mode starts. A validator image change recreates\n * the validator, so the sidecar's mount-less PGDATA must reset with it.\n *\n * Validator data-layer resets that are not config values (absent validator\n * or last exit `137`) are handled by `validatorNeedsIndexerReset`, which\n * removes the sidecar before this hash is applied. */\nexport const composeIndexerConfigHash = (imageRef: string): string =>\n\t['indexer-db', `validator-img=${imageRef}`].join('|');\n\n/** Append a database segment to a BYO DSN only when it has no path (so a\n * caller-supplied `.../mydb` is respected). Rebuilds via the URL object\n * so the db slots into `pathname` BEFORE `?search` — a naive string\n * append on a `host:5432?sslmode=require` DSN (path-less but with a\n * query) would wrongly yield `...?sslmode=require/sui_indexer`. The\n * catch fallback (non-URL-parseable DSN) splits the query off first. */\nconst appendDatabaseIfMissing = (url: string, database: string): string => {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tconst hasPath = parsed.pathname !== '' && parsed.pathname !== '/';\n\t\tif (hasPath) return url;\n\t\tparsed.pathname = `/${database}`;\n\t\treturn parsed.toString();\n\t} catch {\n\t\tconst queryAt = url.indexOf('?');\n\t\tconst base = queryAt === -1 ? url : url.slice(0, queryAt);\n\t\tconst query = queryAt === -1 ? '' : url.slice(queryAt);\n\t\tconst afterScheme = base.replace(/^postgres(ql)?:\\/\\//, '');\n\t\tif (/\\/[^/]+$/.test(afterScheme)) return url;\n\t\treturn `${base.replace(/\\/$/, '')}/${encodeURIComponent(database)}${query}`;\n\t}\n};\n\n/** Whether the Sui-owned indexer DB must be deleted before boot.\n *\n * The validator's chain identity lives in its writable layer. If the\n * validator container is absent, the next boot creates a new layer and a\n * new chain. If it last exited `137`, the runtime's `on-failure` policy\n * also recreates it. Both cases make any existing indexer DB stale. */\nexport const validatorNeedsIndexerReset = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n): Effect.Effect<boolean, ContainerRuntimeError> =>\n\truntime\n\t\t.inspectByLabels({\n\t\t\tapp: identity.app,\n\t\t\tstack: identity.stack,\n\t\t\tplugin: 'sui',\n\t\t\trole: 'validator',\n\t\t})\n\t\t.pipe(\n\t\t\tEffect.map((handles) => {\n\t\t\t\tconst validator = handles[0];\n\t\t\t\treturn validator === undefined || validator.lastExitCode === 137;\n\t\t\t}),\n\t\t);\n\n/** Provision the GraphQL-indexer DB wiring for local mode.\n *\n * Default: sui OWNS a postgres sidecar (labelled under sui) — boot it on\n * a per-stack network and compose the DSN from its in-network alias (NOT\n * the per-stack container DNS host, which isn't parallel-stack-portable).\n * The sidecar resets on the same validator inputs that can make its rows\n * stale: absent/exited-`137` validator state removes the sidecar before\n * boot, and validator image identity is folded into its `configHash`.\n *\n * `validatorImage` is the SAME `ImageRef` the barrel hands `bootLocalMode`\n * for the validator container, so the image the sidecar hashed and the image\n * the validator runs cannot drift.\n *\n * BYO: when `indexerDb` is set, no sidecar — pass the caller's DSN +\n * network straight through (appending the default db iff the DSN has no\n * path). The caller owns that DB's lifecycle. */\nexport const provisionLocalIndexer = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n\topts: SuiLocalOptions,\n\tvalidatorImage: ImageRef,\n): Effect.Effect<LocalIndexer, SuiPluginError, Scope.Scope> => {\n\tconst database = opts.indexerDb?.database ?? DEFAULT_INDEXER_DATABASE;\n\tif (opts.indexerDb !== undefined) {\n\t\treturn Effect.succeed({\n\t\t\turl: appendDatabaseIfMissing(opts.indexerDb.url, database),\n\t\t\tnetwork: opts.indexerDb.network,\n\t\t});\n\t}\n\tconst network = sanitizeAlias(`devstack-${identity.app}-${identity.stack}-sui-indexer`);\n\treturn Effect.gen(function* () {\n\t\tconst resetExistingSidecar = yield* validatorNeedsIndexerReset(runtime, identity);\n\t\tif (resetExistingSidecar) {\n\t\t\tyield* runtime.removeManagedContainers({\n\t\t\t\tapp: identity.app,\n\t\t\t\tstack: identity.stack,\n\t\t\t\tplugin: 'sui',\n\t\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\t});\n\t\t}\n\t\tconst imageRef = validatorImage.tag ?? validatorImage.digest;\n\t\tconst { handle } = yield* bootPostgresSidecar(runtime, identity, {\n\t\t\tnetwork,\n\t\t\talias: SUI_INDEXER_DB_ALIAS,\n\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\tdatabase,\n\t\t\tconfigHash: composeIndexerConfigHash(imageRef),\n\t\t});\n\t\treturn {\n\t\t\turl: withDatabase(\n\t\t\t\tcredentialedUrl({\n\t\t\t\t\tuser: handle.user,\n\t\t\t\t\tpassword: handle.password,\n\t\t\t\t\thost: handle.networkAlias,\n\t\t\t\t\tport: handle.port,\n\t\t\t\t}),\n\t\t\t\tdatabase,\n\t\t\t),\n\t\t\tnetwork: handle.containerNetwork,\n\t\t} satisfies LocalIndexer;\n\t}).pipe(\n\t\t// Postgres / runtime-domain failures wrap into the sui error channel\n\t\t// — the sidecar is a sui implementation detail. Nothing in the gen\n\t\t// body produces a `SuiPluginError` directly, so we wrap every cause.\n\t\tEffect.mapError((cause) =>\n\t\t\tsuiPluginError(\n\t\t\t\t'container-start',\n\t\t\t\t`sui local mode: failed to provision the GraphQL indexer postgres sidecar (${cause._tag})`,\n\t\t\t\tcause,\n\t\t\t),\n\t\t),\n\t);\n};\n\n/** Shared boot + inline contribution emission, parameterised by the\n * resolved external-indexer wiring (`undefined` = no GraphQL, the\n * zero-config / non-local case) and, for local mode, the validator image\n * the barrel pre-resolved (so the sidecar's `configHash` and the validator\n * container share one `ImageRef`; `undefined` on the no-sidecar paths,\n * where `bootLocalMode` resolves it inline). */\nconst bootAndEmit = (\n\topts: SuiOptions,\n\tindexer: LocalIndexer | undefined,\n\tprebuiltImage?: ImageRef,\n) =>\n\tEffect.gen(function* () {\n\t\tconst ctx = yield* PluginContext;\n\t\t// The substrate threads `ContainerRuntime` + `IdentityContext`\n\t\t// via the plugin runtime context; the supervisor provides\n\t\t// these before this body runs.\n\t\tconst runtime = yield* ContainerRuntimeService;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst paths = yield* StackPathsService;\n\t\tconst portBroker = yield* PortBrokerService;\n\t\tconst fundingFaucetLeaseBroker = yield* LeaseBrokerService;\n\t\tconst { client } = yield* bootSuiService(\n\t\t\truntime,\n\t\t\tidentity,\n\t\t\tportBroker,\n\t\t\tpaths,\n\t\t\topts,\n\t\t\tindexer,\n\t\t\tprebuiltImage,\n\t\t);\n\n\t\tconst fundingFaucetStrategy = yield* resolveFundingFaucetStrategy(\n\t\t\topts,\n\t\t\tclient,\n\t\t\tfundingFaucetLeaseBroker,\n\t\t);\n\t\tconst value = {\n\t\t\t...client,\n\t\t\tmode: opts.mode,\n\t\t\tfundingFaucetStrategy,\n\t\t} satisfies SuiResolvedRuntime;\n\t\t// Emit the resolved contributions inline, top-to-bottom: the\n\t\t// decls stamp REAL chain ids / rpc URLs / container names\n\t\t// (the genesis-digest chain id, not the network name).\n\t\t// (`value` is the just-resolved runtime; `identity` from\n\t\t// `IdentityContext`, NOT re-fetched). The shared\n\t\t// `emitContributions` routes each by `kind`. Faucet (conditional\n\t\t// on a resolved strategy) and routables (mode-dependent) are the\n\t\t// only optional members; order is load-bearing.\n\t\tconst realChainId = value.chainId;\n\t\t// Funding-faucet gate: a FIXED policy — ON for every network EXCEPT\n\t\t// live `mainnet`, where the funding faucet must NEVER run.\n\t\t// `identity.network` is the resolved network name. There is NO\n\t\t// per-network override surface for this gate: the override RECORD\n\t\t// (`networkOptions`) is an orchestrator-level concern that the\n\t\t// name-blind substrate does not forward into plugins (this plugin\n\t\t// only receives the closed `IdentityContext` tuple), so a per-network\n\t\t// `{ <network>: { faucet: false } }` toggle could never reach here —\n\t\t// `NetworkScopedOptions` therefore deliberately omits a `faucet`\n\t\t// field rather than advertise a silent no-op (see\n\t\t// `orchestrators/network-options.ts`). The mainnet exclusion is\n\t\t// load-bearing: a resolved strategy on `mainnet` (none of the live\n\t\t// modes build one today, but a future faucet-bearing mainnet config\n\t\t// could) is suppressed and the strategy registry never exposes\n\t\t// `faucet:request:<mainnet-chain-id>`; account funding then surfaces\n\t\t// the actionable \"no faucet strategy\" error rather than silently\n\t\t// faucet-funding against a production network.\n\t\tconst faucetEnabled = identity.network !== 'mainnet';\n\t\tconst faucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t!faucetEnabled || value.fundingFaucetStrategy === null\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\t\tcapabilityKey: faucetCapabilityKey(realChainId),\n\t\t\t\t\t\t\tstrategy: value.fundingFaucetStrategy,\n\t\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\t\t`faucet:request:${string}`,\n\t\t\t\t\t\t\tReturnType<typeof suiLocalStrategy>\n\t\t\t\t\t\t>,\n\t\t\t\t\t];\n\t\tconst localRoutables =\n\t\t\topts.mode === 'local'\n\t\t\t\t? makeSuiLocalRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-validator`,\n\t\t\t\t\t\t// GraphQL routes only when the external indexer is wired.\n\t\t\t\t\t\tincludeGraphql: indexer !== undefined,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\tconst forkRoutables =\n\t\t\topts.mode === 'fork'\n\t\t\t\t? makeSuiForkRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-fork`,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\temitContributions(ctx, [\n\t\t\t// `hasIndexer` (local only) folds the sui-owned indexer-db\n\t\t\t// sidecar into the captured containers; `indexer !== undefined`\n\t\t\t// is the same gate `bootAndEmit`'s caller resolved GraphQL on.\n\t\t\tmakeSnapshotable(opts.mode, identity.app, identity.stack, realChainId, indexer !== undefined),\n\t\t\tmakeCodegenable({\n\t\t\t\tmode: opts.mode,\n\t\t\t\tchainId: realChainId,\n\t\t\t\trpc: value.rpcUrl,\n\t\t\t\tsource: 'default',\n\t\t\t\t...(value.faucetUrl !== null ? { faucet: value.faucetUrl } : {}),\n\t\t\t\t...(value.graphqlUrl !== null ? { graphql: value.graphqlUrl } : {}),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\tcapabilityKey: chainProbeCapabilityKey(realChainId),\n\t\t\t\tstrategy: value.chainProbe,\n\t\t\t\tautoMounted: true,\n\t\t\t} satisfies StrategyContributorDecl<`chain-probe:${string}`, ChainProbe<SuiProbeKey>>,\n\t\t\t...faucetContribution,\n\t\t\t...localRoutables,\n\t\t\t...forkRoutables,\n\t\t]);\n\t\treturn value;\n\t});\n\n/** The single sui plugin builder. No sibling `dependsOn`: in local mode\n * with the indexer on (the default), `start` provisions the GraphQL\n * indexer DB itself — a sui-owned postgres sidecar — inside the boot\n * scope BEFORE the boot-time validator (so the validator can join its\n * network), then boots + emits. Resolution order:\n * - `indexerDb` present → BYO DB (no sidecar)\n * - `indexer === false` → opt out (RPC + faucet only, GraphQL off)\n * - else (local default) → sui-owned sidecar\n * Non-local modes never touch the indexer (GraphQL off). */\nconst buildSuiPlugin = (opts: SuiOptions) =>\n\tdefinePlugin({\n\t\tid: suiResource.id,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tinputIdentity: staticInputIdentity(suiInputIdentity(opts)),\n\t\t// Stack-free codegen: the `codegen` verb derives the committed\n\t\t// `config.ts`'s `network`/`networks` from this hook. Both are\n\t\t// environment/live data (dynamic local rpc port; a real deployment\n\t\t// names a different network), so the committed tree carries\n\t\t// `dep.network`/`Object.fromEntries(networkNames.map(forNetwork))` raw\n\t\t// expressions off the loaded deployment that resolve\n\t\t// at app build/dev time via the injected `__DEVSTACK_DEPLOYMENT__` global —\n\t\t// never literal values. No id-resolver input needed.\n\t\tstaticCodegen: makeStaticCodegen(),\n\t\t// Zero-arg `start` (no `dependsOn`); the substrate supplies the\n\t\t// container runtime + identity via the plugin runtime context.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Resolution order: BYO `indexerDb` wins; then an explicit\n\t\t\t\t// `indexer: false` opt-out; then the sui-owned sidecar default.\n\t\t\t\t// Non-local modes never wire the indexer.\n\t\t\t\tif (opts.mode !== 'local' || (opts.indexerDb === undefined && opts.indexer === false)) {\n\t\t\t\t\treturn yield* bootAndEmit(opts, undefined);\n\t\t\t\t}\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\t// Resolve the validator image ONCE, before the sidecar — single\n\t\t\t\t// source for both the sidecar's configHash (it folds the resolved\n\t\t\t\t// image ref so an image bump resets the indexer DB) and the\n\t\t\t\t// validator container `bootAndEmit` boots below. `resolveImage`\n\t\t\t\t// is deterministic (content-addressed build/pull) so this is the\n\t\t\t\t// SAME ref the validator runs; resolving it here, not twice,\n\t\t\t\t// avoids a redundant build AND any drift from `decideRunAction`.\n\t\t\t\tconst validatorImage = yield* resolveImage(runtime, identity, opts);\n\t\t\t\t// The sidecar boots first; if the validator boot below fails, the\n\t\t\t\t// sidecar's finalizer lingers on the plugin acquire scope until\n\t\t\t\t// teardown/retry. Benign and intentional — the stable name +\n\t\t\t\t// labels mean a retry ADOPTS the existing sidecar container\n\t\t\t\t// (no scope-threading machinery needed).\n\t\t\t\tconst indexer = yield* provisionLocalIndexer(runtime, identity, opts, validatorImage);\n\t\t\t\treturn yield* bootAndEmit(opts, indexer, validatorImage);\n\t\t\t}),\n\t});\n\nconst buildPlugin = <O extends SuiOptions>(opts: O) => buildSuiPlugin(opts);\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local Sui shorthand. Network/env selection belongs to the CLI or\n * `defineDevstackWith(...)`; plain `sui()` always means an in-stack\n * local validator (GraphQL/indexer/Postgres on by default via a\n * sui-owned sidecar). */\nexport const sui = <const O extends SuiOptions = { mode: 'local' }>(\n\topts: O = { mode: 'local' } as O,\n) => buildPlugin(opts);\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local' } as const;\n * suiFor(network).local({...}) // OK\n * suiFor(network).fork({...}) // type error: 'fork' not in 'local' branch\n *\n * The namespace MIRRORS the four mode option records: `local`,\n * `localRpc` (mapped onto the substrate `'local'` branch),\n * `live`, `fork`. */\nexport const suiFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: <const O extends Omit<SuiLocalOptions, 'mode'>>(opts: O = {} as O) =>\n\t\t\tbuildPlugin({ mode: 'local', ...opts }),\n\t\tlocalRpc: (opts: Omit<SuiLocalRpcOptions, 'mode'>) =>\n\t\t\tbuildPlugin({ mode: 'local-rpc', ...opts }),\n\t},\n\tlive: {\n\t\ttestnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'testnet', ...opts }),\n\t\tmainnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'mainnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'devnet', ...opts }),\n\t\tcustom: (opts: Omit<SuiLiveOptions, 'mode' | 'network'>) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'custom', ...opts }),\n\t},\n\tfork: {\n\t\tmainnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'mainnet', ...opts }),\n\t\ttestnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'testnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'devnet', ...opts }),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers (Account/Coin/Wallet/etc.) and for\n// the sibling plugins (Walrus/Seal/Deepbook fork variants).\n// ---------------------------------------------------------------------------\n\nexport type { SuiClient, ForkAdminSurface, WaitForTransactionsReady } from './mode/shared.ts';\nexport type { ResolvedSuiNetwork } from './network-resolver.ts';\nexport type {\n\tNetworkConfig,\n\tNetworkMode,\n\tDevstackNetworkModeRegistry,\n\tDefaultNetwork,\n} from './network-config.ts';\nexport type {\n\tSuiOptions,\n\tSuiLocalOptions,\n\tSuiLocalRpcOptions,\n\tSuiLiveOptions,\n\tSuiForkOptions,\n\tSuiPluginMode,\n} from './mode/spec.ts';\nexport type { SuiNetworkConfigEntry } from './codegen.ts';\nexport type {\n\tSuiError,\n\tSuiPluginError,\n\tSuiCliError,\n\tSuiConfigError,\n\tForkUnsupportedError,\n} from './errors.ts';\n\n// Cross-plugin seams (consumed by Walrus/Seal/Deepbook fork variants\n// and by Account/Coin/Wallet/Package).\nexport {\n\tchainProbeCapabilityKey,\n\ttype ChainProbe,\n\ttype ChainProbeError,\n\ttype ChainProbeMode,\n} from '../../contracts/chain-probe.ts';\nexport {\n\tFORK_UNSUPPORTED_SURFACES,\n\twrapWithForkGuard,\n\ttype ForkLockHolder,\n} from './fork-orchestration.ts';\nexport type { SuiProbeKey, SuiSdkShim } from './chain-probe.ts';\nexport { SuiLogAttr } from './log-attrs.ts';\n\n/** The shape `Transaction.build({ client })` and every `sdk.core.*` call\n * accepts. Re-exported from `@mysten/sui/client` so callers cast\n * `sui.sdk.client as ClientWithCoreApi` without each having to know the\n * SDK subpath. */\nexport type { ClientWithCoreApi } from '@mysten/sui/client';\n// Cross-plugin seams: fork impersonation + chain-build container.\n// Consumed by `action` (Move-call execution against fork) and\n// `package` (publish-to-fork + Move-build orchestration). Consumer\n// plugins import these barrel entries rather than internal modules.\nexport {\n\tbuildForkImpersonationTransactionBytes,\n\tprepareForkImpersonationTransaction,\n\tverifyForkImpersonationSender,\n\tselectSufficientForkCoin,\n\tFORK_IMPERSONATION_GAS_BUDGET,\n\tFORK_IMPERSONATION_GAS_PRICE,\n\ttype ForkGasCoin,\n\ttype ForkImpersonationGasClient,\n} from './fork-transaction.ts';\nexport {\n\tsuiForkFaucetStrategy,\n\ttype SuiForkFaucetStrategyOptions,\n\ttype SuiForkFaucetSerialization,\n} from './fork-faucet-strategy.ts';\nexport {\n\tacquireChainBuildContainer,\n\tcontainerNameForApp,\n\tmoveBuildLockPathFor,\n\tMOVE_BUILD_LOCK_TIMEOUT_MS,\n\ttype ChainBuildContainer,\n\ttype ChainBuildContainerSpec,\n} from './chain-build-container.ts';\n\n// Cross-plugin seams: hoisted exec / move / ledger helpers.\n// Consumed by Account/Action/Coin/Deepbook/Package/Seal/Walrus. These\n// barrel entries route those consumers through the sibling plugin's\n// barrel per the `plugin-boundary` invariant, rather than reaching into\n// the deep `sui/exec`, `sui/move`, and `sui/ledger` internal modules.\nexport {\n\textractExecuteDigest,\n\tformatExecutedFailure,\n\texecuteSuiTx,\n\tisSuiStaleObjectVersionError,\n\ttype ResolvedSigner,\n\ttype ExecutedFailure,\n\ttype ExecutedReceipt,\n\ttype TransactionSignerScope,\n} from './exec/index.ts';\nexport { signAndDispatch } from './exec/sign-and-dispatch.ts';\nexport {\n\thashMoveSources,\n\trunMoveBuild,\n\tscrubLocksHost,\n\twithMoveBuildLock,\n\ttype BuildOutput,\n\ttype MoveBuildContainer,\n\ttype MoveBuildError,\n} from './move/index.ts';\nexport { currentLedgerObjectRef } from './ledger/object-ref.ts';\n"],"mappings":"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"}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
//#region src/plugins/wallet/codegen.d.ts
|
|
2
|
-
/** The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
/** The NON-secret dev-wallet connection metadata the adapter needs to
|
|
3
|
+
* reach the in-process wallet HTTP server. Routed through the deployment
|
|
4
|
+
* envelope's `values['dev-wallet']` channel — NEVER a generated file, and
|
|
5
|
+
* NEVER the secret token (that stays in the `0o600` side-channel).
|
|
6
6
|
*
|
|
7
7
|
* - `walletUrl` : the wallet HTTP server's URL (router-fronted
|
|
8
8
|
* host form when available, direct-loopback
|
|
9
9
|
* fallback otherwise).
|
|
10
|
-
* - `pairUrl` : `walletUrl` + `/#token=<32-hex>` (single
|
|
11
|
-
* source of truth for the token).
|
|
12
|
-
* - `protocolPaths` : path constants the adapter reads. Mirrored
|
|
13
|
-
* here so the adapter doesn't depend on a
|
|
14
|
-
* separate import.
|
|
15
10
|
* - `network` : the network name the wallet's accounts are
|
|
16
11
|
* scoped to (e.g. `localnet`). The dev wallet
|
|
17
12
|
* derives the wallet-standard chain (`sui:<network>`)
|
|
18
13
|
* from it at the wallet-standard boundary; devstack
|
|
19
14
|
* itself never carries the `sui:`-prefixed form.
|
|
15
|
+
* - `protocolPaths` : path constants the adapter reads. Mirrored here
|
|
16
|
+
* so the adapter doesn't depend on a separate import.
|
|
20
17
|
*/
|
|
21
|
-
interface
|
|
18
|
+
interface DevWalletConnection {
|
|
22
19
|
readonly walletUrl: string;
|
|
23
|
-
readonly pairUrl: string;
|
|
24
20
|
readonly network: string;
|
|
25
21
|
readonly protocolPaths: {
|
|
26
22
|
readonly health: string;
|
|
@@ -30,5 +26,5 @@ interface DevWalletConfig {
|
|
|
30
26
|
};
|
|
31
27
|
}
|
|
32
28
|
//#endregion
|
|
33
|
-
export {
|
|
29
|
+
export { DevWalletConnection };
|
|
34
30
|
//# sourceMappingURL=codegen.d.mts.map
|
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import { defineSimpleConstExport } from "../internal/codegen-helpers.mjs";
|
|
2
2
|
//#region src/plugins/wallet/codegen.ts
|
|
3
|
+
/** The deployment-values namespace + key the dev-wallet connection rides.
|
|
4
|
+
* Shared between the producer (this decl's `idConfigValues`) and the
|
|
5
|
+
* Vite plugin's `optionalValue(dep, DEV_WALLET_VALUES_NAMESPACE,
|
|
6
|
+
* DEV_WALLET_VALUES_KEY)` reader so the two can never drift. */
|
|
7
|
+
const DEV_WALLET_VALUES_NAMESPACE = "dev-wallet";
|
|
8
|
+
const DEV_WALLET_VALUES_KEY = "connection";
|
|
3
9
|
/**
|
|
4
10
|
* Construct the Codegenable contribution.
|
|
5
11
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* The `resolved` arg is supplied AFTER acquire (the substrate's
|
|
12
|
-
* "resolve-once" memo). At factory time the barrel passes a
|
|
13
|
-
* placeholder so the type plumbing works; at codegen time the
|
|
14
|
-
* substrate re-evaluates with the resolved values.
|
|
12
|
+
* Values-only: emits NO standalone file. It carries the non-secret
|
|
13
|
+
* dev-wallet connection metadata through `aggregate.idConfigValues`, which
|
|
14
|
+
* boot's `assembleDeployment` folds into the deployment envelope's
|
|
15
|
+
* `values['dev-wallet'].connection`. The secret token is never routed here
|
|
16
|
+
* — it stays in the `0o600` side-channel file (`pairing.ts:tokenPath`).
|
|
15
17
|
*/
|
|
16
|
-
const makeWalletCodegen = (
|
|
17
|
-
emitterName: "
|
|
18
|
+
const makeWalletCodegen = (connection) => defineSimpleConstExport({
|
|
19
|
+
emitterName: "dev-wallet-connection",
|
|
18
20
|
outputPath: "dev-wallet.ts",
|
|
19
21
|
exportName: "devWallet",
|
|
20
|
-
value:
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
value: connection,
|
|
23
|
+
aggregateOnly: true,
|
|
24
|
+
aggregate: {
|
|
25
|
+
bucket: "dev-wallet.ts",
|
|
26
|
+
kind: "dev-wallet",
|
|
27
|
+
project: () => null,
|
|
28
|
+
idConfigValues: { [DEV_WALLET_VALUES_NAMESPACE]: { [DEV_WALLET_VALUES_KEY]: connection } }
|
|
29
|
+
}
|
|
23
30
|
});
|
|
24
31
|
//#endregion
|
|
25
|
-
export { makeWalletCodegen };
|
|
32
|
+
export { DEV_WALLET_VALUES_KEY, DEV_WALLET_VALUES_NAMESPACE, makeWalletCodegen };
|
|
26
33
|
|
|
27
34
|
//# sourceMappingURL=codegen.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/wallet/codegen.ts"],"sourcesContent":["// Wallet plugin — Codegenable contribution.\n//\n// Architecture (15-wallet.md §\"Capabilities PRODUCED\"
|
|
1
|
+
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/wallet/codegen.ts"],"sourcesContent":["// Wallet plugin — Codegenable contribution.\n//\n// Architecture (15-wallet.md §\"Capabilities PRODUCED\"):\n//\n// - The browser-side dev-wallet adapter is the cross-boundary\n// consumer. It reads the dev-wallet CONNECTION metadata (server\n// URL, network, protocol paths) at startup, constructs a\n// `DevstackSignerAdapter`, and registers it with `@mysten/dapp-kit`'s\n// wallet-standard surface (in user-app bundle code — devstack itself\n// NEVER imports dapp-kit).\n//\n// - The connection metadata rides the deployment ENVELOPE's generic\n// `values['dev-wallet']` channel (NOT a generated file): boot's\n// `assembleDeployment` folds this decl's `idConfigValues` into\n// `deployment.networks[net].values['dev-wallet']`, which the Vite\n// plugin reads (`optionalValue(dep, 'dev-wallet', …)`) to assemble\n// the dev-wallet injection. The decl emits NO standalone file — it\n// is a values-only contribution.\n//\n// SECRET TOKEN (manifest-vs-token threat surface):\n//\n// - The pairing token is NEVER routed through `values` (which lands in\n// the world-readable `deployment.json`). It stays in its `0o600`\n// side-channel file (see `pairing.ts:tokenPath`); the Vite `load`\n// hook runs in Node and reads it by path. Only the NON-secret\n// connection fields (`walletUrl`, `network`, `protocolPaths`) ride\n// `values`.\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\n\nimport { defineSimpleConstExport } from '../internal/codegen-helpers.ts';\n\n// ----------------------------------------------------------------------\n// Connection metadata shape\n// ----------------------------------------------------------------------\n\n/** The NON-secret dev-wallet connection metadata the adapter needs to\n * reach the in-process wallet HTTP server. Routed through the deployment\n * envelope's `values['dev-wallet']` channel — NEVER a generated file, and\n * NEVER the secret token (that stays in the `0o600` side-channel).\n *\n * - `walletUrl` : the wallet HTTP server's URL (router-fronted\n * host form when available, direct-loopback\n * fallback otherwise).\n * - `network` : the network name the wallet's accounts are\n * scoped to (e.g. `localnet`). The dev wallet\n * derives the wallet-standard chain (`sui:<network>`)\n * from it at the wallet-standard boundary; devstack\n * itself never carries the `sui:`-prefixed form.\n * - `protocolPaths` : path constants the adapter reads. Mirrored here\n * so the adapter doesn't depend on a separate import.\n */\nexport interface DevWalletConnection {\n\treadonly walletUrl: string;\n\treadonly network: string;\n\treadonly protocolPaths: {\n\t\treadonly health: string;\n\t\treadonly accounts: string;\n\t\treadonly signTransaction: string;\n\t\treadonly signPersonalMessage: string;\n\t};\n}\n\n/** The deployment-values namespace + key the dev-wallet connection rides.\n * Shared between the producer (this decl's `idConfigValues`) and the\n * Vite plugin's `optionalValue(dep, DEV_WALLET_VALUES_NAMESPACE,\n * DEV_WALLET_VALUES_KEY)` reader so the two can never drift. */\nexport const DEV_WALLET_VALUES_NAMESPACE = 'dev-wallet' as const;\nexport const DEV_WALLET_VALUES_KEY = 'connection' as const;\n\n// ----------------------------------------------------------------------\n// Decl construction\n// ----------------------------------------------------------------------\n\n/**\n * Construct the Codegenable contribution.\n *\n * Values-only: emits NO standalone file. It carries the non-secret\n * dev-wallet connection metadata through `aggregate.idConfigValues`, which\n * boot's `assembleDeployment` folds into the deployment envelope's\n * `values['dev-wallet'].connection`. The secret token is never routed here\n * — it stays in the `0o600` side-channel file (`pairing.ts:tokenPath`).\n */\nexport const makeWalletCodegen = (\n\tconnection: DevWalletConnection,\n): CodegenableDecl<'dev-wallet-connection'> =>\n\tdefineSimpleConstExport({\n\t\temitterName: 'dev-wallet-connection',\n\t\t// Never written: `aggregateOnly` skips the standalone file, and the\n\t\t// aggregate carries ONLY `idConfigValues` (no `bucket` file emit at\n\t\t// `assembleDeployment`, which special-cases `config.ts` / `accounts.ts`).\n\t\toutputPath: 'dev-wallet.ts',\n\t\texportName: 'devWallet',\n\t\tvalue: connection,\n\t\taggregateOnly: true,\n\t\taggregate: {\n\t\t\t// A neutral bucket name: `assembleDeployment` only PROJECTS the\n\t\t\t// `config.ts` / `accounts.ts` buckets into the typed deployment\n\t\t\t// fields; every other bucket contributes via `idConfigValues` only.\n\t\t\t// The committed-tree emit cycle never sees this decl (boot emits\n\t\t\t// only the STATIC id-free contributions + the deployment file).\n\t\t\tbucket: 'dev-wallet.ts',\n\t\t\tkind: 'dev-wallet',\n\t\t\t// Pass-through: the connection rides `idConfigValues`, so the\n\t\t\t// projection contributes nothing to a file bucket.\n\t\t\tproject: () => null,\n\t\t\t// The non-secret connection metadata folded into the deployment\n\t\t\t// envelope's `values['dev-wallet'].connection` by `assembleDeployment`.\n\t\t\tidConfigValues: {\n\t\t\t\t[DEV_WALLET_VALUES_NAMESPACE]: {\n\t\t\t\t\t[DEV_WALLET_VALUES_KEY]: connection,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t});\n"],"mappings":";;;;;;AAmEA,MAAa,8BAA8B;AAC3C,MAAa,wBAAwB;;;;;;;;;;AAerC,MAAa,qBACZ,eAEA,wBAAwB;CACvB,aAAa;CAIb,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,eAAe;CACf,WAAW;EAMV,QAAQ;EACR,MAAM;EAGN,eAAe;EAGf,gBAAgB,GACd,8BAA8B,GAC7B,wBAAwB,WAC1B,EACD;CACD;AACD,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { WALLET_AUTH_HEADER, WALLET_BEARER_PREFIX, WALLET_ENDPOINT_KEY, WALLET_E
|
|
|
2
2
|
import { Plugin, ResourceRef } from "../../substrate/plugin.mjs";
|
|
3
3
|
import { SuiOptions } from "../sui/mode/spec.mjs";
|
|
4
4
|
import { SuiClient } from "../sui/mode/shared.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { DevWalletConnection } from "./codegen.mjs";
|
|
6
6
|
import { WalletBootError, WalletBootPhase, WalletError, WalletRequestError, WalletRequestPhase } from "./errors.mjs";
|
|
7
7
|
import { PairingToken } from "./pairing.mjs";
|
|
8
8
|
import { WalletServerHandle } from "./server.mjs";
|
|
@@ -40,8 +40,9 @@ import { WALLET_ACCOUNTS_ALL, WalletAccountMember, WalletOptions, WalletValue }
|
|
|
40
40
|
*
|
|
41
41
|
* - Constant-time bearer compare on every request.
|
|
42
42
|
*
|
|
43
|
-
* - Token file `0o600`.
|
|
44
|
-
* `
|
|
43
|
+
* - Token file `0o600`. The connection metadata rides the deployment
|
|
44
|
+
* envelope's `values['dev-wallet']`; the token NEVER leaves the
|
|
45
|
+
* `0o600` side-channel file.
|
|
45
46
|
*/
|
|
46
47
|
declare function wallet<const Accounts extends ReadonlyArray<WalletAccountMember>>(opts: Omit<WalletOptions, 'accounts'> & {
|
|
47
48
|
readonly accounts: Accounts;
|
|
@@ -116,7 +116,7 @@ function makeWalletMember(opts, accounts) {
|
|
|
116
116
|
routedAppOrigin
|
|
117
117
|
});
|
|
118
118
|
ctx.snapshotExtra(makeWalletSnapshotable());
|
|
119
|
-
ctx.codegen(makeWalletCodegen(resolved.
|
|
119
|
+
ctx.codegen(makeWalletCodegen(resolved.connection));
|
|
120
120
|
ctx.endpoint(makeWalletRoutable({
|
|
121
121
|
app: identity.app,
|
|
122
122
|
stack: identity.stack,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/wallet/index.ts"],"sourcesContent":["// Wallet plugin — barrel + `wallet(opts?)` factory.\n//\n// Architecture (15-wallet.md):\n//\n// The wallet bridges devstack (the supervisor host) and the\n// browser-side dev-wallet adapter (in the separate `dev-wallet`\n// package). Two parts:\n//\n// 1. The IN-PROCESS HTTP server. Owned here. Boot at acquire,\n// serve `/api/v1/devstack/*` routes (health, accounts,\n// sign-transaction, sign-personal-message).\n//\n// 2. The BROWSER-SIDE ADAPTER. Owned by `dev-wallet`. Reads the\n// codegen-emitted `dapp-kit/config.ts`, constructs a\n// `DevstackSignerAdapter`, registers it with `@mysten/dapp-\n// kit`'s wallet-standard surface.\n//\n// The HTTP protocol is the ONE cross-boundary contract. THIS PACKAGE\n// NEVER IMPORTS `@mysten/dapp-kit*` OR `@mysten/wallet-standard`.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.snapshotExtra` — pairing token under `wallet/token`.\n// 2. `ctx.codegen` — `dapp-kit-config` bindings (the dev-wallet\n// adapter consumes this). Sensitive flag set — 0o600 + gitignore.\n// 3. `ctx.endpoint` — wallet UI URL on the stack-scoped router.\n\nimport { Effect } from 'effect';\n\nimport { definePlugin, resource } from '../../api/define-plugin.ts';\nimport { attachPluginExpander } from '../../contracts/plugin-expander.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tDEFAULT_PORT_WINDOW,\n\tPortBrokerService,\n} from '../../substrate/runtime/port-broker/index.ts';\nimport { renderUrl, routedHostname } from '../../substrate/runtime/routed-url.ts';\nimport { suiResource } from '../sui/index.ts';\nimport type { AccountResourceId } from '../account/index.ts';\nimport {\n\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\tHOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n} from '../host-service/index.ts';\n\nimport { makeWalletCodegen } from './codegen.ts';\nimport { walletBootError } from './errors.ts';\nimport { makeWalletRoutable, WALLET_ENTRYPOINT_PORT, WALLET_ROUTE_ROLE } from './routable.ts';\nimport { makeWalletSnapshotable } from './snapshot.ts';\nimport {\n\tacquireWallet,\n\tWALLET_ACCOUNTS_ALL,\n\ttype WalletAcquireContext,\n\ttype WalletAccountMember,\n\ttype WalletOptions,\n\ttype WalletValue,\n} from './service.ts';\nimport type { AnyPlugin } from '../../substrate/plugin.ts';\nimport { PluginContext } from '../../substrate/plugin-ctx.ts';\n\n/** Wallet's expander contributes through the substrate-owned\n * `PluginExpander` contract (`contracts/plugin-expander.ts`). The\n * composer dispatches every member's expander uniformly — wallet has\n * no special-case wiring in `api/define-devstack.ts`.\n *\n * Compose-time symmetry: any plugin needing the \"rewrite this\n * placeholder once the full member tuple is known\" rewrite uses the\n * same `attachPluginExpander(...)` seam wallet uses below. */\n\nconst ACCOUNT_RESOURCE_ID_PREFIX = 'account/' as const;\n\n/** Type-narrowing predicate on a plugin's resource id. The account\n * plugin's `AccountResourceId<Name>` template-literal type IS the\n * substrate-owned discriminator (12-account.md \"resource id flows into\n * the on-disk path, the manifest key, container labels, and generated\n * TypeScript exports\") — every account member's `id` reduces to\n * `account/${Name}`. Probing through this typed predicate (vs. a bare\n * `startsWith` whose narrowing returns `string`) prevents misclassifying\n * a future plugin whose id happens to start with `account/` but is NOT\n * the account-plugin shape, AND surfaces a compile error if the\n * account-id prefix convention ever changes. */\nconst isAccountResourceMember = (\n\tmember: AnyPlugin,\n): member is AnyPlugin & { readonly id: AccountResourceId<string> } =>\n\tmember.id.startsWith(ACCOUNT_RESOURCE_ID_PREFIX);\n\n// ----------------------------------------------------------------------\n// Resource identity\n// ----------------------------------------------------------------------\n\n/** The wallet plugin's resource identity. ONE per stack (15-wallet.md\n * \"singleton per stack\"). The id is `'wallet'` (singular). */\nconst walletResource = resource<'wallet', WalletValue>('wallet');\n\n// ----------------------------------------------------------------------\n// User-facing factory\n// ----------------------------------------------------------------------\n\n/**\n * Construct the wallet plugin.\n *\n * Two parts of the wallet:\n *\n * - HERE: HTTP server + token + pairing protocol + codegen.\n * - DEV-WALLET PACKAGE: `@mysten/dapp-kit`-shaped adapter the\n * user-app's frontend bundle imports.\n *\n * Distilled-doc invariant (15-wallet.md \"Always explicit\"): the\n * composer NEVER auto-mounts the wallet. The user calls `wallet()`,\n * `wallet({ accounts: 'all' })`, or `wallet({ accounts: [alice, bob] })`\n * and passes the result to `defineDevstack(...)`.\n *\n * ### Security defaults\n *\n * - `bindAddress: '0.0.0.0'`. The router runs in Docker, so on\n * native Linux it reaches this host process through the Docker\n * host-gateway address instead of host loopback. The published\n * wallet URL remains stack-scoped through the router.\n *\n * - Origin allowlist is the router-fronted dev-server origin for\n * this stack plus any explicit `allowedOrigins`. The wallet does\n * not auto-allowlist a bare `http://localhost:<vite-port>` form —\n * `localhost` is not stack-scoped, so a sibling stack on the same\n * port could pair with this wallet. See `origin-policy.ts`.\n *\n * - Pairing token in URL fragment only (`#token=<32-hex>`). Never\n * in query params (would land in access logs / referrers).\n *\n * - Constant-time bearer compare on every request.\n *\n * - Token file `0o600`. Codegen output (`dapp-kit/config.ts`)\n * `0o600` + gitignored via `sensitive: true`.\n */\nexport function wallet<const Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: Accounts },\n): ReturnType<typeof makeWalletMember<Accounts>>;\nexport function wallet(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: typeof WALLET_ACCOUNTS_ALL },\n): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(opts?: WalletOptions): AnyPlugin {\n\tconst resolvedOpts: WalletOptions =\n\t\topts ?? ({ accounts: WALLET_ACCOUNTS_ALL } satisfies WalletOptions);\n\tif (resolvedOpts.accounts === WALLET_ACCOUNTS_ALL) {\n\t\t// Deferred placeholder. `dependsOn` carries only `[suiResource]` —\n\t\t// the composer rewrites the member once it knows which account\n\t\t// members are in the stack (api-surface-design §4 D6). Without\n\t\t// composer expansion, the wallet would race account funding;\n\t\t// WITH composer expansion, every per-account dependency edge is\n\t\t// in place by the time the dep-graph builds.\n\t\t//\n\t\t// Type-level: the placeholder's `dependsOn` is `[suiResource]`. A\n\t\t// wider `ReadonlyArray<account/${string}>` would widen the\n\t\t// stack-level `MissingProviders` check to the template literal\n\t\t// (which never reduces to any concrete `account/<name>`), so\n\t\t// the placeholder MUST stay narrow.\n\t\t//\n\t\t// Runtime: the symbol-keyed expander is attached as a value-\n\t\t// only property; the factory's declared return type\n\t\t// intentionally does NOT surface it (a `unique symbol`-keyed\n\t\t// member field would leak into the user's inferred Stack type\n\t\t// and trigger TS2742 \"type cannot be named without a reference\n\t\t// to ./node_modules/.../plugins/wallet\" at every example's\n\t\t// default export).\n\t\tconst placeholder = makeWalletMember(resolvedOpts, [] as const);\n\t\tattachPluginExpander(placeholder, (members) => {\n\t\t\t// Filter the full composed member tuple to the per-account\n\t\t\t// resource members the wallet would otherwise have to receive\n\t\t\t// at factory call. `isAccountResourceMember` narrows on the\n\t\t\t// `AccountResourceId<Name>` template-literal type — the typed\n\t\t\t// discriminator the account plugin's barrel exposes via\n\t\t\t// `AccountResourceId<Name>` — so a future plugin whose id\n\t\t\t// accidentally starts with `account/` cannot masquerade.\n\t\t\tconst accountMembers: Array<WalletAccountMember> = [];\n\t\t\tfor (const m of members) {\n\t\t\t\tif (isAccountResourceMember(m)) {\n\t\t\t\t\taccountMembers.push(m as unknown as WalletAccountMember);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn makeWalletMember({ ...resolvedOpts, accounts: accountMembers }, accountMembers);\n\t\t});\n\t\treturn placeholder;\n\t}\n\treturn makeWalletMember(resolvedOpts, resolvedOpts.accounts);\n}\n\nfunction makeWalletMember<Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: WalletOptions,\n\taccounts: Accounts,\n) {\n\t// Dependencies MUST include every account ref (15-wallet.md\n\t// \"upstreamKeys MUST include suiResource.id + every account id\" — same\n\t// load-bearing invariant). The substrate's topological scheduler\n\t// uses `dependsOn` to drive build order; without including the\n\t// account refs here, the wallet would race account funding and the\n\t// first `signTransaction` would fail with `address-not-found`.\n\t//\n\tconst dependencies = [suiResource, ...accounts] as const;\n\n\t// The resolved-opts shape acquireWallet sees has accounts pinned to\n\t// the resolved tuple — `'all'` is purely a user-surface convenience\n\t// the composer never propagates to the supervisor.\n\tconst resolvedOpts: WalletOptions<Accounts> = { ...opts, accounts };\n\n\treturn definePlugin({\n\t\tid: walletResource.id,\n\t\tdependsOn: dependencies,\n\t\t// The HTTP server is a long-lived host process; per-request\n\t\t// handlers fork off the supervisor-context fiber but the server\n\t\t// itself lives for the stack's lifetime.\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\t// `deps` auto-infers from the resolved `dependsOn`: the hard Sui\n\t\t// ordering edge (discarded) followed by the resolved account\n\t\t// values. `ctx` arrives via the `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\t// Pull identity, the stack-paths bundle, and the port-\n\t\t\t\t// broker from the supervisor-provided substrate context.\n\t\t\t\t// `StackPathsService.stackRoot` is the on-disk root for\n\t\t\t\t// per-stack runtime artifacts (incl. `wallet/token`); the\n\t\t\t\t// port broker is per-stack (Layer-driven, one instance per\n\t\t\t\t// stack scope); the wallet's scope hangs off the\n\t\t\t\t// supervisor's `acquireScope`, so any release finalizer\n\t\t\t\t// installed by these primitives unwinds with the rest of\n\t\t\t\t// the plugin's resources on cycle / teardown.\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst paths = yield* StackPathsService;\n\t\t\t\tconst portBroker = yield* PortBrokerService;\n\n\t\t\t\t// The first dependency is the hard Sui ordering edge; the\n\t\t\t\t// remaining values mirror the explicit account tuple.\n\t\t\t\tconst [, ...resolvedAccounts] = deps;\n\t\t\t\tconst routerFrontedUrl = yield* routedHostname(identity, WALLET_ROUTE_ROLE).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: WALLET_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet router URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst routedAppOrigin = yield* routedHostname(\n\t\t\t\t\tidentity,\n\t\t\t\t\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: HOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet app-origin URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst acquireCtx: WalletAcquireContext = {\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\tstateRoot: paths.stackRoot,\n\t\t\t\t\tallocatePort: (preferred, probeHost) =>\n\t\t\t\t\t\tportBroker\n\t\t\t\t\t\t\t.allocate({\n\t\t\t\t\t\t\t\towner: 'wallet',\n\t\t\t\t\t\t\t\t// Pin the wallet's UX-meaningful range so the dev wallet\n\t\t\t\t\t\t\t\t// adapter's auto-connect-port heuristic keeps working even\n\t\t\t\t\t\t\t\t// after a fallback scan. The broker scan window is half-open\n\t\t\t\t\t\t\t\t// `[start, start + size)`, so `DEFAULT_PORT_WINDOW`\n\t\t\t\t\t\t\t\t// (start 39200, size 1000) covers 39200..40199 inclusive.\n\t\t\t\t\t\t\t\t// Reuse the broker's exported default rather than a local\n\t\t\t\t\t\t\t\t// literal so the two cannot silently drift (pinned by\n\t\t\t\t\t\t\t\t// `test/plugins/wallet/port-window-pin.test.ts`).\n\t\t\t\t\t\t\t\twindowHint: DEFAULT_PORT_WINDOW,\n\t\t\t\t\t\t\t\tpreferredPort: preferred,\n\t\t\t\t\t\t\t\t...(probeHost === undefined ? {} : { probeHost }),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.map((alloc) => alloc.port),\n\t\t\t\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\t\t\t\tphase: 'allocate-port',\n\t\t\t\t\t\t\t\t\t\tmessage: `port-broker allocate failed: ${err.detail}`,\n\t\t\t\t\t\t\t\t\t\thint:\n\t\t\t\t\t\t\t\t\t\t\terr.reason === 'preferred-busy'\n\t\t\t\t\t\t\t\t\t\t\t\t? 'another plugin in this stack is using your preferred port; omit `port` to let the broker pick.'\n\t\t\t\t\t\t\t\t\t\t\t\t: err.reason === 'no-free-port'\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'the wallet port window is exhausted; check for stray devstack supervisors holding ports.'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: 'bind-probe failed — likely a privileged port or jail restriction.',\n\t\t\t\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\tresolveAccounts: () => Effect.succeed(resolvedAccounts),\n\t\t\t\t\trouterFrontedUrl,\n\t\t\t\t\troutedAppOrigin,\n\t\t\t\t};\n\n\t\t\t\tconst resolved = yield* acquireWallet(resolvedOpts, acquireCtx);\n\n\t\t\t\t// Emit the wallet's contributions inline. `resolved` is the\n\t\t\t\t// just-acquired `WalletValue`; `runtime.identity.{app,stack}`\n\t\t\t\t// map to the `identity` already held here\n\t\t\t\t// (`yield* IdentityContext`). Emit each decl via its matching\n\t\t\t\t// buffered verb IN ORDER (snapshotable → `ctx.snapshotExtra`,\n\t\t\t\t// codegenable → `ctx.codegen`, routable → `ctx.endpoint`); decl\n\t\t\t\t// shapes are load-bearing. The verbs are void and buffer for the\n\t\t\t\t// supervisor's post-start replay.\n\t\t\t\tctx.snapshotExtra(makeWalletSnapshotable());\n\t\t\t\tctx.codegen(makeWalletCodegen(resolved.bindings));\n\t\t\t\tctx.endpoint(\n\t\t\t\t\tmakeWalletRoutable({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tport: resolved.localPort,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn resolved;\n\t\t\t}),\n\t});\n}\n\n// ----------------------------------------------------------------------\n// Re-exports\n// ----------------------------------------------------------------------\n\nexport type {\n\tWalletOptions,\n\tWalletValue,\n\tWalletAccountMember,\n\tWalletAccountsAll,\n} from './service.ts';\nexport { WALLET_ACCOUNTS_ALL } from './service.ts';\nexport type { DevWalletConfig } from './codegen.ts';\nexport {\n\tWalletHttpPath,\n\tWALLET_PROTOCOL_PREFIX,\n\tWALLET_AUTH_HEADER,\n\tWALLET_BEARER_PREFIX,\n\tWALLET_TOKEN_FRAGMENT_KEY,\n\tWALLET_TOKEN_HEX_LENGTH,\n\tSignRequestSchema,\n\tSignResponseSchema,\n\tHealthResponseSchema,\n\tAccountsResponseSchema,\n\tAccountSummarySchema,\n\tErrorResponseSchema,\n\tSuiAddressSchema,\n\tBase64Schema,\n\tSignatureSchemeSchema,\n\tAccountSourceSchema,\n\ttype WalletHttpPathValue,\n\ttype SignRequest,\n\ttype SignResponse,\n\ttype HealthResponse,\n\ttype AccountsResponse,\n\ttype AccountSummary,\n\ttype ErrorResponse,\n} from './protocol.ts';\nexport type {\n\tWalletError,\n\tWalletBootError,\n\tWalletBootPhase,\n\tWalletRequestError,\n\tWalletRequestPhase,\n} from './errors.ts';\nexport type { OriginPolicy, OriginPolicyInputs, OriginCheckResult } from './origin-policy.ts';\nexport { resolveOriginPolicy, checkOrigin, corsHeadersFor } from './origin-policy.ts';\nexport type { PairingToken } from './pairing.ts';\nexport {\n\tmintToken,\n\tacquirePairingToken,\n\ttokenPath,\n\tcomposePairUrl,\n\tparsePairUrl,\n\tparseBearerHeader,\n\tsafeBearerEquals,\n\tredactToken,\n} from './pairing.ts';\nexport { WALLET_ENDPOINT_NAME, WALLET_ENDPOINT_KEY, makeWalletRoutable } from './routable.ts';\nexport {\n\tdispatch,\n\tstartHttpServer,\n\tMAX_BODY_BYTES,\n\ttype WalletRequest,\n\ttype WalletResponse,\n\ttype WalletServerConfig,\n\ttype WalletServerHandle,\n} from './server.ts';\nexport { makeWalletCodegen } from './codegen.ts';\nexport { makeWalletSnapshotable } from './snapshot.ts';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAM,6BAA6B;;;;;;;;;;;AAYnC,MAAM,2BACL,WAEA,OAAO,GAAG,WAAW,0BAA0B;;;AAQhD,MAAM,iBAAiB,SAAgC,QAAQ;AAgD/D,SAAgB,OAAO,MAAiC;CACvD,MAAM,eACL,QAAS,EAAE,UAAA,MAA8B;CAC1C,IAAI,aAAa,aAAA,OAAkC;EAqBlD,MAAM,cAAc,iBAAiB,cAAc,CAAC,CAAU;EAC9D,qBAAqB,cAAc,YAAY;GAQ9C,MAAM,iBAA6C,CAAC;GACpD,KAAK,MAAM,KAAK,SACf,IAAI,wBAAwB,CAAC,GAC5B,eAAe,KAAK,CAAmC;GAGzD,OAAO,iBAAiB;IAAE,GAAG;IAAc,UAAU;GAAe,GAAG,cAAc;EACtF,CAAC;EACD,OAAO;CACR;CACA,OAAO,iBAAiB,cAAc,aAAa,QAAQ;AAC5D;AAEA,SAAS,iBACR,MACA,UACC;CAQD,MAAM,eAAe,CAAC,aAAa,GAAG,QAAQ;CAK9C,MAAM,eAAwC;EAAE,GAAG;EAAM;CAAS;CAElE,OAAO,aAAa;EACnB,IAAI,eAAe;EACnB,WAAW;EAIX,MAAM;EACN,SAAS;EAIT,QAAQ,SACP,OAAO,IAAI,aAAa;GACvB,MAAM,MAAM,OAAO;GAUnB,MAAM,WAAW,OAAO;GACxB,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAa,OAAO;GAI1B,MAAM,GAAG,GAAG,oBAAoB;GAChC,MAAM,mBAAmB,OAAO,eAAe,UAAA,KAA2B,CAAC,CAAC,KAC3E,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,0CAA0C,IAAI;IACvD,OAAO;GACR,CAAC,CACF,CACD;GACA,MAAM,kBAAkB,OAAO,eAC9B,UAAA,KAED,CAAC,CAAC,KACD,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,8CAA8C,IAAI;IAC3D,OAAO;GACR,CAAC,CACF,CACD;GA4CA,MAAM,WAAW,OAAO,cAAc,cAAc;IAzCnD,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,SAAS,SAAS;IAClB,WAAW,MAAM;IACjB,eAAe,WAAW,cACzB,WACE,SAAS;KACT,OAAO;KASP,YAAY;KACZ,eAAe;KACf,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;IAChD,CAAC,CAAC,CACD,KACA,OAAO,KAAK,UAAU,MAAM,IAAI,GAChC,OAAO,UAAU,QAChB,gBAAgB;KACf,OAAO;KACP,SAAS,gCAAgC,IAAI;KAC7C,MACC,IAAI,WAAW,mBACZ,mGACA,IAAI,WAAW,iBACd,6FACA;KACL,OAAO;IACR,CAAC,CACF,CACD;IACF,uBAAuB,OAAO,QAAQ,gBAAgB;IACtD;IACA;GAG4D,CAAC;GAU9D,IAAI,cAAc,uBAAuB,CAAC;GAC1C,IAAI,QAAQ,kBAAkB,SAAS,QAAQ,CAAC;GAChD,IAAI,SACH,mBAAmB;IAClB,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,MAAM,SAAS;GAChB,CAAC,CACF;GAEA,OAAO;EACR,CAAC;CACH,CAAC;AACF"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/wallet/index.ts"],"sourcesContent":["// Wallet plugin — barrel + `wallet(opts?)` factory.\n//\n// Architecture (15-wallet.md):\n//\n// The wallet bridges devstack (the supervisor host) and the\n// browser-side dev-wallet adapter (in the separate `dev-wallet`\n// package). Two parts:\n//\n// 1. The IN-PROCESS HTTP server. Owned here. Boot at acquire,\n// serve `/api/v1/devstack/*` routes (health, accounts,\n// sign-transaction, sign-personal-message).\n//\n// 2. The BROWSER-SIDE ADAPTER. Owned by `dev-wallet`. Reads the\n// dev-wallet connection metadata (off the deployment envelope's\n// `values['dev-wallet']`), constructs a `DevstackSignerAdapter`,\n// registers it with `@mysten/dapp-kit`'s wallet-standard surface.\n//\n// The HTTP protocol is the ONE cross-boundary contract. THIS PACKAGE\n// NEVER IMPORTS `@mysten/dapp-kit*` OR `@mysten/wallet-standard`.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.snapshotExtra` — pairing token under `wallet/token`.\n// 2. `ctx.codegen` — `dev-wallet-connection` values (the dev-wallet\n// adapter's NON-secret connection metadata, folded into the\n// deployment envelope's `values['dev-wallet']`; the secret token\n// stays in its 0o600 side-channel file).\n// 3. `ctx.endpoint` — wallet UI URL on the stack-scoped router.\n\nimport { Effect } from 'effect';\n\nimport { definePlugin, resource } from '../../api/define-plugin.ts';\nimport { attachPluginExpander } from '../../contracts/plugin-expander.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tDEFAULT_PORT_WINDOW,\n\tPortBrokerService,\n} from '../../substrate/runtime/port-broker/index.ts';\nimport { renderUrl, routedHostname } from '../../substrate/runtime/routed-url.ts';\nimport { suiResource } from '../sui/index.ts';\nimport type { AccountResourceId } from '../account/index.ts';\nimport {\n\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\tHOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n} from '../host-service/index.ts';\n\nimport { makeWalletCodegen } from './codegen.ts';\nimport { walletBootError } from './errors.ts';\nimport { makeWalletRoutable, WALLET_ENTRYPOINT_PORT, WALLET_ROUTE_ROLE } from './routable.ts';\nimport { makeWalletSnapshotable } from './snapshot.ts';\nimport {\n\tacquireWallet,\n\tWALLET_ACCOUNTS_ALL,\n\ttype WalletAcquireContext,\n\ttype WalletAccountMember,\n\ttype WalletOptions,\n\ttype WalletValue,\n} from './service.ts';\nimport type { AnyPlugin } from '../../substrate/plugin.ts';\nimport { PluginContext } from '../../substrate/plugin-ctx.ts';\n\n/** Wallet's expander contributes through the substrate-owned\n * `PluginExpander` contract (`contracts/plugin-expander.ts`). The\n * composer dispatches every member's expander uniformly — wallet has\n * no special-case wiring in `api/define-devstack.ts`.\n *\n * Compose-time symmetry: any plugin needing the \"rewrite this\n * placeholder once the full member tuple is known\" rewrite uses the\n * same `attachPluginExpander(...)` seam wallet uses below. */\n\nconst ACCOUNT_RESOURCE_ID_PREFIX = 'account/' as const;\n\n/** Type-narrowing predicate on a plugin's resource id. The account\n * plugin's `AccountResourceId<Name>` template-literal type IS the\n * substrate-owned discriminator (12-account.md \"resource id flows into\n * the on-disk path, the manifest key, container labels, and generated\n * TypeScript exports\") — every account member's `id` reduces to\n * `account/${Name}`. Probing through this typed predicate (vs. a bare\n * `startsWith` whose narrowing returns `string`) prevents misclassifying\n * a future plugin whose id happens to start with `account/` but is NOT\n * the account-plugin shape, AND surfaces a compile error if the\n * account-id prefix convention ever changes. */\nconst isAccountResourceMember = (\n\tmember: AnyPlugin,\n): member is AnyPlugin & { readonly id: AccountResourceId<string> } =>\n\tmember.id.startsWith(ACCOUNT_RESOURCE_ID_PREFIX);\n\n// ----------------------------------------------------------------------\n// Resource identity\n// ----------------------------------------------------------------------\n\n/** The wallet plugin's resource identity. ONE per stack (15-wallet.md\n * \"singleton per stack\"). The id is `'wallet'` (singular). */\nconst walletResource = resource<'wallet', WalletValue>('wallet');\n\n// ----------------------------------------------------------------------\n// User-facing factory\n// ----------------------------------------------------------------------\n\n/**\n * Construct the wallet plugin.\n *\n * Two parts of the wallet:\n *\n * - HERE: HTTP server + token + pairing protocol + codegen.\n * - DEV-WALLET PACKAGE: `@mysten/dapp-kit`-shaped adapter the\n * user-app's frontend bundle imports.\n *\n * Distilled-doc invariant (15-wallet.md \"Always explicit\"): the\n * composer NEVER auto-mounts the wallet. The user calls `wallet()`,\n * `wallet({ accounts: 'all' })`, or `wallet({ accounts: [alice, bob] })`\n * and passes the result to `defineDevstack(...)`.\n *\n * ### Security defaults\n *\n * - `bindAddress: '0.0.0.0'`. The router runs in Docker, so on\n * native Linux it reaches this host process through the Docker\n * host-gateway address instead of host loopback. The published\n * wallet URL remains stack-scoped through the router.\n *\n * - Origin allowlist is the router-fronted dev-server origin for\n * this stack plus any explicit `allowedOrigins`. The wallet does\n * not auto-allowlist a bare `http://localhost:<vite-port>` form —\n * `localhost` is not stack-scoped, so a sibling stack on the same\n * port could pair with this wallet. See `origin-policy.ts`.\n *\n * - Pairing token in URL fragment only (`#token=<32-hex>`). Never\n * in query params (would land in access logs / referrers).\n *\n * - Constant-time bearer compare on every request.\n *\n * - Token file `0o600`. The connection metadata rides the deployment\n * envelope's `values['dev-wallet']`; the token NEVER leaves the\n * `0o600` side-channel file.\n */\nexport function wallet<const Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: Accounts },\n): ReturnType<typeof makeWalletMember<Accounts>>;\nexport function wallet(\n\topts: Omit<WalletOptions, 'accounts'> & { readonly accounts: typeof WALLET_ACCOUNTS_ALL },\n): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(): ReturnType<typeof makeWalletMember<readonly []>>;\nexport function wallet(opts?: WalletOptions): AnyPlugin {\n\tconst resolvedOpts: WalletOptions =\n\t\topts ?? ({ accounts: WALLET_ACCOUNTS_ALL } satisfies WalletOptions);\n\tif (resolvedOpts.accounts === WALLET_ACCOUNTS_ALL) {\n\t\t// Deferred placeholder. `dependsOn` carries only `[suiResource]` —\n\t\t// the composer rewrites the member once it knows which account\n\t\t// members are in the stack (api-surface-design §4 D6). Without\n\t\t// composer expansion, the wallet would race account funding;\n\t\t// WITH composer expansion, every per-account dependency edge is\n\t\t// in place by the time the dep-graph builds.\n\t\t//\n\t\t// Type-level: the placeholder's `dependsOn` is `[suiResource]`. A\n\t\t// wider `ReadonlyArray<account/${string}>` would widen the\n\t\t// stack-level `MissingProviders` check to the template literal\n\t\t// (which never reduces to any concrete `account/<name>`), so\n\t\t// the placeholder MUST stay narrow.\n\t\t//\n\t\t// Runtime: the symbol-keyed expander is attached as a value-\n\t\t// only property; the factory's declared return type\n\t\t// intentionally does NOT surface it (a `unique symbol`-keyed\n\t\t// member field would leak into the user's inferred Stack type\n\t\t// and trigger TS2742 \"type cannot be named without a reference\n\t\t// to ./node_modules/.../plugins/wallet\" at every example's\n\t\t// default export).\n\t\tconst placeholder = makeWalletMember(resolvedOpts, [] as const);\n\t\tattachPluginExpander(placeholder, (members) => {\n\t\t\t// Filter the full composed member tuple to the per-account\n\t\t\t// resource members the wallet would otherwise have to receive\n\t\t\t// at factory call. `isAccountResourceMember` narrows on the\n\t\t\t// `AccountResourceId<Name>` template-literal type — the typed\n\t\t\t// discriminator the account plugin's barrel exposes via\n\t\t\t// `AccountResourceId<Name>` — so a future plugin whose id\n\t\t\t// accidentally starts with `account/` cannot masquerade.\n\t\t\tconst accountMembers: Array<WalletAccountMember> = [];\n\t\t\tfor (const m of members) {\n\t\t\t\tif (isAccountResourceMember(m)) {\n\t\t\t\t\taccountMembers.push(m as unknown as WalletAccountMember);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn makeWalletMember({ ...resolvedOpts, accounts: accountMembers }, accountMembers);\n\t\t});\n\t\treturn placeholder;\n\t}\n\treturn makeWalletMember(resolvedOpts, resolvedOpts.accounts);\n}\n\nfunction makeWalletMember<Accounts extends ReadonlyArray<WalletAccountMember>>(\n\topts: WalletOptions,\n\taccounts: Accounts,\n) {\n\t// Dependencies MUST include every account ref (15-wallet.md\n\t// \"upstreamKeys MUST include suiResource.id + every account id\" — same\n\t// load-bearing invariant). The substrate's topological scheduler\n\t// uses `dependsOn` to drive build order; without including the\n\t// account refs here, the wallet would race account funding and the\n\t// first `signTransaction` would fail with `address-not-found`.\n\t//\n\tconst dependencies = [suiResource, ...accounts] as const;\n\n\t// The resolved-opts shape acquireWallet sees has accounts pinned to\n\t// the resolved tuple — `'all'` is purely a user-surface convenience\n\t// the composer never propagates to the supervisor.\n\tconst resolvedOpts: WalletOptions<Accounts> = { ...opts, accounts };\n\n\treturn definePlugin({\n\t\tid: walletResource.id,\n\t\tdependsOn: dependencies,\n\t\t// The HTTP server is a long-lived host process; per-request\n\t\t// handlers fork off the supervisor-context fiber but the server\n\t\t// itself lives for the stack's lifetime.\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\t// `deps` auto-infers from the resolved `dependsOn`: the hard Sui\n\t\t// ordering edge (discarded) followed by the resolved account\n\t\t// values. `ctx` arrives via the `PluginContext` service.\n\t\tstart: (deps) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst ctx = yield* PluginContext;\n\t\t\t\t// Pull identity, the stack-paths bundle, and the port-\n\t\t\t\t// broker from the supervisor-provided substrate context.\n\t\t\t\t// `StackPathsService.stackRoot` is the on-disk root for\n\t\t\t\t// per-stack runtime artifacts (incl. `wallet/token`); the\n\t\t\t\t// port broker is per-stack (Layer-driven, one instance per\n\t\t\t\t// stack scope); the wallet's scope hangs off the\n\t\t\t\t// supervisor's `acquireScope`, so any release finalizer\n\t\t\t\t// installed by these primitives unwinds with the rest of\n\t\t\t\t// the plugin's resources on cycle / teardown.\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\tconst paths = yield* StackPathsService;\n\t\t\t\tconst portBroker = yield* PortBrokerService;\n\n\t\t\t\t// The first dependency is the hard Sui ordering edge; the\n\t\t\t\t// remaining values mirror the explicit account tuple.\n\t\t\t\tconst [, ...resolvedAccounts] = deps;\n\t\t\t\tconst routerFrontedUrl = yield* routedHostname(identity, WALLET_ROUTE_ROLE).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: WALLET_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet router URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst routedAppOrigin = yield* routedHostname(\n\t\t\t\t\tidentity,\n\t\t\t\t\tHOST_SERVICE_DEFAULT_ENDPOINT_NAME,\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map((hostname) =>\n\t\t\t\t\t\trenderUrl({\n\t\t\t\t\t\t\tprotocol: 'http',\n\t\t\t\t\t\t\thostname,\n\t\t\t\t\t\t\tport: HOST_SERVICE_DEFAULT_ENTRYPOINT_PORT,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\tphase: 'route-url',\n\t\t\t\t\t\t\tmessage: `wallet app-origin URL construction failed: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst acquireCtx: WalletAcquireContext = {\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\tstateRoot: paths.stackRoot,\n\t\t\t\t\tallocatePort: (preferred, probeHost) =>\n\t\t\t\t\t\tportBroker\n\t\t\t\t\t\t\t.allocate({\n\t\t\t\t\t\t\t\towner: 'wallet',\n\t\t\t\t\t\t\t\t// Pin the wallet's UX-meaningful range so the dev wallet\n\t\t\t\t\t\t\t\t// adapter's auto-connect-port heuristic keeps working even\n\t\t\t\t\t\t\t\t// after a fallback scan. The broker scan window is half-open\n\t\t\t\t\t\t\t\t// `[start, start + size)`, so `DEFAULT_PORT_WINDOW`\n\t\t\t\t\t\t\t\t// (start 39200, size 1000) covers 39200..40199 inclusive.\n\t\t\t\t\t\t\t\t// Reuse the broker's exported default rather than a local\n\t\t\t\t\t\t\t\t// literal so the two cannot silently drift (pinned by\n\t\t\t\t\t\t\t\t// `test/plugins/wallet/port-window-pin.test.ts`).\n\t\t\t\t\t\t\t\twindowHint: DEFAULT_PORT_WINDOW,\n\t\t\t\t\t\t\t\tpreferredPort: preferred,\n\t\t\t\t\t\t\t\t...(probeHost === undefined ? {} : { probeHost }),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.map((alloc) => alloc.port),\n\t\t\t\t\t\t\t\tEffect.mapError((err) =>\n\t\t\t\t\t\t\t\t\twalletBootError({\n\t\t\t\t\t\t\t\t\t\tphase: 'allocate-port',\n\t\t\t\t\t\t\t\t\t\tmessage: `port-broker allocate failed: ${err.detail}`,\n\t\t\t\t\t\t\t\t\t\thint:\n\t\t\t\t\t\t\t\t\t\t\terr.reason === 'preferred-busy'\n\t\t\t\t\t\t\t\t\t\t\t\t? 'another plugin in this stack is using your preferred port; omit `port` to let the broker pick.'\n\t\t\t\t\t\t\t\t\t\t\t\t: err.reason === 'no-free-port'\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'the wallet port window is exhausted; check for stray devstack supervisors holding ports.'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: 'bind-probe failed — likely a privileged port or jail restriction.',\n\t\t\t\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\tresolveAccounts: () => Effect.succeed(resolvedAccounts),\n\t\t\t\t\trouterFrontedUrl,\n\t\t\t\t\troutedAppOrigin,\n\t\t\t\t};\n\n\t\t\t\tconst resolved = yield* acquireWallet(resolvedOpts, acquireCtx);\n\n\t\t\t\t// Emit the wallet's contributions inline. `resolved` is the\n\t\t\t\t// just-acquired `WalletValue`; `runtime.identity.{app,stack}`\n\t\t\t\t// map to the `identity` already held here\n\t\t\t\t// (`yield* IdentityContext`). Emit each decl via its matching\n\t\t\t\t// buffered verb IN ORDER (snapshotable → `ctx.snapshotExtra`,\n\t\t\t\t// codegenable → `ctx.codegen`, routable → `ctx.endpoint`); decl\n\t\t\t\t// shapes are load-bearing. The verbs are void and buffer for the\n\t\t\t\t// supervisor's post-start replay.\n\t\t\t\tctx.snapshotExtra(makeWalletSnapshotable());\n\t\t\t\tctx.codegen(makeWalletCodegen(resolved.connection));\n\t\t\t\tctx.endpoint(\n\t\t\t\t\tmakeWalletRoutable({\n\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\tport: resolved.localPort,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn resolved;\n\t\t\t}),\n\t});\n}\n\n// ----------------------------------------------------------------------\n// Re-exports\n// ----------------------------------------------------------------------\n\nexport type {\n\tWalletOptions,\n\tWalletValue,\n\tWalletAccountMember,\n\tWalletAccountsAll,\n} from './service.ts';\nexport { WALLET_ACCOUNTS_ALL } from './service.ts';\nexport type { DevWalletConnection } from './codegen.ts';\nexport { DEV_WALLET_VALUES_NAMESPACE, DEV_WALLET_VALUES_KEY } from './codegen.ts';\nexport {\n\tWalletHttpPath,\n\tWALLET_PROTOCOL_PREFIX,\n\tWALLET_AUTH_HEADER,\n\tWALLET_BEARER_PREFIX,\n\tWALLET_TOKEN_FRAGMENT_KEY,\n\tWALLET_TOKEN_HEX_LENGTH,\n\tSignRequestSchema,\n\tSignResponseSchema,\n\tHealthResponseSchema,\n\tAccountsResponseSchema,\n\tAccountSummarySchema,\n\tErrorResponseSchema,\n\tSuiAddressSchema,\n\tBase64Schema,\n\tSignatureSchemeSchema,\n\tAccountSourceSchema,\n\ttype WalletHttpPathValue,\n\ttype SignRequest,\n\ttype SignResponse,\n\ttype HealthResponse,\n\ttype AccountsResponse,\n\ttype AccountSummary,\n\ttype ErrorResponse,\n} from './protocol.ts';\nexport type {\n\tWalletError,\n\tWalletBootError,\n\tWalletBootPhase,\n\tWalletRequestError,\n\tWalletRequestPhase,\n} from './errors.ts';\nexport type { OriginPolicy, OriginPolicyInputs, OriginCheckResult } from './origin-policy.ts';\nexport { resolveOriginPolicy, checkOrigin, corsHeadersFor } from './origin-policy.ts';\nexport type { PairingToken } from './pairing.ts';\nexport {\n\tmintToken,\n\tacquirePairingToken,\n\ttokenPath,\n\tcomposePairUrl,\n\tparsePairUrl,\n\tparseBearerHeader,\n\tsafeBearerEquals,\n\tredactToken,\n} from './pairing.ts';\nexport { WALLET_ENDPOINT_NAME, WALLET_ENDPOINT_KEY, makeWalletRoutable } from './routable.ts';\nexport {\n\tdispatch,\n\tstartHttpServer,\n\tMAX_BODY_BYTES,\n\ttype WalletRequest,\n\ttype WalletResponse,\n\ttype WalletServerConfig,\n\ttype WalletServerHandle,\n} from './server.ts';\nexport { makeWalletCodegen } from './codegen.ts';\nexport { makeWalletSnapshotable } from './snapshot.ts';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,MAAM,6BAA6B;;;;;;;;;;;AAYnC,MAAM,2BACL,WAEA,OAAO,GAAG,WAAW,0BAA0B;;;AAQhD,MAAM,iBAAiB,SAAgC,QAAQ;AAiD/D,SAAgB,OAAO,MAAiC;CACvD,MAAM,eACL,QAAS,EAAE,UAAA,MAA8B;CAC1C,IAAI,aAAa,aAAA,OAAkC;EAqBlD,MAAM,cAAc,iBAAiB,cAAc,CAAC,CAAU;EAC9D,qBAAqB,cAAc,YAAY;GAQ9C,MAAM,iBAA6C,CAAC;GACpD,KAAK,MAAM,KAAK,SACf,IAAI,wBAAwB,CAAC,GAC5B,eAAe,KAAK,CAAmC;GAGzD,OAAO,iBAAiB;IAAE,GAAG;IAAc,UAAU;GAAe,GAAG,cAAc;EACtF,CAAC;EACD,OAAO;CACR;CACA,OAAO,iBAAiB,cAAc,aAAa,QAAQ;AAC5D;AAEA,SAAS,iBACR,MACA,UACC;CAQD,MAAM,eAAe,CAAC,aAAa,GAAG,QAAQ;CAK9C,MAAM,eAAwC;EAAE,GAAG;EAAM;CAAS;CAElE,OAAO,aAAa;EACnB,IAAI,eAAe;EACnB,WAAW;EAIX,MAAM;EACN,SAAS;EAIT,QAAQ,SACP,OAAO,IAAI,aAAa;GACvB,MAAM,MAAM,OAAO;GAUnB,MAAM,WAAW,OAAO;GACxB,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAa,OAAO;GAI1B,MAAM,GAAG,GAAG,oBAAoB;GAChC,MAAM,mBAAmB,OAAO,eAAe,UAAA,KAA2B,CAAC,CAAC,KAC3E,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,0CAA0C,IAAI;IACvD,OAAO;GACR,CAAC,CACF,CACD;GACA,MAAM,kBAAkB,OAAO,eAC9B,UAAA,KAED,CAAC,CAAC,KACD,OAAO,KAAK,aACX,UAAU;IACT,UAAU;IACV;IACA,MAAM;GACP,CAAC,CACF,GACA,OAAO,UAAU,QAChB,gBAAgB;IACf,OAAO;IACP,SAAS,8CAA8C,IAAI;IAC3D,OAAO;GACR,CAAC,CACF,CACD;GA4CA,MAAM,WAAW,OAAO,cAAc,cAAc;IAzCnD,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,SAAS,SAAS;IAClB,WAAW,MAAM;IACjB,eAAe,WAAW,cACzB,WACE,SAAS;KACT,OAAO;KASP,YAAY;KACZ,eAAe;KACf,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;IAChD,CAAC,CAAC,CACD,KACA,OAAO,KAAK,UAAU,MAAM,IAAI,GAChC,OAAO,UAAU,QAChB,gBAAgB;KACf,OAAO;KACP,SAAS,gCAAgC,IAAI;KAC7C,MACC,IAAI,WAAW,mBACZ,mGACA,IAAI,WAAW,iBACd,6FACA;KACL,OAAO;IACR,CAAC,CACF,CACD;IACF,uBAAuB,OAAO,QAAQ,gBAAgB;IACtD;IACA;GAG4D,CAAC;GAU9D,IAAI,cAAc,uBAAuB,CAAC;GAC1C,IAAI,QAAQ,kBAAkB,SAAS,UAAU,CAAC;GAClD,IAAI,SACH,mBAAmB;IAClB,KAAK,SAAS;IACd,OAAO,SAAS;IAChB,MAAM,SAAS;GAChB,CAAC,CACF;GAEA,OAAO;EACR,CAAC;CACH,CAAC;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ResourceRef } from "../../substrate/plugin.mjs";
|
|
2
2
|
import { AccountValue } from "../account/service.mjs";
|
|
3
3
|
import { AccountResourceId } from "../account/index.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { DevWalletConnection } from "./codegen.mjs";
|
|
5
5
|
import { PairingToken } from "./pairing.mjs";
|
|
6
6
|
import { WalletServerHandle } from "./server.mjs";
|
|
7
7
|
import { Effect, FileSystem, Scope } from "effect";
|
|
@@ -51,7 +51,7 @@ interface WalletOptions<Accounts extends ReadonlyArray<WalletAccountMember> = Re
|
|
|
51
51
|
interface WalletValue {
|
|
52
52
|
readonly url: string;
|
|
53
53
|
readonly pairUrl: string;
|
|
54
|
-
readonly
|
|
54
|
+
readonly connection: DevWalletConnection;
|
|
55
55
|
readonly localPort: number;
|
|
56
56
|
readonly token: PairingToken;
|
|
57
57
|
/** Server handle — substrate's scope finalizer chain invokes
|
|
@@ -60,9 +60,8 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
|
|
|
60
60
|
});
|
|
61
61
|
const walletUrl = ctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;
|
|
62
62
|
const pairUrl = composePairUrl(walletUrl, token);
|
|
63
|
-
const
|
|
63
|
+
const connection = {
|
|
64
64
|
walletUrl,
|
|
65
|
-
pairUrl,
|
|
66
65
|
network: ctx.network,
|
|
67
66
|
protocolPaths: {
|
|
68
67
|
health: WalletHttpPath.HEALTH,
|
|
@@ -78,7 +77,7 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
|
|
|
78
77
|
return {
|
|
79
78
|
url: walletUrl,
|
|
80
79
|
pairUrl,
|
|
81
|
-
|
|
80
|
+
connection,
|
|
82
81
|
localPort: port,
|
|
83
82
|
token,
|
|
84
83
|
server
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":[],"sources":["../../../src/plugins/wallet/service.ts"],"sourcesContent":["// Wallet plugin — main acquire Effect.\n//\n// What this file does (15-wallet.md §Lifecycle, Startup ordered):\n//\n// 1. Resolve each consumed account tag and key the AccountValues by\n// address (load-bearing: sign endpoints look up by address).\n// 2. Allocate a port via the substrate's `PortBrokerService`.\n// 3. Mint or rehydrate the pairing token (read-existing-or-mint at\n// `<stateRoot>/wallet/token`, mode 0o600) via the substrate's\n// `atomicWriteFile`.\n// 4. Resolve the stack-scoped origin allowlist via\n// `origin-policy.ts`.\n// 5. Start the in-process HTTP server.\n// 6. Compose the wallet URL + pair URL + return the resolved\n// `WalletValue`.\n//\n// The substrate handles:\n//\n// - Scope finalizers (port release, server close) — declared by the\n// substrate primitives we call into.\n// - Endpoint registry publication (we surface the URL/pairUrl via\n// our resolved tag value; the substrate's endpoint-registry\n// primitive consumes the `RoutableDecl` from `routable.ts`).\n// - Manifest emit — the substrate projects from registry. We never\n// write `.devstack/manifest.json` directly.\n\nimport { Effect } from 'effect';\nimport type { FileSystem, Scope } from 'effect';\n\nimport type { AccountResourceId, AccountValue } from '../account/index.ts';\nimport type { DevWalletConfig } from './codegen.ts';\nimport { walletBootError, type WalletBootError } from './errors.ts';\nimport { resolveOriginPolicy } from './origin-policy.ts';\nimport { acquirePairingToken, composePairUrl, tokenPath, type PairingToken } from './pairing.ts';\nimport { startHttpServer, type WalletServerConfig, type WalletServerHandle } from './server.ts';\nimport { WalletHttpPath } from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// User-facing options\n// ----------------------------------------------------------------------\n\nimport type { ResourceRef } from '../../api/define-plugin.ts';\n\n/** Literal sentinel for `WalletOptions.accounts: 'all'` — every account\n * member in the stack. Expanded by the composer at `defineDevstack`\n * call time (api-surface-design §4 D6). Kept as an exported constant\n * so the wallet factory + composer share one source of truth. */\nexport const WALLET_ACCOUNTS_ALL = 'all' as const;\nexport type WalletAccountsAll = typeof WALLET_ACCOUNTS_ALL;\n\n/** A user-supplied account ref. The user passes the result of\n * `account('alice')` — NOT a bare string. Generic over the literal\n * account name so the wallet's dependency tuple preserves each\n * per-account resource id (`account/alice`, `account/bob`, ...). */\nexport type WalletAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\nexport interface WalletOptions<\n\tAccounts extends ReadonlyArray<WalletAccountMember> = ReadonlyArray<WalletAccountMember>,\n> {\n\t/** Accounts the wallet binds. Each is yielded for ordering AND its\n\t * resolved value is keyed by address into the sign-handler map.\n\t *\n\t * Two shapes:\n\t *\n\t * - Explicit tuple — each entry is the plugin/resource ref returned\n\t * by `account('name')`. Pins the bound set at the wallet's call\n\t * site; preserves each literal `account/${Name}` so stack\n\t * composition can validate and recursively expand the refs.\n\t *\n\t * - The literal `'all'` — shorthand for \"every account member in\n\t * the stack\". The composer expands this against the final\n\t * member tuple at `defineDevstack(...)` time (api-surface-design\n\t * §4 D6). The wallet member returned by the factory carries an\n\t * expander hook keyed off `WALLET_ACCOUNTS_ALL` that the\n\t * composer invokes once the account-providing members are\n\t * known. */\n\treadonly accounts: Accounts | typeof WALLET_ACCOUNTS_ALL;\n\t/** Extra origins merged on top of the router-fronted dev-server\n\t * origin. Useful for headless test runners and custom dev hosts. */\n\treadonly allowedOrigins?: ReadonlyArray<string>;\n\t/** Preferred host port. Substrate's port broker forward-scans if\n\t * this is taken. Default: substrate-picked (no preference). */\n\treadonly port?: number;\n\t/** NIC the HTTP server binds. Defaults to `'0.0.0.0'` because the\n\t * router runs in Docker and must reach the host process through the\n\t * host-gateway address on native Linux. The public wallet URL remains\n\t * router-fronted and stack-scoped. */\n\treadonly bindAddress?: string;\n}\n\n// ----------------------------------------------------------------------\n// Resolved value (what the wallet plugin publishes)\n// ----------------------------------------------------------------------\n\nexport interface WalletValue {\n\treadonly url: string; // router-fronted URL when available, loopback otherwise\n\treadonly pairUrl: string;\n\treadonly bindings: DevWalletConfig;\n\treadonly localPort: number;\n\treadonly token: PairingToken;\n\t/** Server handle — substrate's scope finalizer chain invokes\n\t * `.close()`; callers don't reach in. Exposed here so tests can\n\t * drive teardown explicitly. */\n\treadonly server: WalletServerHandle;\n}\n\nconst WALLET_DEFAULT_BIND_ADDRESS = '0.0.0.0' as const;\nconst WALLET_DIRECT_URL_HOST = '127.0.0.1' as const;\ntype WalletPortProbeHost = '127.0.0.1' | '0.0.0.0';\n\nconst portProbeHostForBindAddress = (bindAddress: string): WalletPortProbeHost =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? '0.0.0.0' : '127.0.0.1';\n\nconst directUrlHostForBindAddress = (bindAddress: string): string =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? WALLET_DIRECT_URL_HOST : bindAddress;\n\n// ----------------------------------------------------------------------\n// Per-acquire context — supplied by the barrel\n// ----------------------------------------------------------------------\n\n/** Inputs from the substrate. The barrel fills these from the\n * BuildContext; tests can construct directly. */\nexport interface WalletAcquireContext {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\t/** State root where `wallet/token` lives. Convention:\n\t * `<appDir>/.devstack/stacks/<stack>/runtime`. */\n\treadonly stateRoot: string;\n\t/** Port broker seam — returns the allocated port + a scope-\n\t * finalizer-installed release. The barrel adapts the substrate's\n\t * `PortBrokerService.allocate` to this signature so tests can pin\n\t * the port without yielding from a substrate Layer. */\n\treadonly allocatePort: (\n\t\tpreferred?: number,\n\t\tprobeHost?: WalletPortProbeHost,\n\t) => Effect.Effect<number, WalletBootError, Scope.Scope>;\n\t/** Account value resolver — the barrel hands this in keyed off the\n\t * BuildContext so the service body stays substrate-agnostic. */\n\treadonly resolveAccounts: () => Effect.Effect<ReadonlyArray<AccountValue>, WalletBootError>;\n\t/** Stable router-fronted base URL for this wallet on the stack-scoped\n\t * hostname (e.g. `http://wallet.<app>.localhost:<router-port>`). Null\n\t * only in tests that bypass the router derivation. */\n\treadonly routerFrontedUrl: string | null;\n\t/** Stable router-fronted dev-server origin for this stack. Added to\n\t * the wallet allowlist so app+wallet stacks work without repeating\n\t * router origins in user configs. */\n\treadonly routedAppOrigin: string | null;\n}\n\n// ----------------------------------------------------------------------\n// Acquire\n// ----------------------------------------------------------------------\n\n/**\n * Acquire the wallet service.\n *\n * Distilled-doc invariants honored:\n *\n * - C12 (mandatory Origin + bearer): both gates are wired in\n * `server.ts:dispatch`.\n * - Token comparison constant-time: `pairing.ts:safeBearerEquals`.\n * - Token file 0o600: `pairing.ts:acquirePairingToken`.\n * - Token in URL fragment only: `pairing.ts:composePairUrl`.\n * - Token NEVER in log lines: handlers log only `bearerValid:\n * boolean`.\n * - Default bindAddress `'0.0.0.0'`: required for the Docker router to\n * reach host-loopback services on native Linux.\n * - Stack-scoped origin allowlist (no cross-stack pairing risk):\n * `origin-policy.ts:resolveOriginPolicy`.\n */\nexport const acquireWallet = (\n\topts: WalletOptions,\n\tctx: WalletAcquireContext,\n): Effect.Effect<WalletValue, WalletBootError, Scope.Scope | FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\t// 1. Resolve the dependency account values. The barrel sets up\n\t\t// `resolveAccounts` to walk the BuildContext via resolved dependencies in\n\t\t// `opts.accounts`; we just project to the address-keyed map.\n\t\tconst accounts = yield* ctx.resolveAccounts();\n\t\tif (accounts.length === 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'no-accounts',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.\",\n\t\t\t\t\thint: \"`wallet()` and `wallet({ accounts: 'all' })` require at least one account member in the final stack.\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst accountsByAddress = new Map<string, AccountValue>();\n\t\tfor (const acct of accounts) {\n\t\t\t// Two accounts resolving to the same address would silently\n\t\t\t// last-write-wins the sign-route map, so a sign request for\n\t\t\t// that address binds to a non-deterministic account. Fail at\n\t\t\t// boot with the colliding address named.\n\t\t\tif (accountsByAddress.has(acct.address)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\twalletBootError({\n\t\t\t\t\t\tphase: 'bind-account',\n\t\t\t\t\t\tmessage: `wallet resolved two accounts at the same address ${acct.address}; each account must own a distinct address.`,\n\t\t\t\t\t\thint: 'Remove the duplicate account member, or give the colliding accounts distinct keypairs.',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\taccountsByAddress.set(acct.address, acct);\n\t\t}\n\n\t\t// 2. Port allocation. Probe the same host family the real\n\t\t// listener uses, otherwise a process bound on a non-loopback\n\t\t// interface could race the wallet's all-interface listen.\n\t\tconst bindAddress = opts.bindAddress ?? WALLET_DEFAULT_BIND_ADDRESS;\n\t\tconst port = yield* ctx.allocatePort(opts.port, portProbeHostForBindAddress(bindAddress));\n\n\t\t// 3. Token mint or rehydrate. Lives at the stack-scoped state root\n\t\t// so warm-start + snapshot-restore preserve the existing\n\t\t// pairing.\n\t\tconst token = yield* acquirePairingToken(tokenPath(ctx.stateRoot));\n\n\t\t// 4. Origin policy. Allowlist is the router-fronted dev-server\n\t\t// origin for this stack plus any explicit `allowedOrigins`.\n\t\tconst policy = yield* resolveOriginPolicy({\n\t\t\tapp: ctx.app,\n\t\t\tstack: ctx.stack,\n\t\t\troutedAppOrigin: ctx.routedAppOrigin,\n\t\t\textraOrigins: opts.allowedOrigins ?? [],\n\t\t});\n\n\t\t// 5. Start the HTTP server. The dispatcher in `server.ts` owns\n\t\t// route matching + the constant-time bearer compare + the\n\t\t// JSON envelope contract.\n\t\tconst serverConfig: WalletServerConfig = {\n\t\t\tbindAddress,\n\t\t\tport,\n\t\t\ttoken,\n\t\t\tpolicy,\n\t\t\taccountsByAddress,\n\t\t};\n\t\tconst server = yield* startHttpServer(serverConfig);\n\n\t\t// 6. Compose URLs. Router-fronted form when available, loopback\n\t\t// fallback otherwise. The token rides ONLY the fragment — never\n\t\t// a query param — per C13.\n\t\tconst walletUrl =\n\t\t\tctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;\n\t\tconst pairUrl = composePairUrl(walletUrl, token);\n\n\t\tconst bindings: DevWalletConfig = {\n\t\t\twalletUrl,\n\t\t\tpairUrl,\n\t\t\tnetwork: ctx.network,\n\t\t\tprotocolPaths: {\n\t\t\t\thealth: WalletHttpPath.HEALTH,\n\t\t\t\taccounts: WalletHttpPath.ACCOUNTS,\n\t\t\t\tsignTransaction: WalletHttpPath.SIGN_TRANSACTION,\n\t\t\t\tsignPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE,\n\t\t\t},\n\t\t};\n\n\t\t// Defensive: NEVER log `pairUrl` directly. It carries the token.\n\t\tyield* Effect.logInfo('wallet ready').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t'wallet.url': walletUrl,\n\t\t\t\t'wallet.token': 'redacted-fragment',\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: walletUrl,\n\t\t\tpairUrl,\n\t\t\tbindings,\n\t\t\tlocalPort: port,\n\t\t\ttoken,\n\t\t\tserver,\n\t\t} satisfies WalletValue;\n\t});\n"],"mappings":";;;;;;;;AA6GA,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AAG/B,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,YAAY;AAE3D,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,yBAAyB;;;;;;;;;;;;;;;;;;AAyDxE,MAAa,iBACZ,MACA,QAEA,OAAO,IAAI,aAAa;CAIvB,MAAM,WAAW,OAAO,IAAI,gBAAgB;CAC5C,IAAI,SAAS,WAAW,GACvB,OAAO,OAAO,OAAO,KACpB,gBAAgB;EACf,OAAO;EACP,SACC;EACD,MAAM;CACP,CAAC,CACF;CAED,MAAM,oCAAoB,IAAI,IAA0B;CACxD,KAAK,MAAM,QAAQ,UAAU;EAK5B,IAAI,kBAAkB,IAAI,KAAK,OAAO,GACrC,OAAO,OAAO,OAAO,KACpB,gBAAgB;GACf,OAAO;GACP,SAAS,oDAAoD,KAAK,QAAQ;GAC1E,MAAM;EACP,CAAC,CACF;EAED,kBAAkB,IAAI,KAAK,SAAS,IAAI;CACzC;CAKA,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,MAAM,4BAA4B,WAAW,CAAC;CAKxF,MAAM,QAAQ,OAAO,oBAAoB,UAAU,IAAI,SAAS,CAAC;CAqBjE,MAAM,SAAS,OAAO,gBAAgB;EANrC;EACA;EACA;EACA,QAAA,OAdqB,oBAAoB;GACzC,KAAK,IAAI;GACT,OAAO,IAAI;GACX,iBAAiB,IAAI;GACrB,cAAc,KAAK,kBAAkB,CAAC;EACvC,CAAC;EAUA;CAEgD,CAAC;CAKlD,MAAM,YACL,IAAI,oBAAoB,UAAU,4BAA4B,WAAW,EAAE,GAAG;CAC/E,MAAM,UAAU,eAAe,WAAW,KAAK;CAE/C,MAAM,WAA4B;EACjC;EACA;EACA,SAAS,IAAI;EACb,eAAe;GACd,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;EACrC;CACD;CAGA,OAAO,OAAO,QAAQ,cAAc,CAAC,CAAC,KACrC,OAAO,aAAa;EACnB,cAAc;EACd,gBAAgB;CACjB,CAAC,CACF;CAEA,OAAO;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX;EACA;CACD;AACD,CAAC"}
|
|
1
|
+
{"version":3,"file":"service.mjs","names":[],"sources":["../../../src/plugins/wallet/service.ts"],"sourcesContent":["// Wallet plugin — main acquire Effect.\n//\n// What this file does (15-wallet.md §Lifecycle, Startup ordered):\n//\n// 1. Resolve each consumed account tag and key the AccountValues by\n// address (load-bearing: sign endpoints look up by address).\n// 2. Allocate a port via the substrate's `PortBrokerService`.\n// 3. Mint or rehydrate the pairing token (read-existing-or-mint at\n// `<stateRoot>/wallet/token`, mode 0o600) via the substrate's\n// `atomicWriteFile`.\n// 4. Resolve the stack-scoped origin allowlist via\n// `origin-policy.ts`.\n// 5. Start the in-process HTTP server.\n// 6. Compose the wallet URL + pair URL + return the resolved\n// `WalletValue`.\n//\n// The substrate handles:\n//\n// - Scope finalizers (port release, server close) — declared by the\n// substrate primitives we call into.\n// - Endpoint registry publication (we surface the URL/pairUrl via\n// our resolved tag value; the substrate's endpoint-registry\n// primitive consumes the `RoutableDecl` from `routable.ts`).\n// - Manifest emit — the substrate projects from registry. We never\n// write `.devstack/manifest.json` directly.\n\nimport { Effect } from 'effect';\nimport type { FileSystem, Scope } from 'effect';\n\nimport type { AccountResourceId, AccountValue } from '../account/index.ts';\nimport type { DevWalletConnection } from './codegen.ts';\nimport { walletBootError, type WalletBootError } from './errors.ts';\nimport { resolveOriginPolicy } from './origin-policy.ts';\nimport { acquirePairingToken, composePairUrl, tokenPath, type PairingToken } from './pairing.ts';\nimport { startHttpServer, type WalletServerConfig, type WalletServerHandle } from './server.ts';\nimport { WalletHttpPath } from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// User-facing options\n// ----------------------------------------------------------------------\n\nimport type { ResourceRef } from '../../api/define-plugin.ts';\n\n/** Literal sentinel for `WalletOptions.accounts: 'all'` — every account\n * member in the stack. Expanded by the composer at `defineDevstack`\n * call time (api-surface-design §4 D6). Kept as an exported constant\n * so the wallet factory + composer share one source of truth. */\nexport const WALLET_ACCOUNTS_ALL = 'all' as const;\nexport type WalletAccountsAll = typeof WALLET_ACCOUNTS_ALL;\n\n/** A user-supplied account ref. The user passes the result of\n * `account('alice')` — NOT a bare string. Generic over the literal\n * account name so the wallet's dependency tuple preserves each\n * per-account resource id (`account/alice`, `account/bob`, ...). */\nexport type WalletAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\nexport interface WalletOptions<\n\tAccounts extends ReadonlyArray<WalletAccountMember> = ReadonlyArray<WalletAccountMember>,\n> {\n\t/** Accounts the wallet binds. Each is yielded for ordering AND its\n\t * resolved value is keyed by address into the sign-handler map.\n\t *\n\t * Two shapes:\n\t *\n\t * - Explicit tuple — each entry is the plugin/resource ref returned\n\t * by `account('name')`. Pins the bound set at the wallet's call\n\t * site; preserves each literal `account/${Name}` so stack\n\t * composition can validate and recursively expand the refs.\n\t *\n\t * - The literal `'all'` — shorthand for \"every account member in\n\t * the stack\". The composer expands this against the final\n\t * member tuple at `defineDevstack(...)` time (api-surface-design\n\t * §4 D6). The wallet member returned by the factory carries an\n\t * expander hook keyed off `WALLET_ACCOUNTS_ALL` that the\n\t * composer invokes once the account-providing members are\n\t * known. */\n\treadonly accounts: Accounts | typeof WALLET_ACCOUNTS_ALL;\n\t/** Extra origins merged on top of the router-fronted dev-server\n\t * origin. Useful for headless test runners and custom dev hosts. */\n\treadonly allowedOrigins?: ReadonlyArray<string>;\n\t/** Preferred host port. Substrate's port broker forward-scans if\n\t * this is taken. Default: substrate-picked (no preference). */\n\treadonly port?: number;\n\t/** NIC the HTTP server binds. Defaults to `'0.0.0.0'` because the\n\t * router runs in Docker and must reach the host process through the\n\t * host-gateway address on native Linux. The public wallet URL remains\n\t * router-fronted and stack-scoped. */\n\treadonly bindAddress?: string;\n}\n\n// ----------------------------------------------------------------------\n// Resolved value (what the wallet plugin publishes)\n// ----------------------------------------------------------------------\n\nexport interface WalletValue {\n\treadonly url: string; // router-fronted URL when available, loopback otherwise\n\treadonly pairUrl: string;\n\treadonly connection: DevWalletConnection;\n\treadonly localPort: number;\n\treadonly token: PairingToken;\n\t/** Server handle — substrate's scope finalizer chain invokes\n\t * `.close()`; callers don't reach in. Exposed here so tests can\n\t * drive teardown explicitly. */\n\treadonly server: WalletServerHandle;\n}\n\nconst WALLET_DEFAULT_BIND_ADDRESS = '0.0.0.0' as const;\nconst WALLET_DIRECT_URL_HOST = '127.0.0.1' as const;\ntype WalletPortProbeHost = '127.0.0.1' | '0.0.0.0';\n\nconst portProbeHostForBindAddress = (bindAddress: string): WalletPortProbeHost =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? '0.0.0.0' : '127.0.0.1';\n\nconst directUrlHostForBindAddress = (bindAddress: string): string =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? WALLET_DIRECT_URL_HOST : bindAddress;\n\n// ----------------------------------------------------------------------\n// Per-acquire context — supplied by the barrel\n// ----------------------------------------------------------------------\n\n/** Inputs from the substrate. The barrel fills these from the\n * BuildContext; tests can construct directly. */\nexport interface WalletAcquireContext {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\t/** State root where `wallet/token` lives. Convention:\n\t * `<appDir>/.devstack/stacks/<stack>/runtime`. */\n\treadonly stateRoot: string;\n\t/** Port broker seam — returns the allocated port + a scope-\n\t * finalizer-installed release. The barrel adapts the substrate's\n\t * `PortBrokerService.allocate` to this signature so tests can pin\n\t * the port without yielding from a substrate Layer. */\n\treadonly allocatePort: (\n\t\tpreferred?: number,\n\t\tprobeHost?: WalletPortProbeHost,\n\t) => Effect.Effect<number, WalletBootError, Scope.Scope>;\n\t/** Account value resolver — the barrel hands this in keyed off the\n\t * BuildContext so the service body stays substrate-agnostic. */\n\treadonly resolveAccounts: () => Effect.Effect<ReadonlyArray<AccountValue>, WalletBootError>;\n\t/** Stable router-fronted base URL for this wallet on the stack-scoped\n\t * hostname (e.g. `http://wallet.<app>.localhost:<router-port>`). Null\n\t * only in tests that bypass the router derivation. */\n\treadonly routerFrontedUrl: string | null;\n\t/** Stable router-fronted dev-server origin for this stack. Added to\n\t * the wallet allowlist so app+wallet stacks work without repeating\n\t * router origins in user configs. */\n\treadonly routedAppOrigin: string | null;\n}\n\n// ----------------------------------------------------------------------\n// Acquire\n// ----------------------------------------------------------------------\n\n/**\n * Acquire the wallet service.\n *\n * Distilled-doc invariants honored:\n *\n * - C12 (mandatory Origin + bearer): both gates are wired in\n * `server.ts:dispatch`.\n * - Token comparison constant-time: `pairing.ts:safeBearerEquals`.\n * - Token file 0o600: `pairing.ts:acquirePairingToken`.\n * - Token in URL fragment only: `pairing.ts:composePairUrl`.\n * - Token NEVER in log lines: handlers log only `bearerValid:\n * boolean`.\n * - Default bindAddress `'0.0.0.0'`: required for the Docker router to\n * reach host-loopback services on native Linux.\n * - Stack-scoped origin allowlist (no cross-stack pairing risk):\n * `origin-policy.ts:resolveOriginPolicy`.\n */\nexport const acquireWallet = (\n\topts: WalletOptions,\n\tctx: WalletAcquireContext,\n): Effect.Effect<WalletValue, WalletBootError, Scope.Scope | FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\t// 1. Resolve the dependency account values. The barrel sets up\n\t\t// `resolveAccounts` to walk the BuildContext via resolved dependencies in\n\t\t// `opts.accounts`; we just project to the address-keyed map.\n\t\tconst accounts = yield* ctx.resolveAccounts();\n\t\tif (accounts.length === 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'no-accounts',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.\",\n\t\t\t\t\thint: \"`wallet()` and `wallet({ accounts: 'all' })` require at least one account member in the final stack.\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst accountsByAddress = new Map<string, AccountValue>();\n\t\tfor (const acct of accounts) {\n\t\t\t// Two accounts resolving to the same address would silently\n\t\t\t// last-write-wins the sign-route map, so a sign request for\n\t\t\t// that address binds to a non-deterministic account. Fail at\n\t\t\t// boot with the colliding address named.\n\t\t\tif (accountsByAddress.has(acct.address)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\twalletBootError({\n\t\t\t\t\t\tphase: 'bind-account',\n\t\t\t\t\t\tmessage: `wallet resolved two accounts at the same address ${acct.address}; each account must own a distinct address.`,\n\t\t\t\t\t\thint: 'Remove the duplicate account member, or give the colliding accounts distinct keypairs.',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\taccountsByAddress.set(acct.address, acct);\n\t\t}\n\n\t\t// 2. Port allocation. Probe the same host family the real\n\t\t// listener uses, otherwise a process bound on a non-loopback\n\t\t// interface could race the wallet's all-interface listen.\n\t\tconst bindAddress = opts.bindAddress ?? WALLET_DEFAULT_BIND_ADDRESS;\n\t\tconst port = yield* ctx.allocatePort(opts.port, portProbeHostForBindAddress(bindAddress));\n\n\t\t// 3. Token mint or rehydrate. Lives at the stack-scoped state root\n\t\t// so warm-start + snapshot-restore preserve the existing\n\t\t// pairing.\n\t\tconst token = yield* acquirePairingToken(tokenPath(ctx.stateRoot));\n\n\t\t// 4. Origin policy. Allowlist is the router-fronted dev-server\n\t\t// origin for this stack plus any explicit `allowedOrigins`.\n\t\tconst policy = yield* resolveOriginPolicy({\n\t\t\tapp: ctx.app,\n\t\t\tstack: ctx.stack,\n\t\t\troutedAppOrigin: ctx.routedAppOrigin,\n\t\t\textraOrigins: opts.allowedOrigins ?? [],\n\t\t});\n\n\t\t// 5. Start the HTTP server. The dispatcher in `server.ts` owns\n\t\t// route matching + the constant-time bearer compare + the\n\t\t// JSON envelope contract.\n\t\tconst serverConfig: WalletServerConfig = {\n\t\t\tbindAddress,\n\t\t\tport,\n\t\t\ttoken,\n\t\t\tpolicy,\n\t\t\taccountsByAddress,\n\t\t};\n\t\tconst server = yield* startHttpServer(serverConfig);\n\n\t\t// 6. Compose URLs. Router-fronted form when available, loopback\n\t\t// fallback otherwise. The token rides ONLY the fragment — never\n\t\t// a query param — per C13.\n\t\tconst walletUrl =\n\t\t\tctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;\n\t\tconst pairUrl = composePairUrl(walletUrl, token);\n\n\t\t// NON-secret connection metadata only — the token never rides this\n\t\t// (it stays in the `0o600` side-channel file the Vite plugin reads by\n\t\t// path; `pairUrl`/`token` remain on the resolved value for tests and the\n\t\t// snapshot subtree, but are NOT routed through `deployment.json`).\n\t\tconst connection: DevWalletConnection = {\n\t\t\twalletUrl,\n\t\t\tnetwork: ctx.network,\n\t\t\tprotocolPaths: {\n\t\t\t\thealth: WalletHttpPath.HEALTH,\n\t\t\t\taccounts: WalletHttpPath.ACCOUNTS,\n\t\t\t\tsignTransaction: WalletHttpPath.SIGN_TRANSACTION,\n\t\t\t\tsignPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE,\n\t\t\t},\n\t\t};\n\n\t\t// Defensive: NEVER log `pairUrl` directly. It carries the token.\n\t\tyield* Effect.logInfo('wallet ready').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t'wallet.url': walletUrl,\n\t\t\t\t'wallet.token': 'redacted-fragment',\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: walletUrl,\n\t\t\tpairUrl,\n\t\t\tconnection,\n\t\t\tlocalPort: port,\n\t\t\ttoken,\n\t\t\tserver,\n\t\t} satisfies WalletValue;\n\t});\n"],"mappings":";;;;;;;;AA6GA,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AAG/B,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,YAAY;AAE3D,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,yBAAyB;;;;;;;;;;;;;;;;;;AAyDxE,MAAa,iBACZ,MACA,QAEA,OAAO,IAAI,aAAa;CAIvB,MAAM,WAAW,OAAO,IAAI,gBAAgB;CAC5C,IAAI,SAAS,WAAW,GACvB,OAAO,OAAO,OAAO,KACpB,gBAAgB;EACf,OAAO;EACP,SACC;EACD,MAAM;CACP,CAAC,CACF;CAED,MAAM,oCAAoB,IAAI,IAA0B;CACxD,KAAK,MAAM,QAAQ,UAAU;EAK5B,IAAI,kBAAkB,IAAI,KAAK,OAAO,GACrC,OAAO,OAAO,OAAO,KACpB,gBAAgB;GACf,OAAO;GACP,SAAS,oDAAoD,KAAK,QAAQ;GAC1E,MAAM;EACP,CAAC,CACF;EAED,kBAAkB,IAAI,KAAK,SAAS,IAAI;CACzC;CAKA,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,MAAM,4BAA4B,WAAW,CAAC;CAKxF,MAAM,QAAQ,OAAO,oBAAoB,UAAU,IAAI,SAAS,CAAC;CAqBjE,MAAM,SAAS,OAAO,gBAAgB;EANrC;EACA;EACA;EACA,QAAA,OAdqB,oBAAoB;GACzC,KAAK,IAAI;GACT,OAAO,IAAI;GACX,iBAAiB,IAAI;GACrB,cAAc,KAAK,kBAAkB,CAAC;EACvC,CAAC;EAUA;CAEgD,CAAC;CAKlD,MAAM,YACL,IAAI,oBAAoB,UAAU,4BAA4B,WAAW,EAAE,GAAG;CAC/E,MAAM,UAAU,eAAe,WAAW,KAAK;CAM/C,MAAM,aAAkC;EACvC;EACA,SAAS,IAAI;EACb,eAAe;GACd,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;EACrC;CACD;CAGA,OAAO,OAAO,QAAQ,cAAc,CAAC,CAAC,KACrC,OAAO,aAAa;EACnB,cAAc;EACd,gBAAgB;CACjB,CAAC,CACF;CAEA,OAAO;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX;EACA;CACD;AACD,CAAC"}
|
|
@@ -2,14 +2,14 @@ import { configCodegenable } from "../../contracts/config-bindings.mjs";
|
|
|
2
2
|
//#region src/plugins/walrus/codegen.ts
|
|
3
3
|
const NAMESPACE = "walrus";
|
|
4
4
|
/** TS source-type strings for the resolved walrus fields — keeps the committed
|
|
5
|
-
* `walrus.ts` typed as `WalrusBindings` declares (the generic `
|
|
5
|
+
* `walrus.ts` typed as `WalrusBindings` declares (the generic `requireValue`
|
|
6
6
|
* channel would otherwise return `unknown`). Composite blobs inline their
|
|
7
7
|
* structural literal types so no emitted type-import is needed. */
|
|
8
8
|
const PACKAGE_CONFIG_TS_TYPE = "{ readonly systemObjectId: string; readonly stakingPoolId: string; readonly exchangeIds?: ReadonlyArray<string> }";
|
|
9
9
|
const NODES_TS_TYPE = "ReadonlyArray<{ readonly nodeIndex: number; readonly publicHostname: string; readonly rpcUrl: string }>";
|
|
10
10
|
/** The walrus config bindings, declared ONCE. `mode` / `network` are
|
|
11
11
|
* structural literals; every id / coin type / URL / committee value is a
|
|
12
|
-
* RESOLVED binding on the generic `
|
|
12
|
+
* RESOLVED binding on the generic `requireValue(dep, 'walrus', '<key>')` channel.
|
|
13
13
|
* Both the live boot decl and the static committed-tree decl derive from it. */
|
|
14
14
|
const walrusConfigBindings = (structural) => {
|
|
15
15
|
const known = structural.known;
|
|
@@ -80,7 +80,7 @@ const walrusConfigBindings = (structural) => {
|
|
|
80
80
|
};
|
|
81
81
|
};
|
|
82
82
|
/** Construct the LIVE Codegenable contribution. Bakes the resolved ids /
|
|
83
|
-
* URLs into the ephemeral tree + feeds the generic
|
|
83
|
+
* URLs into the ephemeral tree + feeds the generic deployment `values`
|
|
84
84
|
* channel. */
|
|
85
85
|
const makeCodegenable = (inputs) => configCodegenable(walrusConfigBindings({
|
|
86
86
|
mode: inputs.mode,
|
|
@@ -90,7 +90,7 @@ const makeCodegenable = (inputs) => configCodegenable(walrusConfigBindings({
|
|
|
90
90
|
state: inputs
|
|
91
91
|
});
|
|
92
92
|
/** Construct the STATIC (stack-free) Codegenable contribution. Emits
|
|
93
|
-
* `
|
|
93
|
+
* `requireValue(dep, 'walrus', '<key>')` for the runtime fields; the committed
|
|
94
94
|
* `walrus.ts` carries no baked object id / endpoint URL. */
|
|
95
95
|
const makeWalrusStaticCodegen = (config) => configCodegenable(walrusConfigBindings(config), "static");
|
|
96
96
|
//#endregion
|