@mysten-incubation/devstack 0.1.1 → 0.2.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 +6 -3
- package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
- package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
- package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
- package/dashboard-ui/index.html +2 -2
- package/dist/api/define-capabilities.d.mts +1 -20
- package/dist/api/define-capabilities.mjs +15 -22
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack-with.d.mts +7 -1
- package/dist/api/define-devstack-with.mjs.map +1 -1
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +2 -1
- package/dist/api/inference-network.d.mts +31 -17
- package/dist/api/inference-network.mjs +40 -43
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/load-config.mjs +88 -0
- package/dist/api/load-config.mjs.map +1 -0
- package/dist/api/mode-narrowed-factory.d.mts +1 -1
- package/dist/api/mode-narrowed-factory.mjs.map +1 -1
- package/dist/api/run-stack-internal.mjs +202 -0
- package/dist/api/run-stack-internal.mjs.map +1 -0
- package/dist/api/run-stack.d.mts +82 -31
- package/dist/api/run-stack.mjs +22 -141
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
- package/dist/build-integrations/dapp-kit/index.mjs +58 -0
- package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
- package/dist/build-integrations/playwright/config.d.mts +1 -30
- package/dist/build-integrations/playwright/config.mjs +1 -27
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +4 -7
- package/dist/build-integrations/playwright/errors.mjs.map +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
- package/dist/build-integrations/playwright/global-setup.mjs +142 -218
- package/dist/build-integrations/playwright/global-setup.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/stack-context.d.mts +3 -1
- package/dist/build-integrations/playwright/stack-context.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
- package/dist/build-integrations/runtime/discover.d.mts +6 -4
- package/dist/build-integrations/runtime/discover.mjs +15 -4
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/errors.mjs.map +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
- package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
- package/dist/build-integrations/vite/index.d.mts +84 -19
- package/dist/build-integrations/vite/index.mjs +203 -28
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/build-integrations/vitest/config.d.mts +20 -2
- package/dist/build-integrations/vitest/config.mjs +16 -4
- package/dist/build-integrations/vitest/config.mjs.map +1 -1
- package/dist/build-integrations/vitest/env.d.mts +18 -10
- package/dist/build-integrations/vitest/env.mjs +11 -10
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +7 -11
- package/dist/build-integrations/vitest/errors.mjs +3 -4
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
- package/dist/build-integrations/vitest/global-setup.mjs +156 -0
- package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
- package/dist/build-integrations/vitest/index.d.mts +3 -2
- package/dist/build-integrations/vitest/index.mjs +3 -2
- package/dist/build-integrations/vitest/setup.mjs +2 -2
- package/dist/build-integrations/vitest/setup.mjs.map +1 -1
- package/dist/build-integrations/vitest/stack-context.d.mts +5 -3
- package/dist/build-integrations/vitest/stack-context.mjs +3 -2
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs.map +1 -1
- package/dist/cli/doctor-probes.mjs +1 -3
- package/dist/cli/doctor-probes.mjs.map +1 -1
- package/dist/cli/main.d.mts +23 -1
- package/dist/cli/main.mjs +63 -8
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +28 -21
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/codegen.mjs +108 -0
- package/dist/cli/wirings/codegen.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +12 -62
- package/dist/cli/wirings/config-loader.mjs.map +1 -1
- package/dist/cli/wirings/dump-ids.mjs +86 -0
- package/dist/cli/wirings/dump-ids.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +1 -1
- package/dist/cli/wirings/engine-command.mjs.map +1 -1
- package/dist/cli/wirings/identity.mjs +2 -2
- package/dist/cli/wirings/identity.mjs.map +1 -1
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
- package/dist/cli/wirings/read-devstack-version.mjs +17 -0
- package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +27 -34
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/cli/wirings/up-ipc.mjs +147 -0
- package/dist/cli/wirings/up-ipc.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +218 -198
- package/dist/cli/wirings/up.mjs.map +1 -1
- package/dist/cli/wirings/wipe.mjs +1 -1
- package/dist/cli/wirings/wipe.mjs.map +1 -1
- package/dist/contracts/chain-probe.d.mts +1 -1
- package/dist/contracts/chain-probe.mjs +1 -1
- package/dist/contracts/chain-probe.mjs.map +1 -1
- package/dist/contracts/codegenable.d.mts +60 -6
- package/dist/contracts/codegenable.mjs +28 -0
- package/dist/contracts/codegenable.mjs.map +1 -0
- package/dist/contracts/config-bindings.mjs +162 -0
- package/dist/contracts/config-bindings.mjs.map +1 -0
- package/dist/contracts/container-runtime.d.mts +23 -17
- package/dist/contracts/plugin-expander.mjs.map +1 -1
- package/dist/contracts/snapshotable.d.mts +5 -7
- package/dist/contracts/wallet-protocol.d.mts +2 -5
- package/dist/contracts/wallet-protocol.mjs +2 -5
- package/dist/contracts/wallet-protocol.mjs.map +1 -1
- package/dist/index.d.mts +21 -26
- package/dist/index.mjs +8 -9
- package/dist/orchestrators/boot.d.mts +28 -0
- package/dist/orchestrators/boot.mjs +440 -0
- package/dist/orchestrators/boot.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +13 -1
- package/dist/orchestrators/codegen/bindings.mjs +23 -9
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
- package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
- package/dist/orchestrators/codegen/emit.mjs +1 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +21 -1
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +2 -0
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +30 -11
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/id-config.mjs +107 -0
- package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
- package/dist/orchestrators/codegen/output-location.mjs +23 -32
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
- package/dist/orchestrators/codegen/paths.mjs +8 -1
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +286 -58
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/layers.mjs +31 -0
- package/dist/orchestrators/layers.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
- package/dist/orchestrators/network-options.d.mts +52 -0
- package/dist/orchestrators/network-options.mjs +43 -0
- package/dist/orchestrators/network-options.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/cors.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/errors.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/profile.mjs +1 -1
- package/dist/orchestrators/router/profile.mjs.map +1 -1
- package/dist/orchestrators/router/service.d.mts +2 -2
- package/dist/orchestrators/router/service.mjs +12 -27
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +1 -1
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture.mjs +171 -195
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
- package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
- package/dist/orchestrators/snapshot/index.mjs +1 -4
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
- package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
- package/dist/orchestrators/snapshot/prune.mjs +40 -31
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/restore.mjs +221 -196
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +72 -62
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -56
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +3 -0
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +2 -2
- package/dist/plugins/account/errors.mjs +1 -4
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.mjs +3 -27
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +3 -8
- package/dist/plugins/account/index.mjs +71 -35
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.d.mts +8 -7
- package/dist/plugins/account/keypair.mjs +5 -18
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +1 -46
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +43 -34
- package/dist/plugins/account/service.mjs +9 -54
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
- package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
- package/dist/plugins/account/variants/signer.mjs.map +1 -1
- package/dist/plugins/action/discriminator.mjs.map +1 -1
- package/dist/plugins/action/errors.mjs +1 -4
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +3 -4
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +2 -2
- package/dist/plugins/action/index.mjs +7 -11
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.d.mts +5 -7
- package/dist/plugins/action/service.mjs +2 -16
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/coin/address-resolution.mjs.map +1 -1
- package/dist/plugins/coin/codegen.mjs +97 -16
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/errors.mjs +1 -4
- package/dist/plugins/coin/errors.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +4 -34
- package/dist/plugins/coin/index.mjs +47 -28
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.d.mts +1 -0
- package/dist/plugins/coin/metadata.mjs +8 -9
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.mjs +7 -21
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +33 -20
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +1 -1
- package/dist/plugins/coin/service.mjs +1 -8
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/snapshot.mjs.map +1 -1
- package/dist/plugins/coin/type-strings.mjs.map +1 -1
- package/dist/plugins/dashboard/domain.mjs +106 -167
- package/dist/plugins/dashboard/domain.mjs.map +1 -1
- package/dist/plugins/dashboard/index.mjs +13 -13
- package/dist/plugins/dashboard/index.mjs.map +1 -1
- package/dist/plugins/dashboard/origin-policy.mjs +3 -3
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
- package/dist/plugins/dashboard/routable.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/root.mjs +23 -44
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/types.mjs +37 -91
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
- package/dist/plugins/dashboard/schema.mjs.map +1 -1
- package/dist/plugins/dashboard/server.mjs.map +1 -1
- package/dist/plugins/deepbook/codegen.d.mts +12 -11
- package/dist/plugins/deepbook/codegen.mjs +108 -11
- package/dist/plugins/deepbook/codegen.mjs.map +1 -1
- package/dist/plugins/deepbook/deploy.mjs +7 -14
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +4 -2
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs.map +1 -1
- package/dist/plugins/deepbook/index.d.mts +38 -34
- package/dist/plugins/deepbook/index.mjs +133 -64
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +4 -8
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +0 -1
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
- package/dist/plugins/deepbook/types.d.mts +2 -1
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +3 -3
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +2 -10
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.mjs +0 -1
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/host-service/errors.d.mts +1 -3
- package/dist/plugins/host-service/errors.mjs +1 -2
- package/dist/plugins/host-service/errors.mjs.map +1 -1
- package/dist/plugins/host-service/index.d.mts +1 -2
- package/dist/plugins/host-service/index.mjs +11 -11
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.mjs +13 -13
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
- package/dist/plugins/internal/codegen-helpers.mjs +2 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
- package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
- package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
- package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
- package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
- package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +2 -2
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.d.mts +7 -14
- package/dist/plugins/package/codegen.mjs +187 -59
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +51 -3
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/errors.mjs +1 -4
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/git-source.d.mts +18 -0
- package/dist/plugins/package/git-source.mjs +119 -0
- package/dist/plugins/package/git-source.mjs.map +1 -0
- package/dist/plugins/package/index.d.mts +28 -19
- package/dist/plugins/package/index.mjs +107 -32
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +2 -2
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +18 -35
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +13 -8
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/publish-output.d.mts +1 -11
- package/dist/plugins/package/publish-output.mjs +1 -9
- package/dist/plugins/package/publish-output.mjs.map +1 -1
- package/dist/plugins/package/registry.d.mts +1 -1
- package/dist/plugins/package/registry.mjs +26 -14
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/snapshot.mjs.map +1 -1
- package/dist/plugins/router-entrypoints.mjs +1 -3
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +1 -5
- package/dist/plugins/seal/codegen.mjs +83 -12
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +2 -6
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +18 -29
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +4 -5
- package/dist/plugins/seal/errors.mjs +1 -8
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +7 -28
- package/dist/plugins/seal/index.mjs +69 -47
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +1 -6
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +2 -3
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.mjs +1 -1
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/plugin-key.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/seal/routable.mjs.map +1 -1
- package/dist/plugins/seal/service.mjs.map +1 -1
- package/dist/plugins/seal/snapshot.mjs +1 -2
- package/dist/plugins/seal/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/auto-tick.mjs +5 -5
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +2 -2
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
- package/dist/plugins/sui/chain-probe.mjs +4 -4
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.d.mts +9 -7
- package/dist/plugins/sui/codegen.mjs +49 -46
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +1 -1
- package/dist/plugins/sui/errors.mjs +1 -9
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
- package/dist/plugins/sui/exec/index.mjs.map +1 -0
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
- package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/fork-orchestration.mjs +13 -23
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +34 -83
- package/dist/plugins/sui/index.mjs +200 -68
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
- package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
- package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/log-attrs.mjs +11 -0
- package/dist/plugins/sui/log-attrs.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +4 -12
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +19 -16
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +4 -17
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.d.mts +1 -0
- package/dist/plugins/sui/mode/local.mjs +203 -61
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +6 -7
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +23 -6
- package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
- package/dist/plugins/sui/move/index.mjs.map +1 -0
- package/dist/plugins/sui/move-summary-runner.mjs +8 -4
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
- package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
- package/dist/plugins/sui/network-resolver.d.mts +6 -6
- package/dist/plugins/sui/routable.mjs.map +1 -1
- package/dist/plugins/sui/service.mjs +10 -6
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +10 -5
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +8 -6
- package/dist/plugins/wallet/codegen.mjs +4 -10
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/errors.mjs +1 -4
- package/dist/plugins/wallet/errors.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +3 -6
- package/dist/plugins/wallet/index.mjs +16 -21
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +3 -3
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -24
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +17 -24
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +2 -2
- package/dist/plugins/wallet/service.mjs +4 -15
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +2 -13
- package/dist/plugins/walrus/codegen.mjs +93 -28
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +2 -3
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +26 -19
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -8
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +13 -44
- package/dist/plugins/walrus/index.mjs +119 -122
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
- package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/snapshot.mjs +3 -3
- package/dist/plugins/walrus/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/storage-nodes.mjs +53 -14
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +4 -8
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.mjs.map +1 -1
- package/dist/primitives/cache.d.mts +1 -1
- package/dist/runtime/docker/client.mjs +47 -3
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +37 -57
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +2 -2
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +10 -11
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +0 -1
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
- package/dist/runtime/docker/inventory.mjs +9 -9
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +12 -1
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +6 -6
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs.map +1 -1
- package/dist/runtime/docker/service.mjs +31 -45
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +14 -64
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +1 -1
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +14 -2
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/brand.d.mts +2 -5
- package/dist/substrate/brand.mjs +3 -2
- package/dist/substrate/brand.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +2 -16
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +0 -2
- package/dist/substrate/event-time.mjs.map +1 -1
- package/dist/substrate/events.d.mts +46 -16
- package/dist/substrate/identity.d.mts +11 -5
- package/dist/substrate/manifest.d.mts +33 -26
- package/dist/substrate/manifest.mjs +8 -3
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/options.d.mts +33 -4
- package/dist/substrate/plugin-ctx.d.mts +70 -0
- package/dist/substrate/plugin-ctx.mjs +66 -0
- package/dist/substrate/plugin-ctx.mjs.map +1 -0
- package/dist/substrate/plugin.d.mts +46 -38
- package/dist/substrate/plugin.mjs +13 -9
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +3 -4
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +50 -20
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +0 -2
- package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +1 -13
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
- package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -3
- package/dist/substrate/runtime/http-probe.mjs.map +1 -1
- package/dist/substrate/runtime/index.mjs +1 -6
- package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
- package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
- package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +5 -1
- package/dist/substrate/runtime/managed-container.mjs +8 -10
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.d.mts +1 -2
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +4 -6
- package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
- package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
- package/dist/substrate/runtime/observability/logger.mjs +3 -3
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
- package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +27 -9
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
- package/dist/substrate/runtime/port-broker/service.mjs +10 -27
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +2 -3
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +87 -11
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
- package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
- package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
- package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/index.mjs +5 -0
- package/dist/substrate/runtime/reconcile/label.mjs +41 -0
- package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
- package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +2 -3
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.mjs.map +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/index.mjs +2 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
- package/dist/substrate/runtime/tar/reader.mjs +216 -0
- package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -1
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
- package/dist/substrate/versioned-doc-sync.mjs +5 -5
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
- package/dist/surfaces/cli/command-tree.mjs +49 -7
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/config.mjs +1 -1
- package/dist/surfaces/cli/commands/config.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +1 -1
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +1 -1
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +3 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/envelope.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +45 -14
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +0 -21
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/dashboard.mjs +0 -48
- package/dist/surfaces/tui/dashboard.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +80 -16
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/errors.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +0 -9
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
- package/dist/surfaces/tui/index.mjs +2 -2
- package/dist/surfaces/tui/index.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
- package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +26 -19
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/dist/surfaces/tui/resource-table.mjs +8 -10
- package/dist/surfaces/tui/resource-table.mjs.map +1 -1
- package/images/postgres/Dockerfile +0 -1
- package/images/sui/Dockerfile +27 -54
- package/images/sui/entrypoint.sh +17 -179
- package/package.json +23 -15
- package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
- package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
- package/dashboard-ui/assets/index-Deml9drg.css +0 -1
- package/dist/api/plugin-errors.d.mts +0 -7
- package/dist/api/plugin-errors.mjs +0 -10
- package/dist/api/plugin-errors.mjs.map +0 -1
- package/dist/cli/wirings/build-verb-layers.mjs +0 -42
- package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
- package/dist/contracts/capability-decl.d.mts +0 -40
- package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
- package/dist/orchestrators/run.mjs +0 -91
- package/dist/orchestrators/run.mjs.map +0 -1
- package/dist/orchestrators/runtime-composition.d.mts +0 -10
- package/dist/orchestrators/runtime-composition.mjs +0 -252
- package/dist/orchestrators/runtime-composition.mjs.map +0 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
- package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
- package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
- package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
- package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
- package/dist/orchestrators/snapshot/state-document.mjs +0 -58
- package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
- package/dist/plugins/account/spans.mjs +0 -16
- package/dist/plugins/account/spans.mjs.map +0 -1
- package/dist/plugins/account/variants/env.mjs +0 -24
- package/dist/plugins/account/variants/env.mjs.map +0 -1
- package/dist/plugins/account/variants/inline.mjs +0 -13
- package/dist/plugins/account/variants/inline.mjs.map +0 -1
- package/dist/plugins/account/variants/keystore.mjs +0 -91
- package/dist/plugins/account/variants/keystore.mjs.map +0 -1
- package/dist/plugins/action/spans.mjs +0 -11
- package/dist/plugins/action/spans.mjs.map +0 -1
- package/dist/plugins/coin/spans.mjs +0 -20
- package/dist/plugins/coin/spans.mjs.map +0 -1
- package/dist/plugins/deepbook/spans.mjs +0 -18
- package/dist/plugins/deepbook/spans.mjs.map +0 -1
- package/dist/plugins/faucet/spans.mjs +0 -12
- package/dist/plugins/faucet/spans.mjs.map +0 -1
- package/dist/plugins/package/spans.mjs +0 -14
- package/dist/plugins/package/spans.mjs.map +0 -1
- package/dist/plugins/postgres/codegen.mjs +0 -42
- package/dist/plugins/postgres/codegen.mjs.map +0 -1
- package/dist/plugins/postgres/connection.d.mts +0 -33
- package/dist/plugins/postgres/connection.mjs.map +0 -1
- package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
- package/dist/plugins/postgres/errors.d.mts +0 -60
- package/dist/plugins/postgres/errors.mjs +0 -29
- package/dist/plugins/postgres/errors.mjs.map +0 -1
- package/dist/plugins/postgres/index.d.mts +0 -37
- package/dist/plugins/postgres/index.mjs +0 -68
- package/dist/plugins/postgres/index.mjs.map +0 -1
- package/dist/plugins/postgres/routable.mjs +0 -33
- package/dist/plugins/postgres/routable.mjs.map +0 -1
- package/dist/plugins/postgres/service.d.mts +0 -50
- package/dist/plugins/postgres/service.mjs +0 -179
- package/dist/plugins/postgres/service.mjs.map +0 -1
- package/dist/plugins/postgres/snapshot.mjs +0 -32
- package/dist/plugins/postgres/snapshot.mjs.map +0 -1
- package/dist/plugins/postgres/spans.mjs +0 -11
- package/dist/plugins/postgres/spans.mjs.map +0 -1
- package/dist/plugins/seal/spans.mjs +0 -18
- package/dist/plugins/seal/spans.mjs.map +0 -1
- package/dist/plugins/sui/spans.mjs +0 -17
- package/dist/plugins/sui/spans.mjs.map +0 -1
- package/dist/plugins/wallet/spans.mjs +0 -22
- package/dist/plugins/wallet/spans.mjs.map +0 -1
- package/dist/plugins/walrus/registry-publish.d.mts +0 -24
- package/dist/plugins/walrus/spans.mjs +0 -18
- package/dist/plugins/walrus/spans.mjs.map +0 -1
- package/dist/runtime/docker/logs.d.mts +0 -1
- package/dist/runtime/docker/logs.mjs +0 -34
- package/dist/runtime/docker/logs.mjs.map +0 -1
- package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
- package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
- package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
- package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
- package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
- package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
- package/dist/substrate/runtime/observability/span-store.mjs +0 -110
- package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
- package/dist/substrate/runtime/observability/spans.d.mts +0 -1
- package/dist/substrate/runtime/observability/spans.mjs +0 -87
- package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
- package/dist/substrate/runtime/projection/index.mjs +0 -4
- package/dist/substrate/runtime/projection/persisted.mjs +0 -213
- package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/index.mjs +0 -3
- package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
- package/dist/substrate/runtime/state-store/schema.mjs +0 -41
- package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/service.d.mts +0 -1
- package/dist/substrate/runtime/state-store/service.mjs +0 -145
- package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
- package/dist/substrate/state-store.d.mts +0 -1
- /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
- /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
- /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
- /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
- /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/plugins/sui/exec/index.ts"],"sourcesContent":["// sui-execute — the Sui-SDK transaction roundtrip.\n//\n// Lives in `plugins/sui`; wraps `@mysten/sui` tx signing + execution.\n// Drives the \"build → sign → execute → wait → project\" roundtrip that\n// is otherwise duplicated verbatim across `plugins/package/publish-\n// executor.ts` and `plugins/action/execute.ts` — ~80% identical\n// line-for-line — and is also needed for `seal` (deploy + key-server\n// registration), `coin.mint`, `action`, and `deepbook` (publish + pool\n// creation).\n//\n// Lifting the dispatch here gives every Sui-tx plugin one well-tested\n// surface and surfaces the SDK-envelope shape decisions (`include:\n// { effects, objectTypes }`, `$kind` projection, `FailedTransaction`\n// detection) ONCE.\n//\n// Boundary discipline:\n//\n// - Consumes the published `ClientWithCoreApi` surface from\n// `@mysten/sui/client` plus an opaque resolved-signer closure. The\n// caller passes in the resolved SDK ref and the signer; this helper\n// just orchestrates the roundtrip.\n// - The return shape (`ExecutedReceipt`) is domain-neutral: a flat\n// digest + a uniform `objectChanges` array. Callers map to their\n// domain shape (PublishReceipt, ActionReceipt, MintReceipt, …).\n// - Transport / protocol failures (serialize, sign, execute, no-digest,\n// wait-for-finality) route through `SuiExecuteError` whose `phase`\n// discriminates the failing step. Callers map to their plugin's\n// phase taxonomy in their produce body's `mapError` closure.\n// - On-chain `FailedTransaction` is a RETURN-CHANNEL variant of\n// `SuiExecuteResult`, NOT an error — mirrors `account.signAndExecute`\n// and STYLE_GUIDE §2's return-channel discriminated-union rule.\n// Callers dispatch on `$kind` after the call.\n\nimport { Effect, Schema, Scope } from 'effect';\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\n\nimport { formatUnknownError } from '../../../substrate/runtime/format-unknown-error.ts';\n\n// ---------------------------------------------------------------------------\n// Errors — substrate-style Schema.TaggedErrorClass\n// ---------------------------------------------------------------------------\n\n/** Tagged failure during one step of the Sui-tx roundtrip. `phase`\n * discriminates which step failed so callers can map to their\n * plugin's phase taxonomy (publish-tx, sign, parse, …) without\n * losing the original cause.\n *\n * On-chain `FailedTransaction` is NOT a phase here — it surfaces as\n * the `$kind: 'FailedTransaction'` variant of `SuiExecuteResult`\n * (return channel). Only transport / protocol failures live in this\n * taxonomy. See STYLE_GUIDE §2. */\nexport class SuiExecuteError extends Schema.TaggedErrorClass<SuiExecuteError>()('SuiExecuteError', {\n\tphase: Schema.Literals(['serialize', 'sign', 'execute', 'no-digest', 'wait-for-finality']),\n\tsignerName: Schema.String,\n\tsignerAddress: Schema.String,\n\tmessage: Schema.String,\n\t/** Echoed digest when the SDK returned one but a later step\n\t * failed — surfaces in cause-walker output. */\n\tdigest: Schema.optional(Schema.String),\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\n// ---------------------------------------------------------------------------\n// Inputs / outputs\n// ---------------------------------------------------------------------------\n\n/** The SDK client this helper drives. Accepts any `ClientWithCoreApi`\n * (the published cross-transport surface from `@mysten/sui/client`).\n * `executeSuiTx` calls `client.core.executeTransaction` /\n * `client.core.waitForTransaction` per STYLE_GUIDE §16. File-local: the\n * `executeSuiTx` params type is the public surface; callers pass a\n * `ClientWithCoreApi` directly. */\ntype SuiExecuteClient = ClientWithCoreApi;\n\n/** Serialised transaction-build callback. Returns the BCS bytes ready\n * for signing. The caller owns the `Transaction` construction (this\n * helper does not import `@mysten/sui/transactions`) and resolves it\n * via `Transaction.build({ client })`. */\ntype SerializedTxBuilder = () => Promise<Uint8Array>;\n\nexport interface TransactionSignerScope<SignError = unknown> {\n\treadonly signTransaction: (\n\t\ttx: Uint8Array,\n\t) => Effect.Effect<{ readonly bytes: string; readonly signature: string }, SignError>;\n}\n\n/** Resolved signer — narrow slice of `AccountValue`. */\nexport interface ResolvedSigner {\n\treadonly name: string;\n\treadonly address: string;\n\treadonly signTransaction: (\n\t\ttx: Uint8Array,\n\t) => Effect.Effect<{ readonly bytes: string; readonly signature: string }, unknown>;\n\treadonly withTransactionSigner: <A, E, R>(\n\t\tbody: (signer: TransactionSignerScope) => Effect.Effect<A, E, R>,\n\t) => Effect.Effect<A, E, R>;\n}\n\n/** Flat per-object change record. Mirrors the union of fields that\n * `PublishObjectChange` (package) and `ActionObjectChange` (action)\n * expose so callers can pick whichever subset they surface. */\ninterface ExecutedObjectChange {\n\treadonly objectId: string;\n\treadonly objectType?: string;\n\treadonly outputState?: string;\n\treadonly idOperation?: string;\n}\n\n/** Result of one successful execute round. Callers map this to their\n * domain shape (PublishReceipt, ActionReceipt, MintReceipt, …). */\nexport interface ExecutedReceipt {\n\treadonly digest: string;\n\treadonly objectChanges: ReadonlyArray<ExecutedObjectChange>;\n}\n\n/** On-chain failure projection. The transaction was delivered +\n * executed by the validator but the on-chain execution failed.\n * Carries the digest (for log correlation) plus the validator's\n * stringified error when one was attached. `executionError` is\n * omitted when the SDK returns no message (STYLE_GUIDE §5: no\n * sentinel placeholders at resolved-value surfaces). An envelope\n * without a digest fails at projection with\n * `SuiExecuteError(phase: 'no-digest')`.\n *\n * This is the single source of truth for the on-chain-failure shape\n * across the devstack: `account.SignAndExecuteResult`'s\n * `FailedTransaction` variant carries it directly; every plugin that\n * renders one of these failures into a user-facing error message goes\n * through `formatExecutedFailure` (below). */\nexport interface ExecutedFailure {\n\treadonly digest: string;\n\treadonly executionError?: string;\n}\n\n/** Render the canonical \"digest + executionError-or-noted-absent\" tail\n * used by every plugin's `FailedTransaction` surface. Pure projection;\n * does not throw.\n *\n * Used at the call site as e.g.\n * `\\`seal publish ... ${formatExecutedFailure(result.FailedTransaction)}\\``.\n * Centralising the wording means future tweaks propagate uniformly\n * across the seal / coin / walrus / deepbook / action / package\n * surfaces. */\nexport const formatExecutedFailure = (failure: ExecutedFailure): string =>\n\tfailure.executionError !== undefined\n\t\t? `at ${failure.digest}: ${failure.executionError}`\n\t\t: `at ${failure.digest} (no validator error attached)`;\n\n/** Outcome of `executeSuiTx`. Mirrors the SDK's discriminated\n * `SuiClientTypes.TransactionResult` shape — on-chain failures are a\n * RETURN VALUE (callers dispatch on `$kind`), NOT an error. Only\n * transport / protocol failures (sign refused, RPC unreachable,\n * finality wait broke, no digest) surface through `SuiExecuteError`.\n * See STYLE_GUIDE §2 for the return-channel discipline this matches. */\ntype SuiExecuteResult =\n\t| { readonly $kind: 'Transaction'; readonly Transaction: ExecutedReceipt }\n\t| { readonly $kind: 'FailedTransaction'; readonly FailedTransaction: ExecutedFailure };\n\n// ---------------------------------------------------------------------------\n// SDK envelope projection (matches publish-executor + action.execute)\n// ---------------------------------------------------------------------------\n\ninterface RawExecuteEnvelope {\n\treadonly $kind?: 'Transaction' | 'FailedTransaction';\n\treadonly Transaction?: {\n\t\treadonly digest?: string;\n\t\treadonly effects?: {\n\t\t\treadonly changedObjects?: ReadonlyArray<{\n\t\t\t\treadonly objectId?: string;\n\t\t\t\treadonly outputState?: string;\n\t\t\t\treadonly idOperation?: string;\n\t\t\t}>;\n\t\t};\n\t\treadonly objectTypes?: Readonly<Record<string, string>>;\n\t};\n\treadonly FailedTransaction?: {\n\t\treadonly digest?: string;\n\t\treadonly status?: { readonly error?: string };\n\t};\n}\n\n/** Pull the digest out of a raw `executeTransaction` envelope without\n * committing to success vs on-chain failure. `FailedTransaction`\n * carries its digest under a sibling key, so callers that only need\n * the digest (impersonate / one-shot dispatch paths that don't run\n * the full `executeSuiTx` projection) read it through this projector\n * rather than re-deriving the `$kind`-branching shape inline.\n * Returns `undefined` for a malformed envelope; callers decide\n * whether a missing digest is a failure. */\nexport const extractExecuteDigest = (raw: unknown): string | undefined => {\n\tconst env = raw as RawExecuteEnvelope;\n\treturn env.$kind === 'FailedTransaction'\n\t\t? env.FailedTransaction?.digest\n\t\t: env.Transaction?.digest;\n};\n\n// ---------------------------------------------------------------------------\n// The helper\n// ---------------------------------------------------------------------------\n\n/** Drive the serialize → sign → execute → wait → project pipeline.\n *\n * The caller passes:\n * - `client`: an opaque SDK client exposing `executeTransaction` +\n * `waitForTransaction` (matches the cast in both existing\n * duplicates).\n * - `signer`: a `ResolvedSigner` slice with an account-owned\n * transaction scope. The build/sign/execute/wait pipeline runs\n * inside that scope so gas/object-version resolution cannot race\n * another transaction from the same address.\n * - `build`: a closure returning the serialised tx bytes (the\n * caller constructed the `Transaction` and resolved gas via\n * `tx.build({ client })`).\n * - `awaitFinality`: opt-out for the `waitForTransaction` step.\n * Defaults to `true`. Some callers (action ready-probe) need it;\n * others (mode-local publish ready probe path) wait separately\n * via `getObject` polling and can pass `false`.\n *\n * Returns a `SuiExecuteResult` discriminated union — callers dispatch\n * on `$kind` to distinguish on-chain success (`'Transaction'`) from\n * on-chain rejection (`'FailedTransaction'`). Transport / protocol\n * failures (serialize, sign, execute, no-digest, wait-for-finality)\n * surface as `SuiExecuteError` with a `phase` discriminator the\n * caller maps to its plugin's phase taxonomy.\n */\nexport const executeSuiTx = (params: {\n\treadonly client: SuiExecuteClient;\n\treadonly signer: ResolvedSigner;\n\treadonly build: SerializedTxBuilder;\n\treadonly awaitFinality?: boolean;\n}): Effect.Effect<SuiExecuteResult, SuiExecuteError, Scope.Scope> =>\n\tparams.signer.withTransactionSigner((lockedSigner) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst { client, signer, build } = params;\n\t\t\tconst awaitFinality = params.awaitFinality ?? true;\n\n\t\t\t// 1. Serialise the transaction. Failures inside the build\n\t\t\t// closure (e.g. `Transaction.build({ client })` rejection)\n\t\t\t// surface as `phase: 'serialize'` so callers can attribute\n\t\t\t// them at the call site.\n\t\t\tconst txBytes = yield* Effect.tryPromise({\n\t\t\t\ttry: () => build(),\n\t\t\t\tcatch: (cause) =>\n\t\t\t\t\tnew SuiExecuteError({\n\t\t\t\t\t\tphase: 'serialize',\n\t\t\t\t\t\tsignerName: signer.name,\n\t\t\t\t\t\tsignerAddress: signer.address,\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t`Transaction.build failed for signer '${signer.name}' ` +\n\t\t\t\t\t\t\t`(address=${signer.address}): ` +\n\t\t\t\t\t\t\tformatUnknownError(cause),\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t});\n\n\t\t\t// 2. Sign with the resolved signer. The signer's own typed\n\t\t\t// error surfaces in `cause`; we collapse to `phase: 'sign'`\n\t\t\t// so the cascade-formatter can walk the cause chain.\n\t\t\tconst signed = yield* lockedSigner.signTransaction(txBytes).pipe(\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(cause) =>\n\t\t\t\t\t\tnew SuiExecuteError({\n\t\t\t\t\t\t\tphase: 'sign',\n\t\t\t\t\t\t\tsignerName: signer.name,\n\t\t\t\t\t\t\tsignerAddress: signer.address,\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t`signer.signTransaction failed for '${signer.name}' ` +\n\t\t\t\t\t\t\t\t`(address=${signer.address}).`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\t// 3. Execute via the SDK. We always request `effects: true` and\n\t\t\t// `objectTypes: true` because every existing caller needs\n\t\t\t// `changedObjects` + types to project to its domain shape.\n\t\t\tconst raw = yield* Effect.tryPromise({\n\t\t\t\ttry: () =>\n\t\t\t\t\tclient.core.executeTransaction({\n\t\t\t\t\t\ttransaction: txBytes,\n\t\t\t\t\t\tsignatures: [signed.signature],\n\t\t\t\t\t\tinclude: { effects: true, objectTypes: true },\n\t\t\t\t\t}),\n\t\t\t\tcatch: (cause) =>\n\t\t\t\t\tnew SuiExecuteError({\n\t\t\t\t\t\tphase: 'execute',\n\t\t\t\t\t\tsignerName: signer.name,\n\t\t\t\t\t\tsignerAddress: signer.address,\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t`executeTransaction rejected for signer '${signer.name}': ` +\n\t\t\t\t\t\t\tformatUnknownError(cause),\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t});\n\n\t\t\t// 4. Project the envelope. `$kind === 'FailedTransaction'`\n\t\t\t// surfaces as a return-channel variant — on-chain\n\t\t\t// rejection is a normal outcome, NOT a transport failure\n\t\t\t// (STYLE_GUIDE §2). Callers dispatch on `$kind` and map\n\t\t\t// to their plugin's on-chain-failure shape.\n\t\t\tconst env = raw as RawExecuteEnvelope;\n\t\t\tif (env.$kind === 'FailedTransaction') {\n\t\t\t\tconst failedDigest = env.FailedTransaction?.digest;\n\t\t\t\tif (failedDigest === undefined) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew SuiExecuteError({\n\t\t\t\t\t\t\tphase: 'no-digest',\n\t\t\t\t\t\t\tsignerName: signer.name,\n\t\t\t\t\t\t\tsignerAddress: signer.address,\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t`executeTransaction returned FailedTransaction with no digest. ` +\n\t\t\t\t\t\t\t\t`Raw=${JSON.stringify(raw).slice(0, 300)}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (awaitFinality) {\n\t\t\t\t\tyield* Effect.tryPromise({\n\t\t\t\t\t\ttry: () => client.core.waitForTransaction({ digest: failedDigest }),\n\t\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\t\tnew SuiExecuteError({\n\t\t\t\t\t\t\t\tphase: 'wait-for-finality',\n\t\t\t\t\t\t\t\tsignerName: signer.name,\n\t\t\t\t\t\t\t\tsignerAddress: signer.address,\n\t\t\t\t\t\t\t\tdigest: failedDigest,\n\t\t\t\t\t\t\t\tmessage: `waitForTransaction(${failedDigest}) failed.`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst executionError = env.FailedTransaction?.status?.error;\n\t\t\t\treturn {\n\t\t\t\t\t$kind: 'FailedTransaction',\n\t\t\t\t\tFailedTransaction: {\n\t\t\t\t\t\tdigest: failedDigest,\n\t\t\t\t\t\t...(executionError !== undefined ? { executionError } : {}),\n\t\t\t\t\t},\n\t\t\t\t} satisfies SuiExecuteResult;\n\t\t\t}\n\t\t\tconst txOk = env.Transaction;\n\t\t\tif (txOk?.digest === undefined) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew SuiExecuteError({\n\t\t\t\t\t\tphase: 'no-digest',\n\t\t\t\t\t\tsignerName: signer.name,\n\t\t\t\t\t\tsignerAddress: signer.address,\n\t\t\t\t\t\tmessage: `executeTransaction returned no digest. Raw=${JSON.stringify(raw).slice(0, 300)}`,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// 5. Wait for finality (opt-out — some callers wait separately).\n\t\t\tif (awaitFinality) {\n\t\t\t\tyield* Effect.tryPromise({\n\t\t\t\t\ttry: () => client.core.waitForTransaction({ digest: txOk.digest! }),\n\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\tnew SuiExecuteError({\n\t\t\t\t\t\t\tphase: 'wait-for-finality',\n\t\t\t\t\t\t\tsignerName: signer.name,\n\t\t\t\t\t\t\tsignerAddress: signer.address,\n\t\t\t\t\t\t\tdigest: txOk.digest,\n\t\t\t\t\t\t\tmessage: `waitForTransaction(${txOk.digest}) failed.`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// 6. Project changedObjects flat. Callers pick which entries\n\t\t\t// they care about (published / created / mutated / by-type\n\t\t\t// substring).\n\t\t\tconst objectTypes = txOk.objectTypes ?? {};\n\t\t\tconst objectChanges: Array<ExecutedObjectChange> = [];\n\t\t\tfor (const ch of txOk.effects?.changedObjects ?? []) {\n\t\t\t\tif (typeof ch.objectId !== 'string') continue;\n\t\t\t\tconst objectType = objectTypes[ch.objectId];\n\t\t\t\tconst entry: { -readonly [K in keyof ExecutedObjectChange]: ExecutedObjectChange[K] } = {\n\t\t\t\t\tobjectId: ch.objectId,\n\t\t\t\t};\n\t\t\t\tif (objectType !== undefined) entry.objectType = objectType;\n\t\t\t\tif (ch.outputState !== undefined) entry.outputState = ch.outputState;\n\t\t\t\tif (ch.idOperation !== undefined) entry.idOperation = ch.idOperation;\n\t\t\t\tobjectChanges.push(entry);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t$kind: 'Transaction',\n\t\t\t\tTransaction: { digest: txOk.digest, objectChanges },\n\t\t\t} satisfies SuiExecuteResult;\n\t\t}),\n\t);\n\n/** Decode a possibly URI-encoded SDK error message. The Sui SDK\n * emits `decodeURIComponent`-able strings for some error paths\n * (notably stale-object-version reports). Falls back to the raw\n * message on decode failure. */\nconst decodeMessage = (message: string): string => {\n\ttry {\n\t\treturn decodeURIComponent(message);\n\t} catch {\n\t\treturn message;\n\t}\n};\n\n/** Typed predicate for the SDK's \"stale object version\" transient\n * failure — the Move VM refuses a transaction that references an\n * object reference older than the current chain version. Consumers\n * retry with fresh refs; pair with `STALE_OBJECT_VERSION_RETRY_PROFILE`\n * from `retry-policy.ts`. Detection sniffs the SDK message because the\n * underlying gRPC error class doesn't expose a structured discriminator\n * for this case. */\nexport const isSuiStaleObjectVersionError = (err: SuiExecuteError): boolean => {\n\tconst message = decodeMessage(err.message);\n\treturn (\n\t\tmessage.includes('needs to be rebuilt because object') && message.includes('current version')\n\t);\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/plugins/sui/exec/sign-and-dispatch.ts
|
|
3
3
|
/** Run `withTransactionSigner( buildTxBytes → signAndExecute → mapSignError →
|
|
4
4
|
* dispatch on $kind )`. The caller owns:
|
|
5
5
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-and-dispatch.mjs","names":[],"sources":["../../../../src/plugins/sui/exec/sign-and-dispatch.ts"],"sourcesContent":["// signAndDispatch — boilerplate compactor for the\n// `withTransactionSigner → buildTxBytes → signAndExecute → $kind\n// dispatch` pattern that recurs across every Sui-tx plugin\n// (`package.publish`, `action.execute`, `coin.mint`, `walrus.fundWal`,\n// `deepbook.fundDeep`, and any future user-authored signer plugin).\n//\n// Distinct from `executeSuiTx` (sibling in this directory):\n// - `executeSuiTx` drives `core.executeTransaction` directly via the\n// raw SDK client + an opaque resolved signer. It splits sign and\n// execute and projects the SDK envelope itself.\n// - `signAndDispatch` consumes the Account plugin's higher-level\n// `signAndExecute` surface (which fuses sign + execute + finality\n// wait + projection into one Effect) and just compacts the\n// surrounding boilerplate.\n//\n// Both helpers coexist: plugins that publish through the Account bus\n// (the majority) use `signAndDispatch`; lower-level callers that drive\n// the SDK client directly use `executeSuiTx`.\n//\n// Lives in `plugins/sui`. Consumes opaque shapes (an `AccountValue`-like\n// contract surface and the `SignAndExecuteResult` union) without reaching\n// into any particular publisher's full contract. The caller owns the\n// transaction build, the error mapping, and the dispatch bodies.\n//\n// On-chain `FailedTransaction` is a RETURN-CHANNEL outcome (caller\n// dispatches on `$kind` via the `onFailed` callback), NOT an error —\n// matches STYLE_GUIDE §2 and the upstream `SignAndExecuteResult` shape.\n\nimport { Effect } from 'effect';\n\nimport type { ExecutedFailure, ExecutedReceipt } from './index.ts';\n\n// ---------------------------------------------------------------------------\n// Resolved-signer shape — narrow slice of `AccountValue`\n// ---------------------------------------------------------------------------\n\n/** The SDK-shaped result of `lockedSigner.signAndExecute`. Mirrors the\n * Account plugin's `SignAndExecuteResult` union; redeclared here so\n * this helper does not import from `plugins/account/*`. The Account\n * plugin's projection uses the `ExecutedFailure` shape already (single\n * source of truth in `./index.ts`). Generic over the success\n * transaction shape because the Account plugin's `TxResult` widens\n * `ExecutedReceipt` with `effects` / `balanceChanges` fields that not\n * every caller needs to surface. */\nexport type SignAndDispatchResult<TxOk extends { readonly digest: string } = ExecutedReceipt> =\n\t| { readonly $kind: 'Transaction'; readonly Transaction: TxOk }\n\t| { readonly $kind: 'FailedTransaction'; readonly FailedTransaction: ExecutedFailure };\n\n/** Locked-signer slice handed to the caller's tx-build closure. Narrow\n * view of `AccountTransactionSigner` — this contract doesn't reach into\n * the Account plugin's surface. Generic over the signer's sign-side\n * error (typically `AccountSignError`) and the success transaction\n * shape. */\nexport interface SignAndDispatchSigner<SignError, TxOk extends { readonly digest: string }> {\n\treadonly signAndExecute: (\n\t\ttx: Uint8Array,\n\t) => Effect.Effect<SignAndDispatchResult<TxOk>, SignError>;\n}\n\n/** Outer signer-source surface required by `signAndDispatch` — anything\n * that exposes a `withTransactionSigner` scope. This helper only\n * reaches `withTransactionSigner`, so plugins composing custom\n * signing surfaces can satisfy the slice without dragging in any\n * particular publisher's full contract. */\nexport interface TransactionSignerSource<SignError, TxOk extends { readonly digest: string }> {\n\treadonly withTransactionSigner: <A, E, R>(\n\t\tbody: (signer: SignAndDispatchSigner<SignError, TxOk>) => Effect.Effect<A, E, R>,\n\t) => Effect.Effect<A, E, R>;\n}\n\n// ---------------------------------------------------------------------------\n// The helper\n// ---------------------------------------------------------------------------\n\n/** Run `withTransactionSigner( buildTxBytes → signAndExecute → mapSignError →\n * dispatch on $kind )`. The caller owns:\n *\n * - `buildTxBytes`: an Effect returning the serialised `Uint8Array`,\n * run INSIDE the signer scope. Failures inside this effect\n * propagate verbatim — the caller maps `Transaction.build` /\n * impersonation-build errors to its plugin domain there.\n * - `mapSignError`: projects the signer's typed sign error\n * (`AccountSignError` for the Account bus) into the caller's\n * domain error before dispatch. Without this projection\n * `mapSignError` and the dispatch callbacks would need to share\n * the same error channel, which forces every caller into a tagged\n * domain error anyway.\n * - `onFailed`: dispatch for the `FailedTransaction` variant.\n * Typically `Effect.fail(domainError(formatExecutedFailure(...)))`.\n * - `onSuccess`: dispatch for the `Transaction` variant — projects\n * the SDK-shaped tx result to the caller's receipt shape.\n *\n * Returns the union of the dispatch callbacks' types so callers do\n * not have to wrap the call in a redundant `Effect.gen`. */\nexport const signAndDispatch = <\n\tA,\n\tBuildError,\n\tBuildEnv,\n\tSignError,\n\tDomainError,\n\tOnFailedError,\n\tOnFailedEnv,\n\tOnSuccessError,\n\tOnSuccessEnv,\n\tTxOk extends { readonly digest: string } = ExecutedReceipt,\n>(args: {\n\treadonly signerSource: TransactionSignerSource<SignError, TxOk>;\n\treadonly buildTxBytes: (\n\t\tsigner: SignAndDispatchSigner<SignError, TxOk>,\n\t) => Effect.Effect<Uint8Array, BuildError, BuildEnv>;\n\treadonly mapSignError: (cause: SignError) => DomainError;\n\treadonly onFailed: (failure: ExecutedFailure) => Effect.Effect<A, OnFailedError, OnFailedEnv>;\n\treadonly onSuccess: (ok: TxOk) => Effect.Effect<A, OnSuccessError, OnSuccessEnv>;\n}): Effect.Effect<\n\tA,\n\tBuildError | DomainError | OnFailedError | OnSuccessError,\n\tBuildEnv | OnFailedEnv | OnSuccessEnv\n> =>\n\targs.signerSource.withTransactionSigner((lockedSigner) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst txBytes = yield* args.buildTxBytes(lockedSigner);\n\t\t\tconst result = yield* lockedSigner\n\t\t\t\t.signAndExecute(txBytes)\n\t\t\t\t.pipe(Effect.mapError(args.mapSignError));\n\t\t\tif (result.$kind === 'FailedTransaction') {\n\t\t\t\treturn yield* args.onFailed(result.FailedTransaction);\n\t\t\t}\n\t\t\treturn yield* args.onSuccess(result.Transaction);\n\t\t}),\n\t);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA8FA,MAAa,mBAWX,SAaD,KAAK,aAAa,uBAAuB,iBACxC,OAAO,IAAI,aAAa;CACvB,MAAM,UAAU,OAAO,KAAK,aAAa,YAAY;CACrD,MAAM,SAAS,OAAO,aACpB,eAAe,OAAO,CAAC,CACvB,KAAK,OAAO,SAAS,KAAK,YAAY,CAAC;CACzC,IAAI,OAAO,UAAU,qBACpB,OAAO,OAAO,KAAK,SAAS,OAAO,iBAAiB;CAErD,OAAO,OAAO,KAAK,UAAU,OAAO,WAAW;AAChD,CAAC,CACF"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { leaseKey } from "../../substrate/runtime/lease-broker/service.mjs";
|
|
2
2
|
import "../../substrate/runtime/lease-broker/index.mjs";
|
|
3
3
|
import { formatUnknownError } from "../../substrate/runtime/format-unknown-error.mjs";
|
|
4
|
-
import { SuiSpans } from "./spans.mjs";
|
|
5
4
|
import { FORK_IMPERSONATION_GAS_BUDGET, buildForkImpersonationTransactionBytes, selectSufficientForkCoin } from "./fork-transaction.mjs";
|
|
6
5
|
import { faucetBodyError } from "../faucet/errors.mjs";
|
|
7
6
|
import "../faucet/index.mjs";
|
|
@@ -22,10 +21,7 @@ const withSerialization = (serialization, effect) => {
|
|
|
22
21
|
return Effect.scoped(Effect.gen(function* () {
|
|
23
22
|
yield* serialization.broker.acquire(leaseKey(serialization.key), serialization.owner);
|
|
24
23
|
yield* effect;
|
|
25
|
-
}))
|
|
26
|
-
[SuiSpans.localFaucetLeaseKey]: serialization.key,
|
|
27
|
-
[SuiSpans.localFaucetLeaseOwner]: serialization.owner
|
|
28
|
-
} }));
|
|
24
|
+
}));
|
|
29
25
|
};
|
|
30
26
|
/** Build a SUI fork-faucet strategy that funds via whale impersonation. */
|
|
31
27
|
const suiForkFaucetStrategy = (opts) => ({ request: ({ address, amount }) => {
|
|
@@ -47,7 +43,7 @@ const suiForkFaucetStrategy = (opts) => ({ request: ({ address, amount }) => {
|
|
|
47
43
|
catch: (cause) => bodyError(opts.whale, address, amount, `sui fork faucet: waitForTransaction(${submitted.digest}) failed: ${formatUnknownError(cause)}`)
|
|
48
44
|
});
|
|
49
45
|
});
|
|
50
|
-
return withSerialization(opts.serialization, transfer)
|
|
46
|
+
return withSerialization(opts.serialization, transfer);
|
|
51
47
|
} });
|
|
52
48
|
//#endregion
|
|
53
49
|
export { suiForkFaucetStrategy };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fork-faucet-strategy.mjs","names":[],"sources":["../../../src/plugins/sui/fork-faucet-strategy.ts"],"sourcesContent":["// SUI fork-faucet strategy — impersonation-based funding.\n//\n// Fork networks have no real faucet. This strategy funds a recipient by\n// IMPERSONATING a large-reserve \"whale\" address on the forked upstream\n// and transferring SUI from it: it builds `splitCoins(tx.gas, [amount])`\n// + `transferObjects([coin], recipient)` with the whale as sender, runs\n// it through the SAME impersonation path the package/action plugins use\n// (`buildForkImpersonationTransactionBytes` -> `fork.impersonate`, with\n// empty signatures), and waits for finality.\n//\n// It is registered into the `faucet:request:<chainId>` strategy registry\n// from `sui/index.ts` exactly like the local-faucet HTTP strategy, so\n// ephemeral-account auto-funding and cross-cutting SUI funding \"just\n// work\" in fork mode. (Dependency direction faucet <- sui: the sui\n// plugin imports the faucet contract/error types, never the reverse.)\n//\n// Error channel: account funding only catches FaucetUnreachable |\n// FaucetExhausted | FaucetBodyError (see account/funding.ts). The\n// impersonation path fails with SuiPluginError, so every internal\n// failure is mapped to FaucetBodyError here — otherwise it would escape\n// funding's `catchTags` and surface as an unhandled error.\n//\n// Concurrency: every request selects one sufficient whale SUI coin as\n// both gas payment and split source. Concurrent requests would race the\n// same coin version, so requests serialize on a chain-scoped lease.\n\nimport { Effect } from 'effect';\n\nimport { Transaction } from '@mysten/sui/transactions';\n\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.ts';\nimport { leaseKey, type LeaseBroker } from '../../substrate/runtime/lease-broker/index.ts';\nimport { faucetBodyError, type FaucetBodyError, type FaucetStrategy } from '../faucet/index.ts';\n\nimport type { SuiSdkShim } from './chain-probe.ts';\nimport type { SuiPluginError } from './errors.ts';\nimport {\n\tFORK_IMPERSONATION_GAS_BUDGET,\n\tbuildForkImpersonationTransactionBytes,\n\tselectSufficientForkCoin,\n\ttype ForkImpersonationGasClient,\n} from './fork-transaction.ts';\nimport type { ForkAdminSurface } from './mode/shared.ts';\
|
|
1
|
+
{"version":3,"file":"fork-faucet-strategy.mjs","names":[],"sources":["../../../src/plugins/sui/fork-faucet-strategy.ts"],"sourcesContent":["// SUI fork-faucet strategy — impersonation-based funding.\n//\n// Fork networks have no real faucet. This strategy funds a recipient by\n// IMPERSONATING a large-reserve \"whale\" address on the forked upstream\n// and transferring SUI from it: it builds `splitCoins(tx.gas, [amount])`\n// + `transferObjects([coin], recipient)` with the whale as sender, runs\n// it through the SAME impersonation path the package/action plugins use\n// (`buildForkImpersonationTransactionBytes` -> `fork.impersonate`, with\n// empty signatures), and waits for finality.\n//\n// It is registered into the `faucet:request:<chainId>` strategy registry\n// from `sui/index.ts` exactly like the local-faucet HTTP strategy, so\n// ephemeral-account auto-funding and cross-cutting SUI funding \"just\n// work\" in fork mode. (Dependency direction faucet <- sui: the sui\n// plugin imports the faucet contract/error types, never the reverse.)\n//\n// Error channel: account funding only catches FaucetUnreachable |\n// FaucetExhausted | FaucetBodyError (see account/funding.ts). The\n// impersonation path fails with SuiPluginError, so every internal\n// failure is mapped to FaucetBodyError here — otherwise it would escape\n// funding's `catchTags` and surface as an unhandled error.\n//\n// Concurrency: every request selects one sufficient whale SUI coin as\n// both gas payment and split source. Concurrent requests would race the\n// same coin version, so requests serialize on a chain-scoped lease.\n\nimport { Effect } from 'effect';\n\nimport { Transaction } from '@mysten/sui/transactions';\n\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.ts';\nimport { leaseKey, type LeaseBroker } from '../../substrate/runtime/lease-broker/index.ts';\nimport { faucetBodyError, type FaucetBodyError, type FaucetStrategy } from '../faucet/index.ts';\n\nimport type { SuiSdkShim } from './chain-probe.ts';\nimport type { SuiPluginError } from './errors.ts';\nimport {\n\tFORK_IMPERSONATION_GAS_BUDGET,\n\tbuildForkImpersonationTransactionBytes,\n\tselectSufficientForkCoin,\n\ttype ForkImpersonationGasClient,\n} from './fork-transaction.ts';\nimport type { ForkAdminSurface } from './mode/shared.ts';\n\n/** Serialization for the shared whale funding coin (mirrors\n * `SuiLocalFaucetSerialization` in `local-faucet-strategy.ts`). */\nexport interface SuiForkFaucetSerialization {\n\treadonly broker: LeaseBroker;\n\treadonly key: string;\n\treadonly owner: string;\n}\n\nexport interface SuiForkFaucetStrategyOptions {\n\t/** Address impersonated as the funding source. Must hold a large SUI\n\t * coin in the fork (auto-seeded + validated at boot). */\n\treadonly whale: string;\n\t/** Fork admin surface (`client.fork`) — submits the impersonation tx. */\n\treadonly fork: ForkAdminSurface;\n\t/** SDK shim (`client.sdk`) — `.core` selects the whale gas coin and\n\t * waits for finality. */\n\treadonly sdk: SuiSdkShim;\n\t/** Upper bound per request (MIST). Requests above this are rejected. */\n\treadonly perRequestCapMist: bigint;\n\t/** Serialize requests so concurrent funds don't race the whale coin. */\n\treadonly serialization?: SuiForkFaucetSerialization;\n}\n\nconst sentinelUrl = (whale: string): string => `fork-impersonation://${whale}`;\n\nconst bodyError = (\n\twhale: string,\n\taddress: string,\n\tamount: bigint,\n\tmessage: string,\n): FaucetBodyError =>\n\tfaucetBodyError({\n\t\turl: sentinelUrl(whale),\n\t\taddress,\n\t\tamount,\n\t\tstatus: 0,\n\t\treason: 'failure-status',\n\t\tmessage,\n\t});\n\nconst withSerialization = (\n\tserialization: SuiForkFaucetSerialization | undefined,\n\teffect: Effect.Effect<void, FaucetBodyError>,\n): Effect.Effect<void, FaucetBodyError> => {\n\tif (serialization === undefined) {\n\t\treturn effect;\n\t}\n\treturn Effect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tyield* serialization.broker.acquire(leaseKey(serialization.key), serialization.owner);\n\t\t\tyield* effect;\n\t\t}),\n\t);\n};\n\n/** Build a SUI fork-faucet strategy that funds via whale impersonation. */\nexport const suiForkFaucetStrategy = (opts: SuiForkFaucetStrategyOptions): FaucetStrategy => ({\n\trequest: ({ address, amount }) => {\n\t\tconst gasClient: ForkImpersonationGasClient = opts.sdk.core;\n\t\t// Internal SuiPluginError -> FaucetBodyError so account funding's\n\t\t// catchTags handle it; carry the actionable root message through.\n\t\tconst mapErr = (cause: SuiPluginError): FaucetBodyError =>\n\t\t\tbodyError(opts.whale, address, amount, `sui fork faucet: ${cause.message}`);\n\n\t\tconst transfer = Effect.gen(function* () {\n\t\t\tif (amount <= 0n) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (amount > opts.perRequestCapMist) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tbodyError(\n\t\t\t\t\t\topts.whale,\n\t\t\t\t\t\taddress,\n\t\t\t\t\t\tamount,\n\t\t\t\t\t\t`sui fork faucet: requested ${amount} MIST exceeds the per-request cap ` +\n\t\t\t\t\t\t\t`${opts.perRequestCapMist} MIST (whale ${opts.whale}).`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// A sufficient whale SUI coin (first past any dust) pays gas AND sources the split, so\n\t\t\t// it must cover amount + the impersonation gas budget.\n\t\t\tconst { coin } = yield* selectSufficientForkCoin(\n\t\t\t\tgasClient,\n\t\t\t\topts.whale,\n\t\t\t\tamount + FORK_IMPERSONATION_GAS_BUDGET,\n\t\t\t).pipe(Effect.mapError(mapErr));\n\n\t\t\tconst tx = new Transaction();\n\t\t\ttx.setSender(opts.whale);\n\t\t\tconst [funded] = tx.splitCoins(tx.gas, [tx.pure.u64(amount)]);\n\t\t\ttx.transferObjects([funded], address);\n\n\t\t\tconst bytes = yield* buildForkImpersonationTransactionBytes(\n\t\t\t\ttx,\n\t\t\t\topts.whale,\n\t\t\t\tgasClient,\n\t\t\t\tcoin,\n\t\t\t).pipe(Effect.mapError(mapErr));\n\n\t\t\tconst submitted = yield* opts.fork\n\t\t\t\t.impersonate(opts.whale, bytes)\n\t\t\t\t.pipe(Effect.mapError(mapErr));\n\t\t\tif (!submitted.success) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tbodyError(\n\t\t\t\t\t\topts.whale,\n\t\t\t\t\t\taddress,\n\t\t\t\t\t\tamount,\n\t\t\t\t\t\t`sui fork faucet: impersonation tx ${submitted.digest} failed on-chain.`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tyield* Effect.tryPromise({\n\t\t\t\ttry: () => opts.sdk.core.waitForTransaction({ digest: submitted.digest }),\n\t\t\t\tcatch: (cause): FaucetBodyError =>\n\t\t\t\t\tbodyError(\n\t\t\t\t\t\topts.whale,\n\t\t\t\t\t\taddress,\n\t\t\t\t\t\tamount,\n\t\t\t\t\t\t`sui fork faucet: waitForTransaction(${submitted.digest}) failed: ${formatUnknownError(cause)}`,\n\t\t\t\t\t),\n\t\t\t});\n\t\t});\n\n\t\treturn withSerialization(opts.serialization, transfer);\n\t},\n});\n"],"mappings":";;;;;;;;;AAmEA,MAAM,eAAe,UAA0B,wBAAwB;AAEvE,MAAM,aACL,OACA,SACA,QACA,YAEA,gBAAgB;CACf,KAAK,YAAY,KAAK;CACtB;CACA;CACA,QAAQ;CACR,QAAQ;CACR;AACD,CAAC;AAEF,MAAM,qBACL,eACA,WAC0C;CAC1C,IAAI,kBAAkB,KAAA,GACrB,OAAO;CAER,OAAO,OAAO,OACb,OAAO,IAAI,aAAa;EACvB,OAAO,cAAc,OAAO,QAAQ,SAAS,cAAc,GAAG,GAAG,cAAc,KAAK;EACpF,OAAO;CACR,CAAC,CACF;AACD;;AAGA,MAAa,yBAAyB,UAAwD,EAC7F,UAAU,EAAE,SAAS,aAAa;CACjC,MAAM,YAAwC,KAAK,IAAI;CAGvD,MAAM,UAAU,UACf,UAAU,KAAK,OAAO,SAAS,QAAQ,oBAAoB,MAAM,SAAS;CAE3E,MAAM,WAAW,OAAO,IAAI,aAAa;EACxC,IAAI,UAAU,IACb;EAED,IAAI,SAAS,KAAK,mBACjB,OAAO,OAAO,OAAO,KACpB,UACC,KAAK,OACL,SACA,QACA,8BAA8B,OAAO,oCACjC,KAAK,kBAAkB,eAAe,KAAK,MAAM,GACtD,CACD;EAKD,MAAM,EAAE,SAAS,OAAO,yBACvB,WACA,KAAK,OACL,SAAS,6BACV,CAAC,CAAC,KAAK,OAAO,SAAS,MAAM,CAAC;EAE9B,MAAM,KAAK,IAAI,YAAY;EAC3B,GAAG,UAAU,KAAK,KAAK;EACvB,MAAM,CAAC,UAAU,GAAG,WAAW,GAAG,KAAK,CAAC,GAAG,KAAK,IAAI,MAAM,CAAC,CAAC;EAC5D,GAAG,gBAAgB,CAAC,MAAM,GAAG,OAAO;EAEpC,MAAM,QAAQ,OAAO,uCACpB,IACA,KAAK,OACL,WACA,IACD,CAAC,CAAC,KAAK,OAAO,SAAS,MAAM,CAAC;EAE9B,MAAM,YAAY,OAAO,KAAK,KAC5B,YAAY,KAAK,OAAO,KAAK,CAAC,CAC9B,KAAK,OAAO,SAAS,MAAM,CAAC;EAC9B,IAAI,CAAC,UAAU,SACd,OAAO,OAAO,OAAO,KACpB,UACC,KAAK,OACL,SACA,QACA,qCAAqC,UAAU,OAAO,kBACvD,CACD;EAGD,OAAO,OAAO,WAAW;GACxB,WAAW,KAAK,IAAI,KAAK,mBAAmB,EAAE,QAAQ,UAAU,OAAO,CAAC;GACxE,QAAQ,UACP,UACC,KAAK,OACL,SACA,QACA,uCAAuC,UAAU,OAAO,YAAY,mBAAmB,KAAK,GAC7F;EACF,CAAC;CACF,CAAC;CAED,OAAO,kBAAkB,KAAK,eAAe,QAAQ;AACtD,EACD"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { SpanAttr } from "../../substrate/runtime/observability/spans.mjs";
|
|
2
1
|
import { selfPid } from "../../substrate/runtime/cross-process/self-pid.mjs";
|
|
3
2
|
import { atomicWriteJsonSync } from "../../substrate/runtime/atomic-write.mjs";
|
|
4
3
|
import { parseVersionedDocumentBodyOrNull } from "../../substrate/versioned-doc-sync.mjs";
|
|
5
4
|
import { isPidAlive, processStartTime } from "../../substrate/runtime/cross-process/liveness.mjs";
|
|
6
5
|
import { acquireStackLock } from "../../substrate/runtime/cross-process/stack-lock.mjs";
|
|
6
|
+
import { LogAttr } from "../../substrate/runtime/observability/log-attrs.mjs";
|
|
7
7
|
import { mintRandomSuffix } from "../../substrate/runtime/random-suffix.mjs";
|
|
8
8
|
import { forkUnsupportedError, suiPluginError } from "./errors.mjs";
|
|
9
9
|
import "../../substrate/runtime/cross-process/index.mjs";
|
|
@@ -14,10 +14,9 @@ import { hostname } from "node:os";
|
|
|
14
14
|
//#region src/plugins/sui/fork-orchestration.ts
|
|
15
15
|
/** Heartbeat cadence — refresh `startedAt` every 10s. Matches the
|
|
16
16
|
* roster's `heartbeatIntervalMillis`. The heartbeat keeps the holder's
|
|
17
|
-
* `startedAt`/`startTime` fresh for diagnostics
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* reclaim gate, mirroring the roster heartbeat fiber. */
|
|
17
|
+
* `startedAt`/`startTime` fresh for diagnostics; it is not a reclaim
|
|
18
|
+
* gate (the pid + start-time check is), mirroring the roster heartbeat
|
|
19
|
+
* fiber. */
|
|
21
20
|
const FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS = 1e4;
|
|
22
21
|
/** Brief acquire timeout for the holder critical section. The OS lock
|
|
23
22
|
* is held only across a read → liveness-check → write, never across
|
|
@@ -34,20 +33,13 @@ const ForkLockHolderSchema = Schema.Struct({
|
|
|
34
33
|
* lifecycle (wipe removes it). */
|
|
35
34
|
const forkHolderPath = (dataDir) => join(dataDir, "holder.json");
|
|
36
35
|
/** Is an existing holder alive? Mirrors `liveness.checkHolderLiveness`
|
|
37
|
-
*
|
|
36
|
+
* (the `RosterHolder` shape doesn't fit — that carries
|
|
38
37
|
* `hostname`/`heartbeatAt`, not our `host`/`startedAt` — so we
|
|
39
38
|
* replicate its logic against the `ForkLockHolder` fields):
|
|
40
39
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* >~20s skew on an NFS root could reclaim a LIVE foreign holder and
|
|
45
|
-
* produce dual-writer RocksDB corruption. The NFS-safe trade is to
|
|
46
|
-
* never auto-reclaim a foreign holder; a truly-abandoned one needs
|
|
47
|
-
* manual cleanup (`rm <dataDir>/holder.json`).
|
|
48
|
-
* - Same-host → pid must be live AND its start-time must match, so a
|
|
49
|
-
* crashed-then-recycled pid (an abandoned dir whose pid the kernel
|
|
50
|
-
* handed to an unrelated process) no longer reads as "in use".
|
|
40
|
+
* The pid must be live AND its start-time must match, so a
|
|
41
|
+
* crashed-then-recycled pid (an abandoned dir whose pid the kernel
|
|
42
|
+
* handed to an unrelated process) no longer reads as "in use".
|
|
51
43
|
*
|
|
52
44
|
* A `null` start-time on either side is handled conservatively the
|
|
53
45
|
* same way roster does — ALIVE — because we have nothing to dispute
|
|
@@ -55,7 +47,6 @@ const forkHolderPath = (dataDir) => join(dataDir, "holder.json");
|
|
|
55
47
|
* recorded `null`, or vice versa, would falsely harvest a live
|
|
56
48
|
* holder). */
|
|
57
49
|
const isForkHolderAlive = (holder) => {
|
|
58
|
-
if (holder.host !== hostname()) return true;
|
|
59
50
|
if (!isPidAlive(holder.pid)) return false;
|
|
60
51
|
const probedStart = processStartTime(holder.pid);
|
|
61
52
|
if (probedStart === null) return true;
|
|
@@ -151,20 +142,19 @@ const writeForkHolder = (path, holder) => Effect.try({
|
|
|
151
142
|
* 3. Register a finalizer that removes our holder file on scope
|
|
152
143
|
* close (wipe / restart / Ctrl-C) — but ONLY if the on-disk holder
|
|
153
144
|
* is still ours, so a legitimate peer reclaim is never clobbered.
|
|
154
|
-
* A crash that skips the finalizer is recovered
|
|
155
|
-
*
|
|
156
|
-
* the NFS-safe trade in `isForkHolderAlive`).
|
|
145
|
+
* A crash that skips the finalizer is recovered by the next peer's
|
|
146
|
+
* liveness check (pid + start-time, see `isForkHolderAlive`).
|
|
157
147
|
*/
|
|
158
148
|
const acquireForkDataDirHolder = (stackLockFile, dataDir) => Effect.gen(function* () {
|
|
159
149
|
const holderPath = forkHolderPath(dataDir);
|
|
160
150
|
const self = yield* claimForkHolder(stackLockFile, holderPath);
|
|
161
151
|
yield* Effect.addFinalizer(() => Effect.sync(() => releaseOwnForkHolder(holderPath, self)));
|
|
162
152
|
yield* refreshForkHolder(stackLockFile, holderPath, self).pipe(Effect.catch((err) => Effect.logWarning("sui fork data-dir holder heartbeat failed; next tick will retry").pipe(Effect.annotateLogs({
|
|
163
|
-
[
|
|
164
|
-
[
|
|
153
|
+
[LogAttr.phase]: err.phase,
|
|
154
|
+
[LogAttr.errorMessage]: err.message
|
|
165
155
|
})))).pipe(Effect.repeat(Schedule.spaced(`${FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS} millis`)), Effect.forkScoped);
|
|
166
156
|
return self;
|
|
167
|
-
})
|
|
157
|
+
});
|
|
168
158
|
/** Surfaces that the sui-fork binary explicitly panics on. New
|
|
169
159
|
* upstream additions fail OPEN by default — architecture
|
|
170
160
|
* invariant. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fork-orchestration.mjs","names":["nodeHostname"],"sources":["../../../src/plugins/sui/fork-orchestration.ts"],"sourcesContent":["// Fork-mode orchestration helpers.\n//\n// Owns the runtime SDK guard for surfaces the sui-fork binary panics\n// on, plus the data-dir mutual-exclusion holder protocol (acquire /\n// heartbeat / release) that serializes two stacks against the same\n// `<stackRoot>/sui-fork/<key>` data dir — concurrent writers to a\n// single fork data dir corrupt the binary's RocksDB.\n\nimport { readFileSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { hostname as nodeHostname } from 'node:os';\n\nimport { Effect, Schedule, Schema, type Scope } from 'effect';\n\nimport { atomicWriteJsonSync } from '../../substrate/runtime/atomic-write.ts';\nimport {\n\tacquireStackLock,\n\tisPidAlive,\n\tprocessStartTime,\n} from '../../substrate/runtime/cross-process/index.ts';\nimport { selfPid } from '../../substrate/runtime/cross-process/self-pid.ts';\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\nimport { SpanAttr } from '../../substrate/runtime/observability/spans.ts';\nimport { parseVersionedDocumentBodyOrNull } from '../../substrate/versioned-doc-sync.ts';\nimport {\n\tforkUnsupportedError,\n\tsuiPluginError,\n\ttype ForkUnsupportedError,\n\ttype SuiPluginError,\n} from './errors.ts';\n\n/** Lock-holder identity persisted alongside the data-dir lock file.\n * Two fork acquires against the same data dir surface this; one\n * wins, the other gets an actionable error. */\nexport interface ForkLockHolder {\n\treadonly pid: number;\n\treadonly host: string;\n\treadonly instanceId: string;\n\treadonly startedAt: number;\n\t/** FNV-1a hash of `ps -o lstart` for `pid`, captured at claim time\n\t * via the SAME `processStartTime` probe roster/liveness use. Lets\n\t * same-host liveness distinguish a LIVE holder from a recycled-PID\n\t * impostor (a crashed-then-reused pid). `null` when the platform\n\t * couldn't probe — treated conservatively (see `isForkHolderAlive`),\n\t * matching roster's `isOwnEntry` / `checkHolderLiveness` null policy. */\n\treadonly startTime: number | null;\n}\n\n// -----------------------------------------------------------------------------\n// Data-dir mutual-exclusion holder protocol\n// -----------------------------------------------------------------------------\n//\n// `acquireStackLock` is a HOLD-BRIEFLY primitive (it reclaims after\n// ~30s of staleness, so it must NOT be held for the fork's lifetime —\n// cross-process § \"the lock is held BRIEFLY\"). So the long-lived claim\n// on a fork data dir is a HOLDER FILE, mirroring the roster's\n// heartbeat + (pid, host) liveness pattern: we take the brief OS lock\n// only to serialize the read-holder → liveness-check → write-holder\n// critical section, then release it. A scope-bound fiber refreshes\n// `startedAt` on a cadence comfortably under the staleness window so\n// peers see we are alive; a finalizer removes the holder on teardown.\n\n/** Heartbeat cadence — refresh `startedAt` every 10s. Matches the\n * roster's `heartbeatIntervalMillis`. The heartbeat keeps the holder's\n * `startedAt`/`startTime` fresh for diagnostics and so a peer that DOES\n * later add a foreign-host liveness signal has recent data; with the\n * current conservative foreign-host policy (always-alive) it is not a\n * reclaim gate, mirroring the roster heartbeat fiber. */\nconst FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS = 10_000;\n\n/** Brief acquire timeout for the holder critical section. The OS lock\n * is held only across a read → liveness-check → write, never across\n * I/O — a tight budget keeps peers reacting quickly to a release. */\nconst FORK_HOLDER_LOCK_TIMEOUT_MILLIS = 5_000;\n\n// No `version` field by design. `parseVersionedDocumentBodyOrNull`\n// decodes the raw body against this schema and treats ANY decode miss\n// (including a future shape change) as `null` ⇒ \"no live holder\",\n// which the acquire path handles by reclaiming. Because the holder file\n// is transient (it lives inside the data dir and a wipe removes it —\n// see `forkHolderPath` below), a leftover old-shape body self-heals on\n// the next acquire rather than blocking it. This mirrors stack-lock's\n// precedent for short-lived holder files; persistent docs that must\n// survive a format change use `versionedDocSchema` with a bumped\n// version stamp instead.\nconst ForkLockHolderSchema = Schema.Struct({\n\tpid: Schema.Number,\n\thost: Schema.String,\n\tinstanceId: Schema.String,\n\tstartedAt: Schema.Number,\n\tstartTime: Schema.NullOr(Schema.Number),\n});\n\n/** Holder file lives inside the data dir so it travels with the dir's\n * lifecycle (wipe removes it). */\nexport const forkHolderPath = (dataDir: string): string => join(dataDir, 'holder.json');\n\n/** Is an existing holder alive? Mirrors `liveness.checkHolderLiveness`\n * exactly (the `RosterHolder` shape doesn't fit — that carries\n * `hostname`/`heartbeatAt`, not our `host`/`startedAt` — so we\n * replicate its logic against the `ForkLockHolder` fields):\n *\n * - Foreign-host → ALWAYS ALIVE. Cross-host pid comparison is\n * meaningless, and comparing our `Date.now()` to the holder's\n * `startedAt` (a different host's clock) is clock-skew-unsafe — a\n * >~20s skew on an NFS root could reclaim a LIVE foreign holder and\n * produce dual-writer RocksDB corruption. The NFS-safe trade is to\n * never auto-reclaim a foreign holder; a truly-abandoned one needs\n * manual cleanup (`rm <dataDir>/holder.json`).\n * - Same-host → pid must be live AND its start-time must match, so a\n * crashed-then-recycled pid (an abandoned dir whose pid the kernel\n * handed to an unrelated process) no longer reads as \"in use\".\n *\n * A `null` start-time on either side is handled conservatively the\n * same way roster does — ALIVE — because we have nothing to dispute\n * the recorded identity with (mismatching a probed stamp against a\n * recorded `null`, or vice versa, would falsely harvest a live\n * holder). */\nconst isForkHolderAlive = (holder: ForkLockHolder): boolean => {\n\tif (holder.host !== nodeHostname()) return true;\n\tif (!isPidAlive(holder.pid)) return false;\n\tconst probedStart = processStartTime(holder.pid);\n\tif (probedStart === null) return true;\n\tif (holder.startTime === null) return true;\n\treturn probedStart === holder.startTime;\n};\n\nconst readForkHolder = (path: string): ForkLockHolder | null =>\n\tparseVersionedDocumentBodyOrNull(\n\t\tsafeReadHolderRaw(path),\n\t\tForkLockHolderSchema,\n\t\t'sui-fork.holder',\n\t);\n\n/** Read the holder file's bytes, returning `''` (→ parses to null) on\n * any I/O error. A missing/unreadable holder means \"no live claim\";\n * the caller writes its own. Sync read is correct here — we are\n * inside the brief OS-lock critical section, same non-yielding\n * discipline as the cross-process roster reader. */\nconst safeReadHolderRaw = (path: string): string => {\n\ttry {\n\t\treturn readFileSync(path, 'utf8');\n\t} catch {\n\t\treturn '';\n\t}\n};\n\nconst ownForkHolder = (): ForkLockHolder => {\n\tconst pid = selfPid();\n\treturn {\n\t\tpid,\n\t\thost: nodeHostname(),\n\t\tinstanceId: mintRandomSuffix(8),\n\t\tstartedAt: Date.now(),\n\t\t// Same probe roster/liveness record at claim time — a `null`\n\t\t// (exotic platform / transient `ps` error) propagates verbatim so\n\t\t// the conservative null branch in `isForkHolderAlive` applies.\n\t\tstartTime: processStartTime(pid),\n\t};\n};\n\n/** Run `body` under a BRIEFLY-held `stack.lock`. The lock serializes\n * the holder file's read → liveness-check → write critical section so\n * two claimers never both decide the dir is free. Released the moment\n * `body` completes — never held across the fork's lifetime. */\nconst underStackLock = <A>(\n\tstackLockFile: string,\n\tholderPath: string,\n\tbody: Effect.Effect<A, SuiPluginError>,\n): Effect.Effect<A, SuiPluginError> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tyield* acquireStackLock(stackLockFile, FORK_HOLDER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\tsuiPluginError(\n\t\t\t\t\t\t'fork-lock',\n\t\t\t\t\t\t`sui fork mode: failed to acquire stack lock for data dir ${holderPath}: ${cause._tag}`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn yield* body;\n\t\t}),\n\t);\n\n/** The brief critical section: under `stack.lock`, read any existing\n * holder; if a LIVE one is present, fail with an actionable error;\n * otherwise claim by writing our own holder atomically. Returns the\n * holder we wrote so the heartbeat fiber can refresh it. */\nconst claimForkHolder = (\n\tstackLockFile: string,\n\tholderPath: string,\n): Effect.Effect<ForkLockHolder, SuiPluginError> =>\n\tunderStackLock(\n\t\tstackLockFile,\n\t\tholderPath,\n\t\tEffect.gen(function* () {\n\t\t\tconst existing = readForkHolder(holderPath);\n\t\t\tif (existing !== null && isForkHolderAlive(existing)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tsuiPluginError(\n\t\t\t\t\t\t'fork-lock',\n\t\t\t\t\t\t`sui fork mode: fork data dir ${holderPath} is in use by pid ${existing.pid} on ` +\n\t\t\t\t\t\t\t`${existing.host}; stop that stack or use a different fork key.`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst self = ownForkHolder();\n\t\t\tyield* writeForkHolder(holderPath, self);\n\t\t\treturn self;\n\t\t}),\n\t);\n\n/** Heartbeat: under the brief lock, re-stamp `startedAt` IF the holder\n * on disk is still ours. A peer that reclaimed us (same-host recycled-\n * pid takeover) owns the file now; we must not clobber its claim, so a\n * mismatched `instanceId` makes this a no-op. */\nconst refreshForkHolder = (\n\tstackLockFile: string,\n\tholderPath: string,\n\tself: ForkLockHolder,\n): Effect.Effect<void, SuiPluginError> =>\n\tunderStackLock(\n\t\tstackLockFile,\n\t\tholderPath,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = readForkHolder(holderPath);\n\t\t\tif (current !== null && current.instanceId !== self.instanceId) return;\n\t\t\tyield* writeForkHolder(holderPath, { ...self, startedAt: Date.now() });\n\t\t}),\n\t);\n\n/** Scope-finalizer release: unlink the holder file ONLY if it is still\n * ours. A finalizer that unconditionally `unlinkSync`d by path would,\n * after a legitimate same-host reclaim (our pid crashed-and-recycled,\n * a peer took over), delete the PEER's holder and unprotect a dir the\n * peer is actively writing. Re-reading and matching `instanceId` makes\n * the release a no-op once we no longer own the file. A missing/\n * unreadable holder is already-gone — also a no-op. Best-effort: a\n * crash that skips this is recovered by the next peer's same-host\n * liveness check. */\nconst releaseOwnForkHolder = (holderPath: string, self: ForkLockHolder): void => {\n\tconst current = readForkHolder(holderPath);\n\tif (current !== null && current.instanceId !== self.instanceId) return;\n\ttry {\n\t\tunlinkSync(holderPath);\n\t} catch {\n\t\t// Already gone — ok.\n\t}\n};\n\nconst writeForkHolder = (\n\tpath: string,\n\tholder: ForkLockHolder,\n): Effect.Effect<void, SuiPluginError> =>\n\tEffect.try({\n\t\ttry: () => atomicWriteJsonSync(path, holder),\n\t\tcatch: (cause) =>\n\t\t\tsuiPluginError(\n\t\t\t\t'fork-lock',\n\t\t\t\t`sui fork mode: failed to write fork data-dir holder ${path}.`,\n\t\t\t\tcause,\n\t\t\t),\n\t});\n\n/**\n * Acquire the data-dir holder for the lifetime of the surrounding\n * scope.\n *\n * 1. Briefly take `stack.lock`, read any existing holder, fail if a\n * live peer holds it, else write our own holder. Release the OS\n * lock immediately (the claim outlives the lock).\n * 2. Fork a scope-bound heartbeat fiber that re-takes the brief lock\n * every `FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS` and refreshes\n * `startedAt` (only while the file is still ours). A heartbeat\n * failure is non-fatal — log and retry next tick (same policy as\n * the roster heartbeat fiber).\n * 3. Register a finalizer that removes our holder file on scope\n * close (wipe / restart / Ctrl-C) — but ONLY if the on-disk holder\n * is still ours, so a legitimate peer reclaim is never clobbered.\n * A crash that skips the finalizer is recovered same-host by the\n * next peer's liveness check (foreign-host needs manual cleanup —\n * the NFS-safe trade in `isForkHolderAlive`).\n */\nexport const acquireForkDataDirHolder = (\n\tstackLockFile: string,\n\tdataDir: string,\n): Effect.Effect<ForkLockHolder, SuiPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst holderPath = forkHolderPath(dataDir);\n\t\tconst self = yield* claimForkHolder(stackLockFile, holderPath);\n\t\tyield* Effect.addFinalizer(() => Effect.sync(() => releaseOwnForkHolder(holderPath, self)));\n\t\tconst refresh = refreshForkHolder(stackLockFile, holderPath, self).pipe(\n\t\t\tEffect.catch((err) =>\n\t\t\t\tEffect.logWarning('sui fork data-dir holder heartbeat failed; next tick will retry').pipe(\n\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t[SpanAttr.phase]: err.phase,\n\t\t\t\t\t\t[SpanAttr.errorMessage]: err.message,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tyield* refresh.pipe(\n\t\t\tEffect.repeat(Schedule.spaced(`${FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS} millis`)),\n\t\t\tEffect.forkScoped,\n\t\t);\n\t\treturn self;\n\t}).pipe(Effect.withSpan('devstack.plugin.sui.fork.acquireDataDirHolder'));\n\n/** Surfaces that the sui-fork binary explicitly panics on. New\n * upstream additions fail OPEN by default — architecture\n * invariant. */\nexport const FORK_UNSUPPORTED_SURFACES: ReadonlyArray<string> = [\n\t// `client.core.*` methods that hit `simulate_transaction` /\n\t// balance-derivation paths the fork binary doesn't implement.\n\t'getBalance',\n\t'listBalances',\n\t'getCoinInfo',\n] as const;\n\n/** Wrap a Sui SDK shim with the fork guard. Property access for a\n * blocklisted surface SYNCHRONOUSLY throws — the wire call never\n * happens, so the fork binary stays up. */\nexport const wrapWithForkGuard = <Sdk extends { readonly core: object }>(sdk: Sdk): Sdk => {\n\tconst guardedCore = new Proxy(sdk.core as Record<string, unknown>, {\n\t\tget(target, prop, receiver) {\n\t\t\tif (typeof prop === 'string' && FORK_UNSUPPORTED_SURFACES.includes(prop)) {\n\t\t\t\tconst err: ForkUnsupportedError = forkUnsupportedError(\n\t\t\t\t\t`client.core.${prop}`,\n\t\t\t\t\t'fork mode does not implement this SDK surface — use the impersonation helper ' +\n\t\t\t\t\t\t'or read state via ChainProbe.',\n\t\t\t\t);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\treturn Reflect.get(target, prop, receiver);\n\t\t},\n\t}) as Sdk['core'];\n\t// Preserve all SDK fields (e.g. SuiSdkShim's opaque `client` for\n\t// Transaction.build) — only the `core` proxy intercepts; siblings\n\t// like `client` flow through unchanged.\n\treturn { ...sdk, core: guardedCore };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoEA,MAAM,wCAAwC;;;;AAK9C,MAAM,kCAAkC;AAYxC,MAAM,uBAAuB,OAAO,OAAO;CAC1C,KAAK,OAAO;CACZ,MAAM,OAAO;CACb,YAAY,OAAO;CACnB,WAAW,OAAO;CAClB,WAAW,OAAO,OAAO,OAAO,OAAO;CACvC,CAAC;;;AAIF,MAAa,kBAAkB,YAA4B,KAAK,SAAS,cAAc;;;;;;;;;;;;;;;;;;;;;;AAuBvF,MAAM,qBAAqB,WAAoC;AAC9D,KAAI,OAAO,SAASA,UAAc,CAAE,QAAO;AAC3C,KAAI,CAAC,WAAW,OAAO,IAAI,CAAE,QAAO;CACpC,MAAM,cAAc,iBAAiB,OAAO,IAAI;AAChD,KAAI,gBAAgB,KAAM,QAAO;AACjC,KAAI,OAAO,cAAc,KAAM,QAAO;AACtC,QAAO,gBAAgB,OAAO;;AAG/B,MAAM,kBAAkB,SACvB,iCACC,kBAAkB,KAAK,EACvB,sBACA,kBACA;;;;;;AAOF,MAAM,qBAAqB,SAAyB;AACnD,KAAI;AACH,SAAO,aAAa,MAAM,OAAO;SAC1B;AACP,SAAO;;;AAIT,MAAM,sBAAsC;CAC3C,MAAM,MAAM,SAAS;AACrB,QAAO;EACN;EACA,MAAMA,UAAc;EACpB,YAAY,iBAAiB,EAAE;EAC/B,WAAW,KAAK,KAAK;EAIrB,WAAW,iBAAiB,IAAI;EAChC;;;;;;AAOF,MAAM,kBACL,eACA,YACA,SAEA,OAAO,OACN,OAAO,IAAI,aAAa;AACvB,QAAO,iBAAiB,eAAe,gCAAgC,CAAC,KACvE,OAAO,UAAU,UAChB,eACC,aACA,4DAA4D,WAAW,IAAI,MAAM,QACjF,MACA,CACD,CACD;AACD,QAAO,OAAO;EACb,CACF;;;;;AAMF,MAAM,mBACL,eACA,eAEA,eACC,eACA,YACA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,eAAe,WAAW;AAC3C,KAAI,aAAa,QAAQ,kBAAkB,SAAS,CACnD,QAAO,OAAO,OAAO,KACpB,eACC,aACA,gCAAgC,WAAW,oBAAoB,SAAS,IAAI,MACxE,SAAS,KAAK,gDAClB,CACD;CAEF,MAAM,OAAO,eAAe;AAC5B,QAAO,gBAAgB,YAAY,KAAK;AACxC,QAAO;EACN,CACF;;;;;AAMF,MAAM,qBACL,eACA,YACA,SAEA,eACC,eACA,YACA,OAAO,IAAI,aAAa;CACvB,MAAM,UAAU,eAAe,WAAW;AAC1C,KAAI,YAAY,QAAQ,QAAQ,eAAe,KAAK,WAAY;AAChE,QAAO,gBAAgB,YAAY;EAAE,GAAG;EAAM,WAAW,KAAK,KAAK;EAAE,CAAC;EACrE,CACF;;;;;;;;;;AAWF,MAAM,wBAAwB,YAAoB,SAA+B;CAChF,MAAM,UAAU,eAAe,WAAW;AAC1C,KAAI,YAAY,QAAQ,QAAQ,eAAe,KAAK,WAAY;AAChE,KAAI;AACH,aAAW,WAAW;SACf;;AAKT,MAAM,mBACL,MACA,WAEA,OAAO,IAAI;CACV,WAAW,oBAAoB,MAAM,OAAO;CAC5C,QAAQ,UACP,eACC,aACA,uDAAuD,KAAK,IAC5D,MACA;CACF,CAAC;;;;;;;;;;;;;;;;;;;;AAqBH,MAAa,4BACZ,eACA,YAEA,OAAO,IAAI,aAAa;CACvB,MAAM,aAAa,eAAe,QAAQ;CAC1C,MAAM,OAAO,OAAO,gBAAgB,eAAe,WAAW;AAC9D,QAAO,OAAO,mBAAmB,OAAO,WAAW,qBAAqB,YAAY,KAAK,CAAC,CAAC;AAW3F,QAVgB,kBAAkB,eAAe,YAAY,KAAK,CAAC,KAClE,OAAO,OAAO,QACb,OAAO,WAAW,kEAAkE,CAAC,KACpF,OAAO,aAAa;GAClB,SAAS,QAAQ,IAAI;GACrB,SAAS,eAAe,IAAI;EAC7B,CAAC,CACF,CACD,CAEY,CAAC,KACd,OAAO,OAAO,SAAS,OAAO,GAAG,sCAAsC,SAAS,CAAC,EACjF,OAAO,WACP;AACD,QAAO;EACN,CAAC,KAAK,OAAO,SAAS,gDAAgD,CAAC;;;;AAK1E,MAAa,4BAAmD;CAG/D;CACA;CACA;CACA;;;;AAKD,MAAa,qBAA4D,QAAkB;CAC1F,MAAM,cAAc,IAAI,MAAM,IAAI,MAAiC,EAClE,IAAI,QAAQ,MAAM,UAAU;AAC3B,MAAI,OAAO,SAAS,YAAY,0BAA0B,SAAS,KAAK,CAMvE,OALkC,qBACjC,eAAe,QACf,6GAGQ;AAEV,SAAO,QAAQ,IAAI,QAAQ,MAAM,SAAS;IAE3C,CAAC;AAIF,QAAO;EAAE,GAAG;EAAK,MAAM;EAAa"}
|
|
1
|
+
{"version":3,"file":"fork-orchestration.mjs","names":["nodeHostname"],"sources":["../../../src/plugins/sui/fork-orchestration.ts"],"sourcesContent":["// Fork-mode orchestration helpers.\n//\n// Owns the runtime SDK guard for surfaces the sui-fork binary panics\n// on, plus the data-dir mutual-exclusion holder protocol (acquire /\n// heartbeat / release) that serializes two stacks against the same\n// `<stackRoot>/sui-fork/<key>` data dir — concurrent writers to a\n// single fork data dir corrupt the binary's RocksDB.\n\nimport { readFileSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { hostname as nodeHostname } from 'node:os';\n\nimport { Effect, Schedule, Schema, type Scope } from 'effect';\n\nimport { atomicWriteJsonSync } from '../../substrate/runtime/atomic-write.ts';\nimport {\n\tacquireStackLock,\n\tisPidAlive,\n\tprocessStartTime,\n} from '../../substrate/runtime/cross-process/index.ts';\nimport { selfPid } from '../../substrate/runtime/cross-process/self-pid.ts';\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\nimport { LogAttr } from '../../substrate/runtime/observability/log-attrs.ts';\nimport { parseVersionedDocumentBodyOrNull } from '../../substrate/versioned-doc-sync.ts';\nimport {\n\tforkUnsupportedError,\n\tsuiPluginError,\n\ttype ForkUnsupportedError,\n\ttype SuiPluginError,\n} from './errors.ts';\n\n/** Lock-holder identity persisted alongside the data-dir lock file.\n * Two fork acquires against the same data dir surface this; one\n * wins, the other gets an actionable error. */\nexport interface ForkLockHolder {\n\treadonly pid: number;\n\treadonly host: string;\n\treadonly instanceId: string;\n\treadonly startedAt: number;\n\t/** FNV-1a hash of `ps -o lstart` for `pid`, captured at claim time\n\t * via the SAME `processStartTime` probe roster/liveness use. Lets\n\t * same-host liveness distinguish a LIVE holder from a recycled-PID\n\t * impostor (a crashed-then-reused pid). `null` when the platform\n\t * couldn't probe — treated conservatively (see `isForkHolderAlive`),\n\t * matching roster's `isOwnEntry` / `checkHolderLiveness` null policy. */\n\treadonly startTime: number | null;\n}\n\n// -----------------------------------------------------------------------------\n// Data-dir mutual-exclusion holder protocol\n// -----------------------------------------------------------------------------\n//\n// `acquireStackLock` is a HOLD-BRIEFLY primitive (it reclaims after\n// ~30s of staleness, so it must NOT be held for the fork's lifetime —\n// cross-process § \"the lock is held BRIEFLY\"). So the long-lived claim\n// on a fork data dir is a HOLDER FILE, mirroring the roster's\n// heartbeat + (pid, host) liveness pattern: we take the brief OS lock\n// only to serialize the read-holder → liveness-check → write-holder\n// critical section, then release it. A scope-bound fiber refreshes\n// `startedAt` on a cadence comfortably under the staleness window so\n// peers see we are alive; a finalizer removes the holder on teardown.\n\n/** Heartbeat cadence — refresh `startedAt` every 10s. Matches the\n * roster's `heartbeatIntervalMillis`. The heartbeat keeps the holder's\n * `startedAt`/`startTime` fresh for diagnostics; it is not a reclaim\n * gate (the pid + start-time check is), mirroring the roster heartbeat\n * fiber. */\nconst FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS = 10_000;\n\n/** Brief acquire timeout for the holder critical section. The OS lock\n * is held only across a read → liveness-check → write, never across\n * I/O — a tight budget keeps peers reacting quickly to a release. */\nconst FORK_HOLDER_LOCK_TIMEOUT_MILLIS = 5_000;\n\n// No `version` field by design. `parseVersionedDocumentBodyOrNull`\n// decodes the raw body against this schema and treats ANY decode miss\n// (including a future shape change) as `null` ⇒ \"no live holder\",\n// which the acquire path handles by reclaiming. Because the holder file\n// is transient (it lives inside the data dir and a wipe removes it —\n// see `forkHolderPath` below), a leftover old-shape body self-heals on\n// the next acquire rather than blocking it. This mirrors stack-lock's\n// precedent for short-lived holder files; persistent docs that must\n// survive a format change use `versionedDocSchema` with a bumped\n// version stamp instead.\nconst ForkLockHolderSchema = Schema.Struct({\n\tpid: Schema.Number,\n\thost: Schema.String,\n\tinstanceId: Schema.String,\n\tstartedAt: Schema.Number,\n\tstartTime: Schema.NullOr(Schema.Number),\n});\n\n/** Holder file lives inside the data dir so it travels with the dir's\n * lifecycle (wipe removes it). */\nexport const forkHolderPath = (dataDir: string): string => join(dataDir, 'holder.json');\n\n/** Is an existing holder alive? Mirrors `liveness.checkHolderLiveness`\n * (the `RosterHolder` shape doesn't fit — that carries\n * `hostname`/`heartbeatAt`, not our `host`/`startedAt` — so we\n * replicate its logic against the `ForkLockHolder` fields):\n *\n * The pid must be live AND its start-time must match, so a\n * crashed-then-recycled pid (an abandoned dir whose pid the kernel\n * handed to an unrelated process) no longer reads as \"in use\".\n *\n * A `null` start-time on either side is handled conservatively the\n * same way roster does — ALIVE — because we have nothing to dispute\n * the recorded identity with (mismatching a probed stamp against a\n * recorded `null`, or vice versa, would falsely harvest a live\n * holder). */\nconst isForkHolderAlive = (holder: ForkLockHolder): boolean => {\n\tif (!isPidAlive(holder.pid)) return false;\n\tconst probedStart = processStartTime(holder.pid);\n\tif (probedStart === null) return true;\n\tif (holder.startTime === null) return true;\n\treturn probedStart === holder.startTime;\n};\n\nconst readForkHolder = (path: string): ForkLockHolder | null =>\n\tparseVersionedDocumentBodyOrNull(\n\t\tsafeReadHolderRaw(path),\n\t\tForkLockHolderSchema,\n\t\t'sui-fork.holder',\n\t);\n\n/** Read the holder file's bytes, returning `''` (→ parses to null) on\n * any I/O error. A missing/unreadable holder means \"no live claim\";\n * the caller writes its own. Sync read is correct here — we are\n * inside the brief OS-lock critical section, same non-yielding\n * discipline as the cross-process roster reader. */\nconst safeReadHolderRaw = (path: string): string => {\n\ttry {\n\t\treturn readFileSync(path, 'utf8');\n\t} catch {\n\t\treturn '';\n\t}\n};\n\nconst ownForkHolder = (): ForkLockHolder => {\n\tconst pid = selfPid();\n\treturn {\n\t\tpid,\n\t\thost: nodeHostname(),\n\t\tinstanceId: mintRandomSuffix(8),\n\t\tstartedAt: Date.now(),\n\t\t// Same probe roster/liveness record at claim time — a `null`\n\t\t// (exotic platform / transient `ps` error) propagates verbatim so\n\t\t// the conservative null branch in `isForkHolderAlive` applies.\n\t\tstartTime: processStartTime(pid),\n\t};\n};\n\n/** Run `body` under a BRIEFLY-held `stack.lock`. The lock serializes\n * the holder file's read → liveness-check → write critical section so\n * two claimers never both decide the dir is free. Released the moment\n * `body` completes — never held across the fork's lifetime. */\nconst underStackLock = <A>(\n\tstackLockFile: string,\n\tholderPath: string,\n\tbody: Effect.Effect<A, SuiPluginError>,\n): Effect.Effect<A, SuiPluginError> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tyield* acquireStackLock(stackLockFile, FORK_HOLDER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\tsuiPluginError(\n\t\t\t\t\t\t'fork-lock',\n\t\t\t\t\t\t`sui fork mode: failed to acquire stack lock for data dir ${holderPath}: ${cause._tag}`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn yield* body;\n\t\t}),\n\t);\n\n/** The brief critical section: under `stack.lock`, read any existing\n * holder; if a LIVE one is present, fail with an actionable error;\n * otherwise claim by writing our own holder atomically. Returns the\n * holder we wrote so the heartbeat fiber can refresh it. */\nconst claimForkHolder = (\n\tstackLockFile: string,\n\tholderPath: string,\n): Effect.Effect<ForkLockHolder, SuiPluginError> =>\n\tunderStackLock(\n\t\tstackLockFile,\n\t\tholderPath,\n\t\tEffect.gen(function* () {\n\t\t\tconst existing = readForkHolder(holderPath);\n\t\t\tif (existing !== null && isForkHolderAlive(existing)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tsuiPluginError(\n\t\t\t\t\t\t'fork-lock',\n\t\t\t\t\t\t`sui fork mode: fork data dir ${holderPath} is in use by pid ${existing.pid} on ` +\n\t\t\t\t\t\t\t`${existing.host}; stop that stack or use a different fork key.`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst self = ownForkHolder();\n\t\t\tyield* writeForkHolder(holderPath, self);\n\t\t\treturn self;\n\t\t}),\n\t);\n\n/** Heartbeat: under the brief lock, re-stamp `startedAt` IF the holder\n * on disk is still ours. A peer that reclaimed us (same-host recycled-\n * pid takeover) owns the file now; we must not clobber its claim, so a\n * mismatched `instanceId` makes this a no-op. */\nconst refreshForkHolder = (\n\tstackLockFile: string,\n\tholderPath: string,\n\tself: ForkLockHolder,\n): Effect.Effect<void, SuiPluginError> =>\n\tunderStackLock(\n\t\tstackLockFile,\n\t\tholderPath,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = readForkHolder(holderPath);\n\t\t\tif (current !== null && current.instanceId !== self.instanceId) return;\n\t\t\tyield* writeForkHolder(holderPath, { ...self, startedAt: Date.now() });\n\t\t}),\n\t);\n\n/** Scope-finalizer release: unlink the holder file ONLY if it is still\n * ours. A finalizer that unconditionally `unlinkSync`d by path would,\n * after a legitimate same-host reclaim (our pid crashed-and-recycled,\n * a peer took over), delete the PEER's holder and unprotect a dir the\n * peer is actively writing. Re-reading and matching `instanceId` makes\n * the release a no-op once we no longer own the file. A missing/\n * unreadable holder is already-gone — also a no-op. Best-effort: a\n * crash that skips this is recovered by the next peer's same-host\n * liveness check. */\nconst releaseOwnForkHolder = (holderPath: string, self: ForkLockHolder): void => {\n\tconst current = readForkHolder(holderPath);\n\tif (current !== null && current.instanceId !== self.instanceId) return;\n\ttry {\n\t\tunlinkSync(holderPath);\n\t} catch {\n\t\t// Already gone — ok.\n\t}\n};\n\nconst writeForkHolder = (\n\tpath: string,\n\tholder: ForkLockHolder,\n): Effect.Effect<void, SuiPluginError> =>\n\tEffect.try({\n\t\ttry: () => atomicWriteJsonSync(path, holder),\n\t\tcatch: (cause) =>\n\t\t\tsuiPluginError(\n\t\t\t\t'fork-lock',\n\t\t\t\t`sui fork mode: failed to write fork data-dir holder ${path}.`,\n\t\t\t\tcause,\n\t\t\t),\n\t});\n\n/**\n * Acquire the data-dir holder for the lifetime of the surrounding\n * scope.\n *\n * 1. Briefly take `stack.lock`, read any existing holder, fail if a\n * live peer holds it, else write our own holder. Release the OS\n * lock immediately (the claim outlives the lock).\n * 2. Fork a scope-bound heartbeat fiber that re-takes the brief lock\n * every `FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS` and refreshes\n * `startedAt` (only while the file is still ours). A heartbeat\n * failure is non-fatal — log and retry next tick (same policy as\n * the roster heartbeat fiber).\n * 3. Register a finalizer that removes our holder file on scope\n * close (wipe / restart / Ctrl-C) — but ONLY if the on-disk holder\n * is still ours, so a legitimate peer reclaim is never clobbered.\n * A crash that skips the finalizer is recovered by the next peer's\n * liveness check (pid + start-time, see `isForkHolderAlive`).\n */\nexport const acquireForkDataDirHolder = (\n\tstackLockFile: string,\n\tdataDir: string,\n): Effect.Effect<ForkLockHolder, SuiPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst holderPath = forkHolderPath(dataDir);\n\t\tconst self = yield* claimForkHolder(stackLockFile, holderPath);\n\t\tyield* Effect.addFinalizer(() => Effect.sync(() => releaseOwnForkHolder(holderPath, self)));\n\t\tconst refresh = refreshForkHolder(stackLockFile, holderPath, self).pipe(\n\t\t\tEffect.catch((err) =>\n\t\t\t\tEffect.logWarning('sui fork data-dir holder heartbeat failed; next tick will retry').pipe(\n\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t[LogAttr.phase]: err.phase,\n\t\t\t\t\t\t[LogAttr.errorMessage]: err.message,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tyield* refresh.pipe(\n\t\t\tEffect.repeat(Schedule.spaced(`${FORK_HOLDER_HEARTBEAT_INTERVAL_MILLIS} millis`)),\n\t\t\tEffect.forkScoped,\n\t\t);\n\t\treturn self;\n\t});\n\n/** Surfaces that the sui-fork binary explicitly panics on. New\n * upstream additions fail OPEN by default — architecture\n * invariant. */\nexport const FORK_UNSUPPORTED_SURFACES: ReadonlyArray<string> = [\n\t// `client.core.*` methods that hit `simulate_transaction` /\n\t// balance-derivation paths the fork binary doesn't implement.\n\t'getBalance',\n\t'listBalances',\n\t'getCoinInfo',\n] as const;\n\n/** Wrap a Sui SDK shim with the fork guard. Property access for a\n * blocklisted surface SYNCHRONOUSLY throws — the wire call never\n * happens, so the fork binary stays up. */\nexport const wrapWithForkGuard = <Sdk extends { readonly core: object }>(sdk: Sdk): Sdk => {\n\tconst guardedCore = new Proxy(sdk.core as Record<string, unknown>, {\n\t\tget(target, prop, receiver) {\n\t\t\tif (typeof prop === 'string' && FORK_UNSUPPORTED_SURFACES.includes(prop)) {\n\t\t\t\tconst err: ForkUnsupportedError = forkUnsupportedError(\n\t\t\t\t\t`client.core.${prop}`,\n\t\t\t\t\t'fork mode does not implement this SDK surface — use the impersonation helper ' +\n\t\t\t\t\t\t'or read state via ChainProbe.',\n\t\t\t\t);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\treturn Reflect.get(target, prop, receiver);\n\t\t},\n\t}) as Sdk['core'];\n\t// Preserve all SDK fields (e.g. SuiSdkShim's opaque `client` for\n\t// Transaction.build) — only the `core` proxy intercepts; siblings\n\t// like `client` flow through unchanged.\n\treturn { ...sdk, core: guardedCore };\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fork-transaction.mjs","names":[],"sources":["../../../src/plugins/sui/fork-transaction.ts"],"sourcesContent":["import { Effect } from 'effect';\n\nimport {\n\tInputs,\n\tTransaction,\n\tTransactionDataBuilder,\n\ttype CallArg,\n} from '@mysten/sui/transactions';\nimport { normalizeSuiAddress } from '@mysten/sui/utils';\n\nimport { suiPluginError, type SuiPluginError } from './errors.ts';\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.ts';\n\n// 0.1 SUI. Deliberately well below the faucet's default per-account fund\n// (1 SUI) so a faucet-funded account keeps headroom to move value:\n// `setGasBudget` reserves the whole gas coin, so a budget equal to the\n// coin balance leaves nothing for `splitCoins(tx.gas, …)` / transfers\n// (InsufficientCoinBalance). 0.1 SUI still covers package publishes.\nexport const FORK_IMPERSONATION_GAS_BUDGET = 100_000_000n;\nexport const FORK_IMPERSONATION_GAS_PRICE = 1_000n;\n\n/** Canonical SUI gas coin type used to filter `listCoins` when picking\n * a gas/funding coin. Kept local so the sui plugin needn't depend on\n * the account plugin's `SUI_FULL_COIN_TYPE`. */\nconst SUI_GAS_COIN_TYPE = '0x2::sui::SUI';\n\n/** `listCoins` page size while scanning for a sufficient coin. */\nconst FORK_COIN_PAGE_SIZE = 50;\n\n/** An object ref usable as gas payment for an impersonation transaction. */\nexport interface ForkGasCoin {\n\treadonly objectId: string;\n\treadonly version: string;\n\treadonly digest: string;\n}\n\nexport interface ForkImpersonationGasClient {\n\treadonly getObject: (input: { readonly objectId: string }) => Promise<unknown>;\n\treadonly listCoins: (input: {\n\t\treadonly owner: string;\n\t\treadonly coinType?: string;\n\t\treadonly limit?: number;\n\t\treadonly cursor?: string | null;\n\t}) => Promise<{\n\t\treadonly objects: ReadonlyArray<{\n\t\t\treadonly objectId: string;\n\t\t\treadonly version: string | number | bigint;\n\t\t\treadonly digest: string;\n\t\t\treadonly balance?: string | number | bigint;\n\t\t}>;\n\t\treadonly hasNextPage?: boolean;\n\t\treadonly cursor?: string | null;\n\t}>;\n}\n\n/** Paginate `owner`'s SUI coins and return the first whose balance covers\n * `minBalanceMist` (with that balance), or `undefined` if none across all\n * pages. `listCoins` is paginated and NOT balance-ordered, so a sufficient\n * coin can sit behind dust on a later page — we must page, never sample\n * page 1. Shared by gas selection and the fork faucet. */\nconst firstSufficientSuiCoin = async (\n\tclient: ForkImpersonationGasClient,\n\towner: string,\n\tminBalanceMist: bigint,\n): Promise<{ readonly coin: ForkGasCoin; readonly balanceMist: bigint } | undefined> => {\n\tlet cursor: string | null = null;\n\tdo {\n\t\tconst page = await client.listCoins({\n\t\t\towner,\n\t\t\tcoinType: SUI_GAS_COIN_TYPE,\n\t\t\tcursor,\n\t\t\tlimit: FORK_COIN_PAGE_SIZE,\n\t\t});\n\t\tfor (const candidate of page.objects) {\n\t\t\tif (candidate.balance === undefined) continue;\n\t\t\tconst balanceMist = BigInt(candidate.balance);\n\t\t\tif (balanceMist >= minBalanceMist) {\n\t\t\t\treturn {\n\t\t\t\t\tcoin: {\n\t\t\t\t\t\tobjectId: candidate.objectId,\n\t\t\t\t\t\tversion: String(candidate.version),\n\t\t\t\t\t\tdigest: candidate.digest,\n\t\t\t\t\t},\n\t\t\t\t\tbalanceMist,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcursor = page.hasNextPage === true ? (page.cursor ?? null) : null;\n\t} while (cursor !== null);\n\treturn undefined;\n};\n\nexport const prepareForkImpersonationTransaction = (\n\ttx: Transaction,\n\tsender: string,\n\tgasPayment: ReadonlyArray<{\n\t\treadonly objectId: string;\n\t\treadonly version: string;\n\t\treadonly digest: string;\n\t}>,\n): void => {\n\ttx.setSender(sender);\n\n\ttx.setGasBudget(FORK_IMPERSONATION_GAS_BUDGET);\n\ttx.setGasPrice(FORK_IMPERSONATION_GAS_PRICE);\n\ttx.setGasOwner(sender);\n\ttx.setGasPayment([...gasPayment]);\n\n\tconst data = tx.getData();\n\tif (data.expiration == null) tx.setExpiration({ None: true });\n};\n\nexport const buildForkImpersonationTransactionBytes = (\n\ttx: Transaction,\n\tsender: string,\n\tclient: ForkImpersonationGasClient,\n\t/** Pre-selected gas coin. When omitted, the first coin owned by\n\t * `sender` is used (legacy behaviour). The fork faucet passes the\n\t * whale's gas coin via {@link selectSufficientForkCoin} so a\n\t * `splitCoins(tx.gas, …)` funding transfer has enough balance. */\n\tgasCoin?: ForkGasCoin,\n): Effect.Effect<Uint8Array, SuiPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tconst gasPayment =\n\t\t\t\tgasCoin !== undefined ? [gasCoin] : await selectForkImpersonationGasPayment(client, sender);\n\t\t\tprepareForkImpersonationTransaction(tx, sender, gasPayment);\n\t\t\tawait tx.prepareForSerialization({});\n\t\t\tconst data = tx.getData();\n\t\t\tdata.inputs = await Promise.all(data.inputs.map((input) => resolveForkInput(input, client)));\n\t\t\treturn TransactionDataBuilder.restore(data).build();\n\t\t},\n\t\tcatch: (cause) => {\n\t\t\t// Pass through pre-tagged SuiPluginError (thrown by inner\n\t\t\t// helpers with structured response fields) so we don't\n\t\t\t// double-wrap or stringify-leak the response payload.\n\t\t\tif (isSuiPluginError(cause)) return cause;\n\t\t\treturn suiPluginError(\n\t\t\t\t'fork-impersonate',\n\t\t\t\t`sui fork mode: failed to build fork impersonation transaction for ${sender}: ${formatUnknownError(cause)}`,\n\t\t\t\tcause,\n\t\t\t);\n\t\t},\n\t});\n\nconst isSuiPluginError = (value: unknown): value is SuiPluginError =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t(value as { _tag?: unknown })._tag === 'SuiPluginError';\n\nconst resolveForkInput = async (\n\tinput: CallArg,\n\tclient: ForkImpersonationGasClient,\n): Promise<CallArg> => {\n\tif (input.UnresolvedObject === undefined) return input;\n\tconst unresolved = input.UnresolvedObject;\n\tif (unresolved.initialSharedVersion != null) {\n\t\treturn Inputs.SharedObjectRef({\n\t\t\tobjectId: unresolved.objectId,\n\t\t\tinitialSharedVersion: unresolved.initialSharedVersion,\n\t\t\tmutable: unresolved.mutable ?? true,\n\t\t});\n\t}\n\tif (unresolved.version != null && unresolved.digest != null) {\n\t\treturn Inputs.ObjectRef({\n\t\t\tobjectId: unresolved.objectId,\n\t\t\tversion: unresolved.version,\n\t\t\tdigest: unresolved.digest,\n\t\t});\n\t}\n\n\tconst object = objectFromGetObjectResponse(\n\t\tawait client.getObject({ objectId: unresolved.objectId }),\n\t);\n\tconst owner = object.owner;\n\tif (owner?.$kind === 'Shared' && owner.Shared !== undefined) {\n\t\treturn Inputs.SharedObjectRef({\n\t\t\tobjectId: object.objectId,\n\t\t\tinitialSharedVersion: owner.Shared.initialSharedVersion,\n\t\t\tmutable: unresolved.mutable ?? true,\n\t\t});\n\t}\n\treturn Inputs.ObjectRef({\n\t\tobjectId: object.objectId,\n\t\tversion: object.version,\n\t\tdigest: object.digest,\n\t});\n};\n\nconst objectFromGetObjectResponse = (\n\tresponse: unknown,\n): {\n\treadonly objectId: string;\n\treadonly version: string;\n\treadonly digest: string;\n\treadonly owner?: {\n\t\treadonly $kind?: string;\n\t\treadonly Shared?: { readonly initialSharedVersion: string };\n\t};\n} => {\n\tconst object = (response as { readonly object?: unknown }).object;\n\tif (typeof object !== 'object' || object === null) {\n\t\tthrow suiPluginError('fork-impersonate', 'sui fork mode: getObject returned no object', {\n\t\t\tresponseKeys: describeKeys(response),\n\t\t});\n\t}\n\tconst candidate = object as {\n\t\treadonly objectId?: unknown;\n\t\treadonly version?: unknown;\n\t\treadonly digest?: unknown;\n\t\treadonly owner?: {\n\t\t\treadonly $kind?: string;\n\t\t\treadonly Shared?: { readonly initialSharedVersion: string };\n\t\t};\n\t};\n\tif (\n\t\ttypeof candidate.objectId !== 'string' ||\n\t\ttypeof candidate.version !== 'string' ||\n\t\ttypeof candidate.digest !== 'string'\n\t) {\n\t\tthrow suiPluginError(\n\t\t\t'fork-impersonate',\n\t\t\t'sui fork mode: getObject returned incomplete object ref',\n\t\t\t{\n\t\t\t\tobjectKeys: describeKeys(object),\n\t\t\t\tmissing: {\n\t\t\t\t\tobjectId: typeof candidate.objectId !== 'string',\n\t\t\t\t\tversion: typeof candidate.version !== 'string',\n\t\t\t\t\tdigest: typeof candidate.digest !== 'string',\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\treturn {\n\t\tobjectId: candidate.objectId,\n\t\tversion: candidate.version,\n\t\tdigest: candidate.digest,\n\t\towner: candidate.owner,\n\t};\n};\n\nconst selectForkImpersonationGasPayment = async (\n\tclient: ForkImpersonationGasClient,\n\tsender: string,\n): Promise<\n\tReadonlyArray<{ readonly objectId: string; readonly version: string; readonly digest: string }>\n> => {\n\t// The gas coin must cover the impersonation gas budget. Pick the first SUI\n\t// coin that does (paginating past dust) — not blindly `objects[0]`, which on\n\t// an account holding change coins may be below the budget → InsufficientGas\n\t// even though a larger coin exists.\n\tconst found = await firstSufficientSuiCoin(client, sender, FORK_IMPERSONATION_GAS_BUDGET);\n\tif (found === undefined) {\n\t\tthrow suiPluginError(\n\t\t\t'fork-impersonate',\n\t\t\t`sui fork mode: no SUI coin >= ${FORK_IMPERSONATION_GAS_BUDGET} MIST (gas budget) found for ${sender}.`,\n\t\t\t{ sender, requiredMist: FORK_IMPERSONATION_GAS_BUDGET.toString() },\n\t\t);\n\t}\n\treturn [found.coin];\n};\n\n/** Find a SUI coin owned by `owner` that covers `minBalanceMist`, for use as\n * BOTH gas payment and the `splitCoins(tx.gas, …)` funding source in a fork\n * faucet transfer. Paginates the coin set (a sufficient coin can sit behind\n * dust on a later page) and returns the first that qualifies. Fails with an\n * actionable `SuiPluginError` when none exists — reused at boot to validate a\n * configured whale before any funding runs. */\nexport const selectSufficientForkCoin = (\n\tclient: ForkImpersonationGasClient,\n\towner: string,\n\tminBalanceMist: bigint,\n): Effect.Effect<{ readonly coin: ForkGasCoin; readonly balanceMist: bigint }, SuiPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tconst found = await firstSufficientSuiCoin(client, owner, minBalanceMist);\n\t\t\tif (found === undefined) {\n\t\t\t\tthrow suiPluginError(\n\t\t\t\t\t'fork-impersonate',\n\t\t\t\t\t`sui fork mode: no SUI coin >= ${minBalanceMist} MIST (request + gas budget) found for ` +\n\t\t\t\t\t\t`${owner}. Use a fork faucet whale with a larger single coin, or fund a smaller amount.`,\n\t\t\t\t\t{ owner, requiredMist: minBalanceMist.toString() },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn found;\n\t\t},\n\t\tcatch: (cause) => {\n\t\t\tif (isSuiPluginError(cause)) return cause;\n\t\t\treturn suiPluginError(\n\t\t\t\t'fork-impersonate',\n\t\t\t\t`sui fork mode: failed to select a SUI coin for ${owner}: ${formatUnknownError(cause)}`,\n\t\t\t\tcause,\n\t\t\t);\n\t\t},\n\t});\n\nexport const verifyForkImpersonationSender = (\n\tsender: string,\n\ttxBytes: Uint8Array,\n): Effect.Effect<void, SuiPluginError> =>\n\tEffect.try({\n\t\ttry: () => {\n\t\t\tconst actual = TransactionDataBuilder.fromBytes(txBytes).snapshot().sender;\n\t\t\tif (actual == null) {\n\t\t\t\tthrow suiPluginError(\n\t\t\t\t\t'fork-impersonate',\n\t\t\t\t\t'sui fork mode: refused impersonation transaction — transaction has no sender',\n\t\t\t\t\t{ expectedSender: sender },\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (normalizeSuiAddress(actual) !== normalizeSuiAddress(sender)) {\n\t\t\t\tthrow suiPluginError(\n\t\t\t\t\t'fork-impersonate',\n\t\t\t\t\t'sui fork mode: refused impersonation transaction — sender mismatch',\n\t\t\t\t\t{ expectedSender: sender, actualSender: actual },\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\tcatch: (cause) => {\n\t\t\tif (isSuiPluginError(cause)) return cause;\n\t\t\treturn suiPluginError(\n\t\t\t\t'fork-impersonate',\n\t\t\t\t`sui fork mode: refused impersonation transaction: ${formatUnknownError(cause)}`,\n\t\t\t\tcause,\n\t\t\t);\n\t\t},\n\t});\n\nconst describeKeys = (value: unknown): ReadonlyArray<string> => {\n\tif (typeof value !== 'object' || value === null) return [];\n\treturn Object.keys(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fork-transaction.mjs","names":[],"sources":["../../../src/plugins/sui/fork-transaction.ts"],"sourcesContent":["import { Effect } from 'effect';\n\nimport {\n\tInputs,\n\tTransaction,\n\tTransactionDataBuilder,\n\ttype CallArg,\n} from '@mysten/sui/transactions';\nimport { normalizeSuiAddress } from '@mysten/sui/utils';\n\nimport { suiPluginError, type SuiPluginError } from './errors.ts';\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.ts';\n\n// 0.1 SUI. Deliberately well below the faucet's default per-account fund\n// (1 SUI) so a faucet-funded account keeps headroom to move value:\n// `setGasBudget` reserves the whole gas coin, so a budget equal to the\n// coin balance leaves nothing for `splitCoins(tx.gas, …)` / transfers\n// (InsufficientCoinBalance). 0.1 SUI still covers package publishes.\nexport const FORK_IMPERSONATION_GAS_BUDGET = 100_000_000n;\nexport const FORK_IMPERSONATION_GAS_PRICE = 1_000n;\n\n/** Canonical SUI gas coin type used to filter `listCoins` when picking\n * a gas/funding coin. Kept local so the sui plugin needn't depend on\n * the account plugin's `SUI_FULL_COIN_TYPE`. */\nconst SUI_GAS_COIN_TYPE = '0x2::sui::SUI';\n\n/** `listCoins` page size while scanning for a sufficient coin. */\nconst FORK_COIN_PAGE_SIZE = 50;\n\n/** An object ref usable as gas payment for an impersonation transaction. */\nexport interface ForkGasCoin {\n\treadonly objectId: string;\n\treadonly version: string;\n\treadonly digest: string;\n}\n\nexport interface ForkImpersonationGasClient {\n\treadonly getObject: (input: { readonly objectId: string }) => Promise<unknown>;\n\treadonly listCoins: (input: {\n\t\treadonly owner: string;\n\t\treadonly coinType?: string;\n\t\treadonly limit?: number;\n\t\treadonly cursor?: string | null;\n\t}) => Promise<{\n\t\treadonly objects: ReadonlyArray<{\n\t\t\treadonly objectId: string;\n\t\t\treadonly version: string | number | bigint;\n\t\t\treadonly digest: string;\n\t\t\treadonly balance?: string | number | bigint;\n\t\t}>;\n\t\treadonly hasNextPage?: boolean;\n\t\treadonly cursor?: string | null;\n\t}>;\n}\n\n/** Paginate `owner`'s SUI coins and return the first whose balance covers\n * `minBalanceMist` (with that balance), or `undefined` if none across all\n * pages. `listCoins` is paginated and NOT balance-ordered, so a sufficient\n * coin can sit behind dust on a later page — we must page, never sample\n * page 1. Shared by gas selection and the fork faucet. */\nconst firstSufficientSuiCoin = async (\n\tclient: ForkImpersonationGasClient,\n\towner: string,\n\tminBalanceMist: bigint,\n): Promise<{ readonly coin: ForkGasCoin; readonly balanceMist: bigint } | undefined> => {\n\tlet cursor: string | null = null;\n\tdo {\n\t\tconst page = await client.listCoins({\n\t\t\towner,\n\t\t\tcoinType: SUI_GAS_COIN_TYPE,\n\t\t\tcursor,\n\t\t\tlimit: FORK_COIN_PAGE_SIZE,\n\t\t});\n\t\tfor (const candidate of page.objects) {\n\t\t\tif (candidate.balance === undefined) continue;\n\t\t\tconst balanceMist = BigInt(candidate.balance);\n\t\t\tif (balanceMist >= minBalanceMist) {\n\t\t\t\treturn {\n\t\t\t\t\tcoin: {\n\t\t\t\t\t\tobjectId: candidate.objectId,\n\t\t\t\t\t\tversion: String(candidate.version),\n\t\t\t\t\t\tdigest: candidate.digest,\n\t\t\t\t\t},\n\t\t\t\t\tbalanceMist,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcursor = page.hasNextPage === true ? (page.cursor ?? null) : null;\n\t} while (cursor !== null);\n\treturn undefined;\n};\n\nexport const prepareForkImpersonationTransaction = (\n\ttx: Transaction,\n\tsender: string,\n\tgasPayment: ReadonlyArray<{\n\t\treadonly objectId: string;\n\t\treadonly version: string;\n\t\treadonly digest: string;\n\t}>,\n): void => {\n\ttx.setSender(sender);\n\n\ttx.setGasBudget(FORK_IMPERSONATION_GAS_BUDGET);\n\ttx.setGasPrice(FORK_IMPERSONATION_GAS_PRICE);\n\ttx.setGasOwner(sender);\n\ttx.setGasPayment([...gasPayment]);\n\n\tconst data = tx.getData();\n\tif (data.expiration == null) tx.setExpiration({ None: true });\n};\n\nexport const buildForkImpersonationTransactionBytes = (\n\ttx: Transaction,\n\tsender: string,\n\tclient: ForkImpersonationGasClient,\n\t/** Pre-selected gas coin. When omitted, the first coin owned by\n\t * `sender` is used (legacy behaviour). The fork faucet passes the\n\t * whale's gas coin via {@link selectSufficientForkCoin} so a\n\t * `splitCoins(tx.gas, …)` funding transfer has enough balance. */\n\tgasCoin?: ForkGasCoin,\n): Effect.Effect<Uint8Array, SuiPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tconst gasPayment =\n\t\t\t\tgasCoin !== undefined ? [gasCoin] : await selectForkImpersonationGasPayment(client, sender);\n\t\t\tprepareForkImpersonationTransaction(tx, sender, gasPayment);\n\t\t\tawait tx.prepareForSerialization({});\n\t\t\tconst data = tx.getData();\n\t\t\tdata.inputs = await Promise.all(data.inputs.map((input) => resolveForkInput(input, client)));\n\t\t\treturn TransactionDataBuilder.restore(data).build();\n\t\t},\n\t\tcatch: (cause) => {\n\t\t\t// Pass through pre-tagged SuiPluginError (thrown by inner\n\t\t\t// helpers with structured response fields) so we don't\n\t\t\t// double-wrap or stringify-leak the response payload.\n\t\t\tif (isSuiPluginError(cause)) return cause;\n\t\t\treturn suiPluginError(\n\t\t\t\t'fork-impersonate',\n\t\t\t\t`sui fork mode: failed to build fork impersonation transaction for ${sender}: ${formatUnknownError(cause)}`,\n\t\t\t\tcause,\n\t\t\t);\n\t\t},\n\t});\n\nconst isSuiPluginError = (value: unknown): value is SuiPluginError =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t(value as { _tag?: unknown })._tag === 'SuiPluginError';\n\nconst resolveForkInput = async (\n\tinput: CallArg,\n\tclient: ForkImpersonationGasClient,\n): Promise<CallArg> => {\n\tif (input.UnresolvedObject === undefined) return input;\n\tconst unresolved = input.UnresolvedObject;\n\tif (unresolved.initialSharedVersion != null) {\n\t\treturn Inputs.SharedObjectRef({\n\t\t\tobjectId: unresolved.objectId,\n\t\t\tinitialSharedVersion: unresolved.initialSharedVersion,\n\t\t\tmutable: unresolved.mutable ?? true,\n\t\t});\n\t}\n\tif (unresolved.version != null && unresolved.digest != null) {\n\t\treturn Inputs.ObjectRef({\n\t\t\tobjectId: unresolved.objectId,\n\t\t\tversion: unresolved.version,\n\t\t\tdigest: unresolved.digest,\n\t\t});\n\t}\n\n\tconst object = objectFromGetObjectResponse(\n\t\tawait client.getObject({ objectId: unresolved.objectId }),\n\t);\n\tconst owner = object.owner;\n\tif (owner?.$kind === 'Shared' && owner.Shared !== undefined) {\n\t\treturn Inputs.SharedObjectRef({\n\t\t\tobjectId: object.objectId,\n\t\t\tinitialSharedVersion: owner.Shared.initialSharedVersion,\n\t\t\tmutable: unresolved.mutable ?? true,\n\t\t});\n\t}\n\treturn Inputs.ObjectRef({\n\t\tobjectId: object.objectId,\n\t\tversion: object.version,\n\t\tdigest: object.digest,\n\t});\n};\n\nconst objectFromGetObjectResponse = (\n\tresponse: unknown,\n): {\n\treadonly objectId: string;\n\treadonly version: string;\n\treadonly digest: string;\n\treadonly owner?: {\n\t\treadonly $kind?: string;\n\t\treadonly Shared?: { readonly initialSharedVersion: string };\n\t};\n} => {\n\tconst object = (response as { readonly object?: unknown }).object;\n\tif (typeof object !== 'object' || object === null) {\n\t\tthrow suiPluginError('fork-impersonate', 'sui fork mode: getObject returned no object', {\n\t\t\tresponseKeys: describeKeys(response),\n\t\t});\n\t}\n\tconst candidate = object as {\n\t\treadonly objectId?: unknown;\n\t\treadonly version?: unknown;\n\t\treadonly digest?: unknown;\n\t\treadonly owner?: {\n\t\t\treadonly $kind?: string;\n\t\t\treadonly Shared?: { readonly initialSharedVersion: string };\n\t\t};\n\t};\n\tif (\n\t\ttypeof candidate.objectId !== 'string' ||\n\t\ttypeof candidate.version !== 'string' ||\n\t\ttypeof candidate.digest !== 'string'\n\t) {\n\t\tthrow suiPluginError(\n\t\t\t'fork-impersonate',\n\t\t\t'sui fork mode: getObject returned incomplete object ref',\n\t\t\t{\n\t\t\t\tobjectKeys: describeKeys(object),\n\t\t\t\tmissing: {\n\t\t\t\t\tobjectId: typeof candidate.objectId !== 'string',\n\t\t\t\t\tversion: typeof candidate.version !== 'string',\n\t\t\t\t\tdigest: typeof candidate.digest !== 'string',\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\treturn {\n\t\tobjectId: candidate.objectId,\n\t\tversion: candidate.version,\n\t\tdigest: candidate.digest,\n\t\towner: candidate.owner,\n\t};\n};\n\nconst selectForkImpersonationGasPayment = async (\n\tclient: ForkImpersonationGasClient,\n\tsender: string,\n): Promise<\n\tReadonlyArray<{ readonly objectId: string; readonly version: string; readonly digest: string }>\n> => {\n\t// The gas coin must cover the impersonation gas budget. Pick the first SUI\n\t// coin that does (paginating past dust) — not blindly `objects[0]`, which on\n\t// an account holding change coins may be below the budget → InsufficientGas\n\t// even though a larger coin exists.\n\tconst found = await firstSufficientSuiCoin(client, sender, FORK_IMPERSONATION_GAS_BUDGET);\n\tif (found === undefined) {\n\t\tthrow suiPluginError(\n\t\t\t'fork-impersonate',\n\t\t\t`sui fork mode: no SUI coin >= ${FORK_IMPERSONATION_GAS_BUDGET} MIST (gas budget) found for ${sender}.`,\n\t\t\t{ sender, requiredMist: FORK_IMPERSONATION_GAS_BUDGET.toString() },\n\t\t);\n\t}\n\treturn [found.coin];\n};\n\n/** Find a SUI coin owned by `owner` that covers `minBalanceMist`, for use as\n * BOTH gas payment and the `splitCoins(tx.gas, …)` funding source in a fork\n * faucet transfer. Paginates the coin set (a sufficient coin can sit behind\n * dust on a later page) and returns the first that qualifies. Fails with an\n * actionable `SuiPluginError` when none exists — reused at boot to validate a\n * configured whale before any funding runs. */\nexport const selectSufficientForkCoin = (\n\tclient: ForkImpersonationGasClient,\n\towner: string,\n\tminBalanceMist: bigint,\n): Effect.Effect<{ readonly coin: ForkGasCoin; readonly balanceMist: bigint }, SuiPluginError> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tconst found = await firstSufficientSuiCoin(client, owner, minBalanceMist);\n\t\t\tif (found === undefined) {\n\t\t\t\tthrow suiPluginError(\n\t\t\t\t\t'fork-impersonate',\n\t\t\t\t\t`sui fork mode: no SUI coin >= ${minBalanceMist} MIST (request + gas budget) found for ` +\n\t\t\t\t\t\t`${owner}. Use a fork faucet whale with a larger single coin, or fund a smaller amount.`,\n\t\t\t\t\t{ owner, requiredMist: minBalanceMist.toString() },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn found;\n\t\t},\n\t\tcatch: (cause) => {\n\t\t\tif (isSuiPluginError(cause)) return cause;\n\t\t\treturn suiPluginError(\n\t\t\t\t'fork-impersonate',\n\t\t\t\t`sui fork mode: failed to select a SUI coin for ${owner}: ${formatUnknownError(cause)}`,\n\t\t\t\tcause,\n\t\t\t);\n\t\t},\n\t});\n\nexport const verifyForkImpersonationSender = (\n\tsender: string,\n\ttxBytes: Uint8Array,\n): Effect.Effect<void, SuiPluginError> =>\n\tEffect.try({\n\t\ttry: () => {\n\t\t\tconst actual = TransactionDataBuilder.fromBytes(txBytes).snapshot().sender;\n\t\t\tif (actual == null) {\n\t\t\t\tthrow suiPluginError(\n\t\t\t\t\t'fork-impersonate',\n\t\t\t\t\t'sui fork mode: refused impersonation transaction — transaction has no sender',\n\t\t\t\t\t{ expectedSender: sender },\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (normalizeSuiAddress(actual) !== normalizeSuiAddress(sender)) {\n\t\t\t\tthrow suiPluginError(\n\t\t\t\t\t'fork-impersonate',\n\t\t\t\t\t'sui fork mode: refused impersonation transaction — sender mismatch',\n\t\t\t\t\t{ expectedSender: sender, actualSender: actual },\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\tcatch: (cause) => {\n\t\t\tif (isSuiPluginError(cause)) return cause;\n\t\t\treturn suiPluginError(\n\t\t\t\t'fork-impersonate',\n\t\t\t\t`sui fork mode: refused impersonation transaction: ${formatUnknownError(cause)}`,\n\t\t\t\tcause,\n\t\t\t);\n\t\t},\n\t});\n\nconst describeKeys = (value: unknown): ReadonlyArray<string> => {\n\tif (typeof value !== 'object' || value === null) return [];\n\treturn Object.keys(value);\n};\n"],"mappings":"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"}
|