@fased/fased 0.1.61 → 0.1.64
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/CHANGELOG.md +43 -0
- package/README.md +38 -30
- package/config/dependency-ownership.json +1 -1
- package/config/signer-policies/README.md +49 -0
- package/config/signer-policies/agent.json.template +32 -0
- package/config/signer-policies/mining.json.template +69 -0
- package/config/signer-policies/network.json.template +5 -0
- package/config/signer-policies/vault.json.template +70 -0
- package/config/signer-protocol-v2.json +76 -0
- package/config/typescript-noemit-baseline.json +680 -0
- package/dist/acp/client.js +2 -2
- package/dist/acp/secret-file.js +2 -2
- package/dist/agents/agent-scope.js +2 -2
- package/dist/agents/apply-patch.js +8 -8
- package/dist/agents/auth-profiles/paths.js +2 -2
- package/dist/agents/auth-profiles/store.js +269 -33
- package/dist/agents/bash-tools.exec.js +10 -4
- package/dist/agents/bash-tools.shared.js +2 -2
- package/dist/agents/cli-credentials.js +4 -4
- package/dist/agents/current-model-catalog.js +3 -3
- package/dist/agents/docs-path.js +3 -3
- package/dist/agents/embedded-pi-mcp.js +4 -4
- package/dist/agents/identity-avatar.js +3 -3
- package/dist/agents/identity-file.js +2 -2
- package/dist/agents/memory-search.js +7 -1
- package/dist/agents/model-catalog-access.js +1 -2
- package/dist/agents/model-catalog-snapshot.js +1 -1
- package/dist/agents/model-catalog-source.js +1 -1
- package/dist/agents/model-metadata.js +1 -1
- package/dist/agents/model-selection.js +2 -28
- package/dist/agents/models-config.providers.js +6 -6
- package/dist/agents/openai-codex-app-server.js +5 -5
- package/dist/agents/pi-embedded-runner/compact.js +15 -7
- package/dist/agents/pi-embedded-runner/model.js +1 -1
- package/dist/agents/pi-embedded-runner/run/attempt.js +24 -13
- package/dist/agents/pi-embedded-runner/run.js +2 -0
- package/dist/agents/pi-embedded-runner/tool-split.js +40 -2
- package/dist/agents/pi-embedded-subscribe.raw-stream.js +3 -3
- package/dist/agents/pi-extensions/compaction-safeguard.js +3 -3
- package/dist/agents/pi-model-discovery.js +6 -6
- package/dist/agents/pi-project-settings.js +3 -3
- package/dist/agents/pi-tool-definition-adapter.js +16 -1
- package/dist/agents/pi-tools.js +2 -1
- package/dist/agents/sandbox/config.js +1 -1
- package/dist/agents/sandbox/context.js +27 -2
- package/dist/agents/sandbox/fs-bridge.js +2 -2
- package/dist/agents/session-dirs.js +4 -4
- package/dist/agents/session-write-lock.js +13 -13
- package/dist/agents/shell-utils.js +5 -5
- package/dist/agents/skills/bundled-dir.js +4 -4
- package/dist/agents/skills/env-overrides.js +38 -28
- package/dist/agents/skills/local-loader.js +6 -6
- package/dist/agents/skills/plugin-skills.js +2 -2
- package/dist/agents/skills/trust.js +93 -0
- package/dist/agents/skills/workspace.js +141 -13
- package/dist/agents/skills-clawhub.js +36 -11
- package/dist/agents/skills-install-download.js +7 -7
- package/dist/agents/skills-install-plan.js +2 -2
- package/dist/agents/skills-install.js +2 -2
- package/dist/agents/skills-marketplace-scan.js +49 -0
- package/dist/agents/skills-status.js +2 -2
- package/dist/agents/subagent-depth.js +2 -2
- package/dist/agents/system-prompt-params.js +2 -2
- package/dist/agents/tools/marketplace-offer-draft-tool.js +1 -1
- package/dist/agents/tools/mining-tool.js +1 -1
- package/dist/agents/tools/session-status-tool.js +1 -1
- package/dist/agents/tools/wallet-action-tool.js +172 -36
- package/dist/agents/tools/wallet-skill-policy.js +65 -23
- package/dist/agents/tools/wallet-tool.js +7 -11
- package/dist/agents/workspace.js +22 -22
- package/dist/auto-reply/reply/agent-runner-execution.js +2 -2
- package/dist/auto-reply/reply/agent-runner.js +2 -2
- package/dist/auto-reply/reply/commands-export-session.js +7 -7
- package/dist/auto-reply/reply/directive-handling.fast-lane.js +2 -1
- package/dist/auto-reply/reply/directive-handling.impl.js +2 -1
- package/dist/auto-reply/reply/directive-handling.model.js +2 -1
- package/dist/auto-reply/reply/directive-handling.persist.js +2 -2
- package/dist/auto-reply/reply/get-reply-directives-apply.js +2 -0
- package/dist/auto-reply/reply/get-reply-inline-actions.js +25 -1
- package/dist/auto-reply/reply/model-selection.js +7 -4
- package/dist/auto-reply/reply/post-compaction-audit.js +3 -3
- package/dist/auto-reply/reply/post-compaction-context.js +3 -3
- package/dist/auto-reply/reply/session-reset-model.js +4 -4
- package/dist/auto-reply/reply/session.js +3 -3
- package/dist/auto-reply/skill-commands.js +3 -3
- package/dist/auto-reply/status.js +6 -6
- package/dist/brand.js +1 -1
- package/dist/browser/chrome.executables.js +3 -3
- package/dist/browser/chrome.js +3 -3
- package/dist/browser/chrome.profile-decoration.js +6 -6
- package/dist/browser/profiles-service.js +2 -2
- package/dist/browser/server-context.js +2 -2
- package/dist/browser/trash.js +4 -4
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/canvas-host/server.js +4 -4
- package/dist/capabilities/catalog.js +3 -3
- package/dist/channel-web.js +2 -2
- package/dist/channels/plugins/catalog.js +3 -3
- package/dist/cli/browser-cli-extension.js +6 -6
- package/dist/cli/command-format.js +2 -2
- package/dist/cli/cron-cli/register.cron-simple.js +2 -2
- package/dist/cli/dns-cli.js +9 -9
- package/dist/cli/gateway-cli/dev.js +5 -5
- package/dist/cli/gateway-cli/run.js +10 -2
- package/dist/cli/hooks-cli.js +5 -5
- package/dist/cli/lightweight/gateway-runtime-probe.js +2 -2
- package/dist/cli/memory-cli.js +7 -7
- package/dist/cli/nodes-media-utils.js +2 -2
- package/dist/cli/plugins-cli.js +2 -2
- package/dist/cli/program/command-registry.js +1 -1
- package/dist/cli/program/register.onboard.js +2 -3
- package/dist/cli/program/register.wallet.js +16 -123
- package/dist/cli/providers-cli.js +1 -1
- package/dist/cli/sat-cli.js +129 -2
- package/dist/cli/secrets-cli.js +3 -3
- package/dist/cli/update-cli/update-command.js +260 -11
- package/dist/cli/windows-argv.js +2 -2
- package/dist/commands/auth-choice.apply.api-providers.js +3 -3
- package/dist/commands/dashboard-link.js +3 -3
- package/dist/commands/doctor-config-flow.js +1 -1
- package/dist/commands/doctor-install.js +5 -5
- package/dist/commands/doctor-legacy-config.js +2 -13
- package/dist/commands/doctor-memory-search.js +2 -2
- package/dist/commands/doctor-platform-notes.js +2 -2
- package/dist/commands/doctor-sandbox.js +2 -2
- package/dist/commands/doctor-state-integrity.js +23 -23
- package/dist/commands/doctor-workspace.js +3 -3
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/managed-up.js +2 -2
- package/dist/commands/model-picker.js +6 -6
- package/dist/commands/models/auth.js +1 -1
- package/dist/commands/onboard-auth.config-gateways.js +1 -1
- package/dist/commands/onboard-auth.credentials.js +35 -11
- package/dist/commands/onboard-non-interactive/local/wallet-config.js +28 -27
- package/dist/commands/onboard-non-interactive/local.js +5 -1
- package/dist/commands/onboarding/plugin-install.js +2 -2
- package/dist/commands/sessions-cleanup.js +2 -2
- package/dist/commands/signal-install.js +6 -6
- package/dist/commands/status-all/channels.js +2 -2
- package/dist/commands/wallet.js +570 -1913
- package/dist/config/cache-utils.js +2 -2
- package/dist/config/config.js +2 -2
- package/dist/config/includes.js +5 -5
- package/dist/config/io.js +66 -4
- package/dist/config/paths.js +3 -3
- package/dist/config/sat-runtime-ids.js +3 -3
- package/dist/config/schema.help.js +8 -7
- package/dist/config/schema.labels.js +5 -4
- package/dist/config/sessions/disk-budget.js +6 -6
- package/dist/config/sessions/paths.js +2 -2
- package/dist/config/sessions/store.js +17 -17
- package/dist/config/sessions/transcript.js +4 -4
- package/dist/config/talk.js +2 -2
- package/dist/config/zod-schema.agent-runtime.js +1 -0
- package/dist/config/zod-schema.js +5 -0
- package/dist/control-ui/assets/{app-BXA4gPvN.js → app-bR-fO5ty.js} +1511 -1555
- package/dist/control-ui/assets/{config-CU5Zi8Dh.js → config-LeImukCW.js} +10 -10
- package/dist/control-ui/assets/{federation-D10Q4eP_.js → federation-DgA9e-Pn.js} +364 -422
- package/dist/control-ui/assets/{index-D2QbJhVg.js → index-ckCxHwjy.js} +3 -3
- package/dist/control-ui/assets/{mining-BdhMn18r.js → mining-BgHFh0ei.js} +134 -114
- package/dist/control-ui/assets/mining-commit-DFpB_4JN.js +2 -0
- package/dist/control-ui/assets/{open-external-url-DwSiwMPl.js → open-external-url-ulF1B-sA.js} +3 -3
- package/dist/control-ui/assets/{providers-QMn4w2_f.js → providers-CIx9fxc6.js} +2 -2
- package/dist/control-ui/assets/{wallet-C2g294QT.js → wallet-DvStR_AT.js} +259 -377
- package/dist/control-ui/index.html +1 -1
- package/dist/control-ui/version.json +1 -1
- package/dist/cron/service/store.js +2 -2
- package/dist/cron/store.js +6 -6
- package/dist/daemon/systemd-system.js +60 -54
- package/dist/discord/monitor/thread-bindings.state.js +2 -2
- package/dist/extensions/sat-mining/src/protocol-contract.js +43 -3
- package/dist/extensions/sat-mining/src/rpc-read.js +235 -67
- package/dist/extensions/sat-mining/src/sat-bond-layout.js +14 -11
- package/dist/extensions/sat-mining/src/signer-codec-manifest.js +406 -0
- package/dist/extensions/sat-mining/src/solana-submit.js +1038 -397
- package/dist/extensions/sat-mining/src/submission-ledger.js +349 -0
- package/dist/federation/auto-connect.js +98 -8
- package/dist/federation/marketplace-capability-adapter.js +33 -7
- package/dist/federation/marketplace-delivery-outbox.js +196 -0
- package/dist/federation/marketplace-delivery.js +351 -215
- package/dist/federation/marketplace-direct-payment.js +272 -141
- package/dist/federation/marketplace-escrow.js +607 -249
- package/dist/federation/marketplace-settlement-ledger.js +352 -0
- package/dist/federation/offers.js +9 -7
- package/dist/federation/peer-auth-v2.js +529 -0
- package/dist/gateway/a2a-http.js +425 -96
- package/dist/gateway/a2a-settlement.js +259 -153
- package/dist/gateway/a2a-task-store.js +524 -0
- package/dist/gateway/auth.js +38 -2
- package/dist/gateway/config-reload.js +46 -33
- package/dist/gateway/control-ui-boot-check.js +6 -6
- package/dist/gateway/control-ui-login.js +7 -7
- package/dist/gateway/control-ui.js +9 -9
- package/dist/gateway/durable-capacity.js +31 -0
- package/dist/gateway/federation-http.js +540 -135
- package/dist/gateway/federation-marketplace-run-store.js +397 -0
- package/dist/gateway/federation-marketplace.js +535 -230
- package/dist/gateway/hooks-mapping.js +4 -4
- package/dist/gateway/mutating-admin-rpc-rate-limit.js +5 -0
- package/dist/gateway/security-path.js +1 -1
- package/dist/gateway/server-discovery.js +2 -2
- package/dist/gateway/server-http.js +1088 -1209
- package/dist/gateway/server-methods/chat.js +3 -3
- package/dist/gateway/server-methods/models.js +1 -1
- package/dist/gateway/server-methods/secrets.js +40 -0
- package/dist/gateway/server-methods/sessions.js +6 -6
- package/dist/gateway/server-methods/usage.js +2 -2
- package/dist/gateway/server-methods.js +1 -2
- package/dist/gateway/server-reload-handlers.js +196 -66
- package/dist/gateway/server-runtime-config.js +1 -1
- package/dist/gateway/server-secrets-runtime.js +288 -0
- package/dist/gateway/server.impl.js +73 -42
- package/dist/gateway/session-compaction-checkpoints.js +4 -4
- package/dist/gateway/session-utils.fs.js +24 -24
- package/dist/gateway/session-utils.js +6 -6
- package/dist/gateway/sessions-patch.js +3 -5
- package/dist/hooks/bundled-dir.js +4 -4
- package/dist/hooks/gmail-setup-utils.js +6 -6
- package/dist/hooks/import-url.js +2 -2
- package/dist/hooks/loader.js +4 -4
- package/dist/hooks/workspace.js +7 -7
- package/dist/index.js +4 -2
- package/dist/infra/archive.js +17 -17
- package/dist/infra/boundary-file-read.js +2 -2
- package/dist/infra/boundary-path.js +15 -15
- package/dist/infra/brew.js +2 -2
- package/dist/infra/control-ui-assets.js +14 -14
- package/dist/infra/device-auth-store.js +8 -8
- package/dist/infra/device-identity.js +8 -8
- package/dist/infra/dotenv.js +2 -2
- package/dist/infra/exec-approvals.js +8 -8
- package/dist/infra/exec-command-resolution.js +4 -4
- package/dist/infra/exec-safe-bin-trust.js +2 -2
- package/dist/infra/fased-root.js +5 -5
- package/dist/infra/fasedhub.js +64 -1
- package/dist/infra/fs-safe.js +14 -14
- package/dist/infra/gateway-lock.js +11 -11
- package/dist/infra/git-commit.js +5 -5
- package/dist/infra/git-root.js +4 -4
- package/dist/infra/hosted-runtime-artifact.js +3 -3
- package/dist/infra/is-main.js +2 -2
- package/dist/infra/json-file.js +7 -7
- package/dist/infra/local-source-paired-update.js +538 -0
- package/dist/infra/managed-runtime-bootstrap.js +6 -6
- package/dist/infra/outbound/delivery-queue.js +16 -16
- package/dist/infra/path-env.js +3 -3
- package/dist/infra/ports-lsof.js +4 -4
- package/dist/infra/provider-usage.auth.js +3 -3
- package/dist/infra/restart.js +41 -10
- package/dist/infra/safe-open-sync.js +2 -2
- package/dist/infra/session-cost-usage.js +9 -9
- package/dist/infra/shell-env.js +2 -2
- package/dist/infra/state-migrations.fs.js +6 -6
- package/dist/infra/state-migrations.js +23 -23
- package/dist/infra/tmp-fased-dir.js +6 -6
- package/dist/infra/update-runner.js +96 -0
- package/dist/infra/widearea-dns.js +3 -3
- package/dist/infra/wsl.js +2 -2
- package/dist/light-update-status.js +11 -11
- package/dist/line/accounts.js +2 -2
- package/dist/line/download.js +2 -2
- package/dist/logging/config.js +3 -3
- package/dist/logging/logger.js +7 -7
- package/dist/managed/federation.js +3 -3
- package/dist/managed/tunnel.js +3 -3
- package/dist/media/store.js +13 -13
- package/dist/media-understanding/runner.js +3 -3
- package/dist/memory/backend-config.js +2 -1
- package/dist/memory/embeddings.js +2 -2
- package/dist/memory/internal.js +10 -10
- package/dist/memory/manager-sync-ops.js +7 -7
- package/dist/memory/repair-executor.js +17 -17
- package/dist/memory/session-files.js +16 -2
- package/dist/node-host/invoke-system-run-plan.js +8 -8
- package/dist/node-host/invoke.js +2 -2
- package/dist/node-host/runner.js +3 -3
- package/dist/pairing/pairing-store.js +3 -3
- package/dist/plugin-sdk/{accounts-DxblobYM.js → accounts-Brn3-uWN.js} +1 -1
- package/dist/plugin-sdk/{actions-D4UNimWa.js → actions-DRu135h0.js} +2 -2
- package/dist/plugin-sdk/{active-listener-CFuy106s.js → active-listener-B_NMtgPL.js} +1 -1
- package/dist/plugin-sdk/{audio-preflight-Bv0hUOWL.js → audio-preflight-BYxAt775.js} +27 -27
- package/dist/plugin-sdk/{audit-Cs0K7LBp.js → audit-C5d2z5U1.js} +1 -1
- package/dist/plugin-sdk/{channel-access-D5aHoTY8.js → channel-access-CcFRdRPO.js} +1 -1
- package/dist/plugin-sdk/{channel-access-configure-Dvch9mMQ.js → channel-access-configure-DaI5wevh.js} +1 -1
- package/dist/plugin-sdk/channel-plugin-common.d.ts +3 -3
- package/dist/plugin-sdk/channel-plugin-common.js +4 -4
- package/dist/plugin-sdk/{chunk-B0FUGc5A.js → chunk-CyoyqpbG.js} +1 -1
- package/dist/plugin-sdk/{chunk-items-Bk--NU3E.js → chunk-items-DIRHG2gJ.js} +1 -1
- package/dist/plugin-sdk/command-status.d.ts +2 -2
- package/dist/plugin-sdk/command-status.js +27 -27
- package/dist/plugin-sdk/{commands-registry-DmDRhRAU.js → commands-registry-BfqdEWdF.js} +2 -2
- package/dist/plugin-sdk/{common-CfUOowOp.js → common-HCcXU-NR.js} +1 -1
- package/dist/plugin-sdk/{config-mxULv0fQ.js → config-DlWVeLFB.js} +7113 -6877
- package/dist/plugin-sdk/{config-schema-CGW6J3Pv.js → config-schema-BOT7kHb6.js} +6779 -3696
- package/dist/plugin-sdk/device-pair.d.ts +1 -1
- package/dist/plugin-sdk/discord.d.ts +5 -5
- package/dist/plugin-sdk/discord.js +19 -19
- package/dist/plugin-sdk/file-lock.js +35 -4
- package/dist/plugin-sdk/{filesystem-manager-ZoHU8oA7.js → filesystem-manager-DoI8IBNF.js} +1 -1
- package/dist/plugin-sdk/{group-mentions-BHRff6j8.d.ts → group-mentions-q3hUTCt7.d.ts} +1 -1
- package/dist/plugin-sdk/{handle-action-DbVCF9HZ.js → handle-action-hvwapMKg.js} +13 -13
- package/dist/plugin-sdk/{helpers-BXbdgs5H.js → helpers-DMBU4oTo.js} +1 -1
- package/dist/plugin-sdk/{image-ops-D7jPxaph.js → image-ops-DQi0n_Tt.js} +1 -1
- package/dist/plugin-sdk/index.d.ts +9 -27
- package/dist/plugin-sdk/index.js +29 -29
- package/dist/plugin-sdk/json-store.js +6 -6
- package/dist/plugin-sdk/{login-BRxww6nv.js → login-Ca583Okc.js} +3 -3
- package/dist/plugin-sdk/{login-qr-DJXsk9XH.js → login-qr-CGciBSNP.js} +3 -3
- package/dist/plugin-sdk/{manager-Y2S_5IlI.js → manager-BegWxNYX.js} +4 -4
- package/dist/plugin-sdk/{markdown-tables-DHLFQqaF.js → markdown-tables-DCOXEUfZ.js} +1 -1
- package/dist/plugin-sdk/{media-Czo4carg.js → media-CAdQF5TG.js} +47 -47
- package/dist/plugin-sdk/{memory-search-DTjIMw8J.js → memory-search-CrFPOeTH.js} +4 -1
- package/dist/plugin-sdk/{monitor-B9Cmt_Je.js → monitor-BCFDaqJ-.js} +31 -31
- package/dist/plugin-sdk/{monitor-B71iwAgC.js → monitor-BgbjRiPM.js} +32 -32
- package/dist/plugin-sdk/{openresponses-http-Du4GMmwo.js → openresponses-http-DFf87SMM.js} +27 -27
- package/dist/plugin-sdk/{optional-runtime-dependency-DdMbxS24.js → optional-runtime-dependency-0dznm3ZO.js} +1 -1
- package/dist/plugin-sdk/{outbound-Db8mRN6P.js → outbound-BgVDdHxx.js} +6 -6
- package/dist/plugin-sdk/{outbound-attachment-BqFjcypb.js → outbound-attachment-QqTnIhZL.js} +2 -2
- package/dist/plugin-sdk/{pairing-message-BMkkS038.d.ts → pairing-message-CS-9BHug.d.ts} +2 -2
- package/dist/plugin-sdk/{pairing-message-B3GC_7CD.js → pairing-message-D0CoLHRJ.js} +1 -1
- package/dist/plugin-sdk/{pairing-store-CQikgIbw.js → pairing-store-BChahgI2.js} +2 -2
- package/dist/plugin-sdk/{pi-model-discovery-CY3m5zJP.js → pi-model-discovery-BX_JLcEJ.js} +1 -1
- package/dist/plugin-sdk/{pi-model-discovery-runtime-DSh1T2pn.js → pi-model-discovery-runtime-DxTMz_XF.js} +2 -2
- package/dist/plugin-sdk/provider-web-search-config-contract.d.ts +2 -2
- package/dist/plugin-sdk/{pw-ai-AFUqn-tQ.js → pw-ai-natjh3rm.js} +3 -3
- package/dist/plugin-sdk/{qmd-manager-DSyx77KY.js → qmd-manager-CgRaTxUq.js} +1 -1
- package/dist/plugin-sdk/{query-expansion-CAI2kKLQ.js → query-expansion-BG3FgzF7.js} +14 -3
- package/dist/plugin-sdk/{replies-Fzh9PhXl.js → replies-ChYo5ZQM.js} +2 -2
- package/dist/plugin-sdk/{resolve-outbound-target-KpF3co6T.js → resolve-outbound-target-D-KGgHJZ.js} +1 -1
- package/dist/plugin-sdk/{resolve-route-CDl74O19.js → resolve-route-C8tvQm8Z.js} +1 -1
- package/dist/plugin-sdk/{retry-policy-D4uiqUOG.js → retry-policy-C6SjKjQA.js} +1 -1
- package/dist/plugin-sdk/{runtime-Bb6KRnCC.js → runtime-2B9_WKoI.js} +8 -3
- package/dist/plugin-sdk/{runtime-dependency-DLDmB94f.js → runtime-dependency-DczkvPxn.js} +2 -2
- package/dist/plugin-sdk/sat-runtime.d.ts +549 -309
- package/dist/plugin-sdk/sat-runtime.js +29 -29
- package/dist/plugin-sdk/{send-C4k4JuJN.js → send-B6KkGKZD.js} +6 -6
- package/dist/plugin-sdk/{send-D-uDKGIq.js → send-CFzGKJXo.js} +6 -6
- package/dist/plugin-sdk/{send-Du8ykP1k.js → send-DCO441U7.js} +8 -8
- package/dist/plugin-sdk/{send-5nFktR1n.js → send-gDoGA-7e.js} +5 -5
- package/dist/plugin-sdk/{send-Bfzf11VY.js → send-q9HXrSwK.js} +7 -7
- package/dist/plugin-sdk/{session-CS-Td9EM.js → session-DHD4ZKyG.js} +2 -2
- package/dist/plugin-sdk/{skill-commands-CFAlXWHk.js → skill-commands-FKEPLS7T.js} +201 -26
- package/dist/plugin-sdk/{slack-D_bbE6Yd.js → slack-BUTt8cxK.js} +39 -39
- package/dist/plugin-sdk/{slack-actions-BcMkNv1Z.js → slack-actions-DhbMcp8M.js} +11 -11
- package/dist/plugin-sdk/slack.d.ts +5 -5
- package/dist/plugin-sdk/slack.js +9 -9
- package/dist/plugin-sdk/{sticker-cache-Dqo9nRhi.js → sticker-cache-D0wHVqjt.js} +3 -3
- package/dist/plugin-sdk/{tables-bFXyRpkx.js → tables-B3jr0Pvq.js} +1 -1
- package/dist/plugin-sdk/{targets-DH7a9lKB.js → targets-CtSbTxYX.js} +1 -1
- package/dist/plugin-sdk/{telegram-actions-Cnag7JTH.js → telegram-actions-CC_qN4v0.js} +30 -30
- package/dist/plugin-sdk/telegram.d.ts +5 -5
- package/dist/plugin-sdk/telegram.js +8 -8
- package/dist/plugin-sdk/{thread-bindings.lifecycle-CgOvvdw8.js → thread-bindings.lifecycle-QbkNl-m6.js} +3 -3
- package/dist/plugin-sdk/{thread-bindings.state-CQpk44Hq.js → thread-bindings.state-D9hg_M1c.js} +2 -2
- package/dist/plugin-sdk/tmp-fased-dir-xOTGlzBE.d.ts +486 -0
- package/dist/plugin-sdk/{types-CLIvQaVg.d.ts → types-C3avNytw.d.ts} +3 -4
- package/dist/plugin-sdk/{types-DyhXnYUr.d.ts → types-D0kDG8l0.d.ts} +3 -2
- package/dist/plugin-sdk/{types-BYDL6dQj.d.ts → types-DWfbSAaM.d.ts} +2 -2
- package/dist/plugin-sdk/{types.plugin-_fbu40Re.d.ts → types.plugin-BcSBq42x.d.ts} +8 -3
- package/dist/plugin-sdk/{web-BInKQkc4.js → web-BXsJ1vqe.js} +32 -32
- package/dist/plugin-sdk/{whatsapp-actions-BtSc2CBv.js → whatsapp-actions-Bj_rOncr.js} +13 -13
- package/dist/plugin-sdk/{whatsapp-heartbeat-Bg9TuYzl.js → whatsapp-heartbeat-B1WHkJIg.js} +2 -2
- package/dist/plugin-sdk/whatsapp.d.ts +5 -5
- package/dist/plugin-sdk/whatsapp.js +12 -12
- package/dist/plugin-sdk/{wsl-C4DGFkOd.js → wsl-J-HscWxS.js} +3 -3
- package/dist/plugins/bundled-dir.js +3 -3
- package/dist/plugins/discovery.js +11 -11
- package/dist/plugins/loader.js +13 -13
- package/dist/plugins/manifest-registry.js +2 -2
- package/dist/plugins/manifest.js +4 -4
- package/dist/plugins/path-safety.js +3 -3
- package/dist/plugins/status-cache.js +9 -9
- package/dist/plugins/update.js +3 -3
- package/dist/plugins/web-search-install-catalog.js +3 -3
- package/dist/providers/refresh.js +1 -1
- package/dist/providers/registry.js +1 -1
- package/dist/secrets/apply.js +17 -17
- package/dist/secrets/audit.js +11 -11
- package/dist/secrets/runtime.js +1 -1
- package/dist/secrets/shared.js +9 -9
- package/dist/security/scan-paths.js +2 -2
- package/dist/shared/config-eval.js +2 -2
- package/dist/shared/pid-alive.js +2 -2
- package/dist/tasks/standing-orders.js +5 -5
- package/dist/tasks/task-flow-registry.js +5 -5
- package/dist/tasks/task-registry.js +5 -5
- package/dist/tasks/workflow-definitions.js +6 -6
- package/dist/telegram/token.js +5 -5
- package/dist/utils.js +5 -5
- package/dist/wallet/external-submission-ledger.js +376 -0
- package/dist/wallet/legacy-embedded-keystore.js +40 -0
- package/dist/wallet/local-socket-signer-archive.js +30 -0
- package/dist/wallet/local-socket-signer-lifecycle.js +97 -0
- package/dist/wallet/local-socket-signer-policy.js +166 -0
- package/dist/wallet/local-socket-signer-protocol.js +561 -118
- package/dist/wallet/native-signer-wallet-id.js +25 -0
- package/dist/wallet/providers/alchemy-adapter.js +3 -0
- package/dist/wallet/providers/local-socket-signer-adapter.js +382 -107
- package/dist/wallet/providers/turnkey-adapter.js +292 -34
- package/dist/wallet/providers/wallet-standard-adapter.js +100 -0
- package/dist/wallet/signer-network-admin.js +184 -0
- package/dist/wallet/signer-protocol-v2.generated.js +90 -0
- package/dist/wallet/solana-bond-signing.js +157 -69
- package/dist/wallet/solana-reviewed-transaction.js +375 -0
- package/dist/wallet/solana-swap.js +520 -64
- package/dist/wallet/solana-transaction-inspection.js +106 -4
- package/dist/wallet/solana-trigger.js +658 -320
- package/dist/wallet/turnkey-reviewed-state.js +370 -0
- package/dist/wallet/wallet-approval-auth.js +20 -31
- package/dist/wallet/wallet-atomic-state.js +53 -0
- package/dist/wallet/wallet-audit-log.js +5 -5
- package/dist/wallet/wallet-canary.js +0 -1
- package/dist/wallet/wallet-inbound-events.js +19 -14
- package/dist/wallet/wallet-observability.js +5 -5
- package/dist/wallet/wallet-policy-simulation.js +1 -1
- package/dist/wallet/wallet-policy.js +95 -41
- package/dist/wallet/wallet-provider-capabilities.js +28 -4
- package/dist/wallet/wallet-provider-registry.js +64 -51
- package/dist/wallet/wallet-provider-resolver.js +62 -63
- package/dist/wallet/wallet-runtime-config.js +31 -44
- package/dist/wallet/wallet-secrets-store.js +44 -22
- package/dist/wallet/wallet-send-approvals.js +1894 -592
- package/dist/wallet/wallet-send-execution-ledger.js +292 -0
- package/dist/wallet/wallet-settlement-links.js +17 -16
- package/dist/wallet/wallet-standard-review.js +353 -0
- package/dist/wallet/wallet-status.js +0 -9
- package/dist/wallet/wallet-task-ledger.js +16 -2
- package/dist/web/accounts.js +3 -3
- package/dist/web/auth-store.js +18 -18
- package/dist/web/auto-reply/heartbeat-runner.js +1 -1
- package/dist/web/auto-reply/monitor.js +2 -2
- package/dist/web/session.js +4 -4
- package/dist/wizard/host-security-capability.js +15 -24
- package/dist/wizard/onboarding.finalize.js +63 -184
- package/dist/wizard/onboarding.host-security.js +118 -1368
- package/dist/wizard/onboarding.js +180 -242
- package/dist/wizard/onboarding.wallet.js +323 -829
- package/extensions/acpx/package.json +1 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/fased-federation/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +18 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +2 -2
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +18 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +18 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/sat-mining/index.ts +498 -153
- package/extensions/sat-mining/package.json +1 -1
- package/extensions/sat-mining/signer-codec-schema.v1.json +374 -0
- package/extensions/sat-mining/src/audit-store.ts +50 -5
- package/extensions/sat-mining/src/claim-service.ts +85 -14
- package/extensions/sat-mining/src/config.ts +9 -0
- package/extensions/sat-mining/src/cycle-progress.ts +16 -8
- package/extensions/sat-mining/src/epoch-service.ts +9 -5
- package/extensions/sat-mining/src/gateway-runner.ts +21 -1
- package/extensions/sat-mining/src/protocol-contract.ts +45 -1
- package/extensions/sat-mining/src/recovery-service.ts +312 -0
- package/extensions/sat-mining/src/round-watcher.ts +391 -81
- package/extensions/sat-mining/src/rpc-read.ts +330 -89
- package/extensions/sat-mining/src/runtime.ts +14 -0
- package/extensions/sat-mining/src/sat-bond-layout.ts +6 -0
- package/extensions/sat-mining/src/signer-codec-manifest.ts +429 -0
- package/extensions/sat-mining/src/solana-submit.ts +1199 -428
- package/extensions/sat-mining/src/submission-ledger.ts +433 -0
- package/extensions/signal/package.json +1 -1
- package/extensions/synology-chat/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +18 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +18 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +18 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +18 -0
- package/extensions/zalouser/package.json +1 -1
- package/install.sh +4955 -0
- package/package.json +24 -5
- package/scripts/fased-host-updater.mjs +1421 -0
- package/scripts/fased-host-updaterctl.mjs +186 -0
- package/scripts/fased-managed-service.sh +36 -0
- package/scripts/fased-managed-updater.mjs +3065 -156
- package/scripts/fased-signer-enroll-hosting.sh +275 -0
- package/scripts/fased-signer-network-hosting.sh +143 -0
- package/scripts/fased-signer-owner-policy.mjs +1430 -0
- package/scripts/fased-signer-policy-hosting.sh +50 -0
- package/scripts/fased-signer-policy-local.sh +26 -0
- package/scripts/hosted-release-manifest.mjs +185 -0
- package/scripts/install-fased-signerd.sh +217 -0
- package/scripts/install-managed-runtime.mjs +212 -10
- package/scripts/managed-runtime-layout.mjs +75 -3
- package/scripts/migrate-hosted-signer-v2.mjs +58 -0
- package/scripts/start-managed.sh +169 -360
- package/dist/control-ui/assets/mining-commit-R-6nu87p.js +0 -2
- package/dist/plugin-sdk/tmp-fased-dir-BdFQvrvw.d.ts +0 -77
- package/dist/wallet/local-socket-signer-broker.js +0 -224
- package/dist/wallet/local-socket-signer-custody.js +0 -42
- package/dist/wallet/providers/embedded-keystore-adapter.js +0 -402
- package/dist/wallet/providers/privy-adapter.js +0 -80
- package/dist/wallet/wallet-custody.js +0 -1836
|
@@ -6,35 +6,41 @@ import { retryAsync } from "../infra/retry.js";
|
|
|
6
6
|
import { safeEqualSecret } from "../security/secret-equal.js";
|
|
7
7
|
import { isPrivateOrLoopbackAddress, isTrustedProxyAddress, resolveClientIp } from "./net.js";
|
|
8
8
|
import { callGatewayScoped } from "./call.js";
|
|
9
|
-
import {
|
|
9
|
+
import { LEGACY_EMBEDDED_KEYSTORE_MIGRATION_MESSAGE, LegacyEmbeddedKeystoreMigrationRequiredError } from "../wallet/legacy-embedded-keystore.js";
|
|
10
|
+
import { resolveLocalSignerSocketPath, resolveWalletRuntimeConfig } from "../wallet/wallet-runtime-config.js";
|
|
10
11
|
import { WALLET_PROVIDER_IDS, checkNamedWalletDeletionSafety, deleteNamedWallet, normalizeWalletUserRole, readWalletProviderRegistry, resolveWalletSelection, resolveWalletUserRole, setAgentWalletAssignment, setDefaultWallet, setNamedWalletRole, setWalletProviderEnabled, upsertNamedWallet } from "../wallet/wallet-provider-registry.js";
|
|
11
12
|
import { resolveAgentPublicOrigin, resolveFederationBaseUrl, resolveFederationBondWalletId, resolveFederationHandle } from "../federation/runtime.js";
|
|
12
13
|
import { isValidSolanaAddress } from "../wallet/solana-address.js";
|
|
13
14
|
import { fetchSolanaMintInfoViaRpc, fetchSolanaNativeBalanceViaRpc, fetchSolanaTokenBalanceViaRpc, fetchSolanaWalletAssetsViaRpc, invalidateSolanaAssetRpcCaches, summarizeSolanaAssetRpcMetrics } from "../wallet/solana-assets.js";
|
|
14
15
|
import { tryResolveSatRuntimeIds } from "../config/sat-runtime-ids.js";
|
|
15
|
-
import { resolveWalletPolicyConfig, resolveWalletRecurringTransferPolicy
|
|
16
|
+
import { commitWalletPolicyConfigUpdate, prepareWalletPolicyConfigUpdate, resolveWalletPolicyConfig, resolveWalletRecurringTransferPolicy } from "../wallet/wallet-policy.js";
|
|
16
17
|
import { incrementWalletObservabilityCounter, readWalletObservabilitySnapshot } from "../wallet/wallet-observability.js";
|
|
17
18
|
import { buildWalletProviderCapabilityMatrix, providerSupportsChainOperation } from "../wallet/wallet-provider-capabilities.js";
|
|
19
|
+
import { walletDiagnosticErrorMessage } from "../wallet/wallet-redaction.js";
|
|
20
|
+
import { LocalSocketSignerAdapter } from "../wallet/providers/local-socket-signer-adapter.js";
|
|
18
21
|
import { deleteWalletProviderSecret, readWalletProviderSecretStatus, readWalletRpcSecretStatus, saveWalletProviderSecret } from "../wallet/wallet-secrets-store.js";
|
|
19
22
|
import { createWalletProviderAdapter, resolveScopedRpcUrlForWallet, resolveWalletProviderId } from "../wallet/wallet-provider-resolver.js";
|
|
20
|
-
import { beginWalletApprovalAssertion, beginWalletPasskeyRegistration, consumeWalletApprovalGrant, createWalletApprovalChallenge, finishWalletApprovalAssertion, finishWalletPasskeyRegistration, listWalletPasskeys, readWalletApprovalAuthSnapshot, removeWalletPasskey, resolveWalletApprovalAuthMode, resolveWalletApprovalChallengeTtlSeconds, resolveWalletApprovalGrantTtlSeconds } from "../wallet/wallet-approval-auth.js";
|
|
21
23
|
import { appendWalletAuditEntry, readWalletAuditEntries } from "../wallet/wallet-audit-log.js";
|
|
22
|
-
import { activateWalletCustodyUnlockSession, enrollWalletCustodyDevice, listSplitKeyWalletCustodyStatuses, lockWalletCustodyUnlockSessions, readWalletCustodyStatus, recoverWalletCustodyCeremony, refreshWalletCustodyUnlockSession, revokeWalletCustodyDevice } from "../wallet/wallet-custody.js";
|
|
23
24
|
import { simulateWalletPolicy } from "../wallet/wallet-policy-simulation.js";
|
|
24
|
-
import { approveWalletSendRequest, createOrExecuteWalletSend, listWalletSendApprovalRequests, rejectWalletSendRequest, sanitizeWalletSendApprovalPayload, sanitizeWalletSendApprovalRequest } from "../wallet/wallet-send-approvals.js";
|
|
25
|
+
import { approveWalletSendRequest, createOrExecuteWalletSend, getWalletSendApprovalRequest, listWalletSendApprovalRequests, markWalletSendRequestBroadcastUnknown, markWalletSendRequestExecutedExternally, rejectWalletSendRequest, sanitizeWalletSendApprovalPayload, sanitizeWalletSendApprovalRequest, signerReviewBindingMatchesWalletApprovalPayload, signerReviewMatchesWalletApprovalPayload } from "../wallet/wallet-send-approvals.js";
|
|
25
26
|
import { searchSolanaTokens } from "../wallet/solana-token-resolver.js";
|
|
26
27
|
import { normalizeCanvasScopedUrl } from "./canvas-capability.js";
|
|
27
28
|
import { A2UI_PATH, CANVAS_HOST_PATH, CANVAS_WS_PATH, handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
|
28
29
|
import { resolveFederationBondWallet } from "../wallet/solana-bond-signing.js";
|
|
29
|
-
import { createAndSubmitFederationBondProof, loadPersistedFederationBondProof } from "../federation/auto-connect.js";
|
|
30
|
+
import { createAndSubmitFederationBondProof, loadPersistedFederationBondProof, submitFederationBondProof } from "../federation/auto-connect.js";
|
|
31
|
+
import { beginWalletApprovalAssertion, beginWalletPasskeyRegistration, consumeWalletApprovalGrant, createWalletApprovalChallenge, finishWalletApprovalAssertion, finishWalletPasskeyRegistration, listWalletPasskeys, readWalletApprovalAuthSnapshot, removeWalletPasskey, resolveWalletApprovalAuthMode, resolveWalletApprovalChallengeTtlSeconds, resolveWalletApprovalGrantTtlSeconds } from "../wallet/wallet-approval-auth.js";
|
|
30
32
|
import { readWalletStatusSnapshot } from "../wallet/wallet-status.js";
|
|
31
|
-
import { inspectSatBondPosition, inspectSatBondStakingDistributor, inspectSatBondStakingPosition,
|
|
32
|
-
import {
|
|
33
|
+
import { inspectSatBondPosition, inspectSatBondStakingDistributor, inspectSatBondStakingPosition, inspectSatChainSlot, invalidateSatReadCaches } from "../extensions/sat-mining/src/rpc-read.js";
|
|
34
|
+
import { digestSatSubmissionIntent } from "../extensions/sat-mining/src/submission-ledger.js";
|
|
35
|
+
import { runWithSatSubmissionWorkflow, submitSatCancelBondUnlock, submitSatClaimBondStakingRewards, submitSatFinalizeBondUnlock, submitSatIncreaseBondPosition, submitSatOpenBondPosition, submitSatRequestBondUnlock, submitSatSyncBondStakingPosition, submitSatSyncBondStakingRewards } from "../extensions/sat-mining/src/solana-submit.js";
|
|
33
36
|
import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
|
37
|
+
import { resolveNativeSignerWalletId } from "../wallet/native-signer-wallet-id.js";
|
|
34
38
|
import { listWalletInboundEvents, pollWalletInboundEvents, reconcileWalletInboundEvents, recordWalletInboundWebhookEvent } from "../wallet/wallet-inbound-events.js";
|
|
35
|
-
import {
|
|
39
|
+
import { walletSetupCommand } from "../commands/wallet.js";
|
|
36
40
|
import { getDiagnosticStabilitySnapshot } from "../logging/diagnostic-stability.js";
|
|
37
41
|
import { resolveManagedFederationPublicUrl } from "../managed/federation.js";
|
|
42
|
+
import { LocalSignerPolicyAdminRequiredError, buildLocalSignerPolicyTightening, localSignerPolicyState } from "../wallet/local-socket-signer-policy.js";
|
|
43
|
+
import { executeWalletStandardReview, prepareWalletStandardReview, readWalletStandardReviewTxHash } from "../wallet/wallet-standard-review.js";
|
|
38
44
|
import { createA2aHandler } from "./a2a-http.js";
|
|
39
45
|
import { CONTROL_UI_BOOT_CHECK_PATH, resolveControlUiBootCheck } from "./control-ui-boot-check.js";
|
|
40
46
|
import { normalizePublicHost, resolveControlUiPublicHost } from "./control-ui-login.js";
|
|
@@ -48,10 +54,10 @@ import { sendGatewayAuthFailure, setDefaultSecurityHeaders } from "./http-common
|
|
|
48
54
|
import { getBearerToken, getHeader } from "./http-utils.js";
|
|
49
55
|
import { handleOpenAiHttpRequest } from "./openai-http.js";
|
|
50
56
|
import { buildGatewayProbePayload } from "./probe-payload.js";
|
|
51
|
-
import { canonicalizePathVariant } from "./security-path.js";
|
|
57
|
+
import { canonicalizePathVariant, isPathProtectedByPrefixes } from "./security-path.js";
|
|
52
58
|
import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
|
|
53
|
-
import { authorizeGatewayConnect, isLocalDirectRequest } from "./auth.js";
|
|
54
|
-
import
|
|
59
|
+
import { authorizeGatewayConnect, isDirectLoopbackRequest, isLocalDirectRequest } from "./auth.js";
|
|
60
|
+
import syncFs from "node:fs";
|
|
55
61
|
import path from "node:path";
|
|
56
62
|
import { createServer } from "node:http";
|
|
57
63
|
import { createServer as createServer$1 } from "node:https";
|
|
@@ -60,6 +66,8 @@ import { createServer as createServer$1 } from "node:https";
|
|
|
60
66
|
const HOOK_AUTH_FAILURE_LIMIT = 20;
|
|
61
67
|
const HOOK_AUTH_FAILURE_WINDOW_MS = 6e4;
|
|
62
68
|
const HOOK_AUTH_FAILURE_TRACK_MAX = 2048;
|
|
69
|
+
const FEDERATION_HTTP_ROUTE_PREFIXES = ["/api/federation"];
|
|
70
|
+
const SIGNED_FEDERATION_INBOUND_ROUTES = new Set(["/api/federation/marketplace/orders", "/api/federation/marketplace/deliveries"]);
|
|
63
71
|
const CONTROL_UI_SETTINGS_STORAGE_KEY = "fased.control.settings.v1";
|
|
64
72
|
const CONTROL_UI_TOKEN_LOCAL_STORAGE_KEY = "fased.control.token.local.v1";
|
|
65
73
|
const CONTROL_UI_TOKEN_SESSION_STORAGE_KEY = "fased.control.token.session.v1";
|
|
@@ -575,7 +583,7 @@ function parseWalletSettingsPatchInput(input) {
|
|
|
575
583
|
return {
|
|
576
584
|
walletId: toOptionalString(payload.walletId),
|
|
577
585
|
policyTemplate,
|
|
578
|
-
providerId: payload.providerId === "
|
|
586
|
+
providerId: payload.providerId === "local-socket-signer" || payload.providerId === "alchemy" || payload.providerId === "turnkey" || payload.providerId === "wallet-standard" ? payload.providerId : undefined,
|
|
579
587
|
executionMode: payload.executionMode === "manual" || payload.executionMode === "autonomous" ? payload.executionMode : undefined,
|
|
580
588
|
approvalAuthMode: payload.approvalAuthMode === "none" || payload.approvalAuthMode === "webauthn" ? payload.approvalAuthMode : undefined,
|
|
581
589
|
approvalChallengeTtlSeconds: toOptionalInt(payload.approvalChallengeTtlSeconds),
|
|
@@ -612,14 +620,56 @@ function resolveProviderCredentialStatus(params) {
|
|
|
612
620
|
const env = params.env ?? process.env;
|
|
613
621
|
return readWalletProviderSecretStatus(params.providerId, env);
|
|
614
622
|
}
|
|
615
|
-
function
|
|
623
|
+
function hasTurnkeyPolicyCredentialsConfigured(env = process.env) {
|
|
616
624
|
const status = readWalletProviderSecretStatus("turnkey", env);
|
|
617
|
-
|
|
618
|
-
|
|
625
|
+
const configuredFields = new Set(status.fields);
|
|
626
|
+
const hasApiPublicKey = configuredFields.has("apiPublicKey") || Boolean(String(env.FASED_WALLET_TURNKEY_API_PUBLIC_KEY ?? "").trim());
|
|
627
|
+
const hasApiPrivateKey = configuredFields.has("apiPrivateKey") || Boolean(String(env.FASED_WALLET_TURNKEY_API_PRIVATE_KEY ?? "").trim());
|
|
628
|
+
const hasOrganizationId = configuredFields.has("organizationId") || Boolean(String(env.FASED_WALLET_TURNKEY_ORGANIZATION_ID ?? "").trim());
|
|
629
|
+
const hasPolicyId = configuredFields.has("policyId") || Boolean(String(env.FASED_WALLET_TURNKEY_POLICY_ID ?? "").trim());
|
|
630
|
+
const hasRpcUrl = configuredFields.has("rpcUrl") || Boolean(String(env.FASED_WALLET_TURNKEY_RPC_URL ?? "").trim()) || Boolean(String(env.FASED_WALLET_SOLANA_RPC_URL ?? "").trim());
|
|
631
|
+
return hasApiPublicKey && hasApiPrivateKey && hasOrganizationId && hasPolicyId && hasRpcUrl;
|
|
632
|
+
}
|
|
633
|
+
async function readWalletSettingsSignerPolicy(params) {
|
|
634
|
+
const walletId = params.walletId?.trim();
|
|
635
|
+
if (!walletId) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
const effectiveEnv = {
|
|
639
|
+
...params.env,
|
|
640
|
+
...params.cfg.env?.vars
|
|
641
|
+
};
|
|
642
|
+
const wallet = readWalletProviderRegistry(effectiveEnv).wallets.find((entry) => entry.id === walletId);
|
|
643
|
+
if (wallet?.providerId !== "local-socket-signer") {
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
const signerWalletId = resolveNativeSignerWalletId(wallet);
|
|
648
|
+
const policy = params.acknowledged?.walletId === signerWalletId ? params.acknowledged : await new LocalSocketSignerAdapter(resolveLocalSignerSocketPath(effectiveEnv)).getSignerPolicy(signerWalletId);
|
|
649
|
+
const state = localSignerPolicyState(policy);
|
|
650
|
+
return {
|
|
651
|
+
state,
|
|
652
|
+
walletId: policy.walletId,
|
|
653
|
+
role: policy.role,
|
|
654
|
+
version: policy.version,
|
|
655
|
+
hash: policy.hash,
|
|
656
|
+
operations: [...policy.operations],
|
|
657
|
+
programs: [...policy.programs],
|
|
658
|
+
assets: policy.assets.map((asset) => ({
|
|
659
|
+
...asset,
|
|
660
|
+
destinations: [...asset.destinations]
|
|
661
|
+
})),
|
|
662
|
+
...state === "locked" ? { guidance: "Signer policy is deny-all. Install an owner-reviewed policy through the native signer control socket; the Gateway can only tighten it." } : {}
|
|
663
|
+
};
|
|
664
|
+
} catch {
|
|
665
|
+
return {
|
|
666
|
+
state: "unavailable",
|
|
667
|
+
walletId,
|
|
668
|
+
guidance: "Signer policy could not be verified. No policy change will be shown as saved until the signer returns its exact durable version and hash."
|
|
669
|
+
};
|
|
619
670
|
}
|
|
620
|
-
return Boolean(String(env.FASED_WALLET_TURNKEY_STAMP ?? "").trim());
|
|
621
671
|
}
|
|
622
|
-
function buildWalletSettingsPayload(cfg, walletId, env = process.env) {
|
|
672
|
+
async function buildWalletSettingsPayload(cfg, walletId, env = process.env, acknowledgedSignerPolicy) {
|
|
623
673
|
const runtimeWallet = resolveWalletRuntimeConfig(cfg, env);
|
|
624
674
|
const wallet = resolveWalletPolicyConfig(cfg, env, walletId);
|
|
625
675
|
const providerId = resolveWalletProviderId(cfg, env);
|
|
@@ -634,13 +684,19 @@ function buildWalletSettingsPayload(cfg, walletId, env = process.env) {
|
|
|
634
684
|
wallet,
|
|
635
685
|
env
|
|
636
686
|
});
|
|
687
|
+
const signerPolicy = await readWalletSettingsSignerPolicy({
|
|
688
|
+
cfg,
|
|
689
|
+
walletId,
|
|
690
|
+
env,
|
|
691
|
+
acknowledged: acknowledgedSignerPolicy
|
|
692
|
+
});
|
|
637
693
|
return {
|
|
638
694
|
managedMode: isManagedGatewayMode(env),
|
|
639
695
|
provider: {
|
|
640
696
|
id: providerId,
|
|
641
|
-
operationsImplemented: providerAdapter.capabilities.supportsPrepare && providerAdapter.capabilities.supportsSend,
|
|
697
|
+
operationsImplemented: providerAdapter.capabilities.supportsPrepare && providerAdapter.capabilities.supportsSend || providerCapabilities.signing.interactiveSend,
|
|
642
698
|
supportedChains: providerAdapter.capabilities.supportedChains,
|
|
643
|
-
requiresCredentials:
|
|
699
|
+
requiresCredentials: providerCapabilities.requiresCredentials,
|
|
644
700
|
capabilities: providerCapabilities
|
|
645
701
|
},
|
|
646
702
|
runtime: {
|
|
@@ -678,6 +734,7 @@ function buildWalletSettingsPayload(cfg, walletId, env = process.env) {
|
|
|
678
734
|
walletId
|
|
679
735
|
})
|
|
680
736
|
},
|
|
737
|
+
...signerPolicy ? { signerPolicy } : {},
|
|
681
738
|
toolAccess: {
|
|
682
739
|
mode: wallet.toolAccess.mode,
|
|
683
740
|
allowAgents: wallet.toolAccess.allowAgents
|
|
@@ -693,6 +750,7 @@ function parseWalletProviderId(value) {
|
|
|
693
750
|
case "local-socket-signer":
|
|
694
751
|
case "alchemy":
|
|
695
752
|
case "turnkey":
|
|
753
|
+
case "wallet-standard":
|
|
696
754
|
case "privy": return value;
|
|
697
755
|
default: return null;
|
|
698
756
|
}
|
|
@@ -813,7 +871,7 @@ function resolveWalletRpcUrlFromEnv(env, _chain, walletId) {
|
|
|
813
871
|
const perWalletKey = suffix ? `FASED_WALLET_SOLANA_RPC_URL__${suffix}` : "";
|
|
814
872
|
const perChainKey = "FASED_WALLET_SOLANA_RPC_URL";
|
|
815
873
|
const scopedOrChain = (perWalletKey ? String(env[perWalletKey] ?? "").trim() : "") || String(env[perChainKey] ?? "").trim();
|
|
816
|
-
return scopedOrChain || String(env.
|
|
874
|
+
return scopedOrChain || String(env.FASED_WALLET_RPC_URL ?? "").trim();
|
|
817
875
|
}
|
|
818
876
|
function inferLocalSignerCreateChain(params) {
|
|
819
877
|
if (params.payloadChain === "solana") {
|
|
@@ -835,13 +893,6 @@ const silentWalletSetupRuntime = {
|
|
|
835
893
|
throw new Error(`wallet setup exited with code ${code}`);
|
|
836
894
|
}
|
|
837
895
|
};
|
|
838
|
-
function listSplitKeyWalletsBlockingPasskeyRemoval(cfg, env = process.env) {
|
|
839
|
-
return listSplitKeyWalletCustodyStatuses({
|
|
840
|
-
wallet: resolveWalletRuntimeConfig(cfg, env),
|
|
841
|
-
cfg,
|
|
842
|
-
env
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
896
|
const walletGatewayRpcStartedAt = new Date().toISOString();
|
|
846
897
|
const walletGatewayRpcMetrics = new Map();
|
|
847
898
|
function recordWalletGatewayRpcMethod(method, outcome) {
|
|
@@ -878,11 +929,6 @@ function summarizeWalletGatewayRpcMetrics() {
|
|
|
878
929
|
})).toSorted((left, right) => left.method.localeCompare(right.method))
|
|
879
930
|
};
|
|
880
931
|
}
|
|
881
|
-
function formatSplitKeyPasskeyBlockMessage(splitKeyWallets) {
|
|
882
|
-
const walletList = splitKeyWallets.map((entry) => entry.target.walletId).filter(Boolean).slice(0, 4).join(", ");
|
|
883
|
-
const suffix = walletList.length > 0 ? ` Split-key wallet${splitKeyWallets.length === 1 ? "" : "s"}: ${walletList}.` : "";
|
|
884
|
-
return `Cannot remove the last Wallet Control Passkey while wallet security is enabled. Disable wallet security on every secured wallet before removing this passkey.${suffix}`;
|
|
885
|
-
}
|
|
886
932
|
async function fetchSolanaLamportsViaRpc(params) {
|
|
887
933
|
const rpcUrl = String(params.rpcUrl ?? "").trim();
|
|
888
934
|
const address = String(params.address ?? "").trim();
|
|
@@ -998,6 +1044,12 @@ async function buildWalletProviderPayload(params) {
|
|
|
998
1044
|
providerId,
|
|
999
1045
|
supportedChains: [],
|
|
1000
1046
|
integrationMode: "native",
|
|
1047
|
+
signingLocation: "unavailable",
|
|
1048
|
+
signing: {
|
|
1049
|
+
transaction: false,
|
|
1050
|
+
message: false,
|
|
1051
|
+
interactiveSend: false
|
|
1052
|
+
},
|
|
1001
1053
|
operations: {
|
|
1002
1054
|
createWallet: false,
|
|
1003
1055
|
receiveAddress: false,
|
|
@@ -1015,7 +1067,7 @@ async function buildWalletProviderPayload(params) {
|
|
|
1015
1067
|
prepare: false,
|
|
1016
1068
|
send: false
|
|
1017
1069
|
} },
|
|
1018
|
-
requiresCredentials: providerId !== "
|
|
1070
|
+
requiresCredentials: providerId !== "local-socket-signer" && providerId !== "wallet-standard",
|
|
1019
1071
|
requiresRpcSecret: false
|
|
1020
1072
|
};
|
|
1021
1073
|
let health = {
|
|
@@ -1029,8 +1081,8 @@ async function buildWalletProviderPayload(params) {
|
|
|
1029
1081
|
env,
|
|
1030
1082
|
providerIdOverride: providerId
|
|
1031
1083
|
});
|
|
1032
|
-
operationsImplemented = adapter.capabilities.supportsPrepare && adapter.capabilities.supportsSend;
|
|
1033
1084
|
capabilities = buildWalletProviderCapabilityMatrix(adapter);
|
|
1085
|
+
operationsImplemented = providerId === "local-socket-signer" || adapter.capabilities.supportsPrepare && adapter.capabilities.supportsSend || capabilities.signing.interactiveSend;
|
|
1034
1086
|
const healthResult = await withWalletProbeTimeout(adapter.health(), probeTimeoutMs, `provider.health:${providerId}`);
|
|
1035
1087
|
health = {
|
|
1036
1088
|
ok: healthResult.ok,
|
|
@@ -1049,7 +1101,7 @@ async function buildWalletProviderPayload(params) {
|
|
|
1049
1101
|
});
|
|
1050
1102
|
return {
|
|
1051
1103
|
id: providerId,
|
|
1052
|
-
enabled: registry.providers[providerId]?.enabled ??
|
|
1104
|
+
enabled: registry.providers[providerId]?.enabled ?? false,
|
|
1053
1105
|
label: registry.providers[providerId]?.label,
|
|
1054
1106
|
isDefault: providerId === defaultProviderId,
|
|
1055
1107
|
operationsImplemented,
|
|
@@ -1309,9 +1361,17 @@ function parseFederationBondMutationInput(input) {
|
|
|
1309
1361
|
walletId: typeof body.walletId === "string" ? body.walletId.trim() || undefined : undefined,
|
|
1310
1362
|
amountSat: typeof body.amountSat === "string" ? body.amountSat.trim() || undefined : undefined,
|
|
1311
1363
|
tier: body.tier === "basic-bond" || body.tier === "operator-bond" ? body.tier : undefined,
|
|
1312
|
-
autoSubmitProof: typeof body.autoSubmitProof === "boolean" ? body.autoSubmitProof : undefined
|
|
1364
|
+
autoSubmitProof: typeof body.autoSubmitProof === "boolean" ? body.autoSubmitProof : undefined,
|
|
1365
|
+
idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey.trim() || undefined : undefined
|
|
1313
1366
|
};
|
|
1314
1367
|
}
|
|
1368
|
+
function normalizeFederationBondIdempotencyKey(value) {
|
|
1369
|
+
const normalized = value.trim();
|
|
1370
|
+
if (!normalized || normalized.length > 160 || /[^\x20-\x7e]/u.test(normalized)) {
|
|
1371
|
+
throw new Error("idempotency key must contain 1-160 printable characters");
|
|
1372
|
+
}
|
|
1373
|
+
return normalized;
|
|
1374
|
+
}
|
|
1315
1375
|
function validateFederationBondVaultWallet(cfg, walletId) {
|
|
1316
1376
|
const registry = readWalletProviderRegistry(process.env);
|
|
1317
1377
|
const wallet = registry.wallets.find((entry) => entry.id === walletId);
|
|
@@ -1502,19 +1562,6 @@ function parseSatAmountToRawNumber(amountSatInput) {
|
|
|
1502
1562
|
safeInteger: Number(raw)
|
|
1503
1563
|
};
|
|
1504
1564
|
}
|
|
1505
|
-
function parseSatRawToSafeNumber(rawInput) {
|
|
1506
|
-
const raw = BigInt(rawInput || "0");
|
|
1507
|
-
if (raw <= 0n) {
|
|
1508
|
-
throw new Error("amountRaw must be greater than zero");
|
|
1509
|
-
}
|
|
1510
|
-
if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
1511
|
-
throw new Error("amountRaw is too large for the current gateway bond action path");
|
|
1512
|
-
}
|
|
1513
|
-
return {
|
|
1514
|
-
amountRaw: raw.toString(),
|
|
1515
|
-
safeInteger: Number(raw)
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1518
1565
|
async function runFederationBondProof(params) {
|
|
1519
1566
|
const bondView = params.liveBond ?? await resolveFederationBondWallet({
|
|
1520
1567
|
env: process.env,
|
|
@@ -1524,6 +1571,17 @@ async function runFederationBondProof(params) {
|
|
|
1524
1571
|
if (!bondView || bondView.statusLabel !== "active") {
|
|
1525
1572
|
throw new Error("active SAT bond not found for proof submission");
|
|
1526
1573
|
}
|
|
1574
|
+
const pendingProof = await loadPersistedFederationBondProof(process.env);
|
|
1575
|
+
if (pendingProof && pendingProof.walletId === params.walletId && pendingProof.bondId === bondView.address && pendingProof.bondAmountRaw === bondView.amountRaw && pendingProof.bondTier === bondView.tierLabel && Date.parse(pendingProof.expiresAt) > Date.now()) {
|
|
1576
|
+
if (pendingProof.verifiedAt) {
|
|
1577
|
+
return { proof: pendingProof };
|
|
1578
|
+
}
|
|
1579
|
+
return await submitFederationBondProof({
|
|
1580
|
+
env: process.env,
|
|
1581
|
+
cfg: params.cfg,
|
|
1582
|
+
proof: pendingProof
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1527
1585
|
return await createAndSubmitFederationBondProof({
|
|
1528
1586
|
env: process.env,
|
|
1529
1587
|
cfg: params.cfg,
|
|
@@ -2102,7 +2160,7 @@ async function readLocalFederationStatus(env = process.env, fetchImpl = fetch) {
|
|
|
2102
2160
|
const sourcePath = path.join(resolveStateDir(env), "federation", "access-token.json");
|
|
2103
2161
|
const managed = (env.FASED_GATEWAY_MODE ?? "").trim().toLowerCase() === "managed";
|
|
2104
2162
|
const configured = resolveConfiguredFederationStatus(env);
|
|
2105
|
-
if (!
|
|
2163
|
+
if (!syncFs.existsSync(sourcePath)) {
|
|
2106
2164
|
return {
|
|
2107
2165
|
managed,
|
|
2108
2166
|
sourcePath,
|
|
@@ -2114,7 +2172,7 @@ async function readLocalFederationStatus(env = process.env, fetchImpl = fetch) {
|
|
|
2114
2172
|
}
|
|
2115
2173
|
let rawText = "";
|
|
2116
2174
|
try {
|
|
2117
|
-
rawText =
|
|
2175
|
+
rawText = syncFs.readFileSync(sourcePath, "utf-8");
|
|
2118
2176
|
} catch {
|
|
2119
2177
|
return {
|
|
2120
2178
|
managed,
|
|
@@ -2163,7 +2221,7 @@ async function readLocalFederationStatus(env = process.env, fetchImpl = fetch) {
|
|
|
2163
2221
|
const lifecycle = Number.isFinite(expiresAtMs) ? expiresAtMs > Date.now() ? "active" : "expired" : "invalid";
|
|
2164
2222
|
let fileUpdatedAt;
|
|
2165
2223
|
try {
|
|
2166
|
-
fileUpdatedAt =
|
|
2224
|
+
fileUpdatedAt = syncFs.statSync(sourcePath).mtime.toISOString();
|
|
2167
2225
|
} catch {
|
|
2168
2226
|
fileUpdatedAt = undefined;
|
|
2169
2227
|
}
|
|
@@ -2318,6 +2376,7 @@ async function readLocalFederationStatus(env = process.env, fetchImpl = fetch) {
|
|
|
2318
2376
|
totalActiveStakeRaw: liveBondStakingDistributor?.totalActiveStakeRaw,
|
|
2319
2377
|
rewardIndexFp: liveBondStakingDistributor?.rewardIndexFp,
|
|
2320
2378
|
observedRewardVaultRaw: liveBondStakingDistributor?.observedRewardVaultRaw,
|
|
2379
|
+
unallocatedRewardRaw: liveBondStakingDistributor?.unallocatedRewardRaw,
|
|
2321
2380
|
rewardVaultBalanceRaw: liveBondStakingDistributor?.rewardVaultBalanceRaw,
|
|
2322
2381
|
lastSyncedSlot: liveBondStakingDistributor?.lastSyncedSlot,
|
|
2323
2382
|
mintMatchesRuntime: liveBondStakingDistributor?.mintMatchesRuntime,
|
|
@@ -2386,6 +2445,9 @@ async function readLocalFederationStatus(env = process.env, fetchImpl = fetch) {
|
|
|
2386
2445
|
function isCanvasPath(pathname) {
|
|
2387
2446
|
return pathname === A2UI_PATH || pathname.startsWith(`${A2UI_PATH}/`) || pathname === CANVAS_HOST_PATH || pathname.startsWith(`${CANVAS_HOST_PATH}/`) || pathname === CANVAS_WS_PATH;
|
|
2388
2447
|
}
|
|
2448
|
+
function isSignedFederationInboundRequest(req) {
|
|
2449
|
+
return req.method === "POST" && SIGNED_FEDERATION_INBOUND_ROUTES.has(req.url ?? "");
|
|
2450
|
+
}
|
|
2389
2451
|
function hasAuthorizedWsClientForIp(clients, clientIp) {
|
|
2390
2452
|
for (const client of clients) {
|
|
2391
2453
|
if (client.clientIp && client.clientIp === clientIp) {
|
|
@@ -2999,8 +3061,10 @@ function createGatewayHttpServer(opts) {
|
|
|
2999
3061
|
});
|
|
3000
3062
|
};
|
|
3001
3063
|
const isRemoteHost = host ? !isLocalHostName(host) : true;
|
|
3064
|
+
const tailscaleProxyConfigured = resolvedAuth.allowTailscale || (configSnapshot.gateway?.tailscale?.mode ?? "off") !== "off";
|
|
3065
|
+
const directLocalRequest = !tailscaleProxyConfigured && isDirectLoopbackRequest(req, trustedProxies);
|
|
3002
3066
|
const ensureWalletApiAuthorized = async () => {
|
|
3003
|
-
if (
|
|
3067
|
+
if (directLocalRequest) {
|
|
3004
3068
|
return true;
|
|
3005
3069
|
}
|
|
3006
3070
|
let authorized = false;
|
|
@@ -3387,8 +3451,7 @@ function createGatewayHttpServer(opts) {
|
|
|
3387
3451
|
});
|
|
3388
3452
|
return;
|
|
3389
3453
|
}
|
|
3390
|
-
|
|
3391
|
-
if (isRemoteHost) {
|
|
3454
|
+
if (!directLocalRequest) {
|
|
3392
3455
|
let authorized = false;
|
|
3393
3456
|
const bearer = getBearerToken(req);
|
|
3394
3457
|
if (controlUiLogin && host) {
|
|
@@ -3542,6 +3605,21 @@ function createGatewayHttpServer(opts) {
|
|
|
3542
3605
|
return;
|
|
3543
3606
|
}
|
|
3544
3607
|
const parsed = parseFederationBondMutationInput(body.value);
|
|
3608
|
+
const headerIdempotencyKey = Array.isArray(req.headers["idempotency-key"]) ? req.headers["idempotency-key"][0] : req.headers["idempotency-key"];
|
|
3609
|
+
let idempotencyKey;
|
|
3610
|
+
try {
|
|
3611
|
+
idempotencyKey = normalizeFederationBondIdempotencyKey(parsed.idempotencyKey ?? (typeof headerIdempotencyKey === "string" && headerIdempotencyKey.trim() ? headerIdempotencyKey : `derived:${digestSatSubmissionIntent(body.value)}`));
|
|
3612
|
+
} catch (error) {
|
|
3613
|
+
sendLoginResponse(400, {
|
|
3614
|
+
ok: false,
|
|
3615
|
+
error: {
|
|
3616
|
+
code: "invalid_idempotency_key",
|
|
3617
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3618
|
+
}
|
|
3619
|
+
});
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
const runBondSubmission = async (step, task) => await runWithSatSubmissionWorkflow(`http:${requestPath}:${idempotencyKey}:${step}`, task);
|
|
3545
3623
|
const cfg = loadConfig();
|
|
3546
3624
|
const walletId = parsed.walletId ?? resolveFederationBondDefaultWalletId(cfg);
|
|
3547
3625
|
if (!walletId) {
|
|
@@ -3585,8 +3663,8 @@ function createGatewayHttpServer(opts) {
|
|
|
3585
3663
|
return;
|
|
3586
3664
|
}
|
|
3587
3665
|
try {
|
|
3588
|
-
await retrySolanaRateLimit("sync bond staking rewards", async () => await submitSatSyncBondStakingRewards(bondCfg));
|
|
3589
|
-
await retrySolanaRateLimit("sync bond staking position", async () => await submitSatSyncBondStakingPosition(bondCfg));
|
|
3666
|
+
await retrySolanaRateLimit("sync bond staking rewards", async () => await runBondSubmission("sync-staking-rewards", async () => await submitSatSyncBondStakingRewards(bondCfg)));
|
|
3667
|
+
await retrySolanaRateLimit("sync bond staking position", async () => await runBondSubmission("sync-staking-position", async () => await submitSatSyncBondStakingPosition(bondCfg)));
|
|
3590
3668
|
invalidateSatReadCaches({ preserveStable: true });
|
|
3591
3669
|
} catch (error) {
|
|
3592
3670
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3597,7 +3675,7 @@ function createGatewayHttpServer(opts) {
|
|
|
3597
3675
|
if (requestPath === "/api/federation/bond/open") {
|
|
3598
3676
|
const amountSat = parsed.amountSat ?? resolveDefaultBondAmountSat(parsed.tier);
|
|
3599
3677
|
const amount = parseSatAmountToRawNumber(amountSat);
|
|
3600
|
-
tx = await submitSatOpenBondPosition(bondCfg, { amountRaw: amount.safeInteger });
|
|
3678
|
+
tx = await runBondSubmission("open", async () => await submitSatOpenBondPosition(bondCfg, { amountRaw: amount.safeInteger }));
|
|
3601
3679
|
invalidateSatReadCaches({ preserveStable: true });
|
|
3602
3680
|
liveBond = await readBond();
|
|
3603
3681
|
if (liveBond?.statusLabel === "active") {
|
|
@@ -3605,18 +3683,18 @@ function createGatewayHttpServer(opts) {
|
|
|
3605
3683
|
liveBond = await readBond();
|
|
3606
3684
|
}
|
|
3607
3685
|
if ((parsed.autoSubmitProof ?? true) && liveBond?.statusLabel === "active") {
|
|
3608
|
-
const proof = await tryRunFederationBondProof({
|
|
3686
|
+
const proof = await runBondSubmission("refresh-proof", async () => await tryRunFederationBondProof({
|
|
3609
3687
|
cfg,
|
|
3610
3688
|
walletId,
|
|
3611
3689
|
liveBond
|
|
3612
|
-
});
|
|
3690
|
+
}));
|
|
3613
3691
|
proofSubmitted = proof.submitted;
|
|
3614
3692
|
proofWarning = appendActionWarning(proofWarning, proof.warning ?? "");
|
|
3615
3693
|
}
|
|
3616
3694
|
} else if (requestPath === "/api/federation/bond/increase") {
|
|
3617
3695
|
const amountSat = parsed.amountSat ?? "1";
|
|
3618
3696
|
const amount = parseSatAmountToRawNumber(amountSat);
|
|
3619
|
-
tx = await submitSatIncreaseBondPosition(bondCfg, { amountRaw: amount.safeInteger });
|
|
3697
|
+
tx = await runBondSubmission("increase", async () => await submitSatIncreaseBondPosition(bondCfg, { amountRaw: amount.safeInteger }));
|
|
3620
3698
|
invalidateSatReadCaches({ preserveStable: true });
|
|
3621
3699
|
liveBond = await readBond();
|
|
3622
3700
|
if (liveBond?.statusLabel === "active") {
|
|
@@ -3624,22 +3702,22 @@ function createGatewayHttpServer(opts) {
|
|
|
3624
3702
|
liveBond = await readBond();
|
|
3625
3703
|
}
|
|
3626
3704
|
if ((parsed.autoSubmitProof ?? true) && liveBond?.statusLabel === "active") {
|
|
3627
|
-
const proof = await tryRunFederationBondProof({
|
|
3705
|
+
const proof = await runBondSubmission("refresh-proof", async () => await tryRunFederationBondProof({
|
|
3628
3706
|
cfg,
|
|
3629
3707
|
walletId,
|
|
3630
3708
|
liveBond
|
|
3631
|
-
});
|
|
3709
|
+
}));
|
|
3632
3710
|
proofSubmitted = proof.submitted;
|
|
3633
3711
|
proofWarning = appendActionWarning(proofWarning, proof.warning ?? "");
|
|
3634
3712
|
}
|
|
3635
3713
|
} else if (requestPath === "/api/federation/bond/request-unlock") {
|
|
3636
|
-
tx = await submitSatRequestBondUnlock(bondCfg);
|
|
3714
|
+
tx = await runBondSubmission("request-unlock", async () => await submitSatRequestBondUnlock(bondCfg));
|
|
3637
3715
|
invalidateSatReadCaches({ preserveStable: true });
|
|
3638
3716
|
liveBond = await readBond();
|
|
3639
3717
|
await syncBondStakingPositionForAction();
|
|
3640
3718
|
liveBond = await readBond();
|
|
3641
3719
|
} else if (requestPath === "/api/federation/bond/cancel-unlock") {
|
|
3642
|
-
tx = await submitSatCancelBondUnlock(bondCfg);
|
|
3720
|
+
tx = await runBondSubmission("cancel-unlock", async () => await submitSatCancelBondUnlock(bondCfg));
|
|
3643
3721
|
invalidateSatReadCaches({ preserveStable: true });
|
|
3644
3722
|
liveBond = await readBond();
|
|
3645
3723
|
if (liveBond?.statusLabel === "active") {
|
|
@@ -3653,32 +3731,25 @@ function createGatewayHttpServer(opts) {
|
|
|
3653
3731
|
throw new Error(`bond unlock is not ready yet: current slot ${currentSlot}, unlock available at ${liveBond.unlockAvailableAtSlot}`);
|
|
3654
3732
|
}
|
|
3655
3733
|
}
|
|
3656
|
-
tx = await submitSatFinalizeBondUnlock(bondCfg);
|
|
3734
|
+
tx = await runBondSubmission("finalize-unlock", async () => await submitSatFinalizeBondUnlock(bondCfg));
|
|
3657
3735
|
invalidateSatReadCaches({ preserveStable: true });
|
|
3658
3736
|
liveBond = await readBond();
|
|
3659
3737
|
await syncBondStakingPositionForAction();
|
|
3660
3738
|
liveBond = await readBond();
|
|
3661
3739
|
} else if (requestPath === "/api/federation/bond/prove") {
|
|
3662
3740
|
liveBond = await readBond();
|
|
3663
|
-
await runFederationBondProof({
|
|
3741
|
+
await runBondSubmission("proof", async () => await runFederationBondProof({
|
|
3664
3742
|
cfg,
|
|
3665
3743
|
walletId,
|
|
3666
3744
|
liveBond
|
|
3667
|
-
});
|
|
3745
|
+
}));
|
|
3668
3746
|
proofSubmitted = true;
|
|
3669
3747
|
liveBond = await readBond();
|
|
3670
3748
|
} else if (requestPath === "/api/federation/bond/staking/init") {
|
|
3671
|
-
|
|
3672
|
-
const amount = parsed.amountSat ? parseSatAmountToRawNumber(parsed.amountSat) : policy?.operatorMinRaw ? parseSatRawToSafeNumber(policy.operatorMinRaw) : parseSatAmountToRawNumber(resolveDefaultBondAmountSat("operator-bond"));
|
|
3673
|
-
if (!Number.isSafeInteger(amount.safeInteger) || amount.safeInteger <= 0) {
|
|
3674
|
-
throw new Error("staking distributor minimum could not be resolved from bond policy");
|
|
3675
|
-
}
|
|
3676
|
-
tx = await submitSatInitBondStakingDistributor(bondCfg, { minStakeRaw: amount.safeInteger });
|
|
3677
|
-
invalidateSatReadCaches({ preserveStable: true });
|
|
3678
|
-
liveBond = await readBond();
|
|
3749
|
+
throw new Error("SAT bond staking distributor initialization is a protocol-genesis operation; use the approved token/sat operator workflow");
|
|
3679
3750
|
} else if (requestPath === "/api/federation/bond/staking/sync") {
|
|
3680
|
-
tx = await retrySolanaRateLimit("sync bond staking rewards", async () => await submitSatSyncBondStakingRewards(bondCfg));
|
|
3681
|
-
await retrySolanaRateLimit("sync bond staking position", async () => await submitSatSyncBondStakingPosition(bondCfg));
|
|
3751
|
+
tx = await retrySolanaRateLimit("sync bond staking rewards", async () => await runBondSubmission("sync-staking-rewards", async () => await submitSatSyncBondStakingRewards(bondCfg)));
|
|
3752
|
+
await retrySolanaRateLimit("sync bond staking position", async () => await runBondSubmission("sync-staking-position", async () => await submitSatSyncBondStakingPosition(bondCfg)));
|
|
3682
3753
|
invalidateSatReadCaches({ preserveStable: true });
|
|
3683
3754
|
liveBond = await readBond();
|
|
3684
3755
|
} else if (requestPath === "/api/federation/bond/staking/claim") {
|
|
@@ -3688,7 +3759,7 @@ function createGatewayHttpServer(opts) {
|
|
|
3688
3759
|
const estimatedClaimRaw = estimateBondStakingClaimRaw(stakingPositionBeforeClaim, stakingDistributorBeforeClaim);
|
|
3689
3760
|
if (isPositiveSatRawAmount(estimatedClaimRaw)) {
|
|
3690
3761
|
stakingClaimedRaw = estimatedClaimRaw;
|
|
3691
|
-
tx = await retrySolanaRateLimit("claim bond staking rewards", async () => await submitSatClaimBondStakingRewards(bondCfg));
|
|
3762
|
+
tx = await retrySolanaRateLimit("claim bond staking rewards", async () => await runBondSubmission("claim-staking-rewards", async () => await submitSatClaimBondStakingRewards(bondCfg)));
|
|
3692
3763
|
} else {
|
|
3693
3764
|
proofWarning = "No claimable distributor SAT after sync.";
|
|
3694
3765
|
}
|
|
@@ -3769,7 +3840,7 @@ function createGatewayHttpServer(opts) {
|
|
|
3769
3840
|
const selectedWalletId = parsedUrl.searchParams.get("walletId")?.trim() || undefined;
|
|
3770
3841
|
sendLoginResponse(200, {
|
|
3771
3842
|
ok: true,
|
|
3772
|
-
settings: buildWalletSettingsPayload(cfg, selectedWalletId, process.env)
|
|
3843
|
+
settings: await buildWalletSettingsPayload(cfg, selectedWalletId, process.env)
|
|
3773
3844
|
});
|
|
3774
3845
|
return;
|
|
3775
3846
|
}
|
|
@@ -3788,6 +3859,27 @@ function createGatewayHttpServer(opts) {
|
|
|
3788
3859
|
});
|
|
3789
3860
|
return;
|
|
3790
3861
|
}
|
|
3862
|
+
const settingsPayload = body.value ?? {};
|
|
3863
|
+
if (settingsPayload.providerId === "embedded-keystore") {
|
|
3864
|
+
sendLoginResponse(409, {
|
|
3865
|
+
ok: false,
|
|
3866
|
+
error: {
|
|
3867
|
+
code: "wallet_legacy_embedded_keystore_migration_required",
|
|
3868
|
+
message: LEGACY_EMBEDDED_KEYSTORE_MIGRATION_MESSAGE
|
|
3869
|
+
}
|
|
3870
|
+
});
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
if (settingsPayload.providerId === "privy") {
|
|
3874
|
+
sendLoginResponse(400, {
|
|
3875
|
+
ok: false,
|
|
3876
|
+
error: {
|
|
3877
|
+
code: "provider_unavailable",
|
|
3878
|
+
message: "Privy wallet creation and signing are unavailable."
|
|
3879
|
+
}
|
|
3880
|
+
});
|
|
3881
|
+
return;
|
|
3882
|
+
}
|
|
3791
3883
|
const patch = parseWalletSettingsPatchInput(body.value);
|
|
3792
3884
|
if (!isManagedGatewayMode(process.env) && !isWalletPolicyPatch(patch)) {
|
|
3793
3885
|
sendLoginResponse(400, {
|
|
@@ -3820,19 +3912,6 @@ function createGatewayHttpServer(opts) {
|
|
|
3820
3912
|
const currentWallet = resolveWalletRuntimeConfig(currentCfg, process.env);
|
|
3821
3913
|
const targetProviderId = patch.providerId ?? currentProviderId;
|
|
3822
3914
|
const switchingToAutonomous = patch.executionMode === "autonomous" && currentWallet.execution.mode !== "autonomous";
|
|
3823
|
-
if (patch.approvalAuthMode === "none") {
|
|
3824
|
-
const splitKeyWallets = listSplitKeyWalletsBlockingPasskeyRemoval(currentCfg, process.env);
|
|
3825
|
-
if (splitKeyWallets.length > 0) {
|
|
3826
|
-
sendLoginResponse(409, {
|
|
3827
|
-
ok: false,
|
|
3828
|
-
error: {
|
|
3829
|
-
code: "wallet_passkey_required_by_split_key",
|
|
3830
|
-
message: formatSplitKeyPasskeyBlockMessage(splitKeyWallets)
|
|
3831
|
-
}
|
|
3832
|
-
});
|
|
3833
|
-
return;
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
3915
|
if (patch.executionMode === "autonomous" && targetProviderId !== "local-socket-signer") {
|
|
3837
3916
|
sendLoginResponse(400, {
|
|
3838
3917
|
ok: false,
|
|
@@ -3843,12 +3922,12 @@ function createGatewayHttpServer(opts) {
|
|
|
3843
3922
|
});
|
|
3844
3923
|
return;
|
|
3845
3924
|
}
|
|
3846
|
-
if (patch.providerId === "turnkey" && !
|
|
3925
|
+
if (patch.providerId === "turnkey" && !hasTurnkeyPolicyCredentialsConfigured(process.env)) {
|
|
3847
3926
|
sendLoginResponse(400, {
|
|
3848
3927
|
ok: false,
|
|
3849
3928
|
error: {
|
|
3850
3929
|
code: "provider_prerequisite_missing",
|
|
3851
|
-
message: "turnkey cannot be set as default
|
|
3930
|
+
message: "turnkey cannot be set as default until its dedicated API key, organization, policy, and Solana RPC are configured"
|
|
3852
3931
|
}
|
|
3853
3932
|
});
|
|
3854
3933
|
return;
|
|
@@ -3859,6 +3938,158 @@ function createGatewayHttpServer(opts) {
|
|
|
3859
3938
|
})) {
|
|
3860
3939
|
return;
|
|
3861
3940
|
}
|
|
3941
|
+
const effectiveEnv = {
|
|
3942
|
+
...process.env,
|
|
3943
|
+
...currentCfg.env?.vars
|
|
3944
|
+
};
|
|
3945
|
+
const registry = readWalletProviderRegistry(effectiveEnv);
|
|
3946
|
+
const selectedRegistryWallet = selectedWalletId ? registry.wallets.find((wallet) => wallet.id === selectedWalletId) : undefined;
|
|
3947
|
+
if (hasScopedPolicyPatch && !selectedWalletId && currentProviderId === "local-socket-signer") {
|
|
3948
|
+
sendLoginResponse(400, {
|
|
3949
|
+
ok: false,
|
|
3950
|
+
error: {
|
|
3951
|
+
code: "wallet_id_required",
|
|
3952
|
+
message: "Signer-backed policy changes require an explicit walletId so one immutable signer policy can acknowledge the change."
|
|
3953
|
+
}
|
|
3954
|
+
});
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
let preparedScopedPolicy;
|
|
3958
|
+
if (selectedWalletId && hasScopedPolicyPatch) {
|
|
3959
|
+
try {
|
|
3960
|
+
preparedScopedPolicy = prepareWalletPolicyConfigUpdate({
|
|
3961
|
+
cfg: currentCfg,
|
|
3962
|
+
env: effectiveEnv,
|
|
3963
|
+
walletId: selectedWalletId,
|
|
3964
|
+
patch: {
|
|
3965
|
+
template: patch.policyTemplate,
|
|
3966
|
+
capsEnabled: patch.capsEnabled,
|
|
3967
|
+
directSigning: patch.directSigning,
|
|
3968
|
+
skillsEnabled: patch.skillsEnabled,
|
|
3969
|
+
solanaAllowPrograms: patch.solanaAllowPrograms,
|
|
3970
|
+
solanaMaxPerTx: patch.solanaMaxPerTx,
|
|
3971
|
+
solanaMaxDaily: patch.solanaMaxDaily,
|
|
3972
|
+
solanaTokenCaps: patch.solanaTokenCaps,
|
|
3973
|
+
recurringTransfer: patch.recurringTransfer
|
|
3974
|
+
}
|
|
3975
|
+
});
|
|
3976
|
+
} catch (err) {
|
|
3977
|
+
sendLoginResponse(400, {
|
|
3978
|
+
ok: false,
|
|
3979
|
+
error: {
|
|
3980
|
+
code: "invalid_wallet_settings",
|
|
3981
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3982
|
+
}
|
|
3983
|
+
});
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
let acknowledgedSignerPolicy;
|
|
3988
|
+
if (preparedScopedPolicy && selectedRegistryWallet?.providerId === "local-socket-signer") {
|
|
3989
|
+
try {
|
|
3990
|
+
const scopedWalletId = resolveNativeSignerWalletId(selectedRegistryWallet);
|
|
3991
|
+
const signer = new LocalSocketSignerAdapter(resolveLocalSignerSocketPath(effectiveEnv));
|
|
3992
|
+
const currentSignerPolicy = await signer.getSignerPolicy(scopedWalletId);
|
|
3993
|
+
const currentGatewayPolicy = resolveWalletPolicyConfig(currentCfg, effectiveEnv, scopedWalletId).policy;
|
|
3994
|
+
const candidate = buildLocalSignerPolicyTightening({
|
|
3995
|
+
current: currentSignerPolicy,
|
|
3996
|
+
expectedRole: preparedScopedPolicy.role,
|
|
3997
|
+
gatewayPolicy: {
|
|
3998
|
+
capsEnabled: currentGatewayPolicy.capsEnabled,
|
|
3999
|
+
directSigning: currentGatewayPolicy.directSigning,
|
|
4000
|
+
skillsEnabled: currentGatewayPolicy.skillsEnabled,
|
|
4001
|
+
solana: {
|
|
4002
|
+
allowPrograms: currentGatewayPolicy.solana.allowPrograms,
|
|
4003
|
+
maxPerTx: currentGatewayPolicy.solana.caps.maxPerTx.toString(),
|
|
4004
|
+
maxDaily: currentGatewayPolicy.solana.caps.maxDaily.toString(),
|
|
4005
|
+
tokenCaps: Object.fromEntries(Object.entries(currentGatewayPolicy.solana.tokenCaps).map(([mint, cap]) => [mint, {
|
|
4006
|
+
maxPerTx: cap.maxPerTx.toString(),
|
|
4007
|
+
maxDaily: cap.maxDaily.toString()
|
|
4008
|
+
}]))
|
|
4009
|
+
}
|
|
4010
|
+
},
|
|
4011
|
+
patch,
|
|
4012
|
+
hosting: String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting"
|
|
4013
|
+
});
|
|
4014
|
+
acknowledgedSignerPolicy = await signer.tightenSignerPolicy({
|
|
4015
|
+
walletId: scopedWalletId,
|
|
4016
|
+
expectedVersion: currentSignerPolicy.version,
|
|
4017
|
+
policy: candidate
|
|
4018
|
+
});
|
|
4019
|
+
} catch (err) {
|
|
4020
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4021
|
+
if (err instanceof LocalSignerPolicyAdminRequiredError) {
|
|
4022
|
+
sendLoginResponse(409, {
|
|
4023
|
+
ok: false,
|
|
4024
|
+
error: {
|
|
4025
|
+
code: err.code,
|
|
4026
|
+
message
|
|
4027
|
+
}
|
|
4028
|
+
});
|
|
4029
|
+
return;
|
|
4030
|
+
}
|
|
4031
|
+
if (/version conflict/i.test(message)) {
|
|
4032
|
+
sendLoginResponse(409, {
|
|
4033
|
+
ok: false,
|
|
4034
|
+
error: {
|
|
4035
|
+
code: "signer_policy_conflict",
|
|
4036
|
+
message: "Signer policy changed concurrently. Reload the wallet policy and review the new version/hash before retrying."
|
|
4037
|
+
}
|
|
4038
|
+
});
|
|
4039
|
+
return;
|
|
4040
|
+
}
|
|
4041
|
+
if (/cannot (?:add|alter|raise)|policy expansion/i.test(message)) {
|
|
4042
|
+
sendLoginResponse(409, {
|
|
4043
|
+
ok: false,
|
|
4044
|
+
error: {
|
|
4045
|
+
code: "signer_policy_admin_required",
|
|
4046
|
+
message: `${message} Policy expansion or role changes require the native signer control socket and an authenticated host administrator; the Gateway cannot perform them.`
|
|
4047
|
+
}
|
|
4048
|
+
});
|
|
4049
|
+
return;
|
|
4050
|
+
}
|
|
4051
|
+
sendLoginResponse(502, {
|
|
4052
|
+
ok: false,
|
|
4053
|
+
error: {
|
|
4054
|
+
code: "signer_policy_unavailable",
|
|
4055
|
+
message: "Signer policy acknowledgement failed. No wallet policy settings or metadata were saved."
|
|
4056
|
+
}
|
|
4057
|
+
});
|
|
4058
|
+
return;
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
if (acknowledgedSignerPolicy && selectedRegistryWallet) {
|
|
4062
|
+
try {
|
|
4063
|
+
const latestRegistryWallet = readWalletProviderRegistry(effectiveEnv).wallets.find((wallet) => wallet.id === selectedRegistryWallet.id);
|
|
4064
|
+
if (latestRegistryWallet?.providerId !== "local-socket-signer") {
|
|
4065
|
+
throw new Error("signer-backed wallet registry entry changed concurrently");
|
|
4066
|
+
}
|
|
4067
|
+
upsertNamedWallet({
|
|
4068
|
+
walletId: latestRegistryWallet.id,
|
|
4069
|
+
name: latestRegistryWallet.name,
|
|
4070
|
+
providerId: latestRegistryWallet.providerId,
|
|
4071
|
+
addresses: latestRegistryWallet.addresses,
|
|
4072
|
+
metadata: {
|
|
4073
|
+
...latestRegistryWallet.metadata,
|
|
4074
|
+
role: acknowledgedSignerPolicy.role,
|
|
4075
|
+
purpose: acknowledgedSignerPolicy.role,
|
|
4076
|
+
policyState: localSignerPolicyState(acknowledgedSignerPolicy),
|
|
4077
|
+
policyVersion: acknowledgedSignerPolicy.version,
|
|
4078
|
+
policyHash: acknowledgedSignerPolicy.hash
|
|
4079
|
+
},
|
|
4080
|
+
env: effectiveEnv
|
|
4081
|
+
});
|
|
4082
|
+
} catch (err) {
|
|
4083
|
+
sendLoginResponse(409, {
|
|
4084
|
+
ok: false,
|
|
4085
|
+
error: {
|
|
4086
|
+
code: "signer_policy_metadata_conflict",
|
|
4087
|
+
message: `The signer acknowledged policy version ${acknowledgedSignerPolicy.version} (${acknowledgedSignerPolicy.hash}), but its wallet metadata could not be recorded: ` + `${err instanceof Error ? err.message : String(err)}. No app policy settings were saved; reload before retrying.`
|
|
4088
|
+
}
|
|
4089
|
+
});
|
|
4090
|
+
return;
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
3862
4093
|
let nextCfg = currentCfg;
|
|
3863
4094
|
if (hasGlobalConfigPatch) {
|
|
3864
4095
|
const updated = await updateWalletConfig({
|
|
@@ -3938,30 +4169,15 @@ function createGatewayHttpServer(opts) {
|
|
|
3938
4169
|
}
|
|
3939
4170
|
nextCfg = updated.cfg;
|
|
3940
4171
|
}
|
|
3941
|
-
if (
|
|
4172
|
+
if (preparedScopedPolicy) {
|
|
3942
4173
|
try {
|
|
3943
|
-
|
|
3944
|
-
cfg: nextCfg,
|
|
3945
|
-
env: process.env,
|
|
3946
|
-
walletId: selectedWalletId,
|
|
3947
|
-
patch: {
|
|
3948
|
-
template: patch.policyTemplate,
|
|
3949
|
-
capsEnabled: patch.capsEnabled,
|
|
3950
|
-
directSigning: patch.directSigning,
|
|
3951
|
-
skillsEnabled: patch.skillsEnabled,
|
|
3952
|
-
solanaAllowPrograms: patch.solanaAllowPrograms,
|
|
3953
|
-
solanaMaxPerTx: patch.solanaMaxPerTx,
|
|
3954
|
-
solanaMaxDaily: patch.solanaMaxDaily,
|
|
3955
|
-
solanaTokenCaps: patch.solanaTokenCaps,
|
|
3956
|
-
recurringTransfer: patch.recurringTransfer
|
|
3957
|
-
}
|
|
3958
|
-
});
|
|
4174
|
+
commitWalletPolicyConfigUpdate(preparedScopedPolicy, effectiveEnv);
|
|
3959
4175
|
} catch (err) {
|
|
3960
|
-
sendLoginResponse(
|
|
4176
|
+
sendLoginResponse(409, {
|
|
3961
4177
|
ok: false,
|
|
3962
4178
|
error: {
|
|
3963
|
-
code: "
|
|
3964
|
-
message: String(err)
|
|
4179
|
+
code: "wallet_policy_conflict",
|
|
4180
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3965
4181
|
}
|
|
3966
4182
|
});
|
|
3967
4183
|
return;
|
|
@@ -3981,7 +4197,7 @@ function createGatewayHttpServer(opts) {
|
|
|
3981
4197
|
}
|
|
3982
4198
|
sendLoginResponse(200, {
|
|
3983
4199
|
ok: true,
|
|
3984
|
-
settings: buildWalletSettingsPayload(nextCfg, selectedWalletId,
|
|
4200
|
+
settings: await buildWalletSettingsPayload(nextCfg, selectedWalletId, effectiveEnv, acknowledgedSignerPolicy)
|
|
3985
4201
|
});
|
|
3986
4202
|
return;
|
|
3987
4203
|
}
|
|
@@ -4079,7 +4295,37 @@ function createGatewayHttpServer(opts) {
|
|
|
4079
4295
|
});
|
|
4080
4296
|
return;
|
|
4081
4297
|
}
|
|
4298
|
+
if (body.value != null && (typeof body.value !== "object" || Array.isArray(body.value))) {
|
|
4299
|
+
sendLoginResponse(400, {
|
|
4300
|
+
ok: false,
|
|
4301
|
+
error: {
|
|
4302
|
+
code: "invalid_request",
|
|
4303
|
+
message: "signer-reviewed approval body must be a JSON object"
|
|
4304
|
+
}
|
|
4305
|
+
});
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4082
4308
|
const payload = body.value ?? {};
|
|
4309
|
+
if (payload.providerId === "embedded-keystore") {
|
|
4310
|
+
sendLoginResponse(409, {
|
|
4311
|
+
ok: false,
|
|
4312
|
+
error: {
|
|
4313
|
+
code: "wallet_legacy_embedded_keystore_migration_required",
|
|
4314
|
+
message: LEGACY_EMBEDDED_KEYSTORE_MIGRATION_MESSAGE
|
|
4315
|
+
}
|
|
4316
|
+
});
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
if (payload.providerId === "privy") {
|
|
4320
|
+
sendLoginResponse(400, {
|
|
4321
|
+
ok: false,
|
|
4322
|
+
error: {
|
|
4323
|
+
code: "provider_unavailable",
|
|
4324
|
+
message: "Privy wallet creation and signing are unavailable."
|
|
4325
|
+
}
|
|
4326
|
+
});
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4083
4329
|
const providerId = parseWalletProviderId(payload.providerId);
|
|
4084
4330
|
if (!providerId) {
|
|
4085
4331
|
sendLoginResponse(400, {
|
|
@@ -4100,12 +4346,12 @@ function createGatewayHttpServer(opts) {
|
|
|
4100
4346
|
});
|
|
4101
4347
|
}
|
|
4102
4348
|
if (payload.setDefault === true) {
|
|
4103
|
-
if (providerId === "turnkey" && !
|
|
4349
|
+
if (providerId === "turnkey" && !hasTurnkeyPolicyCredentialsConfigured(process.env)) {
|
|
4104
4350
|
sendLoginResponse(400, {
|
|
4105
4351
|
ok: false,
|
|
4106
4352
|
error: {
|
|
4107
4353
|
code: "provider_prerequisite_missing",
|
|
4108
|
-
message: "turnkey cannot be set as default
|
|
4354
|
+
message: "turnkey cannot be set as default until its dedicated API key, organization, policy, and Solana RPC are configured"
|
|
4109
4355
|
}
|
|
4110
4356
|
});
|
|
4111
4357
|
return;
|
|
@@ -4166,7 +4412,7 @@ function createGatewayHttpServer(opts) {
|
|
|
4166
4412
|
return {
|
|
4167
4413
|
...wallet,
|
|
4168
4414
|
readiness: {
|
|
4169
|
-
keystore:
|
|
4415
|
+
keystore: wallet.providerId !== "embedded-keystore" && wallet.providerId !== "privy",
|
|
4170
4416
|
rpc: Boolean(solanaRpc)
|
|
4171
4417
|
}
|
|
4172
4418
|
};
|
|
@@ -4203,6 +4449,26 @@ function createGatewayHttpServer(opts) {
|
|
|
4203
4449
|
return;
|
|
4204
4450
|
}
|
|
4205
4451
|
const payload = body.value ?? {};
|
|
4452
|
+
if (payload.providerId === "embedded-keystore") {
|
|
4453
|
+
sendLoginResponse(409, {
|
|
4454
|
+
ok: false,
|
|
4455
|
+
error: {
|
|
4456
|
+
code: "wallet_legacy_embedded_keystore_migration_required",
|
|
4457
|
+
message: LEGACY_EMBEDDED_KEYSTORE_MIGRATION_MESSAGE
|
|
4458
|
+
}
|
|
4459
|
+
});
|
|
4460
|
+
return;
|
|
4461
|
+
}
|
|
4462
|
+
if (payload.providerId === "privy") {
|
|
4463
|
+
sendLoginResponse(400, {
|
|
4464
|
+
ok: false,
|
|
4465
|
+
error: {
|
|
4466
|
+
code: "provider_unavailable",
|
|
4467
|
+
message: "Privy wallet creation and signing are unavailable."
|
|
4468
|
+
}
|
|
4469
|
+
});
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4206
4472
|
const cfg = loadConfig();
|
|
4207
4473
|
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
4208
4474
|
const providerId = parseWalletProviderId(payload.providerId) ?? resolveWalletProviderId(cfg, process.env);
|
|
@@ -4242,54 +4508,42 @@ function createGatewayHttpServer(opts) {
|
|
|
4242
4508
|
});
|
|
4243
4509
|
return;
|
|
4244
4510
|
}
|
|
4245
|
-
if (providerId === "
|
|
4246
|
-
|
|
4511
|
+
if (providerId === "wallet-standard") {
|
|
4512
|
+
const address = typeof payload.address === "string" ? payload.address.trim() : "";
|
|
4513
|
+
if (!address || !isValidSolanaAddress(address)) {
|
|
4247
4514
|
sendLoginResponse(400, {
|
|
4248
4515
|
ok: false,
|
|
4249
4516
|
error: {
|
|
4250
|
-
code: "
|
|
4251
|
-
message: "
|
|
4517
|
+
code: "invalid_solana_address",
|
|
4518
|
+
message: "a valid browser-selected Solana account is required"
|
|
4252
4519
|
}
|
|
4253
4520
|
});
|
|
4254
4521
|
return;
|
|
4255
4522
|
}
|
|
4256
|
-
|
|
4257
|
-
payloadChain: payload.chain,
|
|
4258
|
-
walletId: requestedWalletId,
|
|
4259
|
-
walletName,
|
|
4260
|
-
runtimeChains: walletCfg.chains
|
|
4261
|
-
});
|
|
4262
|
-
if (!chain) {
|
|
4523
|
+
if (requestedRole && requestedRole !== "vault") {
|
|
4263
4524
|
sendLoginResponse(400, {
|
|
4264
4525
|
ok: false,
|
|
4265
4526
|
error: {
|
|
4266
|
-
code: "
|
|
4267
|
-
message: "
|
|
4527
|
+
code: "invalid_wallet_role",
|
|
4528
|
+
message: "Wallet Standard accounts are supported as reviewed Vault wallets only"
|
|
4268
4529
|
}
|
|
4269
4530
|
});
|
|
4270
4531
|
return;
|
|
4271
4532
|
}
|
|
4272
4533
|
try {
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
noSignerHints: true,
|
|
4286
|
-
noDoctor: true
|
|
4534
|
+
const wallet = upsertNamedWallet({
|
|
4535
|
+
walletId: requestedWalletId || undefined,
|
|
4536
|
+
name: walletName,
|
|
4537
|
+
providerId,
|
|
4538
|
+
addresses: { solana: address },
|
|
4539
|
+
metadata: {
|
|
4540
|
+
role: "vault",
|
|
4541
|
+
purpose: "vault",
|
|
4542
|
+
browserSigner: true,
|
|
4543
|
+
selfHosted: false
|
|
4544
|
+
},
|
|
4545
|
+
env: process.env
|
|
4287
4546
|
});
|
|
4288
|
-
const registry = readWalletProviderRegistry(process.env);
|
|
4289
|
-
const wallet = registry.wallets.find((entry) => entry.id === requestedWalletId);
|
|
4290
|
-
if (!wallet) {
|
|
4291
|
-
throw new Error("local signer wallet was created but not registered");
|
|
4292
|
-
}
|
|
4293
4547
|
appendWalletAuditEntry({
|
|
4294
4548
|
action: "wallet_named_created",
|
|
4295
4549
|
actor: "control-ui",
|
|
@@ -4305,9 +4559,83 @@ function createGatewayHttpServer(opts) {
|
|
|
4305
4559
|
ok: true,
|
|
4306
4560
|
wallet
|
|
4307
4561
|
});
|
|
4308
|
-
return;
|
|
4309
4562
|
} catch (err) {
|
|
4310
|
-
sendLoginResponse(
|
|
4563
|
+
sendLoginResponse(400, {
|
|
4564
|
+
ok: false,
|
|
4565
|
+
error: {
|
|
4566
|
+
code: "invalid_request",
|
|
4567
|
+
message: err instanceof Error ? err.message : String(err)
|
|
4568
|
+
}
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
4573
|
+
if (providerId === "local-socket-signer") {
|
|
4574
|
+
if (!requestedWalletId) {
|
|
4575
|
+
sendLoginResponse(400, {
|
|
4576
|
+
ok: false,
|
|
4577
|
+
error: {
|
|
4578
|
+
code: "invalid_request",
|
|
4579
|
+
message: "walletId is required when creating a local signer wallet"
|
|
4580
|
+
}
|
|
4581
|
+
});
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4584
|
+
const chain = inferLocalSignerCreateChain({
|
|
4585
|
+
payloadChain: payload.chain,
|
|
4586
|
+
walletId: requestedWalletId,
|
|
4587
|
+
walletName,
|
|
4588
|
+
runtimeChains: walletCfg.chains
|
|
4589
|
+
});
|
|
4590
|
+
if (!chain) {
|
|
4591
|
+
sendLoginResponse(400, {
|
|
4592
|
+
ok: false,
|
|
4593
|
+
error: {
|
|
4594
|
+
code: "invalid_request",
|
|
4595
|
+
message: "chain is required for local signer wallet creation; use chain=solana"
|
|
4596
|
+
}
|
|
4597
|
+
});
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4600
|
+
try {
|
|
4601
|
+
const effectiveEnv = {
|
|
4602
|
+
...process.env,
|
|
4603
|
+
...cfg.env?.vars
|
|
4604
|
+
};
|
|
4605
|
+
await walletSetupCommand(silentWalletSetupRuntime, {
|
|
4606
|
+
mode: "local-signer-create",
|
|
4607
|
+
chain,
|
|
4608
|
+
walletId: requestedWalletId,
|
|
4609
|
+
walletName,
|
|
4610
|
+
rpcUrl: resolveWalletRpcUrlFromEnv(effectiveEnv, chain, requestedWalletId),
|
|
4611
|
+
role: requestedRole,
|
|
4612
|
+
nonInteractive: true,
|
|
4613
|
+
noSignerHints: true,
|
|
4614
|
+
noDoctor: true
|
|
4615
|
+
});
|
|
4616
|
+
const registry = readWalletProviderRegistry(process.env);
|
|
4617
|
+
const wallet = registry.wallets.find((entry) => entry.id === requestedWalletId);
|
|
4618
|
+
if (!wallet) {
|
|
4619
|
+
throw new Error("local signer wallet was created but not registered");
|
|
4620
|
+
}
|
|
4621
|
+
appendWalletAuditEntry({
|
|
4622
|
+
action: "wallet_named_created",
|
|
4623
|
+
actor: "control-ui",
|
|
4624
|
+
details: {
|
|
4625
|
+
walletId: wallet.id,
|
|
4626
|
+
walletName: wallet.name,
|
|
4627
|
+
providerId: wallet.providerId,
|
|
4628
|
+
addresses: wallet.addresses
|
|
4629
|
+
},
|
|
4630
|
+
env: process.env
|
|
4631
|
+
});
|
|
4632
|
+
sendLoginResponse(200, {
|
|
4633
|
+
ok: true,
|
|
4634
|
+
wallet
|
|
4635
|
+
});
|
|
4636
|
+
return;
|
|
4637
|
+
} catch (err) {
|
|
4638
|
+
sendLoginResponse(502, {
|
|
4311
4639
|
ok: false,
|
|
4312
4640
|
error: {
|
|
4313
4641
|
code: "wallet_provider_error",
|
|
@@ -4465,6 +4793,35 @@ function createGatewayHttpServer(opts) {
|
|
|
4465
4793
|
return;
|
|
4466
4794
|
}
|
|
4467
4795
|
if (requestedRole) {
|
|
4796
|
+
if (existing.providerId === "local-socket-signer") {
|
|
4797
|
+
try {
|
|
4798
|
+
const cfg = loadConfig();
|
|
4799
|
+
const effectiveEnv = {
|
|
4800
|
+
...process.env,
|
|
4801
|
+
...cfg.env?.vars
|
|
4802
|
+
};
|
|
4803
|
+
const signerPolicy = await new LocalSocketSignerAdapter(resolveLocalSignerSocketPath(effectiveEnv)).getSignerPolicy(resolveNativeSignerWalletId(existing));
|
|
4804
|
+
if (signerPolicy.role !== requestedRole) {
|
|
4805
|
+
sendLoginResponse(409, {
|
|
4806
|
+
ok: false,
|
|
4807
|
+
error: {
|
|
4808
|
+
code: "signer_wallet_role_immutable",
|
|
4809
|
+
message: `The native signer owns this wallet as role=${signerPolicy.role}; the Gateway cannot change it to ${requestedRole}. ` + "Create a new signer-owned wallet with the required locked role through Local onboarding or an authenticated Hosting administrator session."
|
|
4810
|
+
}
|
|
4811
|
+
});
|
|
4812
|
+
return;
|
|
4813
|
+
}
|
|
4814
|
+
} catch {
|
|
4815
|
+
sendLoginResponse(502, {
|
|
4816
|
+
ok: false,
|
|
4817
|
+
error: {
|
|
4818
|
+
code: "signer_policy_unavailable",
|
|
4819
|
+
message: "The native signer role could not be verified, so wallet role metadata was not changed."
|
|
4820
|
+
}
|
|
4821
|
+
});
|
|
4822
|
+
return;
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4468
4825
|
const currentRole = resolveWalletUserRole(existing) ?? (registry.defaultWalletId === walletId ? "agent" : undefined);
|
|
4469
4826
|
if (currentRole && currentRole !== requestedRole) {
|
|
4470
4827
|
sendLoginResponse(409, {
|
|
@@ -4559,7 +4916,7 @@ function createGatewayHttpServer(opts) {
|
|
|
4559
4916
|
ok: false,
|
|
4560
4917
|
error: {
|
|
4561
4918
|
code: "wallet_requires_onboarding_delete",
|
|
4562
|
-
message: "
|
|
4919
|
+
message: "signer-owned wallets must be removed through the native signer/onboarding flow so signer state and mining attachments are handled safely"
|
|
4563
4920
|
}
|
|
4564
4921
|
});
|
|
4565
4922
|
return;
|
|
@@ -4830,7 +5187,37 @@ function createGatewayHttpServer(opts) {
|
|
|
4830
5187
|
return;
|
|
4831
5188
|
}
|
|
4832
5189
|
const payload = body.value ?? {};
|
|
5190
|
+
if (payload.providerId === "embedded-keystore") {
|
|
5191
|
+
sendLoginResponse(409, {
|
|
5192
|
+
ok: false,
|
|
5193
|
+
error: {
|
|
5194
|
+
code: "wallet_legacy_embedded_keystore_migration_required",
|
|
5195
|
+
message: LEGACY_EMBEDDED_KEYSTORE_MIGRATION_MESSAGE
|
|
5196
|
+
}
|
|
5197
|
+
});
|
|
5198
|
+
return;
|
|
5199
|
+
}
|
|
5200
|
+
if (payload.providerId === "privy") {
|
|
5201
|
+
sendLoginResponse(400, {
|
|
5202
|
+
ok: false,
|
|
5203
|
+
error: {
|
|
5204
|
+
code: "provider_unavailable",
|
|
5205
|
+
message: "Privy wallet creation and signing are unavailable."
|
|
5206
|
+
}
|
|
5207
|
+
});
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
4833
5210
|
const providerId = resolveProviderFromInput(payload.providerId) ?? configuredProviderId;
|
|
5211
|
+
if (providerId !== "alchemy" && providerId !== "turnkey") {
|
|
5212
|
+
sendLoginResponse(400, {
|
|
5213
|
+
ok: false,
|
|
5214
|
+
error: {
|
|
5215
|
+
code: "provider_credentials_not_accepted",
|
|
5216
|
+
message: `${providerId} does not accept Gateway-held provider credentials.`
|
|
5217
|
+
}
|
|
5218
|
+
});
|
|
5219
|
+
return;
|
|
5220
|
+
}
|
|
4834
5221
|
let credentials = {};
|
|
4835
5222
|
if (providerId === "alchemy") {
|
|
4836
5223
|
const apiKey = toOptionalString(payload.apiKey);
|
|
@@ -4884,24 +5271,33 @@ function createGatewayHttpServer(opts) {
|
|
|
4884
5271
|
return;
|
|
4885
5272
|
}
|
|
4886
5273
|
if (providerId === "turnkey") {
|
|
4887
|
-
|
|
5274
|
+
const allowedTurnkeyFields = new Set([
|
|
5275
|
+
"apiPublicKey",
|
|
5276
|
+
"apiPrivateKey",
|
|
5277
|
+
"organizationId",
|
|
5278
|
+
"policyId",
|
|
5279
|
+
"baseUrl",
|
|
5280
|
+
"rpcUrl",
|
|
5281
|
+
"defaultSolanaAddress",
|
|
5282
|
+
"providerWalletId"
|
|
5283
|
+
]);
|
|
5284
|
+
const unsupportedField = Object.keys(credentials).find((field) => !allowedTurnkeyFields.has(field));
|
|
5285
|
+
if (unsupportedField) {
|
|
4888
5286
|
sendLoginResponse(400, {
|
|
4889
5287
|
ok: false,
|
|
4890
5288
|
error: {
|
|
4891
5289
|
code: "invalid_request",
|
|
4892
|
-
message:
|
|
5290
|
+
message: `unsupported turnkey credential field: ${unsupportedField}`
|
|
4893
5291
|
}
|
|
4894
5292
|
});
|
|
4895
5293
|
return;
|
|
4896
5294
|
}
|
|
4897
|
-
|
|
4898
|
-
if (providerId === "privy") {
|
|
4899
|
-
if (typeof credentials.appId !== "string" || typeof credentials.appSecret !== "string") {
|
|
5295
|
+
if (typeof credentials.apiPublicKey !== "string" || typeof credentials.apiPrivateKey !== "string" || typeof credentials.organizationId !== "string" || typeof credentials.policyId !== "string" || typeof credentials.rpcUrl !== "string") {
|
|
4900
5296
|
sendLoginResponse(400, {
|
|
4901
5297
|
ok: false,
|
|
4902
5298
|
error: {
|
|
4903
5299
|
code: "invalid_request",
|
|
4904
|
-
message: "
|
|
5300
|
+
message: "turnkey credentials must include apiPublicKey, apiPrivateKey, organizationId, policyId, and rpcUrl"
|
|
4905
5301
|
}
|
|
4906
5302
|
});
|
|
4907
5303
|
return;
|
|
@@ -4986,20 +5382,20 @@ function createGatewayHttpServer(opts) {
|
|
|
4986
5382
|
checks.push({
|
|
4987
5383
|
id: "wallet.rpc.configured",
|
|
4988
5384
|
ok: true,
|
|
4989
|
-
message:
|
|
5385
|
+
message: `${providerId} uses signer/provider-owned network configuration`
|
|
4990
5386
|
});
|
|
4991
5387
|
}
|
|
4992
5388
|
checks.push({
|
|
4993
5389
|
id: "wallet.provider.credentials",
|
|
4994
|
-
ok: providerId === "
|
|
4995
|
-
message: providerId === "
|
|
5390
|
+
ok: providerId === "local-socket-signer" || providerId === "wallet-standard" || providerSecret.configured,
|
|
5391
|
+
message: providerId === "local-socket-signer" || providerId === "wallet-standard" ? `${providerId} does not use Gateway-held provider credentials` : providerSecret.configured ? `${providerId} provider credentials configured` : `${providerId} provider credentials are missing`
|
|
4996
5392
|
});
|
|
4997
5393
|
if (providerId === "turnkey") {
|
|
4998
|
-
const
|
|
5394
|
+
const hasTurnkeyPolicyCredentials = hasTurnkeyPolicyCredentialsConfigured(process.env);
|
|
4999
5395
|
checks.push({
|
|
5000
|
-
id: "wallet.provider.turnkey.
|
|
5001
|
-
ok:
|
|
5002
|
-
message:
|
|
5396
|
+
id: "wallet.provider.turnkey.policy_credentials",
|
|
5397
|
+
ok: hasTurnkeyPolicyCredentials,
|
|
5398
|
+
message: hasTurnkeyPolicyCredentials ? "turnkey dedicated API credential, organization, policy, and Solana RPC are configured" : "turnkey requires a dedicated API user covered by a restrictive organization policy, plus organizationId, policyId, and rpcUrl"
|
|
5003
5399
|
});
|
|
5004
5400
|
}
|
|
5005
5401
|
try {
|
|
@@ -5083,8 +5479,7 @@ function createGatewayHttpServer(opts) {
|
|
|
5083
5479
|
let snapshot = await readWalletStatusSnapshot({
|
|
5084
5480
|
config: cfg,
|
|
5085
5481
|
env: effectiveEnv,
|
|
5086
|
-
walletId: selectedWalletId
|
|
5087
|
-
approvalHost: host
|
|
5482
|
+
walletId: selectedWalletId
|
|
5088
5483
|
});
|
|
5089
5484
|
if (configuredProviderId === "local-socket-signer" && !snapshot.service.healthy) {
|
|
5090
5485
|
try {
|
|
@@ -5093,8 +5488,7 @@ function createGatewayHttpServer(opts) {
|
|
|
5093
5488
|
snapshot = await readWalletStatusSnapshot({
|
|
5094
5489
|
config: cfg,
|
|
5095
5490
|
env: effectiveEnv,
|
|
5096
|
-
walletId: selectedWalletId
|
|
5097
|
-
approvalHost: host
|
|
5491
|
+
walletId: selectedWalletId
|
|
5098
5492
|
});
|
|
5099
5493
|
} catch {}
|
|
5100
5494
|
}
|
|
@@ -5105,11 +5499,11 @@ function createGatewayHttpServer(opts) {
|
|
|
5105
5499
|
if (configuredProviderId === "turnkey" || configuredProviderId === "privy" || configuredProviderId === "alchemy") {
|
|
5106
5500
|
return "hosted-provider";
|
|
5107
5501
|
}
|
|
5108
|
-
return "
|
|
5502
|
+
return "local-native-signer";
|
|
5109
5503
|
})();
|
|
5110
5504
|
const providerSummary = {
|
|
5111
5505
|
id: configuredProviderId,
|
|
5112
|
-
label: configuredProviderId === "
|
|
5506
|
+
label: configuredProviderId === "local-socket-signer" ? "Local signer socket" : configuredProviderId === "turnkey" ? "Turnkey" : configuredProviderId === "privy" ? "Privy" : configuredProviderId === "alchemy" ? "Alchemy" : configuredProviderId,
|
|
5113
5507
|
category: activeSignerMode === "hosted-provider" ? "hosted-provider" : activeSignerMode === "local-native-signer" ? "local-signer" : "embedded",
|
|
5114
5508
|
signerMode: activeSignerMode
|
|
5115
5509
|
};
|
|
@@ -5157,7 +5551,7 @@ function createGatewayHttpServer(opts) {
|
|
|
5157
5551
|
health: readiness.keystore ? "ok" : "degraded"
|
|
5158
5552
|
};
|
|
5159
5553
|
});
|
|
5160
|
-
if (configuredProviderId === "
|
|
5554
|
+
if (configuredProviderId === "local-socket-signer") {
|
|
5161
5555
|
statusPayload.providerAuthMode = snapshot.authMode;
|
|
5162
5556
|
statusPayload.providerAuthSource = snapshot.authSource;
|
|
5163
5557
|
statusPayload.providerAuthDetails = snapshot.authBootstrap ? {
|
|
@@ -5200,20 +5594,8 @@ function createGatewayHttpServer(opts) {
|
|
|
5200
5594
|
};
|
|
5201
5595
|
const doctor = await collectWalletSignerDoctorReport(effectiveEnv, { config: cfg });
|
|
5202
5596
|
const registry = readWalletProviderRegistry(effectiveEnv);
|
|
5203
|
-
const parseWalletIds = (
|
|
5204
|
-
const ids = new Set(
|
|
5205
|
-
for (const [key, value] of Object.entries(effectiveEnv)) {
|
|
5206
|
-
if (!key.startsWith(prefix)) {
|
|
5207
|
-
continue;
|
|
5208
|
-
}
|
|
5209
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
5210
|
-
continue;
|
|
5211
|
-
}
|
|
5212
|
-
const id = key.slice(prefix.length).trim().toLowerCase();
|
|
5213
|
-
if (id) {
|
|
5214
|
-
ids.add(id);
|
|
5215
|
-
}
|
|
5216
|
-
}
|
|
5597
|
+
const parseWalletIds = (_chain) => {
|
|
5598
|
+
const ids = new Set();
|
|
5217
5599
|
for (const wallet of registry.wallets) {
|
|
5218
5600
|
const hasChain = wallet.addresses?.solana;
|
|
5219
5601
|
if (hasChain && wallet.id.trim()) {
|
|
@@ -5249,7 +5631,7 @@ function createGatewayHttpServer(opts) {
|
|
|
5249
5631
|
running: doctor.checks.find((c) => c.check === "socket.health")?.ok ?? false,
|
|
5250
5632
|
checks: doctor.checks
|
|
5251
5633
|
},
|
|
5252
|
-
chainWallets: { solana: buildChainEntries("solana", parseWalletIds("
|
|
5634
|
+
chainWallets: { solana: buildChainEntries("solana", parseWalletIds("solana")) }
|
|
5253
5635
|
});
|
|
5254
5636
|
return;
|
|
5255
5637
|
}
|
|
@@ -5918,7 +6300,7 @@ function createGatewayHttpServer(opts) {
|
|
|
5918
6300
|
}
|
|
5919
6301
|
let result;
|
|
5920
6302
|
try {
|
|
5921
|
-
result = await callSatMiningGateway("sat.
|
|
6303
|
+
result = await callSatMiningGateway("sat.topUpRegistryReserve", { targetBalanceLamports: 0 });
|
|
5922
6304
|
} catch (error) {
|
|
5923
6305
|
sendSatMiningGatewayError(error);
|
|
5924
6306
|
return;
|
|
@@ -6450,7 +6832,7 @@ function createGatewayHttpServer(opts) {
|
|
|
6450
6832
|
});
|
|
6451
6833
|
return;
|
|
6452
6834
|
}
|
|
6453
|
-
if (requestPath === "/api/wallet/
|
|
6835
|
+
if (requestPath === "/api/wallet/approval-auth/status") {
|
|
6454
6836
|
if (req.method !== "GET") {
|
|
6455
6837
|
res.statusCode = 405;
|
|
6456
6838
|
res.setHeader("Allow", "GET");
|
|
@@ -6467,29 +6849,23 @@ function createGatewayHttpServer(opts) {
|
|
|
6467
6849
|
return;
|
|
6468
6850
|
}
|
|
6469
6851
|
const cfg = loadConfig();
|
|
6470
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
6471
|
-
const walletId = parsedUrl.searchParams.get("walletId")?.trim() || undefined;
|
|
6472
6852
|
sendLoginResponse(200, {
|
|
6473
6853
|
ok: true,
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
cfg,
|
|
6478
|
-
walletId,
|
|
6479
|
-
approvalHost: host
|
|
6480
|
-
})
|
|
6854
|
+
status: readWalletApprovalAuthSnapshot(process.env, cfg),
|
|
6855
|
+
challengeTtlSeconds: resolveWalletApprovalChallengeTtlSeconds(process.env, cfg),
|
|
6856
|
+
grantTtlSeconds: resolveWalletApprovalGrantTtlSeconds(process.env, cfg)
|
|
6481
6857
|
});
|
|
6482
6858
|
return;
|
|
6483
6859
|
}
|
|
6484
|
-
if (requestPath === "/api/wallet/
|
|
6485
|
-
if (req.method !== "
|
|
6860
|
+
if (requestPath === "/api/wallet/approval-auth/passkeys") {
|
|
6861
|
+
if (req.method !== "GET") {
|
|
6486
6862
|
res.statusCode = 405;
|
|
6487
|
-
res.setHeader("Allow", "
|
|
6863
|
+
res.setHeader("Allow", "GET");
|
|
6488
6864
|
sendLoginResponse(405, {
|
|
6489
6865
|
ok: false,
|
|
6490
6866
|
error: {
|
|
6491
6867
|
code: "method_not_allowed",
|
|
6492
|
-
message: "method must be
|
|
6868
|
+
message: "method must be GET"
|
|
6493
6869
|
}
|
|
6494
6870
|
});
|
|
6495
6871
|
return;
|
|
@@ -6497,84 +6873,94 @@ function createGatewayHttpServer(opts) {
|
|
|
6497
6873
|
if (!await ensureWalletApiAuthorized()) {
|
|
6498
6874
|
return;
|
|
6499
6875
|
}
|
|
6500
|
-
const
|
|
6501
|
-
|
|
6502
|
-
|
|
6876
|
+
const cfg = loadConfig();
|
|
6877
|
+
sendLoginResponse(200, {
|
|
6878
|
+
ok: true,
|
|
6879
|
+
passkeys: listWalletPasskeys(process.env, cfg)
|
|
6880
|
+
});
|
|
6881
|
+
return;
|
|
6882
|
+
}
|
|
6883
|
+
if (requestPath.startsWith("/api/wallet/approval-auth/passkeys/") && !requestPath.startsWith("/api/wallet/approval-auth/passkeys/register/")) {
|
|
6884
|
+
if (req.method !== "DELETE") {
|
|
6885
|
+
res.statusCode = 405;
|
|
6886
|
+
res.setHeader("Allow", "DELETE");
|
|
6887
|
+
sendLoginResponse(405, {
|
|
6503
6888
|
ok: false,
|
|
6504
6889
|
error: {
|
|
6505
|
-
code: "
|
|
6506
|
-
message:
|
|
6890
|
+
code: "method_not_allowed",
|
|
6891
|
+
message: "method must be DELETE"
|
|
6507
6892
|
}
|
|
6508
6893
|
});
|
|
6509
6894
|
return;
|
|
6510
6895
|
}
|
|
6511
|
-
|
|
6512
|
-
|
|
6896
|
+
if (!await ensureWalletApiAuthorized()) {
|
|
6897
|
+
return;
|
|
6898
|
+
}
|
|
6899
|
+
const credentialId = decodeURIComponent(requestPath.slice("/api/wallet/approval-auth/passkeys/".length)).trim();
|
|
6513
6900
|
const cfg = loadConfig();
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6901
|
+
if (!ensureWalletApprovalAuthorized({
|
|
6902
|
+
operation: "wallet.passkey-remove",
|
|
6903
|
+
cfg
|
|
6904
|
+
})) {
|
|
6905
|
+
return;
|
|
6906
|
+
}
|
|
6907
|
+
const passkeys = listWalletPasskeys(process.env, cfg);
|
|
6908
|
+
const removingExistingPasskey = passkeys.some((passkey) => passkey.id === credentialId);
|
|
6909
|
+
const result = removeWalletPasskey({
|
|
6910
|
+
credentialId,
|
|
6518
6911
|
env: process.env,
|
|
6519
6912
|
cfg
|
|
6520
6913
|
});
|
|
6521
|
-
if (!
|
|
6522
|
-
sendLoginResponse(
|
|
6914
|
+
if (!result.ok) {
|
|
6915
|
+
sendLoginResponse(400, {
|
|
6523
6916
|
ok: false,
|
|
6524
6917
|
error: {
|
|
6525
|
-
code:
|
|
6526
|
-
message:
|
|
6918
|
+
code: result.code,
|
|
6919
|
+
message: result.message
|
|
6527
6920
|
}
|
|
6528
6921
|
});
|
|
6529
6922
|
return;
|
|
6530
6923
|
}
|
|
6531
|
-
|
|
6532
|
-
|
|
6924
|
+
let snapshotCfg = cfg;
|
|
6925
|
+
if (removingExistingPasskey && passkeys.length <= 1) {
|
|
6926
|
+
const updated = await updateWalletConfig({
|
|
6533
6927
|
env: process.env,
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
appendWalletAuditEntry({
|
|
6539
|
-
action: "custody_initialized",
|
|
6540
|
-
actor: "control-ui",
|
|
6541
|
-
details: {
|
|
6542
|
-
walletId: initialized.walletId,
|
|
6543
|
-
role: initialized.role,
|
|
6544
|
-
migratedKeystores: initialized.migratedKeystores.length,
|
|
6545
|
-
signerRestarted: initialized.signerRestarted,
|
|
6546
|
-
signerRestartError: initialized.signerRestartError
|
|
6547
|
-
},
|
|
6548
|
-
env: process.env
|
|
6549
|
-
});
|
|
6550
|
-
sendLoginResponse(200, {
|
|
6551
|
-
ok: true,
|
|
6552
|
-
walletId: initialized.walletId,
|
|
6553
|
-
role: initialized.role,
|
|
6554
|
-
deviceShare: initialized.deviceShare,
|
|
6555
|
-
recoveryShare: initialized.recoveryShare,
|
|
6556
|
-
signerRestarted: initialized.signerRestarted,
|
|
6557
|
-
signerRestartError: initialized.signerRestartError,
|
|
6558
|
-
custody: readWalletCustodyStatus({
|
|
6559
|
-
wallet: walletCfg,
|
|
6560
|
-
env: process.env,
|
|
6561
|
-
cfg,
|
|
6562
|
-
walletId: initialized.walletId,
|
|
6563
|
-
approvalHost: host
|
|
6564
|
-
})
|
|
6565
|
-
});
|
|
6566
|
-
} catch (err) {
|
|
6567
|
-
sendLoginResponse(400, {
|
|
6568
|
-
ok: false,
|
|
6569
|
-
error: {
|
|
6570
|
-
code: "wallet_custody_init_failed",
|
|
6571
|
-
message: err instanceof Error ? err.message : String(err)
|
|
6928
|
+
mutate: (nextCfg) => {
|
|
6929
|
+
nextCfg.wallet = nextCfg.wallet ?? {};
|
|
6930
|
+
nextCfg.wallet.approvalAuth = nextCfg.wallet.approvalAuth ?? {};
|
|
6931
|
+
nextCfg.wallet.approvalAuth.mode = "none";
|
|
6572
6932
|
}
|
|
6573
6933
|
});
|
|
6934
|
+
if (!updated.ok) {
|
|
6935
|
+
sendLoginResponse(500, {
|
|
6936
|
+
ok: false,
|
|
6937
|
+
error: {
|
|
6938
|
+
code: "wallet_passkey_config_update_failed",
|
|
6939
|
+
message: updated.message
|
|
6940
|
+
}
|
|
6941
|
+
});
|
|
6942
|
+
return;
|
|
6943
|
+
}
|
|
6944
|
+
snapshotCfg = updated.cfg;
|
|
6574
6945
|
}
|
|
6946
|
+
appendWalletAuditEntry({
|
|
6947
|
+
action: "passkey_removed",
|
|
6948
|
+
actor: "control-ui",
|
|
6949
|
+
details: {
|
|
6950
|
+
passkeyId: result.passkey.id,
|
|
6951
|
+
label: result.passkey.label,
|
|
6952
|
+
approvalAuthMode: removingExistingPasskey && passkeys.length <= 1 ? "none" : undefined
|
|
6953
|
+
},
|
|
6954
|
+
env: process.env
|
|
6955
|
+
});
|
|
6956
|
+
sendLoginResponse(200, {
|
|
6957
|
+
ok: true,
|
|
6958
|
+
passkey: result.passkey,
|
|
6959
|
+
snapshot: removingExistingPasskey && passkeys.length <= 1 ? readWalletApprovalAuthSnapshot(process.env, snapshotCfg) : result.snapshot
|
|
6960
|
+
});
|
|
6575
6961
|
return;
|
|
6576
6962
|
}
|
|
6577
|
-
if (requestPath === "/api/wallet/
|
|
6963
|
+
if (requestPath === "/api/wallet/approval-auth/passkeys/register/options") {
|
|
6578
6964
|
if (req.method !== "POST") {
|
|
6579
6965
|
res.statusCode = 405;
|
|
6580
6966
|
res.setHeader("Allow", "POST");
|
|
@@ -6601,786 +6987,21 @@ function createGatewayHttpServer(opts) {
|
|
|
6601
6987
|
});
|
|
6602
6988
|
return;
|
|
6603
6989
|
}
|
|
6604
|
-
const
|
|
6605
|
-
const
|
|
6606
|
-
const recoveryShare = (typeof body.value?.recoveryShare === "string" ? String(body.value.recoveryShare).trim() : "") || undefined;
|
|
6990
|
+
const payloadRaw = body.value ?? {};
|
|
6991
|
+
const label = typeof payloadRaw.label === "string" ? payloadRaw.label.trim() || undefined : undefined;
|
|
6607
6992
|
const cfg = loadConfig();
|
|
6608
|
-
const
|
|
6993
|
+
const result = beginWalletPasskeyRegistration({
|
|
6609
6994
|
host,
|
|
6610
|
-
|
|
6611
|
-
token: approvalToken,
|
|
6612
|
-
env: process.env,
|
|
6613
|
-
cfg
|
|
6614
|
-
});
|
|
6615
|
-
if (!consumed.ok) {
|
|
6616
|
-
sendLoginResponse(401, {
|
|
6617
|
-
ok: false,
|
|
6618
|
-
error: {
|
|
6619
|
-
code: consumed.code ?? "approval_token_required",
|
|
6620
|
-
message: consumed.message ?? "passkey approval token is required"
|
|
6621
|
-
}
|
|
6622
|
-
});
|
|
6623
|
-
return;
|
|
6624
|
-
}
|
|
6625
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
6626
|
-
const recovered = recoverWalletCustodyCeremony({
|
|
6995
|
+
label,
|
|
6627
6996
|
env: process.env,
|
|
6628
|
-
walletId,
|
|
6629
|
-
recoveryShare,
|
|
6630
|
-
wallet: walletCfg,
|
|
6631
6997
|
cfg
|
|
6632
6998
|
});
|
|
6633
|
-
if (!
|
|
6999
|
+
if (!result.ok) {
|
|
6634
7000
|
sendLoginResponse(400, {
|
|
6635
7001
|
ok: false,
|
|
6636
7002
|
error: {
|
|
6637
|
-
code:
|
|
6638
|
-
message:
|
|
6639
|
-
}
|
|
6640
|
-
});
|
|
6641
|
-
return;
|
|
6642
|
-
}
|
|
6643
|
-
const locked = await lockWalletCustodyUnlockSessions({
|
|
6644
|
-
env: process.env,
|
|
6645
|
-
walletId: recovered.walletId
|
|
6646
|
-
});
|
|
6647
|
-
if (!locked.ok) {
|
|
6648
|
-
sendLoginResponse(400, {
|
|
6649
|
-
ok: false,
|
|
6650
|
-
error: {
|
|
6651
|
-
code: locked.code,
|
|
6652
|
-
message: locked.message
|
|
6653
|
-
}
|
|
6654
|
-
});
|
|
6655
|
-
return;
|
|
6656
|
-
}
|
|
6657
|
-
appendWalletAuditEntry({
|
|
6658
|
-
action: "custody_recovered",
|
|
6659
|
-
actor: "control-ui",
|
|
6660
|
-
details: {
|
|
6661
|
-
walletId: recovered.walletId,
|
|
6662
|
-
role: recovered.role
|
|
6663
|
-
},
|
|
6664
|
-
env: process.env
|
|
6665
|
-
});
|
|
6666
|
-
sendLoginResponse(200, {
|
|
6667
|
-
ok: true,
|
|
6668
|
-
walletId: recovered.walletId,
|
|
6669
|
-
role: recovered.role,
|
|
6670
|
-
deviceShare: recovered.deviceShare,
|
|
6671
|
-
recoveryShare: recovered.recoveryShare,
|
|
6672
|
-
custody: readWalletCustodyStatus({
|
|
6673
|
-
wallet: walletCfg,
|
|
6674
|
-
env: process.env,
|
|
6675
|
-
cfg,
|
|
6676
|
-
walletId: recovered.walletId,
|
|
6677
|
-
approvalHost: host
|
|
6678
|
-
})
|
|
6679
|
-
});
|
|
6680
|
-
return;
|
|
6681
|
-
}
|
|
6682
|
-
if (requestPath === "/api/wallet/custody/enroll-device") {
|
|
6683
|
-
if (req.method !== "POST") {
|
|
6684
|
-
res.statusCode = 405;
|
|
6685
|
-
res.setHeader("Allow", "POST");
|
|
6686
|
-
sendLoginResponse(405, {
|
|
6687
|
-
ok: false,
|
|
6688
|
-
error: {
|
|
6689
|
-
code: "method_not_allowed",
|
|
6690
|
-
message: "method must be POST"
|
|
6691
|
-
}
|
|
6692
|
-
});
|
|
6693
|
-
return;
|
|
6694
|
-
}
|
|
6695
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
6696
|
-
return;
|
|
6697
|
-
}
|
|
6698
|
-
const body = await readJsonBody(req, 64 * 1024);
|
|
6699
|
-
if (!body.ok) {
|
|
6700
|
-
sendLoginResponse(400, {
|
|
6701
|
-
ok: false,
|
|
6702
|
-
error: {
|
|
6703
|
-
code: "invalid_request",
|
|
6704
|
-
message: body.error
|
|
6705
|
-
}
|
|
6706
|
-
});
|
|
6707
|
-
return;
|
|
6708
|
-
}
|
|
6709
|
-
const approvalToken = String(getHeader(req, "x-wallet-approval-token") ?? "").trim();
|
|
6710
|
-
const walletId = (typeof body.value?.walletId === "string" ? String(body.value.walletId).trim() : "") || undefined;
|
|
6711
|
-
const deviceShare = String(getHeader(req, "x-wallet-device-share") ?? "").trim() || (typeof body.value?.deviceShare === "string" ? String(body.value.deviceShare).trim() : "") || undefined;
|
|
6712
|
-
const label = (typeof body.value?.label === "string" ? String(body.value.label).trim() : "") || undefined;
|
|
6713
|
-
const cfg = loadConfig();
|
|
6714
|
-
const consumed = consumeWalletApprovalGrant({
|
|
6715
|
-
host,
|
|
6716
|
-
operation: "wallet.custody-enroll-device",
|
|
6717
|
-
token: approvalToken,
|
|
6718
|
-
env: process.env,
|
|
6719
|
-
cfg
|
|
6720
|
-
});
|
|
6721
|
-
if (!consumed.ok) {
|
|
6722
|
-
sendLoginResponse(401, {
|
|
6723
|
-
ok: false,
|
|
6724
|
-
error: {
|
|
6725
|
-
code: consumed.code,
|
|
6726
|
-
message: consumed.message
|
|
6727
|
-
}
|
|
6728
|
-
});
|
|
6729
|
-
return;
|
|
6730
|
-
}
|
|
6731
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
6732
|
-
const enrolled = enrollWalletCustodyDevice({
|
|
6733
|
-
env: process.env,
|
|
6734
|
-
cfg,
|
|
6735
|
-
wallet: walletCfg,
|
|
6736
|
-
walletId,
|
|
6737
|
-
deviceShare,
|
|
6738
|
-
label
|
|
6739
|
-
});
|
|
6740
|
-
if (!enrolled.ok) {
|
|
6741
|
-
sendLoginResponse(400, {
|
|
6742
|
-
ok: false,
|
|
6743
|
-
error: {
|
|
6744
|
-
code: enrolled.code,
|
|
6745
|
-
message: enrolled.message
|
|
6746
|
-
}
|
|
6747
|
-
});
|
|
6748
|
-
return;
|
|
6749
|
-
}
|
|
6750
|
-
appendWalletAuditEntry({
|
|
6751
|
-
action: "custody_device_enrolled",
|
|
6752
|
-
actor: "control-ui",
|
|
6753
|
-
details: {
|
|
6754
|
-
walletId: enrolled.walletId,
|
|
6755
|
-
role: enrolled.role,
|
|
6756
|
-
deviceId: enrolled.deviceId,
|
|
6757
|
-
label: enrolled.label
|
|
6758
|
-
},
|
|
6759
|
-
env: process.env
|
|
6760
|
-
});
|
|
6761
|
-
sendLoginResponse(200, {
|
|
6762
|
-
ok: true,
|
|
6763
|
-
walletId: enrolled.walletId,
|
|
6764
|
-
role: enrolled.role,
|
|
6765
|
-
deviceId: enrolled.deviceId,
|
|
6766
|
-
label: enrolled.label,
|
|
6767
|
-
deviceShare: enrolled.deviceShare,
|
|
6768
|
-
custody: readWalletCustodyStatus({
|
|
6769
|
-
wallet: walletCfg,
|
|
6770
|
-
env: process.env,
|
|
6771
|
-
cfg,
|
|
6772
|
-
walletId: enrolled.walletId,
|
|
6773
|
-
approvalHost: host
|
|
6774
|
-
})
|
|
6775
|
-
});
|
|
6776
|
-
return;
|
|
6777
|
-
}
|
|
6778
|
-
if (requestPath === "/api/wallet/custody/revoke-device") {
|
|
6779
|
-
if (req.method !== "POST") {
|
|
6780
|
-
res.statusCode = 405;
|
|
6781
|
-
res.setHeader("Allow", "POST");
|
|
6782
|
-
sendLoginResponse(405, {
|
|
6783
|
-
ok: false,
|
|
6784
|
-
error: {
|
|
6785
|
-
code: "method_not_allowed",
|
|
6786
|
-
message: "method must be POST"
|
|
6787
|
-
}
|
|
6788
|
-
});
|
|
6789
|
-
return;
|
|
6790
|
-
}
|
|
6791
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
6792
|
-
return;
|
|
6793
|
-
}
|
|
6794
|
-
const body = await readJsonBody(req, 64 * 1024);
|
|
6795
|
-
if (!body.ok) {
|
|
6796
|
-
sendLoginResponse(400, {
|
|
6797
|
-
ok: false,
|
|
6798
|
-
error: {
|
|
6799
|
-
code: "invalid_request",
|
|
6800
|
-
message: body.error
|
|
6801
|
-
}
|
|
6802
|
-
});
|
|
6803
|
-
return;
|
|
6804
|
-
}
|
|
6805
|
-
const approvalToken = String(getHeader(req, "x-wallet-approval-token") ?? "").trim();
|
|
6806
|
-
const walletId = (typeof body.value?.walletId === "string" ? String(body.value.walletId).trim() : "") || undefined;
|
|
6807
|
-
const deviceId = (typeof body.value?.deviceId === "string" ? String(body.value.deviceId).trim() : "") || undefined;
|
|
6808
|
-
const deviceShare = String(getHeader(req, "x-wallet-device-share") ?? "").trim() || (typeof body.value?.deviceShare === "string" ? String(body.value.deviceShare).trim() : "") || undefined;
|
|
6809
|
-
const cfg = loadConfig();
|
|
6810
|
-
const consumed = consumeWalletApprovalGrant({
|
|
6811
|
-
host,
|
|
6812
|
-
operation: "wallet.custody-revoke-device",
|
|
6813
|
-
token: approvalToken,
|
|
6814
|
-
env: process.env,
|
|
6815
|
-
cfg
|
|
6816
|
-
});
|
|
6817
|
-
if (!consumed.ok) {
|
|
6818
|
-
sendLoginResponse(401, {
|
|
6819
|
-
ok: false,
|
|
6820
|
-
error: {
|
|
6821
|
-
code: consumed.code,
|
|
6822
|
-
message: consumed.message
|
|
6823
|
-
}
|
|
6824
|
-
});
|
|
6825
|
-
return;
|
|
6826
|
-
}
|
|
6827
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
6828
|
-
const revoked = revokeWalletCustodyDevice({
|
|
6829
|
-
env: process.env,
|
|
6830
|
-
cfg,
|
|
6831
|
-
wallet: walletCfg,
|
|
6832
|
-
walletId,
|
|
6833
|
-
deviceId,
|
|
6834
|
-
deviceShare
|
|
6835
|
-
});
|
|
6836
|
-
if (!revoked.ok) {
|
|
6837
|
-
sendLoginResponse(400, {
|
|
6838
|
-
ok: false,
|
|
6839
|
-
error: {
|
|
6840
|
-
code: revoked.code,
|
|
6841
|
-
message: revoked.message
|
|
6842
|
-
}
|
|
6843
|
-
});
|
|
6844
|
-
return;
|
|
6845
|
-
}
|
|
6846
|
-
appendWalletAuditEntry({
|
|
6847
|
-
action: "custody_device_revoked",
|
|
6848
|
-
actor: "control-ui",
|
|
6849
|
-
details: {
|
|
6850
|
-
walletId: revoked.walletId,
|
|
6851
|
-
role: revoked.role,
|
|
6852
|
-
deviceId: revoked.removedDeviceId,
|
|
6853
|
-
label: revoked.removedDeviceLabel
|
|
6854
|
-
},
|
|
6855
|
-
env: process.env
|
|
6856
|
-
});
|
|
6857
|
-
sendLoginResponse(200, {
|
|
6858
|
-
ok: true,
|
|
6859
|
-
walletId: revoked.walletId,
|
|
6860
|
-
role: revoked.role,
|
|
6861
|
-
removedDeviceId: revoked.removedDeviceId,
|
|
6862
|
-
removedDeviceLabel: revoked.removedDeviceLabel,
|
|
6863
|
-
custody: readWalletCustodyStatus({
|
|
6864
|
-
wallet: walletCfg,
|
|
6865
|
-
env: process.env,
|
|
6866
|
-
cfg,
|
|
6867
|
-
walletId: revoked.walletId,
|
|
6868
|
-
approvalHost: host
|
|
6869
|
-
})
|
|
6870
|
-
});
|
|
6871
|
-
return;
|
|
6872
|
-
}
|
|
6873
|
-
if (requestPath === "/api/wallet/custody/disable") {
|
|
6874
|
-
if (req.method !== "POST") {
|
|
6875
|
-
res.statusCode = 405;
|
|
6876
|
-
res.setHeader("Allow", "POST");
|
|
6877
|
-
sendLoginResponse(405, {
|
|
6878
|
-
ok: false,
|
|
6879
|
-
error: {
|
|
6880
|
-
code: "method_not_allowed",
|
|
6881
|
-
message: "method must be POST"
|
|
6882
|
-
}
|
|
6883
|
-
});
|
|
6884
|
-
return;
|
|
6885
|
-
}
|
|
6886
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
6887
|
-
return;
|
|
6888
|
-
}
|
|
6889
|
-
const body = await readJsonBody(req, 64 * 1024);
|
|
6890
|
-
if (!body.ok) {
|
|
6891
|
-
sendLoginResponse(400, {
|
|
6892
|
-
ok: false,
|
|
6893
|
-
error: {
|
|
6894
|
-
code: "invalid_request",
|
|
6895
|
-
message: body.error
|
|
6896
|
-
}
|
|
6897
|
-
});
|
|
6898
|
-
return;
|
|
6899
|
-
}
|
|
6900
|
-
const approvalToken = String(getHeader(req, "x-wallet-approval-token") ?? "").trim();
|
|
6901
|
-
const walletId = (typeof body.value?.walletId === "string" ? String(body.value.walletId).trim() : "") || undefined;
|
|
6902
|
-
const deviceShare = String(getHeader(req, "x-wallet-device-share") ?? "").trim() || (typeof body.value?.deviceShare === "string" ? String(body.value.deviceShare).trim() : "") || undefined;
|
|
6903
|
-
const recoveryShare = (typeof body.value?.recoveryShare === "string" ? String(body.value.recoveryShare).trim() : "") || undefined;
|
|
6904
|
-
const cfg = loadConfig();
|
|
6905
|
-
const consumed = consumeWalletApprovalGrant({
|
|
6906
|
-
host,
|
|
6907
|
-
operation: "wallet.custody-disable",
|
|
6908
|
-
token: approvalToken,
|
|
6909
|
-
env: process.env,
|
|
6910
|
-
cfg
|
|
6911
|
-
});
|
|
6912
|
-
if (!consumed.ok) {
|
|
6913
|
-
sendLoginResponse(401, {
|
|
6914
|
-
ok: false,
|
|
6915
|
-
error: {
|
|
6916
|
-
code: consumed.code,
|
|
6917
|
-
message: consumed.message
|
|
6918
|
-
}
|
|
6919
|
-
});
|
|
6920
|
-
return;
|
|
6921
|
-
}
|
|
6922
|
-
try {
|
|
6923
|
-
const disabled = await disableWalletCustodyForWallet({
|
|
6924
|
-
env: process.env,
|
|
6925
|
-
cfg,
|
|
6926
|
-
walletId,
|
|
6927
|
-
deviceShare,
|
|
6928
|
-
recoveryShare
|
|
6929
|
-
});
|
|
6930
|
-
const nextCfg = loadConfig();
|
|
6931
|
-
const walletCfg = resolveWalletRuntimeConfig(nextCfg, process.env);
|
|
6932
|
-
appendWalletAuditEntry({
|
|
6933
|
-
action: "custody_disabled",
|
|
6934
|
-
actor: "control-ui",
|
|
6935
|
-
details: {
|
|
6936
|
-
walletId: disabled.walletId,
|
|
6937
|
-
migratedKeystores: disabled.migratedKeystores.length,
|
|
6938
|
-
remainingCustodyWallets: disabled.remainingCustodyWallets,
|
|
6939
|
-
signerRestarted: disabled.signerRestarted,
|
|
6940
|
-
signerRestartError: disabled.signerRestartError
|
|
6941
|
-
},
|
|
6942
|
-
env: process.env
|
|
6943
|
-
});
|
|
6944
|
-
sendLoginResponse(200, {
|
|
6945
|
-
ok: true,
|
|
6946
|
-
walletId: disabled.walletId,
|
|
6947
|
-
migratedKeystores: disabled.migratedKeystores,
|
|
6948
|
-
remainingCustodyWallets: disabled.remainingCustodyWallets,
|
|
6949
|
-
signerRestarted: disabled.signerRestarted,
|
|
6950
|
-
signerRestartError: disabled.signerRestartError,
|
|
6951
|
-
custody: readWalletCustodyStatus({
|
|
6952
|
-
wallet: walletCfg,
|
|
6953
|
-
env: process.env,
|
|
6954
|
-
cfg: nextCfg,
|
|
6955
|
-
walletId: disabled.walletId,
|
|
6956
|
-
approvalHost: host
|
|
6957
|
-
})
|
|
6958
|
-
});
|
|
6959
|
-
} catch (err) {
|
|
6960
|
-
sendLoginResponse(400, {
|
|
6961
|
-
ok: false,
|
|
6962
|
-
error: {
|
|
6963
|
-
code: "wallet_custody_disable_failed",
|
|
6964
|
-
message: err instanceof Error ? err.message : String(err)
|
|
6965
|
-
}
|
|
6966
|
-
});
|
|
6967
|
-
}
|
|
6968
|
-
return;
|
|
6969
|
-
}
|
|
6970
|
-
if (requestPath === "/api/wallet/custody/unlock") {
|
|
6971
|
-
if (req.method !== "POST") {
|
|
6972
|
-
res.statusCode = 405;
|
|
6973
|
-
res.setHeader("Allow", "POST");
|
|
6974
|
-
sendLoginResponse(405, {
|
|
6975
|
-
ok: false,
|
|
6976
|
-
error: {
|
|
6977
|
-
code: "method_not_allowed",
|
|
6978
|
-
message: "method must be POST"
|
|
6979
|
-
}
|
|
6980
|
-
});
|
|
6981
|
-
return;
|
|
6982
|
-
}
|
|
6983
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
6984
|
-
return;
|
|
6985
|
-
}
|
|
6986
|
-
const body = await readJsonBody(req, 64 * 1024);
|
|
6987
|
-
if (!body.ok) {
|
|
6988
|
-
sendLoginResponse(400, {
|
|
6989
|
-
ok: false,
|
|
6990
|
-
error: {
|
|
6991
|
-
code: "invalid_request",
|
|
6992
|
-
message: body.error
|
|
6993
|
-
}
|
|
6994
|
-
});
|
|
6995
|
-
return;
|
|
6996
|
-
}
|
|
6997
|
-
const deviceShare = String(getHeader(req, "x-wallet-device-share") ?? "").trim() || (typeof body.value?.deviceShare === "string" ? String(body.value.deviceShare).trim() : "");
|
|
6998
|
-
const cfg = loadConfig();
|
|
6999
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
7000
|
-
const approvalToken = String(getHeader(req, "x-wallet-approval-token") ?? "").trim();
|
|
7001
|
-
const walletId = (typeof body.value?.walletId === "string" ? String(body.value.walletId).trim() : "") || undefined;
|
|
7002
|
-
const ttlSecondsRaw = body.value?.ttlSeconds;
|
|
7003
|
-
const ttlSeconds = typeof ttlSecondsRaw === "number" && Number.isFinite(ttlSecondsRaw) ? ttlSecondsRaw : typeof ttlSecondsRaw === "string" && ttlSecondsRaw.trim() ? Number(ttlSecondsRaw) : undefined;
|
|
7004
|
-
const unlocked = await activateWalletCustodyUnlockSession({
|
|
7005
|
-
host,
|
|
7006
|
-
approvalToken,
|
|
7007
|
-
env: process.env,
|
|
7008
|
-
cfg,
|
|
7009
|
-
wallet: walletCfg,
|
|
7010
|
-
walletId,
|
|
7011
|
-
deviceShare,
|
|
7012
|
-
ttlSeconds
|
|
7013
|
-
});
|
|
7014
|
-
if (!unlocked.ok) {
|
|
7015
|
-
const authCodes = new Set([
|
|
7016
|
-
"approval_token_required",
|
|
7017
|
-
"invalid_approval_token",
|
|
7018
|
-
"host_mismatch",
|
|
7019
|
-
"operation_mismatch",
|
|
7020
|
-
"request_mismatch",
|
|
7021
|
-
"wallet_control_passkey_not_ready"
|
|
7022
|
-
]);
|
|
7023
|
-
const status = authCodes.has(unlocked.code) ? 401 : 400;
|
|
7024
|
-
sendLoginResponse(status, {
|
|
7025
|
-
ok: false,
|
|
7026
|
-
error: {
|
|
7027
|
-
code: unlocked.code ?? "wallet_custody_unlock_failed",
|
|
7028
|
-
message: unlocked.message ?? "wallet custody unlock failed"
|
|
7029
|
-
}
|
|
7030
|
-
});
|
|
7031
|
-
return;
|
|
7032
|
-
}
|
|
7033
|
-
appendWalletAuditEntry({
|
|
7034
|
-
action: "custody_unlocked",
|
|
7035
|
-
actor: "control-ui",
|
|
7036
|
-
details: {
|
|
7037
|
-
walletId: unlocked.session.walletId,
|
|
7038
|
-
host: unlocked.session.host,
|
|
7039
|
-
expiresAt: unlocked.session.expiresAt
|
|
7040
|
-
},
|
|
7041
|
-
env: process.env
|
|
7042
|
-
});
|
|
7043
|
-
sendLoginResponse(200, {
|
|
7044
|
-
ok: true,
|
|
7045
|
-
session: {
|
|
7046
|
-
id: unlocked.session.id,
|
|
7047
|
-
host: unlocked.session.host,
|
|
7048
|
-
expiresAt: unlocked.session.expiresAt
|
|
7049
|
-
},
|
|
7050
|
-
custody: readWalletCustodyStatus({
|
|
7051
|
-
wallet: walletCfg,
|
|
7052
|
-
env: process.env,
|
|
7053
|
-
cfg,
|
|
7054
|
-
walletId,
|
|
7055
|
-
approvalHost: host
|
|
7056
|
-
})
|
|
7057
|
-
});
|
|
7058
|
-
return;
|
|
7059
|
-
}
|
|
7060
|
-
if (requestPath === "/api/wallet/custody/refresh") {
|
|
7061
|
-
if (req.method !== "POST") {
|
|
7062
|
-
res.statusCode = 405;
|
|
7063
|
-
res.setHeader("Allow", "POST");
|
|
7064
|
-
sendLoginResponse(405, {
|
|
7065
|
-
ok: false,
|
|
7066
|
-
error: {
|
|
7067
|
-
code: "method_not_allowed",
|
|
7068
|
-
message: "method must be POST"
|
|
7069
|
-
}
|
|
7070
|
-
});
|
|
7071
|
-
return;
|
|
7072
|
-
}
|
|
7073
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
7074
|
-
return;
|
|
7075
|
-
}
|
|
7076
|
-
const body = await readJsonBody(req, 64 * 1024);
|
|
7077
|
-
if (!body.ok) {
|
|
7078
|
-
sendLoginResponse(400, {
|
|
7079
|
-
ok: false,
|
|
7080
|
-
error: {
|
|
7081
|
-
code: "invalid_request",
|
|
7082
|
-
message: body.error
|
|
7083
|
-
}
|
|
7084
|
-
});
|
|
7085
|
-
return;
|
|
7086
|
-
}
|
|
7087
|
-
const deviceShare = String(getHeader(req, "x-wallet-device-share") ?? "").trim() || (typeof body.value?.deviceShare === "string" ? String(body.value.deviceShare).trim() : "");
|
|
7088
|
-
const cfg = loadConfig();
|
|
7089
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
7090
|
-
const walletId = (typeof body.value?.walletId === "string" ? String(body.value.walletId).trim() : "") || undefined;
|
|
7091
|
-
const ttlSecondsRaw = body.value?.ttlSeconds;
|
|
7092
|
-
const ttlSeconds = typeof ttlSecondsRaw === "number" && Number.isFinite(ttlSecondsRaw) ? ttlSecondsRaw : typeof ttlSecondsRaw === "string" && ttlSecondsRaw.trim() ? Number(ttlSecondsRaw) : undefined;
|
|
7093
|
-
const refreshed = await refreshWalletCustodyUnlockSession({
|
|
7094
|
-
host,
|
|
7095
|
-
env: process.env,
|
|
7096
|
-
cfg,
|
|
7097
|
-
wallet: walletCfg,
|
|
7098
|
-
walletId,
|
|
7099
|
-
deviceShare,
|
|
7100
|
-
ttlSeconds
|
|
7101
|
-
});
|
|
7102
|
-
if (!refreshed.ok) {
|
|
7103
|
-
const authCodes = new Set([
|
|
7104
|
-
"custody_device_share_required",
|
|
7105
|
-
"custody_device_share_mismatch",
|
|
7106
|
-
"custody_unlock_required"
|
|
7107
|
-
]);
|
|
7108
|
-
const status = authCodes.has(refreshed.code) ? 401 : 400;
|
|
7109
|
-
sendLoginResponse(status, {
|
|
7110
|
-
ok: false,
|
|
7111
|
-
error: {
|
|
7112
|
-
code: refreshed.code ?? "wallet_custody_refresh_failed",
|
|
7113
|
-
message: refreshed.message ?? "wallet custody refresh failed"
|
|
7114
|
-
}
|
|
7115
|
-
});
|
|
7116
|
-
return;
|
|
7117
|
-
}
|
|
7118
|
-
sendLoginResponse(200, {
|
|
7119
|
-
ok: true,
|
|
7120
|
-
session: {
|
|
7121
|
-
id: refreshed.session.id,
|
|
7122
|
-
host: refreshed.session.host,
|
|
7123
|
-
expiresAt: refreshed.session.expiresAt
|
|
7124
|
-
},
|
|
7125
|
-
custody: readWalletCustodyStatus({
|
|
7126
|
-
wallet: walletCfg,
|
|
7127
|
-
env: process.env,
|
|
7128
|
-
cfg,
|
|
7129
|
-
walletId: refreshed.session.walletId,
|
|
7130
|
-
approvalHost: host
|
|
7131
|
-
})
|
|
7132
|
-
});
|
|
7133
|
-
return;
|
|
7134
|
-
}
|
|
7135
|
-
if (requestPath === "/api/wallet/custody/lock") {
|
|
7136
|
-
if (req.method !== "POST") {
|
|
7137
|
-
res.statusCode = 405;
|
|
7138
|
-
res.setHeader("Allow", "POST");
|
|
7139
|
-
sendLoginResponse(405, {
|
|
7140
|
-
ok: false,
|
|
7141
|
-
error: {
|
|
7142
|
-
code: "method_not_allowed",
|
|
7143
|
-
message: "method must be POST"
|
|
7144
|
-
}
|
|
7145
|
-
});
|
|
7146
|
-
return;
|
|
7147
|
-
}
|
|
7148
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
7149
|
-
return;
|
|
7150
|
-
}
|
|
7151
|
-
const body = await readJsonBody(req, 32 * 1024);
|
|
7152
|
-
if (!body.ok) {
|
|
7153
|
-
sendLoginResponse(400, {
|
|
7154
|
-
ok: false,
|
|
7155
|
-
error: {
|
|
7156
|
-
code: "invalid_request",
|
|
7157
|
-
message: body.error
|
|
7158
|
-
}
|
|
7159
|
-
});
|
|
7160
|
-
return;
|
|
7161
|
-
}
|
|
7162
|
-
const cfg = loadConfig();
|
|
7163
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
7164
|
-
const walletId = (typeof body.value?.walletId === "string" ? String(body.value.walletId).trim() : "") || undefined;
|
|
7165
|
-
const locked = await lockWalletCustodyUnlockSessions({
|
|
7166
|
-
env: process.env,
|
|
7167
|
-
host,
|
|
7168
|
-
walletId
|
|
7169
|
-
});
|
|
7170
|
-
if (!locked.ok) {
|
|
7171
|
-
sendLoginResponse(400, {
|
|
7172
|
-
ok: false,
|
|
7173
|
-
error: {
|
|
7174
|
-
code: locked.code ?? "wallet_custody_lock_failed",
|
|
7175
|
-
message: locked.message ?? "wallet custody lock failed"
|
|
7176
|
-
}
|
|
7177
|
-
});
|
|
7178
|
-
return;
|
|
7179
|
-
}
|
|
7180
|
-
appendWalletAuditEntry({
|
|
7181
|
-
action: "custody_locked",
|
|
7182
|
-
actor: "control-ui",
|
|
7183
|
-
details: {
|
|
7184
|
-
walletId: locked.walletId ?? walletId ?? "default",
|
|
7185
|
-
removed: locked.removed
|
|
7186
|
-
},
|
|
7187
|
-
env: process.env
|
|
7188
|
-
});
|
|
7189
|
-
sendLoginResponse(200, {
|
|
7190
|
-
ok: true,
|
|
7191
|
-
removed: locked.removed,
|
|
7192
|
-
remaining: locked.remaining,
|
|
7193
|
-
custody: readWalletCustodyStatus({
|
|
7194
|
-
wallet: walletCfg,
|
|
7195
|
-
env: process.env,
|
|
7196
|
-
cfg,
|
|
7197
|
-
walletId: locked.walletId ?? walletId,
|
|
7198
|
-
approvalHost: host
|
|
7199
|
-
})
|
|
7200
|
-
});
|
|
7201
|
-
return;
|
|
7202
|
-
}
|
|
7203
|
-
if (requestPath === "/api/wallet/approval-auth/status") {
|
|
7204
|
-
if (req.method !== "GET") {
|
|
7205
|
-
res.statusCode = 405;
|
|
7206
|
-
res.setHeader("Allow", "GET");
|
|
7207
|
-
sendLoginResponse(405, {
|
|
7208
|
-
ok: false,
|
|
7209
|
-
error: {
|
|
7210
|
-
code: "method_not_allowed",
|
|
7211
|
-
message: "method must be GET"
|
|
7212
|
-
}
|
|
7213
|
-
});
|
|
7214
|
-
return;
|
|
7215
|
-
}
|
|
7216
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
7217
|
-
return;
|
|
7218
|
-
}
|
|
7219
|
-
const cfg = loadConfig();
|
|
7220
|
-
sendLoginResponse(200, {
|
|
7221
|
-
ok: true,
|
|
7222
|
-
status: readWalletApprovalAuthSnapshot(process.env, cfg),
|
|
7223
|
-
challengeTtlSeconds: resolveWalletApprovalChallengeTtlSeconds(process.env, cfg),
|
|
7224
|
-
grantTtlSeconds: resolveWalletApprovalGrantTtlSeconds(process.env, cfg)
|
|
7225
|
-
});
|
|
7226
|
-
return;
|
|
7227
|
-
}
|
|
7228
|
-
if (requestPath === "/api/wallet/approval-auth/passkeys") {
|
|
7229
|
-
if (req.method !== "GET") {
|
|
7230
|
-
res.statusCode = 405;
|
|
7231
|
-
res.setHeader("Allow", "GET");
|
|
7232
|
-
sendLoginResponse(405, {
|
|
7233
|
-
ok: false,
|
|
7234
|
-
error: {
|
|
7235
|
-
code: "method_not_allowed",
|
|
7236
|
-
message: "method must be GET"
|
|
7237
|
-
}
|
|
7238
|
-
});
|
|
7239
|
-
return;
|
|
7240
|
-
}
|
|
7241
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
7242
|
-
return;
|
|
7243
|
-
}
|
|
7244
|
-
const cfg = loadConfig();
|
|
7245
|
-
sendLoginResponse(200, {
|
|
7246
|
-
ok: true,
|
|
7247
|
-
passkeys: listWalletPasskeys(process.env, cfg)
|
|
7248
|
-
});
|
|
7249
|
-
return;
|
|
7250
|
-
}
|
|
7251
|
-
if (requestPath.startsWith("/api/wallet/approval-auth/passkeys/") && !requestPath.startsWith("/api/wallet/approval-auth/passkeys/register/")) {
|
|
7252
|
-
if (req.method !== "DELETE") {
|
|
7253
|
-
res.statusCode = 405;
|
|
7254
|
-
res.setHeader("Allow", "DELETE");
|
|
7255
|
-
sendLoginResponse(405, {
|
|
7256
|
-
ok: false,
|
|
7257
|
-
error: {
|
|
7258
|
-
code: "method_not_allowed",
|
|
7259
|
-
message: "method must be DELETE"
|
|
7260
|
-
}
|
|
7261
|
-
});
|
|
7262
|
-
return;
|
|
7263
|
-
}
|
|
7264
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
7265
|
-
return;
|
|
7266
|
-
}
|
|
7267
|
-
const credentialId = decodeURIComponent(requestPath.slice("/api/wallet/approval-auth/passkeys/".length)).trim();
|
|
7268
|
-
const cfg = loadConfig();
|
|
7269
|
-
if (!ensureWalletApprovalAuthorized({
|
|
7270
|
-
operation: "wallet.passkey-remove",
|
|
7271
|
-
cfg
|
|
7272
|
-
})) {
|
|
7273
|
-
return;
|
|
7274
|
-
}
|
|
7275
|
-
const passkeys = listWalletPasskeys(process.env, cfg);
|
|
7276
|
-
const removingExistingPasskey = passkeys.some((passkey) => passkey.id === credentialId);
|
|
7277
|
-
const splitKeyWallets = listSplitKeyWalletsBlockingPasskeyRemoval(cfg, process.env);
|
|
7278
|
-
if (removingExistingPasskey && passkeys.length <= 1 && splitKeyWallets.length > 0) {
|
|
7279
|
-
sendLoginResponse(409, {
|
|
7280
|
-
ok: false,
|
|
7281
|
-
error: {
|
|
7282
|
-
code: "wallet_passkey_required_by_split_key",
|
|
7283
|
-
message: formatSplitKeyPasskeyBlockMessage(splitKeyWallets)
|
|
7284
|
-
}
|
|
7285
|
-
});
|
|
7286
|
-
return;
|
|
7287
|
-
}
|
|
7288
|
-
const result = removeWalletPasskey({
|
|
7289
|
-
credentialId,
|
|
7290
|
-
env: process.env,
|
|
7291
|
-
cfg
|
|
7292
|
-
});
|
|
7293
|
-
if (!result.ok) {
|
|
7294
|
-
sendLoginResponse(400, {
|
|
7295
|
-
ok: false,
|
|
7296
|
-
error: {
|
|
7297
|
-
code: result.code,
|
|
7298
|
-
message: result.message
|
|
7299
|
-
}
|
|
7300
|
-
});
|
|
7301
|
-
return;
|
|
7302
|
-
}
|
|
7303
|
-
let snapshotCfg = cfg;
|
|
7304
|
-
if (removingExistingPasskey && passkeys.length <= 1) {
|
|
7305
|
-
const updated = await updateWalletConfig({
|
|
7306
|
-
env: process.env,
|
|
7307
|
-
mutate: (nextCfg) => {
|
|
7308
|
-
nextCfg.wallet = nextCfg.wallet ?? {};
|
|
7309
|
-
nextCfg.wallet.approvalAuth = nextCfg.wallet.approvalAuth ?? {};
|
|
7310
|
-
nextCfg.wallet.approvalAuth.mode = "none";
|
|
7311
|
-
}
|
|
7312
|
-
});
|
|
7313
|
-
if (!updated.ok) {
|
|
7314
|
-
sendLoginResponse(500, {
|
|
7315
|
-
ok: false,
|
|
7316
|
-
error: {
|
|
7317
|
-
code: "wallet_passkey_config_update_failed",
|
|
7318
|
-
message: updated.message
|
|
7319
|
-
}
|
|
7320
|
-
});
|
|
7321
|
-
return;
|
|
7322
|
-
}
|
|
7323
|
-
snapshotCfg = updated.cfg;
|
|
7324
|
-
}
|
|
7325
|
-
appendWalletAuditEntry({
|
|
7326
|
-
action: "passkey_removed",
|
|
7327
|
-
actor: "control-ui",
|
|
7328
|
-
details: {
|
|
7329
|
-
passkeyId: result.passkey.id,
|
|
7330
|
-
label: result.passkey.label,
|
|
7331
|
-
approvalAuthMode: removingExistingPasskey && passkeys.length <= 1 ? "none" : undefined
|
|
7332
|
-
},
|
|
7333
|
-
env: process.env
|
|
7334
|
-
});
|
|
7335
|
-
sendLoginResponse(200, {
|
|
7336
|
-
ok: true,
|
|
7337
|
-
passkey: result.passkey,
|
|
7338
|
-
snapshot: removingExistingPasskey && passkeys.length <= 1 ? readWalletApprovalAuthSnapshot(process.env, snapshotCfg) : result.snapshot
|
|
7339
|
-
});
|
|
7340
|
-
return;
|
|
7341
|
-
}
|
|
7342
|
-
if (requestPath === "/api/wallet/approval-auth/passkeys/register/options") {
|
|
7343
|
-
if (req.method !== "POST") {
|
|
7344
|
-
res.statusCode = 405;
|
|
7345
|
-
res.setHeader("Allow", "POST");
|
|
7346
|
-
sendLoginResponse(405, {
|
|
7347
|
-
ok: false,
|
|
7348
|
-
error: {
|
|
7349
|
-
code: "method_not_allowed",
|
|
7350
|
-
message: "method must be POST"
|
|
7351
|
-
}
|
|
7352
|
-
});
|
|
7353
|
-
return;
|
|
7354
|
-
}
|
|
7355
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
7356
|
-
return;
|
|
7357
|
-
}
|
|
7358
|
-
const body = await readJsonBody(req, 64 * 1024);
|
|
7359
|
-
if (!body.ok) {
|
|
7360
|
-
sendLoginResponse(400, {
|
|
7361
|
-
ok: false,
|
|
7362
|
-
error: {
|
|
7363
|
-
code: "invalid_request",
|
|
7364
|
-
message: body.error
|
|
7365
|
-
}
|
|
7366
|
-
});
|
|
7367
|
-
return;
|
|
7368
|
-
}
|
|
7369
|
-
const payloadRaw = body.value ?? {};
|
|
7370
|
-
const label = typeof payloadRaw.label === "string" ? payloadRaw.label.trim() || undefined : undefined;
|
|
7371
|
-
const cfg = loadConfig();
|
|
7372
|
-
const result = beginWalletPasskeyRegistration({
|
|
7373
|
-
host,
|
|
7374
|
-
label,
|
|
7375
|
-
env: process.env,
|
|
7376
|
-
cfg
|
|
7377
|
-
});
|
|
7378
|
-
if (!result.ok) {
|
|
7379
|
-
sendLoginResponse(400, {
|
|
7380
|
-
ok: false,
|
|
7381
|
-
error: {
|
|
7382
|
-
code: result.code,
|
|
7383
|
-
message: result.message
|
|
7003
|
+
code: result.code,
|
|
7004
|
+
message: result.message
|
|
7384
7005
|
}
|
|
7385
7006
|
});
|
|
7386
7007
|
return;
|
|
@@ -7508,7 +7129,7 @@ function createGatewayHttpServer(opts) {
|
|
|
7508
7129
|
});
|
|
7509
7130
|
return;
|
|
7510
7131
|
}
|
|
7511
|
-
if (operation !== "wallet.approve" && operation !== "wallet.policy" && operation !== "wallet.settings" && operation !== "wallet.provider-credentials" && operation !== "wallet.passkey-enroll" && operation !== "wallet.passkey-remove" && operation !== "wallet.
|
|
7132
|
+
if (operation !== "wallet.approve" && operation !== "wallet.policy" && operation !== "wallet.settings" && operation !== "wallet.provider-credentials" && operation !== "wallet.passkey-enroll" && operation !== "wallet.passkey-remove" && operation !== "wallet.reset" && operation !== "wallet.rotate" && operation !== "wallet.execution-mode" && operation !== "wallet.send" && operation !== "mining.capital" && operation !== "mining.policy") {
|
|
7512
7133
|
sendLoginResponse(400, {
|
|
7513
7134
|
ok: false,
|
|
7514
7135
|
error: {
|
|
@@ -7642,7 +7263,7 @@ function createGatewayHttpServer(opts) {
|
|
|
7642
7263
|
const payloadRaw = body.value ?? {};
|
|
7643
7264
|
const requestId = typeof payloadRaw.requestId === "string" ? payloadRaw.requestId.trim() || undefined : undefined;
|
|
7644
7265
|
const operation = typeof payloadRaw.operation === "string" ? payloadRaw.operation.trim() || undefined : undefined;
|
|
7645
|
-
if (operation && operation !== "wallet.approve" && operation !== "wallet.policy" && operation !== "wallet.settings" && operation !== "wallet.provider-credentials" && operation !== "wallet.passkey-enroll" && operation !== "wallet.passkey-remove" && operation !== "wallet.
|
|
7266
|
+
if (operation && operation !== "wallet.approve" && operation !== "wallet.policy" && operation !== "wallet.settings" && operation !== "wallet.provider-credentials" && operation !== "wallet.passkey-enroll" && operation !== "wallet.passkey-remove" && operation !== "wallet.reset" && operation !== "wallet.rotate" && operation !== "wallet.execution-mode" && operation !== "wallet.send" && operation !== "mining.capital" && operation !== "mining.policy") {
|
|
7646
7267
|
sendLoginResponse(400, {
|
|
7647
7268
|
ok: false,
|
|
7648
7269
|
error: {
|
|
@@ -7694,171 +7315,37 @@ function createGatewayHttpServer(opts) {
|
|
|
7694
7315
|
if (!await ensureWalletApiAuthorized()) {
|
|
7695
7316
|
return;
|
|
7696
7317
|
}
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
ok: false,
|
|
7704
|
-
error: {
|
|
7705
|
-
code: "wallet_disabled",
|
|
7706
|
-
message: "wallet is disabled"
|
|
7707
|
-
}
|
|
7708
|
-
});
|
|
7709
|
-
return;
|
|
7710
|
-
}
|
|
7711
|
-
if (!ensureWalletApprovalAuthorized({
|
|
7712
|
-
operation: "wallet.rotate",
|
|
7713
|
-
cfg
|
|
7714
|
-
})) {
|
|
7715
|
-
return;
|
|
7716
|
-
}
|
|
7717
|
-
const provider = createWalletProviderAdapter({
|
|
7718
|
-
cfg,
|
|
7719
|
-
wallet: walletCfg,
|
|
7720
|
-
env: process.env,
|
|
7721
|
-
providerIdOverride: providerId
|
|
7722
|
-
});
|
|
7723
|
-
if (!provider.rotateKeys) {
|
|
7724
|
-
sendLoginResponse(400, {
|
|
7725
|
-
ok: false,
|
|
7726
|
-
error: {
|
|
7727
|
-
code: "provider_operation_not_supported",
|
|
7728
|
-
message: `provider ${provider.id} does not support rotate keys`
|
|
7729
|
-
}
|
|
7730
|
-
});
|
|
7731
|
-
return;
|
|
7732
|
-
}
|
|
7733
|
-
let result;
|
|
7734
|
-
try {
|
|
7735
|
-
result = await provider.rotateKeys();
|
|
7736
|
-
} catch (err) {
|
|
7737
|
-
sendLoginResponse(502, {
|
|
7738
|
-
ok: false,
|
|
7739
|
-
error: {
|
|
7740
|
-
code: "wallet_provider_error",
|
|
7741
|
-
message: String(err)
|
|
7742
|
-
}
|
|
7743
|
-
});
|
|
7744
|
-
return;
|
|
7745
|
-
}
|
|
7746
|
-
appendWalletAuditEntry({
|
|
7747
|
-
action: "rotate_keys",
|
|
7748
|
-
actor: "control-ui",
|
|
7749
|
-
details: {
|
|
7750
|
-
provider: provider.id,
|
|
7751
|
-
providerId,
|
|
7752
|
-
addresses: result.addresses ?? {}
|
|
7753
|
-
},
|
|
7754
|
-
env: process.env
|
|
7755
|
-
});
|
|
7756
|
-
sendLoginResponse(200, {
|
|
7757
|
-
ok: true,
|
|
7758
|
-
result
|
|
7318
|
+
sendLoginResponse(410, {
|
|
7319
|
+
ok: false,
|
|
7320
|
+
error: {
|
|
7321
|
+
code: "gateway_wallet_key_lifecycle_removed",
|
|
7322
|
+
message: "Wallet key rotation is signer/provider-owned. Use `fased-signerd admin wallet reencrypt` as the control-socket owner, or rotate through Turnkey/hardware wallet authority."
|
|
7323
|
+
}
|
|
7759
7324
|
});
|
|
7760
7325
|
return;
|
|
7761
7326
|
}
|
|
7762
|
-
if (requestPath === "/api/wallet/reset") {
|
|
7763
|
-
if (req.method !== "POST") {
|
|
7764
|
-
res.statusCode = 405;
|
|
7765
|
-
res.setHeader("Allow", "POST");
|
|
7766
|
-
sendLoginResponse(405, {
|
|
7767
|
-
ok: false,
|
|
7768
|
-
error: {
|
|
7769
|
-
code: "method_not_allowed",
|
|
7770
|
-
message: "method must be POST"
|
|
7771
|
-
}
|
|
7772
|
-
});
|
|
7773
|
-
return;
|
|
7774
|
-
}
|
|
7775
|
-
if (!await ensureWalletApiAuthorized()) {
|
|
7776
|
-
return;
|
|
7777
|
-
}
|
|
7778
|
-
const body = await readJsonBody(req, 64 * 1024);
|
|
7779
|
-
if (!body.ok) {
|
|
7780
|
-
sendLoginResponse(400, {
|
|
7781
|
-
ok: false,
|
|
7782
|
-
error: {
|
|
7783
|
-
code: "invalid_request",
|
|
7784
|
-
message: body.error
|
|
7785
|
-
}
|
|
7786
|
-
});
|
|
7787
|
-
return;
|
|
7788
|
-
}
|
|
7789
|
-
const confirmText = typeof body.value?.confirmText === "string" ? body.value.confirmText.trim() : "";
|
|
7790
|
-
if (confirmText !== "RESET WALLET") {
|
|
7791
|
-
sendLoginResponse(400, {
|
|
7792
|
-
ok: false,
|
|
7793
|
-
error: {
|
|
7794
|
-
code: "confirm_text_mismatch",
|
|
7795
|
-
message: "confirmText must equal \"RESET WALLET\""
|
|
7796
|
-
}
|
|
7797
|
-
});
|
|
7798
|
-
return;
|
|
7799
|
-
}
|
|
7800
|
-
const cfg = loadConfig();
|
|
7801
|
-
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
7802
|
-
const parsedUrl = new URL(req.url || "/", "http://localhost");
|
|
7803
|
-
const providerId = parseWalletProviderId(parsedUrl.searchParams.get("providerId")) ?? resolveWalletProviderId(cfg, process.env);
|
|
7804
|
-
if (!walletCfg.enabled) {
|
|
7805
|
-
sendLoginResponse(400, {
|
|
7806
|
-
ok: false,
|
|
7807
|
-
error: {
|
|
7808
|
-
code: "wallet_disabled",
|
|
7809
|
-
message: "wallet is disabled"
|
|
7810
|
-
}
|
|
7811
|
-
});
|
|
7812
|
-
return;
|
|
7813
|
-
}
|
|
7814
|
-
if (!ensureWalletApprovalAuthorized({
|
|
7815
|
-
operation: "wallet.reset",
|
|
7816
|
-
cfg
|
|
7817
|
-
})) {
|
|
7818
|
-
return;
|
|
7819
|
-
}
|
|
7820
|
-
const provider = createWalletProviderAdapter({
|
|
7821
|
-
cfg,
|
|
7822
|
-
wallet: walletCfg,
|
|
7823
|
-
env: process.env,
|
|
7824
|
-
providerIdOverride: providerId
|
|
7825
|
-
});
|
|
7826
|
-
if (!provider.resetKeys) {
|
|
7827
|
-
sendLoginResponse(400, {
|
|
7327
|
+
if (requestPath === "/api/wallet/reset") {
|
|
7328
|
+
if (req.method !== "POST") {
|
|
7329
|
+
res.statusCode = 405;
|
|
7330
|
+
res.setHeader("Allow", "POST");
|
|
7331
|
+
sendLoginResponse(405, {
|
|
7828
7332
|
ok: false,
|
|
7829
7333
|
error: {
|
|
7830
|
-
code: "
|
|
7831
|
-
message:
|
|
7334
|
+
code: "method_not_allowed",
|
|
7335
|
+
message: "method must be POST"
|
|
7832
7336
|
}
|
|
7833
7337
|
});
|
|
7834
7338
|
return;
|
|
7835
7339
|
}
|
|
7836
|
-
|
|
7837
|
-
try {
|
|
7838
|
-
result = await provider.resetKeys();
|
|
7839
|
-
} catch (err) {
|
|
7840
|
-
sendLoginResponse(502, {
|
|
7841
|
-
ok: false,
|
|
7842
|
-
error: {
|
|
7843
|
-
code: "wallet_provider_error",
|
|
7844
|
-
message: String(err)
|
|
7845
|
-
}
|
|
7846
|
-
});
|
|
7340
|
+
if (!await ensureWalletApiAuthorized()) {
|
|
7847
7341
|
return;
|
|
7848
7342
|
}
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
addresses: result.addresses ?? {}
|
|
7856
|
-
},
|
|
7857
|
-
env: process.env
|
|
7858
|
-
});
|
|
7859
|
-
sendLoginResponse(200, {
|
|
7860
|
-
ok: true,
|
|
7861
|
-
result
|
|
7343
|
+
sendLoginResponse(410, {
|
|
7344
|
+
ok: false,
|
|
7345
|
+
error: {
|
|
7346
|
+
code: "gateway_wallet_key_lifecycle_removed",
|
|
7347
|
+
message: "Wallet reset is unavailable through the Gateway. Create or import only in the signer/provider/hardware-wallet authority surface."
|
|
7348
|
+
}
|
|
7862
7349
|
});
|
|
7863
7350
|
return;
|
|
7864
7351
|
}
|
|
@@ -8127,8 +7614,6 @@ function createGatewayHttpServer(opts) {
|
|
|
8127
7614
|
return;
|
|
8128
7615
|
}
|
|
8129
7616
|
payload.amount = normalizedAmount.amount;
|
|
8130
|
-
const approvalToken = String(getHeader(req, "x-wallet-approval-token") ?? "").trim();
|
|
8131
|
-
const custodyDeviceShare = String(getHeader(req, "x-wallet-device-share") ?? "").trim();
|
|
8132
7617
|
const registrySnapshot = readWalletProviderRegistry(process.env);
|
|
8133
7618
|
let walletSelection;
|
|
8134
7619
|
try {
|
|
@@ -8157,22 +7642,23 @@ function createGatewayHttpServer(opts) {
|
|
|
8157
7642
|
providerIdOverride: selectedProviderId,
|
|
8158
7643
|
walletId: walletSelection?.walletId ?? payload.walletId
|
|
8159
7644
|
});
|
|
8160
|
-
if (selectedProviderId === "turnkey" && !
|
|
7645
|
+
if (selectedProviderId === "turnkey" && !hasTurnkeyPolicyCredentialsConfigured(process.env)) {
|
|
8161
7646
|
sendLoginResponse(400, {
|
|
8162
7647
|
ok: false,
|
|
8163
7648
|
error: {
|
|
8164
7649
|
code: "provider_prerequisite_missing",
|
|
8165
|
-
message: "turnkey
|
|
7650
|
+
message: "turnkey requires a dedicated API user covered by a restrictive organization policy, plus organizationId, policyId, and rpcUrl before prepare/send"
|
|
8166
7651
|
}
|
|
8167
7652
|
});
|
|
8168
7653
|
return;
|
|
8169
7654
|
}
|
|
8170
7655
|
const selectedProviderCapabilities = buildWalletProviderCapabilityMatrix(selectedProvider);
|
|
8171
|
-
|
|
7656
|
+
const supportsServerSend = providerSupportsChainOperation({
|
|
8172
7657
|
matrix: selectedProviderCapabilities,
|
|
8173
7658
|
chain,
|
|
8174
7659
|
operation: "send"
|
|
8175
|
-
})
|
|
7660
|
+
});
|
|
7661
|
+
if (!supportsServerSend && !selectedProviderCapabilities.signing.interactiveSend) {
|
|
8176
7662
|
sendLoginResponse(400, {
|
|
8177
7663
|
ok: false,
|
|
8178
7664
|
error: {
|
|
@@ -8205,9 +7691,6 @@ function createGatewayHttpServer(opts) {
|
|
|
8205
7691
|
config: walletCfg,
|
|
8206
7692
|
runtimeConfig: cfg,
|
|
8207
7693
|
providerIdOverride: selectedProviderId,
|
|
8208
|
-
approvalToken,
|
|
8209
|
-
approvalHost: host,
|
|
8210
|
-
custodyDeviceShare,
|
|
8211
7694
|
env: process.env
|
|
8212
7695
|
});
|
|
8213
7696
|
if (!created.ok) {
|
|
@@ -8216,9 +7699,7 @@ function createGatewayHttpServer(opts) {
|
|
|
8216
7699
|
"invalid_approval_token",
|
|
8217
7700
|
"host_mismatch",
|
|
8218
7701
|
"operation_mismatch",
|
|
8219
|
-
"request_mismatch"
|
|
8220
|
-
"custody_device_share_required",
|
|
8221
|
-
"custody_device_share_mismatch"
|
|
7702
|
+
"request_mismatch"
|
|
8222
7703
|
]);
|
|
8223
7704
|
const status = authCodes.has(created.code) ? 401 : 400;
|
|
8224
7705
|
sendLoginResponse(status, {
|
|
@@ -8309,7 +7790,7 @@ function createGatewayHttpServer(opts) {
|
|
|
8309
7790
|
}
|
|
8310
7791
|
const requestId = parts[3] ?? "";
|
|
8311
7792
|
const action = parts[4] ?? "";
|
|
8312
|
-
if (!requestId || action !== "approve" && action !== "reject") {
|
|
7793
|
+
if (!requestId || action !== "approve" && action !== "reject" && action !== "execute") {
|
|
8313
7794
|
sendLoginResponse(404, {
|
|
8314
7795
|
ok: false,
|
|
8315
7796
|
error: {
|
|
@@ -8319,6 +7800,129 @@ function createGatewayHttpServer(opts) {
|
|
|
8319
7800
|
});
|
|
8320
7801
|
return;
|
|
8321
7802
|
}
|
|
7803
|
+
if (action === "execute") {
|
|
7804
|
+
const request = getWalletSendApprovalRequest({
|
|
7805
|
+
requestId,
|
|
7806
|
+
env: process.env
|
|
7807
|
+
});
|
|
7808
|
+
if (!request || request.payload.providerId !== "wallet-standard") {
|
|
7809
|
+
sendLoginResponse(404, {
|
|
7810
|
+
ok: false,
|
|
7811
|
+
error: {
|
|
7812
|
+
code: "not_found",
|
|
7813
|
+
message: "hardware-wallet approval not found"
|
|
7814
|
+
}
|
|
7815
|
+
});
|
|
7816
|
+
return;
|
|
7817
|
+
}
|
|
7818
|
+
if (request.status !== "pending" && request.status !== "approved" && request.status !== "executed") {
|
|
7819
|
+
sendLoginResponse(409, {
|
|
7820
|
+
ok: false,
|
|
7821
|
+
error: {
|
|
7822
|
+
code: "invalid_state",
|
|
7823
|
+
message: `hardware-wallet approval is ${request.status}`
|
|
7824
|
+
}
|
|
7825
|
+
});
|
|
7826
|
+
return;
|
|
7827
|
+
}
|
|
7828
|
+
const body = await readJsonBody(req, 256 * 1024);
|
|
7829
|
+
if (!body.ok) {
|
|
7830
|
+
sendLoginResponse(400, {
|
|
7831
|
+
ok: false,
|
|
7832
|
+
error: {
|
|
7833
|
+
code: "invalid_request",
|
|
7834
|
+
message: body.error
|
|
7835
|
+
}
|
|
7836
|
+
});
|
|
7837
|
+
return;
|
|
7838
|
+
}
|
|
7839
|
+
const payload = body.value ?? {};
|
|
7840
|
+
const preparedId = toOptionalString(payload.preparedId);
|
|
7841
|
+
const intentDigest = toOptionalString(payload.intentDigest);
|
|
7842
|
+
const signedTxBase64 = toOptionalString(payload.signedTxBase64);
|
|
7843
|
+
if (!preparedId || !intentDigest || !signedTxBase64) {
|
|
7844
|
+
sendLoginResponse(400, {
|
|
7845
|
+
ok: false,
|
|
7846
|
+
error: {
|
|
7847
|
+
code: "invalid_request",
|
|
7848
|
+
message: "preparedId, intentDigest, and signedTxBase64 are required"
|
|
7849
|
+
}
|
|
7850
|
+
});
|
|
7851
|
+
return;
|
|
7852
|
+
}
|
|
7853
|
+
const cfg = loadConfig();
|
|
7854
|
+
const effectiveEnv = {
|
|
7855
|
+
...process.env,
|
|
7856
|
+
...cfg.env?.vars
|
|
7857
|
+
};
|
|
7858
|
+
const walletId = request.payload.walletId?.trim() || "";
|
|
7859
|
+
const rpcUrl = resolveScopedRpcUrlForWallet({
|
|
7860
|
+
env: effectiveEnv,
|
|
7861
|
+
chains: ["solana"],
|
|
7862
|
+
walletId: walletId || undefined
|
|
7863
|
+
});
|
|
7864
|
+
if (!rpcUrl) {
|
|
7865
|
+
sendLoginResponse(400, {
|
|
7866
|
+
ok: false,
|
|
7867
|
+
error: {
|
|
7868
|
+
code: "wallet_provider_invalid_config",
|
|
7869
|
+
message: "hardware Vault requires a configured Solana RPC URL"
|
|
7870
|
+
}
|
|
7871
|
+
});
|
|
7872
|
+
return;
|
|
7873
|
+
}
|
|
7874
|
+
try {
|
|
7875
|
+
const executed = await executeWalletStandardReview({
|
|
7876
|
+
requestId,
|
|
7877
|
+
preparedId,
|
|
7878
|
+
intentDigest,
|
|
7879
|
+
signedTxBase64,
|
|
7880
|
+
rpcUrl,
|
|
7881
|
+
env: process.env
|
|
7882
|
+
});
|
|
7883
|
+
const updated = await markWalletSendRequestExecutedExternally({
|
|
7884
|
+
requestId,
|
|
7885
|
+
txHash: executed.txHash,
|
|
7886
|
+
signer: executed.signer,
|
|
7887
|
+
actor: "control-ui",
|
|
7888
|
+
env: process.env
|
|
7889
|
+
});
|
|
7890
|
+
sendLoginResponse(200, {
|
|
7891
|
+
ok: true,
|
|
7892
|
+
request: sanitizeWalletSendApprovalRequest(updated),
|
|
7893
|
+
tx: {
|
|
7894
|
+
ok: true,
|
|
7895
|
+
chain: "solana",
|
|
7896
|
+
txHash: executed.txHash,
|
|
7897
|
+
signer: executed.signer,
|
|
7898
|
+
idempotent: executed.idempotent
|
|
7899
|
+
}
|
|
7900
|
+
});
|
|
7901
|
+
} catch (error) {
|
|
7902
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "wallet_provider_error";
|
|
7903
|
+
const message = walletDiagnosticErrorMessage(error);
|
|
7904
|
+
if (code === "wallet_provider_ambiguous") {
|
|
7905
|
+
await markWalletSendRequestBroadcastUnknown({
|
|
7906
|
+
requestId,
|
|
7907
|
+
txHash: readWalletStandardReviewTxHash({
|
|
7908
|
+
requestId,
|
|
7909
|
+
env: process.env
|
|
7910
|
+
}),
|
|
7911
|
+
reason: message,
|
|
7912
|
+
actor: "control-ui",
|
|
7913
|
+
env: process.env
|
|
7914
|
+
});
|
|
7915
|
+
}
|
|
7916
|
+
sendLoginResponse(code === "wallet_provider_ambiguous" ? 409 : 400, {
|
|
7917
|
+
ok: false,
|
|
7918
|
+
error: {
|
|
7919
|
+
code,
|
|
7920
|
+
message
|
|
7921
|
+
}
|
|
7922
|
+
});
|
|
7923
|
+
}
|
|
7924
|
+
return;
|
|
7925
|
+
}
|
|
8322
7926
|
if (action === "reject") {
|
|
8323
7927
|
const cfg = loadConfig();
|
|
8324
7928
|
if (!ensureWalletApprovalAuthorized({
|
|
@@ -8364,6 +7968,206 @@ function createGatewayHttpServer(opts) {
|
|
|
8364
7968
|
}
|
|
8365
7969
|
const cfg = loadConfig();
|
|
8366
7970
|
const walletCfg = resolveWalletRuntimeConfig(cfg, process.env);
|
|
7971
|
+
const pendingRequest = getWalletSendApprovalRequest({
|
|
7972
|
+
requestId,
|
|
7973
|
+
env: process.env
|
|
7974
|
+
});
|
|
7975
|
+
const signerReviewId = pendingRequest?.payload.signerReviewId?.trim() || "";
|
|
7976
|
+
const signerWalletId = pendingRequest?.payload.signerWalletId?.trim() || "";
|
|
7977
|
+
const applicationWalletId = pendingRequest?.payload.walletId?.trim() || "";
|
|
7978
|
+
const isSignerOwnedReview = pendingRequest?.payload.providerId === "local-socket-signer" && Boolean(signerReviewId) && Boolean(signerWalletId) && Boolean(applicationWalletId);
|
|
7979
|
+
if (isSignerOwnedReview) {
|
|
7980
|
+
if (pendingRequest.status !== "pending" && pendingRequest.status !== "expired") {
|
|
7981
|
+
sendLoginResponse(409, {
|
|
7982
|
+
ok: false,
|
|
7983
|
+
error: {
|
|
7984
|
+
code: "invalid_state",
|
|
7985
|
+
message: `signer-reviewed approval is ${pendingRequest.status}`
|
|
7986
|
+
}
|
|
7987
|
+
});
|
|
7988
|
+
return;
|
|
7989
|
+
}
|
|
7990
|
+
const body = await readJsonBody(req, 256 * 1024);
|
|
7991
|
+
if (!body.ok) {
|
|
7992
|
+
sendLoginResponse(400, {
|
|
7993
|
+
ok: false,
|
|
7994
|
+
error: {
|
|
7995
|
+
code: "invalid_request",
|
|
7996
|
+
message: body.error
|
|
7997
|
+
}
|
|
7998
|
+
});
|
|
7999
|
+
return;
|
|
8000
|
+
}
|
|
8001
|
+
const payload = body.value ?? {};
|
|
8002
|
+
const payloadKeys = Object.keys(payload);
|
|
8003
|
+
const provider = createWalletProviderAdapter({
|
|
8004
|
+
cfg,
|
|
8005
|
+
wallet: walletCfg,
|
|
8006
|
+
env: process.env,
|
|
8007
|
+
providerIdOverride: "local-socket-signer",
|
|
8008
|
+
walletId: applicationWalletId
|
|
8009
|
+
});
|
|
8010
|
+
const bindingMatchesApproval = (binding) => signerReviewBindingMatchesWalletApprovalPayload(binding, pendingRequest.payload);
|
|
8011
|
+
if (payloadKeys.length === 0) {
|
|
8012
|
+
if (!provider.getSignerReview || !provider.beginSignerReviewAuthorization) {
|
|
8013
|
+
sendLoginResponse(400, {
|
|
8014
|
+
ok: false,
|
|
8015
|
+
error: {
|
|
8016
|
+
code: "wallet_signer_webauthn_unavailable",
|
|
8017
|
+
message: "local-socket-signer does not expose exact review recovery and signer-owned WebAuthn begin"
|
|
8018
|
+
}
|
|
8019
|
+
});
|
|
8020
|
+
return;
|
|
8021
|
+
}
|
|
8022
|
+
try {
|
|
8023
|
+
const storedReview = await provider.getSignerReview({
|
|
8024
|
+
walletId: signerWalletId,
|
|
8025
|
+
requestId: signerReviewId
|
|
8026
|
+
});
|
|
8027
|
+
if (!signerReviewMatchesWalletApprovalPayload(storedReview, pendingRequest.payload)) {
|
|
8028
|
+
throw new Error("stored signer review does not match the persisted approval");
|
|
8029
|
+
}
|
|
8030
|
+
if (storedReview.state === "signed") {
|
|
8031
|
+
const recovered = await approveWalletSendRequest({
|
|
8032
|
+
requestId,
|
|
8033
|
+
actor: "control-ui",
|
|
8034
|
+
config: walletCfg,
|
|
8035
|
+
env: process.env
|
|
8036
|
+
});
|
|
8037
|
+
if (!recovered.ok) {
|
|
8038
|
+
sendLoginResponse(recovered.code === "wallet_provider_ambiguous" ? 409 : 400, {
|
|
8039
|
+
ok: false,
|
|
8040
|
+
error: {
|
|
8041
|
+
code: recovered.code,
|
|
8042
|
+
message: recovered.message
|
|
8043
|
+
},
|
|
8044
|
+
request: "request" in recovered && recovered.request ? sanitizeWalletSendApprovalRequest(recovered.request) : undefined
|
|
8045
|
+
});
|
|
8046
|
+
return;
|
|
8047
|
+
}
|
|
8048
|
+
sendLoginResponse(200, {
|
|
8049
|
+
ok: true,
|
|
8050
|
+
request: sanitizeWalletSendApprovalRequest(recovered.request),
|
|
8051
|
+
tx: recovered.tx
|
|
8052
|
+
});
|
|
8053
|
+
return;
|
|
8054
|
+
}
|
|
8055
|
+
if (pendingRequest.status === "expired" || Date.parse(pendingRequest.expiresAt) <= Date.now()) {
|
|
8056
|
+
const expired = await approveWalletSendRequest({
|
|
8057
|
+
requestId,
|
|
8058
|
+
actor: "control-ui",
|
|
8059
|
+
config: walletCfg,
|
|
8060
|
+
env: process.env
|
|
8061
|
+
});
|
|
8062
|
+
sendLoginResponse(409, {
|
|
8063
|
+
ok: false,
|
|
8064
|
+
error: {
|
|
8065
|
+
code: expired.ok ? "invalid_state" : expired.code,
|
|
8066
|
+
message: expired.ok ? "expired signer review unexpectedly executed" : expired.message
|
|
8067
|
+
},
|
|
8068
|
+
request: !expired.ok && "request" in expired && expired.request ? sanitizeWalletSendApprovalRequest(expired.request) : undefined
|
|
8069
|
+
});
|
|
8070
|
+
return;
|
|
8071
|
+
}
|
|
8072
|
+
const authorization = await provider.beginSignerReviewAuthorization({
|
|
8073
|
+
walletId: signerWalletId,
|
|
8074
|
+
requestId: signerReviewId
|
|
8075
|
+
});
|
|
8076
|
+
if (!bindingMatchesApproval(authorization.binding)) {
|
|
8077
|
+
throw new Error("signer WebAuthn binding does not match the persisted approval");
|
|
8078
|
+
}
|
|
8079
|
+
sendLoginResponse(200, {
|
|
8080
|
+
ok: true,
|
|
8081
|
+
mode: "signer-webauthn",
|
|
8082
|
+
request: sanitizeWalletSendApprovalRequest(pendingRequest),
|
|
8083
|
+
signerAuthorization: authorization
|
|
8084
|
+
});
|
|
8085
|
+
} catch (error) {
|
|
8086
|
+
const message = walletDiagnosticErrorMessage(error);
|
|
8087
|
+
sendLoginResponse(400, {
|
|
8088
|
+
ok: false,
|
|
8089
|
+
error: {
|
|
8090
|
+
code: message.includes("no signer-owned WebAuthn credential is enrolled") ? "wallet_signer_webauthn_not_enrolled" : "wallet_signer_webauthn_failed",
|
|
8091
|
+
message
|
|
8092
|
+
}
|
|
8093
|
+
});
|
|
8094
|
+
}
|
|
8095
|
+
return;
|
|
8096
|
+
}
|
|
8097
|
+
if (payloadKeys.length !== 1 || payloadKeys[0] !== "signerAuthorization" || !payload.signerAuthorization || typeof payload.signerAuthorization !== "object" || Array.isArray(payload.signerAuthorization)) {
|
|
8098
|
+
sendLoginResponse(400, {
|
|
8099
|
+
ok: false,
|
|
8100
|
+
error: {
|
|
8101
|
+
code: "invalid_request",
|
|
8102
|
+
message: "signer-reviewed approval accepts only signerAuthorization"
|
|
8103
|
+
}
|
|
8104
|
+
});
|
|
8105
|
+
return;
|
|
8106
|
+
}
|
|
8107
|
+
const signerAuthorization = payload.signerAuthorization;
|
|
8108
|
+
if (Object.keys(signerAuthorization).some((key) => key !== "challengeId" && key !== "credential") || typeof signerAuthorization.challengeId !== "string" || !signerAuthorization.challengeId.trim() || !signerAuthorization.credential || typeof signerAuthorization.credential !== "object" || Array.isArray(signerAuthorization.credential)) {
|
|
8109
|
+
sendLoginResponse(400, {
|
|
8110
|
+
ok: false,
|
|
8111
|
+
error: {
|
|
8112
|
+
code: "invalid_request",
|
|
8113
|
+
message: "challengeId and WebAuthn credential are required"
|
|
8114
|
+
}
|
|
8115
|
+
});
|
|
8116
|
+
return;
|
|
8117
|
+
}
|
|
8118
|
+
if (!provider.finishSignerReviewAuthorization) {
|
|
8119
|
+
sendLoginResponse(400, {
|
|
8120
|
+
ok: false,
|
|
8121
|
+
error: {
|
|
8122
|
+
code: "wallet_signer_webauthn_unavailable",
|
|
8123
|
+
message: "local-socket-signer does not expose signer-owned WebAuthn finish"
|
|
8124
|
+
}
|
|
8125
|
+
});
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
try {
|
|
8129
|
+
const finished = await provider.finishSignerReviewAuthorization({
|
|
8130
|
+
walletId: signerWalletId,
|
|
8131
|
+
challengeId: signerAuthorization.challengeId.trim(),
|
|
8132
|
+
credential: signerAuthorization.credential
|
|
8133
|
+
});
|
|
8134
|
+
if (!bindingMatchesApproval(finished.binding)) {
|
|
8135
|
+
throw new Error("completed signer WebAuthn binding does not match the approval");
|
|
8136
|
+
}
|
|
8137
|
+
const approved = await approveWalletSendRequest({
|
|
8138
|
+
requestId,
|
|
8139
|
+
actor: "control-ui",
|
|
8140
|
+
config: walletCfg,
|
|
8141
|
+
reviewAuthorization: finished.authorization,
|
|
8142
|
+
env: process.env
|
|
8143
|
+
});
|
|
8144
|
+
if (!approved.ok) {
|
|
8145
|
+
sendLoginResponse(approved.code === "wallet_provider_ambiguous" ? 409 : 400, {
|
|
8146
|
+
ok: false,
|
|
8147
|
+
error: {
|
|
8148
|
+
code: approved.code,
|
|
8149
|
+
message: approved.message
|
|
8150
|
+
},
|
|
8151
|
+
request: "request" in approved && approved.request ? sanitizeWalletSendApprovalRequest(approved.request) : undefined
|
|
8152
|
+
});
|
|
8153
|
+
return;
|
|
8154
|
+
}
|
|
8155
|
+
sendLoginResponse(200, {
|
|
8156
|
+
ok: true,
|
|
8157
|
+
request: sanitizeWalletSendApprovalRequest(approved.request),
|
|
8158
|
+
tx: approved.tx
|
|
8159
|
+
});
|
|
8160
|
+
} catch (error) {
|
|
8161
|
+
sendLoginResponse(400, {
|
|
8162
|
+
ok: false,
|
|
8163
|
+
error: {
|
|
8164
|
+
code: "wallet_signer_webauthn_failed",
|
|
8165
|
+
message: walletDiagnosticErrorMessage(error)
|
|
8166
|
+
}
|
|
8167
|
+
});
|
|
8168
|
+
}
|
|
8169
|
+
return;
|
|
8170
|
+
}
|
|
8367
8171
|
if (!ensureWalletApprovalAuthorized({
|
|
8368
8172
|
operation: "wallet.approve",
|
|
8369
8173
|
requestId,
|
|
@@ -8371,11 +8175,79 @@ function createGatewayHttpServer(opts) {
|
|
|
8371
8175
|
})) {
|
|
8372
8176
|
return;
|
|
8373
8177
|
}
|
|
8178
|
+
if (pendingRequest?.payload.providerId === "wallet-standard") {
|
|
8179
|
+
if (pendingRequest.status !== "pending") {
|
|
8180
|
+
sendLoginResponse(400, {
|
|
8181
|
+
ok: false,
|
|
8182
|
+
error: {
|
|
8183
|
+
code: "invalid_state",
|
|
8184
|
+
message: `approval request is ${pendingRequest.status}`
|
|
8185
|
+
}
|
|
8186
|
+
});
|
|
8187
|
+
return;
|
|
8188
|
+
}
|
|
8189
|
+
const effectiveEnv = {
|
|
8190
|
+
...process.env,
|
|
8191
|
+
...cfg.env?.vars
|
|
8192
|
+
};
|
|
8193
|
+
const walletId = pendingRequest.payload.walletId?.trim() || "";
|
|
8194
|
+
const registry = readWalletProviderRegistry(effectiveEnv);
|
|
8195
|
+
const selectedWallet = registry.wallets.find((wallet) => wallet.id === walletId);
|
|
8196
|
+
const signerAddress = selectedWallet?.addresses?.solana?.trim() || "";
|
|
8197
|
+
const rpcUrl = resolveScopedRpcUrlForWallet({
|
|
8198
|
+
env: effectiveEnv,
|
|
8199
|
+
chains: ["solana"],
|
|
8200
|
+
walletId: walletId || undefined
|
|
8201
|
+
});
|
|
8202
|
+
if (!selectedWallet || selectedWallet.providerId !== "wallet-standard" || !signerAddress) {
|
|
8203
|
+
sendLoginResponse(400, {
|
|
8204
|
+
ok: false,
|
|
8205
|
+
error: {
|
|
8206
|
+
code: "wallet_provider_invalid_config",
|
|
8207
|
+
message: "hardware Vault account is not registered for this approval"
|
|
8208
|
+
}
|
|
8209
|
+
});
|
|
8210
|
+
return;
|
|
8211
|
+
}
|
|
8212
|
+
if (!rpcUrl) {
|
|
8213
|
+
sendLoginResponse(400, {
|
|
8214
|
+
ok: false,
|
|
8215
|
+
error: {
|
|
8216
|
+
code: "wallet_provider_invalid_config",
|
|
8217
|
+
message: "hardware Vault requires a configured Solana RPC URL"
|
|
8218
|
+
}
|
|
8219
|
+
});
|
|
8220
|
+
return;
|
|
8221
|
+
}
|
|
8222
|
+
try {
|
|
8223
|
+
const browserReview = await prepareWalletStandardReview({
|
|
8224
|
+
requestId,
|
|
8225
|
+
payload: pendingRequest.payload,
|
|
8226
|
+
signerAddress,
|
|
8227
|
+
rpcUrl,
|
|
8228
|
+
env: process.env
|
|
8229
|
+
});
|
|
8230
|
+
sendLoginResponse(200, {
|
|
8231
|
+
ok: true,
|
|
8232
|
+
mode: "browser",
|
|
8233
|
+
request: sanitizeWalletSendApprovalRequest(pendingRequest),
|
|
8234
|
+
browserReview
|
|
8235
|
+
});
|
|
8236
|
+
} catch (error) {
|
|
8237
|
+
sendLoginResponse(400, {
|
|
8238
|
+
ok: false,
|
|
8239
|
+
error: {
|
|
8240
|
+
code: error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "wallet_provider_error",
|
|
8241
|
+
message: walletDiagnosticErrorMessage(error)
|
|
8242
|
+
}
|
|
8243
|
+
});
|
|
8244
|
+
}
|
|
8245
|
+
return;
|
|
8246
|
+
}
|
|
8374
8247
|
const approved = await approveWalletSendRequest({
|
|
8375
8248
|
requestId,
|
|
8376
8249
|
actor: "control-ui",
|
|
8377
8250
|
config: walletCfg,
|
|
8378
|
-
approvalHost: host,
|
|
8379
8251
|
env: process.env
|
|
8380
8252
|
});
|
|
8381
8253
|
if (!approved.ok) {
|
|
@@ -8662,7 +8534,15 @@ function createGatewayHttpServer(opts) {
|
|
|
8662
8534
|
if (await a2aHandler(req, res)) {
|
|
8663
8535
|
return;
|
|
8664
8536
|
}
|
|
8665
|
-
if (
|
|
8537
|
+
if (isPathProtectedByPrefixes(requestPath, FEDERATION_HTTP_ROUTE_PREFIXES) && !isSignedFederationInboundRequest(req) && !await ensureWalletApiAuthorized()) {
|
|
8538
|
+
return;
|
|
8539
|
+
}
|
|
8540
|
+
if (await handleFederationHttpRequest(req, res, { peerAuthClientIp: resolveClientIp({
|
|
8541
|
+
remoteAddr: req.socket?.remoteAddress ?? "",
|
|
8542
|
+
forwardedFor: getHeader(req, "x-forwarded-for"),
|
|
8543
|
+
realIp: getHeader(req, "x-real-ip"),
|
|
8544
|
+
trustedProxies
|
|
8545
|
+
}) })) {
|
|
8666
8546
|
return;
|
|
8667
8547
|
}
|
|
8668
8548
|
if (await fedifyHandler(req, res)) {
|
|
@@ -8684,9 +8564,7 @@ function createGatewayHttpServer(opts) {
|
|
|
8684
8564
|
if (canonicalPluginPath === "/api/channels" || canonicalPluginPath.startsWith("/api/channels/") || rawChannelCandidatePath.startsWith("/api/channels")) {
|
|
8685
8565
|
const bearerToken = getBearerToken(req);
|
|
8686
8566
|
let pluginAuthOk = false;
|
|
8687
|
-
if (resolvedAuth.mode === "
|
|
8688
|
-
pluginAuthOk = true;
|
|
8689
|
-
} else if (resolvedAuth.mode === "token" && resolvedAuth.token) {
|
|
8567
|
+
if (resolvedAuth.mode === "token" && resolvedAuth.token) {
|
|
8690
8568
|
pluginAuthOk = Boolean(bearerToken && safeEqualSecret(resolvedAuth.token, bearerToken));
|
|
8691
8569
|
} else if (resolvedAuth.mode === "password" && resolvedAuth.password) {
|
|
8692
8570
|
pluginAuthOk = Boolean(bearerToken && safeEqualSecret(resolvedAuth.password, bearerToken));
|
|
@@ -8870,13 +8748,14 @@ function createGatewayHttpServer(opts) {
|
|
|
8870
8748
|
} catch (error) {
|
|
8871
8749
|
if (!res.headersSent && requestPath.startsWith("/api/")) {
|
|
8872
8750
|
const message = error instanceof Error ? error.message : String(error);
|
|
8873
|
-
|
|
8751
|
+
const legacyMigrationRequired = error instanceof LegacyEmbeddedKeystoreMigrationRequiredError;
|
|
8752
|
+
res.statusCode = legacyMigrationRequired ? 409 : 500;
|
|
8874
8753
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
8875
8754
|
res.setHeader("Cache-Control", "no-store");
|
|
8876
8755
|
res.end(JSON.stringify({
|
|
8877
8756
|
ok: false,
|
|
8878
8757
|
error: {
|
|
8879
|
-
code: "internal_server_error",
|
|
8758
|
+
code: legacyMigrationRequired ? "wallet_legacy_embedded_keystore_migration_required" : "internal_server_error",
|
|
8880
8759
|
message: message || "Internal Server Error"
|
|
8881
8760
|
}
|
|
8882
8761
|
}));
|