@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
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { resolveProcessScopedMap } from "../shared/process-scoped-map.js";
|
|
3
|
+
import { acquireFileLock, withFileLock } from "../plugin-sdk/file-lock.js";
|
|
4
|
+
import "../infra/file-lock.js";
|
|
2
5
|
import { loadConfig } from "../config/io.js";
|
|
3
6
|
import "../config/config.js";
|
|
4
7
|
import { ensureWalletStateDir } from "./wallet-runtime-config.js";
|
|
8
|
+
import { readWalletProviderRegistry } from "./wallet-provider-registry.js";
|
|
5
9
|
import { tryResolveSatRuntimeIds } from "../config/sat-runtime-ids.js";
|
|
6
10
|
import { applyWalletPolicyConfig, enforceWalletDailyCap, resolveWalletRoleForId } from "./wallet-policy.js";
|
|
7
11
|
import { buildWalletProviderCapabilityMatrix, providerSupportsChainOperation } from "./wallet-provider-capabilities.js";
|
|
@@ -10,25 +14,31 @@ import { walletDiagnosticErrorMessage, walletDiagnosticErrorString } from "./wal
|
|
|
10
14
|
import { createWalletProviderAdapter, resolveWalletProviderId } from "./wallet-provider-resolver.js";
|
|
11
15
|
import { publishFederationSettlementEvidence } from "../federation/settlement-evidence.js";
|
|
12
16
|
import { executeSolanaSwapApprovalPayload, isSolanaSwapApprovalPayload } from "./solana-swap.js";
|
|
13
|
-
import { consumeWalletApprovalGrant, resolveWalletApprovalAuthMode } from "./wallet-approval-auth.js";
|
|
14
17
|
import { appendWalletAuditEntry } from "./wallet-audit-log.js";
|
|
15
|
-
import { enforceWalletCustodyForAutonomousSend, withWalletCustodySigningMaterial } from "./wallet-custody.js";
|
|
16
18
|
import { simulateWalletPolicy } from "./wallet-policy-simulation.js";
|
|
19
|
+
import { beginWalletSendExecution, claimWalletSendExecution, getWalletSendExecution, updateWalletSendExecution, walletSendIntentDigest, walletSendRequestId } from "./wallet-send-execution-ledger.js";
|
|
17
20
|
import { getWalletSettlementLinkByRequestId, markWalletSettlementLinkOutcome, upsertWalletSettlementLink } from "./wallet-settlement-links.js";
|
|
18
21
|
import { syncWalletApprovalTask, walletApprovalTaskId } from "./wallet-task-ledger.js";
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
+
import syncFs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
22
25
|
|
|
23
26
|
//#region src/wallet/wallet-send-approvals.ts
|
|
24
27
|
var wallet_send_approvals_exports = /* @__PURE__ */ __exportAll({
|
|
25
28
|
approveWalletSendRequest: () => approveWalletSendRequest,
|
|
29
|
+
bindSignerReviewToWalletApprovalPayload: () => bindSignerReviewToWalletApprovalPayload,
|
|
26
30
|
createOrExecuteWalletSend: () => createOrExecuteWalletSend,
|
|
31
|
+
createSignerReviewApprovalRequest: () => createSignerReviewApprovalRequest,
|
|
27
32
|
createWalletSendApprovalRequest: () => createWalletSendApprovalRequest,
|
|
33
|
+
getWalletSendApprovalRequest: () => getWalletSendApprovalRequest,
|
|
28
34
|
listWalletSendApprovalRequests: () => listWalletSendApprovalRequests,
|
|
35
|
+
markWalletSendRequestBroadcastUnknown: () => markWalletSendRequestBroadcastUnknown,
|
|
36
|
+
markWalletSendRequestExecutedExternally: () => markWalletSendRequestExecutedExternally,
|
|
29
37
|
rejectWalletSendRequest: () => rejectWalletSendRequest,
|
|
30
38
|
sanitizeWalletSendApprovalPayload: () => sanitizeWalletSendApprovalPayload,
|
|
31
|
-
sanitizeWalletSendApprovalRequest: () => sanitizeWalletSendApprovalRequest
|
|
39
|
+
sanitizeWalletSendApprovalRequest: () => sanitizeWalletSendApprovalRequest,
|
|
40
|
+
signerReviewBindingMatchesWalletApprovalPayload: () => signerReviewBindingMatchesWalletApprovalPayload,
|
|
41
|
+
signerReviewMatchesWalletApprovalPayload: () => signerReviewMatchesWalletApprovalPayload
|
|
32
42
|
});
|
|
33
43
|
function sanitizeWalletSendApprovalPayload(payload) {
|
|
34
44
|
const { serializedTxBase64, ...rest } = payload;
|
|
@@ -37,6 +47,63 @@ function sanitizeWalletSendApprovalPayload(payload) {
|
|
|
37
47
|
...serializedTxBase64 ? { hasSerializedTx: true } : {}
|
|
38
48
|
};
|
|
39
49
|
}
|
|
50
|
+
function sameSignerPrograms(left, right) {
|
|
51
|
+
return Boolean(left) && left?.length === right.length && left.every((value, index) => value === right[index]);
|
|
52
|
+
}
|
|
53
|
+
function sameOptionalSignerValue(left, right) {
|
|
54
|
+
return (left?.trim() || undefined) === (right?.trim() || undefined);
|
|
55
|
+
}
|
|
56
|
+
function sameSignerSemanticIntent(left, right) {
|
|
57
|
+
if (!left) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return canonicalApprovalJson(left) === canonicalApprovalJson(right);
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function cloneSignerSemanticIntent(intent) {
|
|
67
|
+
return JSON.parse(canonicalApprovalJson(intent));
|
|
68
|
+
}
|
|
69
|
+
function signerReviewMatchesWalletApprovalPayload(review, payload) {
|
|
70
|
+
return review.requestId === payload.signerReviewId?.trim() && review.walletId === payload.signerWalletId?.trim() && sameOptionalSignerValue(review.walletPublicKey, payload.signerWalletPublicKey) && review.intentType === payload.signerIntentType?.trim() && review.policyHash === payload.signerPolicyHash?.trim() && review.intentDigest === payload.signerIntentDigest?.trim() && sameSignerSemanticIntent(payload.signerSemanticIntent, review.semanticIntent) && review.artifactKind === payload.signerArtifactKind && review.artifactDigest === payload.signerArtifactDigest?.trim() && sameOptionalSignerValue(review.transactionDigest, payload.signerTransactionDigest) && sameOptionalSignerValue(review.stateDigest, payload.signerStateDigest) && (review.stateSlot ?? undefined) === payload.signerStateSlot && review.asset === payload.signerAsset?.trim() && review.amount === payload.signerAmount?.trim() && review.destination === payload.signerDestination?.trim() && review.policyOperation === payload.signerPolicyOperation?.trim() && sameSignerPrograms(payload.signerRequiredPrograms, review.requiredPrograms) && (!review.requiredRole || review.requiredRole === payload.signerRequiredRole) && review.nonce === payload.signerNonce?.trim() && review.issuedAt === payload.signerIssuedAt?.trim() && review.expiresAt === payload.signerReviewExpiresAt?.trim();
|
|
71
|
+
}
|
|
72
|
+
function signerReviewBindingMatchesWalletApprovalPayload(binding, payload) {
|
|
73
|
+
return binding.requestId === payload.signerReviewId?.trim() && binding.walletId === payload.signerWalletId?.trim() && sameOptionalSignerValue(binding.walletPublicKey, payload.signerWalletPublicKey) && binding.role === payload.signerRequiredRole && binding.intentType === payload.signerIntentType?.trim() && binding.policyHash === payload.signerPolicyHash?.trim() && binding.intentDigest === payload.signerIntentDigest?.trim() && sameSignerSemanticIntent(payload.signerSemanticIntent, binding.semanticIntent) && binding.artifactKind === payload.signerArtifactKind && binding.artifactDigest === payload.signerArtifactDigest?.trim() && sameOptionalSignerValue(binding.transactionDigest, payload.signerTransactionDigest) && sameOptionalSignerValue(binding.stateDigest, payload.signerStateDigest) && (binding.stateSlot ?? undefined) === payload.signerStateSlot && binding.asset === payload.signerAsset?.trim() && binding.amount === payload.signerAmount?.trim() && binding.destination === payload.signerDestination?.trim() && binding.policyOperation === payload.signerPolicyOperation?.trim() && sameSignerPrograms(payload.signerRequiredPrograms, binding.requiredPrograms) && binding.nonce === payload.signerNonce?.trim() && binding.issuedAt === payload.signerIssuedAt?.trim() && binding.expiresAt === payload.signerReviewExpiresAt?.trim();
|
|
74
|
+
}
|
|
75
|
+
function bindSignerReviewToWalletApprovalPayload(params) {
|
|
76
|
+
const { review, role } = params;
|
|
77
|
+
if (review.mode !== "reviewed" || review.state !== "prepared" || !review.requestId.trim() || !review.walletId.trim() || !review.requiredPrograms.length || review.requiredRole && review.requiredRole !== role) {
|
|
78
|
+
throw new Error("signer review is not a valid pending reviewed operation");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
...params.payload,
|
|
82
|
+
providerId: "local-socket-signer",
|
|
83
|
+
walletId: params.payload.walletId || review.walletId,
|
|
84
|
+
signerReviewId: review.requestId,
|
|
85
|
+
signerWalletId: review.walletId,
|
|
86
|
+
signerWalletPublicKey: review.walletPublicKey,
|
|
87
|
+
signerIntentType: review.intentType,
|
|
88
|
+
signerPolicyHash: review.policyHash,
|
|
89
|
+
signerIntentDigest: review.intentDigest,
|
|
90
|
+
signerSemanticIntent: cloneSignerSemanticIntent(review.semanticIntent),
|
|
91
|
+
signerArtifactKind: review.artifactKind,
|
|
92
|
+
signerArtifactDigest: review.artifactDigest,
|
|
93
|
+
signerTransactionDigest: review.transactionDigest,
|
|
94
|
+
signerStateDigest: review.stateDigest,
|
|
95
|
+
signerStateSlot: review.stateSlot,
|
|
96
|
+
signerAsset: review.asset,
|
|
97
|
+
signerAmount: review.amount,
|
|
98
|
+
signerDestination: review.destination,
|
|
99
|
+
signerPolicyOperation: review.policyOperation,
|
|
100
|
+
signerRequiredPrograms: [...review.requiredPrograms],
|
|
101
|
+
signerRequiredRole: role,
|
|
102
|
+
signerNonce: review.nonce,
|
|
103
|
+
signerIssuedAt: review.issuedAt,
|
|
104
|
+
signerReviewExpiresAt: review.expiresAt
|
|
105
|
+
};
|
|
106
|
+
}
|
|
40
107
|
function sanitizeWalletSendApprovalRequest(request) {
|
|
41
108
|
return {
|
|
42
109
|
...request,
|
|
@@ -44,8 +111,28 @@ function sanitizeWalletSendApprovalRequest(request) {
|
|
|
44
111
|
};
|
|
45
112
|
}
|
|
46
113
|
const DEFAULT_TTL_SECONDS = 15 * 60;
|
|
47
|
-
const
|
|
48
|
-
|
|
114
|
+
const APPROVAL_LOCK_OPTIONS = {
|
|
115
|
+
retries: {
|
|
116
|
+
retries: 100,
|
|
117
|
+
factor: 1.15,
|
|
118
|
+
minTimeout: 10,
|
|
119
|
+
maxTimeout: 200,
|
|
120
|
+
randomize: true
|
|
121
|
+
},
|
|
122
|
+
stale: 3e4
|
|
123
|
+
};
|
|
124
|
+
const APPROVAL_MUTATION_QUEUES = resolveProcessScopedMap(Symbol.for("fased.wallet.sendApprovals.mutationQueues"));
|
|
125
|
+
const APPROVAL_ACTIVE_EXECUTIONS = resolveProcessScopedMap(Symbol.for("fased.wallet.sendApprovals.activeExecutions"));
|
|
126
|
+
const APPROVAL_EXECUTION_LOCK_OPTIONS = {
|
|
127
|
+
retries: {
|
|
128
|
+
retries: 1,
|
|
129
|
+
factor: 1,
|
|
130
|
+
minTimeout: 10,
|
|
131
|
+
maxTimeout: 10,
|
|
132
|
+
randomize: false
|
|
133
|
+
},
|
|
134
|
+
stale: 3e4
|
|
135
|
+
};
|
|
49
136
|
function isReviewedMiningNativeSolanaSend(params) {
|
|
50
137
|
if (params.payload.chain !== "solana" || String(params.payload.program ?? "").trim()) {
|
|
51
138
|
return false;
|
|
@@ -60,23 +147,144 @@ function isReviewedMiningNativeSolanaSend(params) {
|
|
|
60
147
|
env: params.env
|
|
61
148
|
}) === "mining";
|
|
62
149
|
}
|
|
63
|
-
function
|
|
150
|
+
function isRecord(value) {
|
|
151
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
152
|
+
}
|
|
153
|
+
function parseUnsignedInteger(value) {
|
|
154
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
155
|
+
if (!/^\d+$/u.test(text)) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
return BigInt(text);
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function resolveConfiguredSatSweepDestination(params) {
|
|
165
|
+
const rawConfig = params.cfg.plugins?.entries?.["sat-mining"]?.config;
|
|
166
|
+
if (!isRecord(rawConfig) || !isRecord(rawConfig.automation)) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const rawSweep = rawConfig.automation.satSweep;
|
|
170
|
+
if (!isRecord(rawSweep) || rawSweep.enabled !== true) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const explicitAddress = typeof rawSweep.destinationAddress === "string" ? rawSweep.destinationAddress.trim() : "";
|
|
174
|
+
const registry = readWalletProviderRegistry(params.env);
|
|
175
|
+
const source = registry.wallets.find((wallet) => wallet.id === params.walletId);
|
|
176
|
+
if (explicitAddress) {
|
|
177
|
+
return explicitAddress !== source?.addresses?.solana?.trim() ? explicitAddress : null;
|
|
178
|
+
}
|
|
179
|
+
const destinationWalletId = typeof rawSweep.destinationWalletId === "string" ? rawSweep.destinationWalletId.trim() : "";
|
|
180
|
+
if (!destinationWalletId || destinationWalletId === params.walletId) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return registry.wallets.find((wallet) => wallet.id === destinationWalletId)?.addresses?.solana?.trim() || null;
|
|
184
|
+
}
|
|
185
|
+
function validateSatMiningTokenSweep(params) {
|
|
64
186
|
if (params.payload.chain !== "solana") {
|
|
65
|
-
return
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
message: "SAT sweep must be a typed Solana transfer"
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (params.payload.actionKind && params.payload.actionKind !== "send") {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
message: "SAT sweep cannot use a generic signing action"
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (params.payload.contract?.trim()) {
|
|
199
|
+
return {
|
|
200
|
+
ok: false,
|
|
201
|
+
message: "SAT sweep cannot include a contract override"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const walletId = params.walletId?.trim() || "";
|
|
205
|
+
if (!walletId || resolveWalletRoleForId({
|
|
206
|
+
walletId,
|
|
207
|
+
cfg: params.cfg,
|
|
208
|
+
env: params.env
|
|
209
|
+
}) !== "mining") {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
message: "SAT sweep requires the configured Mining wallet"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (params.sendPath !== "automation" || String(params.requestedBy ?? "").trim() !== "sat-mining:auto-sweep") {
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
message: "Mining wallet automation is limited to the SAT sweep worker"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const authorization = params.authorization;
|
|
222
|
+
if (!authorization || authorization.kind !== "sat-auto-sweep-v1") {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
message: "SAT sweep requires an exact typed sweep authorization"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const occurrenceId = authorization.occurrenceId.trim();
|
|
229
|
+
const expectedExecutionIntentId = `sat-auto-sweep:${walletId}:${occurrenceId}`;
|
|
230
|
+
if (!occurrenceId || params.executionIntentId?.trim() !== expectedExecutionIntentId || authorization.walletId.trim() !== walletId) {
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
message: "SAT sweep occurrence identity does not match the Mining wallet"
|
|
234
|
+
};
|
|
66
235
|
}
|
|
67
236
|
const ids = tryResolveSatRuntimeIds(params.env);
|
|
237
|
+
const rawConfig = params.cfg.plugins?.entries?.["sat-mining"]?.config;
|
|
238
|
+
const rawTokenConfig = isRecord(rawConfig) && isRecord(rawConfig.tokenConfig) ? rawConfig.tokenConfig : null;
|
|
239
|
+
const configuredMint = (rawTokenConfig && typeof rawTokenConfig.mintAddress === "string" ? rawTokenConfig.mintAddress.trim() : "") || ids?.mintAddress || "";
|
|
68
240
|
const program = String(params.payload.program ?? "").trim();
|
|
69
|
-
if (!program || !
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
241
|
+
if (!program || !configuredMint || program !== configuredMint || authorization.mint.trim() !== configuredMint) {
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
message: "Mining wallet automation can transfer only the configured SAT mint"
|
|
245
|
+
};
|
|
74
246
|
}
|
|
75
|
-
|
|
76
|
-
walletId: params.walletId,
|
|
247
|
+
const destination = resolveConfiguredSatSweepDestination({
|
|
77
248
|
cfg: params.cfg,
|
|
249
|
+
walletId,
|
|
78
250
|
env: params.env
|
|
79
|
-
})
|
|
251
|
+
});
|
|
252
|
+
if (!destination || params.payload.to?.trim() !== destination || authorization.destination.trim() !== destination) {
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
message: "SAT sweep destination does not match the configured destination"
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const sourceBalance = parseUnsignedInteger(authorization.sourceBalanceRaw);
|
|
259
|
+
const keepRaw = parseUnsignedInteger(authorization.keepRaw);
|
|
260
|
+
const minRaw = parseUnsignedInteger(authorization.minRaw);
|
|
261
|
+
const amountRaw = parseUnsignedInteger(authorization.amountRaw);
|
|
262
|
+
if (sourceBalance === null || keepRaw === null || minRaw === null || amountRaw === null || !Number.isFinite(authorization.percentage)) {
|
|
263
|
+
return {
|
|
264
|
+
ok: false,
|
|
265
|
+
message: "SAT sweep authorization contains invalid raw amounts"
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const rawSweep = isRecord(rawConfig) && isRecord(rawConfig.automation) && isRecord(rawConfig.automation.satSweep) ? rawConfig.automation.satSweep : null;
|
|
269
|
+
const configuredMode = rawSweep?.mode === "percentage" ? "percentage" : "all";
|
|
270
|
+
const configuredPercentage = typeof rawSweep?.percentage === "number" && Number.isFinite(rawSweep.percentage) ? Math.max(0, Math.min(100, rawSweep.percentage)) : 100;
|
|
271
|
+
const configuredKeep = parseUnsignedInteger(typeof rawSweep?.keepRaw === "string" && rawSweep.keepRaw.trim() ? rawSweep.keepRaw : "0");
|
|
272
|
+
const configuredMin = parseUnsignedInteger(typeof rawSweep?.minRaw === "string" && rawSweep.minRaw.trim() ? rawSweep.minRaw : "1");
|
|
273
|
+
if (authorization.mode !== configuredMode || authorization.percentage !== configuredPercentage || keepRaw !== configuredKeep || minRaw !== configuredMin) {
|
|
274
|
+
return {
|
|
275
|
+
ok: false,
|
|
276
|
+
message: "SAT sweep authorization does not match the active sweep policy"
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
const spendable = sourceBalance > keepRaw ? sourceBalance - keepRaw : 0n;
|
|
280
|
+
const expectedAmount = configuredMode === "percentage" ? spendable * BigInt(Math.round(configuredPercentage * 1e4)) / 1000000n : spendable;
|
|
281
|
+
if (expectedAmount <= 0n || expectedAmount < minRaw || amountRaw !== expectedAmount || params.payload.amount?.trim() !== expectedAmount.toString()) {
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
message: "SAT sweep amount does not match the exact computed occurrence"
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return { ok: true };
|
|
80
288
|
}
|
|
81
289
|
function upsertSettlementLinkForPayload(params) {
|
|
82
290
|
if (!params.settlementContext?.taskId) {
|
|
@@ -214,11 +422,13 @@ function resolveSendProviderForPayload(params) {
|
|
|
214
422
|
};
|
|
215
423
|
}
|
|
216
424
|
const matrix = buildWalletProviderCapabilityMatrix(provider);
|
|
217
|
-
|
|
425
|
+
const supportsServerSend = providerSupportsChainOperation({
|
|
218
426
|
matrix,
|
|
219
427
|
chain: params.payload.chain,
|
|
220
428
|
operation: "send"
|
|
221
|
-
})
|
|
429
|
+
});
|
|
430
|
+
const supportsInteractiveBrowser = params.allowInteractiveBrowser === true && matrix.signing.interactiveSend;
|
|
431
|
+
if (!supportsServerSend && !supportsInteractiveBrowser) {
|
|
222
432
|
return {
|
|
223
433
|
ok: false,
|
|
224
434
|
code: "wallet_provider_unsupported_chain",
|
|
@@ -244,27 +454,6 @@ function resolveTtlMs(env = process.env) {
|
|
|
244
454
|
}
|
|
245
455
|
return Math.min(24 * 60 * 60 * 1e3, raw * 1e3);
|
|
246
456
|
}
|
|
247
|
-
function resolveSendRetryAttempts(env = process.env) {
|
|
248
|
-
const raw = Number.parseInt(String(env.FASED_WALLET_SEND_RETRY_ATTEMPTS ?? ""), 10);
|
|
249
|
-
if (!Number.isFinite(raw) || raw < 1) {
|
|
250
|
-
return DEFAULT_SEND_RETRY_ATTEMPTS;
|
|
251
|
-
}
|
|
252
|
-
return Math.min(6, raw);
|
|
253
|
-
}
|
|
254
|
-
function resolveSendRetryDelayMs(env = process.env) {
|
|
255
|
-
const raw = Number.parseInt(String(env.FASED_WALLET_SEND_RETRY_DELAY_MS ?? ""), 10);
|
|
256
|
-
if (!Number.isFinite(raw) || raw < 0) {
|
|
257
|
-
return DEFAULT_SEND_RETRY_DELAY_MS;
|
|
258
|
-
}
|
|
259
|
-
return Math.min(15e3, raw);
|
|
260
|
-
}
|
|
261
|
-
function isRetryableSendError(error) {
|
|
262
|
-
if (error instanceof WalletProviderError) {
|
|
263
|
-
return error.retryable;
|
|
264
|
-
}
|
|
265
|
-
const message = String(error).toLowerCase();
|
|
266
|
-
return message.includes("timeout") || message.includes("timed out") || message.includes("network") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("429") || message.includes("rate limit") || message.includes("503") || message.includes("504");
|
|
267
|
-
}
|
|
268
457
|
function normalizeErrorMessage(error) {
|
|
269
458
|
return walletDiagnosticErrorMessage(error);
|
|
270
459
|
}
|
|
@@ -280,71 +469,395 @@ function withSendAttemptMetadata(value, attempts) {
|
|
|
280
469
|
}
|
|
281
470
|
};
|
|
282
471
|
}
|
|
283
|
-
async function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
472
|
+
async function executeWalletSendOnce(params) {
|
|
473
|
+
try {
|
|
474
|
+
const tx = await params.execute();
|
|
475
|
+
return {
|
|
476
|
+
ok: true,
|
|
477
|
+
tx,
|
|
478
|
+
attempts: 1
|
|
479
|
+
};
|
|
480
|
+
} catch (error) {
|
|
481
|
+
return {
|
|
482
|
+
ok: false,
|
|
483
|
+
error,
|
|
484
|
+
attempts: 1
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async function prepareAndSendProviderTransaction(params) {
|
|
489
|
+
const payload = {
|
|
490
|
+
...params.payload,
|
|
491
|
+
requestId: params.requestId
|
|
492
|
+
};
|
|
493
|
+
if (params.provider.id !== "turnkey") {
|
|
494
|
+
return await params.provider.sendTx(payload);
|
|
495
|
+
}
|
|
496
|
+
if (!params.provider.prepareTx) {
|
|
497
|
+
throw new Error("turnkey provider does not expose transaction preparation");
|
|
498
|
+
}
|
|
499
|
+
const prepared = await params.provider.prepareTx(payload);
|
|
500
|
+
return await params.provider.sendTx({
|
|
501
|
+
...payload,
|
|
502
|
+
preparedId: prepared.preparedId
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
function autonomousResultFromExecution(params) {
|
|
506
|
+
if (params.entry.state === "executed" && params.entry.result) {
|
|
507
|
+
return {
|
|
508
|
+
ok: true,
|
|
509
|
+
mode: "autonomous",
|
|
510
|
+
tx: {
|
|
293
511
|
ok: true,
|
|
294
|
-
|
|
295
|
-
|
|
512
|
+
...params.entry.result
|
|
513
|
+
},
|
|
514
|
+
payload: params.payload,
|
|
515
|
+
requestId: params.entry.requestId
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
if (params.entry.state === "unknown" || params.entry.state === "executing") {
|
|
519
|
+
return {
|
|
520
|
+
ok: false,
|
|
521
|
+
code: params.entry.state === "unknown" ? "wallet_provider_ambiguous" : "wallet_send_in_progress",
|
|
522
|
+
message: params.entry.reason ?? (params.entry.state === "unknown" ? "wallet send result is unknown; reconcile the original signer request before retrying" : "wallet send is already executing; wait for its durable result"),
|
|
523
|
+
requestId: params.entry.requestId
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (params.entry.state === "failed") {
|
|
527
|
+
return {
|
|
528
|
+
ok: false,
|
|
529
|
+
code: "send_failed",
|
|
530
|
+
message: params.entry.reason ?? "wallet send failed",
|
|
531
|
+
requestId: params.entry.requestId
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
async function reconcileTypedWalletSend(params) {
|
|
537
|
+
if (!params.provider.getSignerOperation) {
|
|
538
|
+
return {
|
|
539
|
+
state: "unknown",
|
|
540
|
+
reason: "wallet provider cannot reconcile the original send request"
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
let operation = await params.provider.getSignerOperation({
|
|
545
|
+
walletId: params.walletId,
|
|
546
|
+
requestId: params.requestId
|
|
547
|
+
});
|
|
548
|
+
if ((operation.state === "broadcast" || operation.state === "unknown") && params.provider.reconcileSignerOperation) {
|
|
549
|
+
operation = await params.provider.reconcileSignerOperation({
|
|
550
|
+
walletId: params.walletId,
|
|
551
|
+
requestId: params.requestId
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
if (operation.state === "confirmed" && operation.signature) {
|
|
555
|
+
return {
|
|
556
|
+
state: "confirmed",
|
|
557
|
+
signature: operation.signature,
|
|
558
|
+
metadata: {
|
|
559
|
+
provider: params.provider.id,
|
|
560
|
+
requestId: operation.requestId,
|
|
561
|
+
intentDigest: operation.intentDigest,
|
|
562
|
+
transactionDigest: operation.transactionDigest,
|
|
563
|
+
policyHash: operation.policyHash,
|
|
564
|
+
operationState: operation.state,
|
|
565
|
+
reconciled: true
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (operation.state === "failed") {
|
|
570
|
+
return {
|
|
571
|
+
state: "failed",
|
|
572
|
+
reason: operation.error?.trim() || "signer operation failed before confirmation"
|
|
296
573
|
};
|
|
297
|
-
} catch (error) {
|
|
298
|
-
const shouldRetry = isRetryableSendError(error) && attempts < maxAttempts;
|
|
299
|
-
if (!shouldRetry) {
|
|
300
|
-
return {
|
|
301
|
-
ok: false,
|
|
302
|
-
error,
|
|
303
|
-
attempts
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
if (baseDelayMs > 0) {
|
|
307
|
-
await setTimeout(baseDelayMs * attempts);
|
|
308
|
-
}
|
|
309
574
|
}
|
|
575
|
+
return {
|
|
576
|
+
state: "unknown",
|
|
577
|
+
signature: operation.signature,
|
|
578
|
+
reason: `signer operation ${operation.requestId} is ${operation.state}; no new broadcast is allowed`
|
|
579
|
+
};
|
|
580
|
+
} catch (error) {
|
|
581
|
+
const message = normalizeErrorMessage(error);
|
|
582
|
+
if (/signer operation not found/iu.test(message)) {
|
|
583
|
+
return { state: "not_found" };
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
state: "unknown",
|
|
587
|
+
reason: `could not reconcile the original signer request: ${message}`
|
|
588
|
+
};
|
|
310
589
|
}
|
|
311
|
-
return {
|
|
312
|
-
ok: false,
|
|
313
|
-
error: new Error("wallet send failed after retry attempts"),
|
|
314
|
-
attempts: maxAttempts
|
|
315
|
-
};
|
|
316
590
|
}
|
|
317
591
|
function loadFile(env = process.env) {
|
|
318
592
|
const paths = ensureWalletStateDir(env);
|
|
319
|
-
if (!
|
|
593
|
+
if (!syncFs.existsSync(paths.sendApprovalsPath)) {
|
|
320
594
|
return {
|
|
321
595
|
version: 1,
|
|
322
596
|
requests: []
|
|
323
597
|
};
|
|
324
598
|
}
|
|
325
599
|
try {
|
|
326
|
-
const parsed = JSON.parse(
|
|
600
|
+
const parsed = JSON.parse(syncFs.readFileSync(paths.sendApprovalsPath, "utf8"));
|
|
327
601
|
if (parsed?.version === 1 && Array.isArray(parsed.requests)) {
|
|
328
602
|
return {
|
|
329
603
|
version: 1,
|
|
330
604
|
requests: parsed.requests
|
|
331
605
|
};
|
|
332
606
|
}
|
|
333
|
-
} catch {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
};
|
|
607
|
+
} catch (error) {
|
|
608
|
+
throw new Error("wallet approval state is unreadable; refusing to reset persisted requests", { cause: error });
|
|
609
|
+
}
|
|
610
|
+
throw new Error("wallet approval state has an unsupported shape; refusing to reset persisted requests");
|
|
338
611
|
}
|
|
339
612
|
function saveFile(file, env = process.env) {
|
|
340
613
|
const paths = ensureWalletStateDir(env);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
614
|
+
const serialized = `${JSON.stringify(file, null, 2)}\n`;
|
|
615
|
+
const tempPath = path.join(path.dirname(paths.sendApprovalsPath), `.${path.basename(paths.sendApprovalsPath)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
|
|
616
|
+
let tempFd;
|
|
617
|
+
let renamed = false;
|
|
618
|
+
try {
|
|
619
|
+
tempFd = syncFs.openSync(tempPath, "wx", 384);
|
|
620
|
+
syncFs.writeFileSync(tempFd, serialized, { encoding: "utf8" });
|
|
621
|
+
syncFs.fsyncSync(tempFd);
|
|
622
|
+
syncFs.closeSync(tempFd);
|
|
623
|
+
tempFd = undefined;
|
|
624
|
+
syncFs.renameSync(tempPath, paths.sendApprovalsPath);
|
|
625
|
+
renamed = true;
|
|
626
|
+
const directoryFd = syncFs.openSync(path.dirname(paths.sendApprovalsPath), "r");
|
|
627
|
+
try {
|
|
628
|
+
syncFs.fsyncSync(directoryFd);
|
|
629
|
+
} finally {
|
|
630
|
+
syncFs.closeSync(directoryFd);
|
|
631
|
+
}
|
|
632
|
+
} catch (error) {
|
|
633
|
+
if (tempFd !== undefined) {
|
|
634
|
+
try {
|
|
635
|
+
syncFs.closeSync(tempFd);
|
|
636
|
+
} catch {}
|
|
637
|
+
}
|
|
638
|
+
if (!renamed) {
|
|
639
|
+
try {
|
|
640
|
+
syncFs.unlinkSync(tempPath);
|
|
641
|
+
} catch {}
|
|
642
|
+
}
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function canonicalApprovalJson(value) {
|
|
647
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
648
|
+
return JSON.stringify(value);
|
|
649
|
+
}
|
|
650
|
+
if (typeof value === "number") {
|
|
651
|
+
if (!Number.isFinite(value)) {
|
|
652
|
+
throw new Error("wallet approval contains a non-finite number");
|
|
653
|
+
}
|
|
654
|
+
return JSON.stringify(value);
|
|
655
|
+
}
|
|
656
|
+
if (Array.isArray(value)) {
|
|
657
|
+
return `[${value.map((entry) => canonicalApprovalJson(entry)).join(",")}]`;
|
|
658
|
+
}
|
|
659
|
+
if (value && typeof value === "object") {
|
|
660
|
+
const record = value;
|
|
661
|
+
return `{${Object.keys(record).filter((key) => record[key] !== undefined).toSorted().map((key) => `${JSON.stringify(key)}:${canonicalApprovalJson(record[key])}`).join(",")}}`;
|
|
662
|
+
}
|
|
663
|
+
throw new Error("wallet approval contains an unsupported value");
|
|
664
|
+
}
|
|
665
|
+
function walletApprovalRequestDigest(request) {
|
|
666
|
+
return `sha256:${createHash("sha256").update(canonicalApprovalJson({
|
|
667
|
+
id: request.id,
|
|
668
|
+
createdAt: request.createdAt,
|
|
669
|
+
expiresAt: request.expiresAt,
|
|
670
|
+
requestedBy: request.requestedBy,
|
|
671
|
+
payload: request.payload
|
|
672
|
+
})).digest("hex")}`;
|
|
673
|
+
}
|
|
674
|
+
function approvalRequestDigest(request) {
|
|
675
|
+
const computed = walletApprovalRequestDigest(request);
|
|
676
|
+
if (request.requestDigest && request.requestDigest !== computed) {
|
|
677
|
+
throw new Error(`wallet approval ${request.id} immutable request digest does not match its persisted payload`);
|
|
678
|
+
}
|
|
679
|
+
return computed;
|
|
680
|
+
}
|
|
681
|
+
function reserveWalletApprovalIdentity(request, env) {
|
|
682
|
+
const identityDir = path.join(path.dirname(ensureWalletStateDir(env).sendApprovalsPath), ".wallet-approval-identities");
|
|
683
|
+
syncFs.mkdirSync(identityDir, {
|
|
684
|
+
recursive: true,
|
|
685
|
+
mode: 448
|
|
344
686
|
});
|
|
687
|
+
const identityPath = path.join(identityDir, `${createHash("sha256").update(request.id).digest("hex")}.json`);
|
|
688
|
+
const payload = `${JSON.stringify({
|
|
689
|
+
version: 1,
|
|
690
|
+
requestId: request.id,
|
|
691
|
+
requestDigest: approvalRequestDigest(request)
|
|
692
|
+
})}\n`;
|
|
693
|
+
let descriptor;
|
|
345
694
|
try {
|
|
346
|
-
|
|
347
|
-
|
|
695
|
+
descriptor = syncFs.openSync(identityPath, "wx", 384);
|
|
696
|
+
syncFs.writeFileSync(descriptor, payload, "utf8");
|
|
697
|
+
syncFs.fsyncSync(descriptor);
|
|
698
|
+
syncFs.closeSync(descriptor);
|
|
699
|
+
descriptor = undefined;
|
|
700
|
+
} catch (error) {
|
|
701
|
+
if (descriptor !== undefined) {
|
|
702
|
+
syncFs.closeSync(descriptor);
|
|
703
|
+
}
|
|
704
|
+
if (error.code === "EEXIST") {
|
|
705
|
+
let existingDigest;
|
|
706
|
+
try {
|
|
707
|
+
const existing = JSON.parse(syncFs.readFileSync(identityPath, "utf8"));
|
|
708
|
+
existingDigest = typeof existing.requestDigest === "string" ? existing.requestDigest : undefined;
|
|
709
|
+
} catch {}
|
|
710
|
+
throw new Error(existingDigest === request.requestDigest ? "wallet approval request ID already exists" : "wallet approval request ID collides with a different immutable request", { cause: error });
|
|
711
|
+
}
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
714
|
+
return () => {
|
|
715
|
+
try {
|
|
716
|
+
syncFs.unlinkSync(identityPath);
|
|
717
|
+
} catch {}
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
async function withApprovalMutationLock(env, task) {
|
|
721
|
+
const filePath = ensureWalletStateDir(env).sendApprovalsPath;
|
|
722
|
+
const previous = APPROVAL_MUTATION_QUEUES.get(filePath) ?? Promise.resolve();
|
|
723
|
+
let releaseQueue;
|
|
724
|
+
const turn = new Promise((resolve) => {
|
|
725
|
+
releaseQueue = resolve;
|
|
726
|
+
});
|
|
727
|
+
const queued = previous.then(() => turn, () => turn);
|
|
728
|
+
APPROVAL_MUTATION_QUEUES.set(filePath, queued);
|
|
729
|
+
await previous.catch(() => undefined);
|
|
730
|
+
try {
|
|
731
|
+
return await withFileLock(filePath, APPROVAL_LOCK_OPTIONS, async () => await task());
|
|
732
|
+
} finally {
|
|
733
|
+
releaseQueue();
|
|
734
|
+
if (APPROVAL_MUTATION_QUEUES.get(filePath) === queued) {
|
|
735
|
+
APPROVAL_MUTATION_QUEUES.delete(filePath);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
async function claimWalletApprovalProcessExecution(params) {
|
|
740
|
+
const requestId = params.requestId.trim();
|
|
741
|
+
const approvalsPath = ensureWalletStateDir(params.env).sendApprovalsPath;
|
|
742
|
+
const executionKey = `${approvalsPath}\0${requestId}`;
|
|
743
|
+
if (APPROVAL_ACTIVE_EXECUTIONS.has(executionKey)) {
|
|
744
|
+
throw new Error("wallet approval execution is already in progress");
|
|
745
|
+
}
|
|
746
|
+
APPROVAL_ACTIVE_EXECUTIONS.set(executionKey, true);
|
|
747
|
+
const target = path.join(path.dirname(approvalsPath), ".wallet-approval-executions", createHash("sha256").update(requestId).digest("hex"));
|
|
748
|
+
let lock;
|
|
749
|
+
try {
|
|
750
|
+
lock = await acquireFileLock(target, APPROVAL_EXECUTION_LOCK_OPTIONS);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
APPROVAL_ACTIVE_EXECUTIONS.delete(executionKey);
|
|
753
|
+
throw new Error("wallet approval execution is already in progress", { cause: error });
|
|
754
|
+
}
|
|
755
|
+
let released = false;
|
|
756
|
+
return async () => {
|
|
757
|
+
if (released) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
released = true;
|
|
761
|
+
APPROVAL_ACTIVE_EXECUTIONS.delete(executionKey);
|
|
762
|
+
await lock.release();
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
async function claimWalletSendApprovalExecution(params) {
|
|
766
|
+
let release;
|
|
767
|
+
try {
|
|
768
|
+
release = await claimWalletApprovalProcessExecution({
|
|
769
|
+
requestId: params.requestId,
|
|
770
|
+
env: params.env
|
|
771
|
+
});
|
|
772
|
+
} catch (error) {
|
|
773
|
+
return {
|
|
774
|
+
ok: false,
|
|
775
|
+
code: "execution_in_progress_or_unknown",
|
|
776
|
+
message: normalizeErrorMessage(error)
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
const result = await withApprovalMutationLock(params.env, () => {
|
|
780
|
+
const file = loadFile(params.env);
|
|
781
|
+
const request = findRequest(file, params.requestId);
|
|
782
|
+
if (!request) {
|
|
783
|
+
return {
|
|
784
|
+
ok: false,
|
|
785
|
+
code: "not_found",
|
|
786
|
+
message: "approval request not found"
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
let requestDigest;
|
|
790
|
+
try {
|
|
791
|
+
requestDigest = approvalRequestDigest(request);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
return {
|
|
794
|
+
ok: false,
|
|
795
|
+
request,
|
|
796
|
+
code: "immutable_request_mismatch",
|
|
797
|
+
message: normalizeErrorMessage(error)
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const recovery = request.status === "executing" || request.status === "unknown";
|
|
801
|
+
const claimable = request.status === "pending" || recovery || params.allowExpiredSignerReview === true && request.status === "expired";
|
|
802
|
+
if (!claimable) {
|
|
803
|
+
return {
|
|
804
|
+
ok: false,
|
|
805
|
+
request,
|
|
806
|
+
code: request.status === "executing" || request.status === "unknown" ? "execution_in_progress_or_unknown" : "invalid_state",
|
|
807
|
+
message: request.status === "executing" || request.status === "unknown" ? `approval request is ${request.status}; reconcile its durable execution before any retry` : `approval request is ${request.status}`
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
request.requestDigest = requestDigest;
|
|
811
|
+
if (!recovery) {
|
|
812
|
+
request.status = "executing";
|
|
813
|
+
request.execution = {
|
|
814
|
+
id: randomBytes(24).toString("base64url"),
|
|
815
|
+
requestDigest,
|
|
816
|
+
startedAt: new Date().toISOString()
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
request.approvedBy = params.actor?.trim() || "operator";
|
|
820
|
+
request.decisionAt = request.decisionAt ?? new Date().toISOString();
|
|
821
|
+
saveFile(file, params.env);
|
|
822
|
+
return {
|
|
823
|
+
ok: true,
|
|
824
|
+
file,
|
|
825
|
+
request,
|
|
826
|
+
recovered: recovery
|
|
827
|
+
};
|
|
828
|
+
});
|
|
829
|
+
if (!result.ok) {
|
|
830
|
+
await release();
|
|
831
|
+
return result;
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
...result,
|
|
835
|
+
release
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function hasSignerOwnedReviewMetadata(payload) {
|
|
839
|
+
const hasSignerField = Object.entries(payload).some(([key, value]) => key.startsWith("signer") && value !== undefined && value !== null && (!Array.isArray(value) || value.length > 0) && (typeof value !== "string" || Boolean(value.trim())));
|
|
840
|
+
return hasSignerField || payload.providerId === "local-socket-signer" && (payload.actionKind === "solana_swap" || payload.actionKind === "signer_review");
|
|
841
|
+
}
|
|
842
|
+
function hasExactSignerOwnedReviewBinding(request) {
|
|
843
|
+
const payload = request.payload;
|
|
844
|
+
const reviewExpiresAt = payload.signerReviewExpiresAt?.trim();
|
|
845
|
+
const transactionArtifactHasDigest = payload.signerArtifactKind !== "solana-transaction" || Boolean(payload.signerTransactionDigest?.trim());
|
|
846
|
+
return payload.providerId === "local-socket-signer" && Boolean(payload.signerReviewId?.trim()) && Boolean(payload.signerWalletId?.trim()) && Boolean(payload.signerIntentType?.trim()) && Boolean(payload.signerPolicyHash?.trim()) && Boolean(payload.signerIntentDigest?.trim()) && payload.signerSemanticIntent !== undefined && Boolean(payload.signerArtifactKind) && Boolean(payload.signerArtifactDigest?.trim()) && transactionArtifactHasDigest && Boolean(payload.signerAsset?.trim()) && Boolean(payload.signerAmount?.trim()) && Boolean(payload.signerDestination?.trim()) && Boolean(payload.signerPolicyOperation?.trim()) && Boolean(payload.signerRequiredPrograms?.length) && payload.signerRequiredPrograms?.every((program) => Boolean(program.trim())) === true && Boolean(payload.signerRequiredRole) && Boolean(payload.signerNonce?.trim()) && Boolean(payload.signerIssuedAt?.trim()) && Boolean(reviewExpiresAt) && request.expiresAt === reviewExpiresAt;
|
|
847
|
+
}
|
|
848
|
+
function isWalletSendApprovalExpired(request, now = nowMs()) {
|
|
849
|
+
const expiresAt = Date.parse(request.expiresAt);
|
|
850
|
+
return Number.isFinite(expiresAt) && expiresAt <= now;
|
|
851
|
+
}
|
|
852
|
+
function expireWalletSendApprovalRequest(params) {
|
|
853
|
+
const now = params.now ?? nowMs();
|
|
854
|
+
params.request.status = "expired";
|
|
855
|
+
params.request.decisionAt = new Date(now).toISOString();
|
|
856
|
+
params.request.reason = "expired";
|
|
857
|
+
syncApprovalTaskForRequest({
|
|
858
|
+
request: params.request,
|
|
859
|
+
...params.settlementLink ? { settlementLink: params.settlementLink } : {}
|
|
860
|
+
});
|
|
348
861
|
}
|
|
349
862
|
function markExpired(file) {
|
|
350
863
|
const now = nowMs();
|
|
@@ -353,12 +866,14 @@ function markExpired(file) {
|
|
|
353
866
|
if (request.status !== "pending") {
|
|
354
867
|
continue;
|
|
355
868
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
869
|
+
if (hasExactSignerOwnedReviewBinding(request)) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (isWalletSendApprovalExpired(request, now)) {
|
|
873
|
+
expireWalletSendApprovalRequest({
|
|
874
|
+
request,
|
|
875
|
+
now
|
|
876
|
+
});
|
|
362
877
|
changed = true;
|
|
363
878
|
}
|
|
364
879
|
}
|
|
@@ -369,20 +884,40 @@ function createWalletSendApprovalRequest(params) {
|
|
|
369
884
|
const file = loadFile(env);
|
|
370
885
|
const changed = markExpired(file);
|
|
371
886
|
const createdAtMs = nowMs();
|
|
372
|
-
const requestId = createRequestId();
|
|
887
|
+
const requestId = params.requestId?.trim() || createRequestId();
|
|
888
|
+
const requestedExpiresAt = params.expiresAt?.trim();
|
|
889
|
+
const requestedExpiresAtMs = requestedExpiresAt ? Date.parse(requestedExpiresAt) : Number.NaN;
|
|
890
|
+
if (requestedExpiresAt && (!Number.isFinite(requestedExpiresAtMs) || requestedExpiresAtMs <= createdAtMs)) {
|
|
891
|
+
throw new Error("approval expiration must be a future timestamp");
|
|
892
|
+
}
|
|
373
893
|
const req = {
|
|
374
894
|
id: requestId,
|
|
375
895
|
taskLedgerId: walletApprovalTaskId(requestId),
|
|
376
896
|
createdAt: new Date(createdAtMs).toISOString(),
|
|
377
|
-
expiresAt: new Date(createdAtMs + resolveTtlMs(env)).toISOString(),
|
|
897
|
+
expiresAt: requestedExpiresAt || new Date(createdAtMs + resolveTtlMs(env)).toISOString(),
|
|
378
898
|
status: "pending",
|
|
379
899
|
requestedBy: params.requestedBy?.trim() || "agent",
|
|
380
900
|
payload: params.payload,
|
|
381
901
|
...params.simulation ? { simulation: params.simulation } : {},
|
|
382
902
|
...params.approvalDiff ? { approvalDiff: params.approvalDiff } : {}
|
|
383
903
|
};
|
|
904
|
+
if (hasSignerOwnedReviewMetadata(req.payload) && !hasExactSignerOwnedReviewBinding(req)) {
|
|
905
|
+
throw new Error("signer-reviewed approval requires a complete exact signer review binding");
|
|
906
|
+
}
|
|
907
|
+
req.requestDigest = walletApprovalRequestDigest(req);
|
|
908
|
+
const collision = findRequest(file, requestId);
|
|
909
|
+
if (collision) {
|
|
910
|
+
const collisionDigest = approvalRequestDigest(collision);
|
|
911
|
+
throw new Error(collisionDigest === req.requestDigest ? "wallet approval request ID already exists" : "wallet approval request ID collides with a different immutable request");
|
|
912
|
+
}
|
|
913
|
+
const releaseIdentityReservation = reserveWalletApprovalIdentity(req, env);
|
|
384
914
|
file.requests.push(req);
|
|
385
|
-
|
|
915
|
+
try {
|
|
916
|
+
saveFile(file, env);
|
|
917
|
+
} catch (error) {
|
|
918
|
+
releaseIdentityReservation();
|
|
919
|
+
throw error;
|
|
920
|
+
}
|
|
386
921
|
syncApprovalTaskForRequest({
|
|
387
922
|
request: req,
|
|
388
923
|
settlementContext: params.settlementContext
|
|
@@ -410,20 +945,70 @@ function createWalletSendApprovalRequest(params) {
|
|
|
410
945
|
});
|
|
411
946
|
return req;
|
|
412
947
|
}
|
|
413
|
-
|
|
948
|
+
function createSignerReviewApprovalRequest(params) {
|
|
414
949
|
const env = params.env ?? process.env;
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
950
|
+
const review = params.review;
|
|
951
|
+
const splMint = review.asset.startsWith("solana:spl:") ? review.asset.slice("solana:spl:".length).trim() : undefined;
|
|
952
|
+
const payload = bindSignerReviewToWalletApprovalPayload({
|
|
953
|
+
role: params.role,
|
|
954
|
+
review,
|
|
955
|
+
payload: {
|
|
956
|
+
chain: "solana",
|
|
957
|
+
actionKind: "signer_review",
|
|
958
|
+
assetId: review.asset,
|
|
959
|
+
assetSymbol: params.assetSymbol,
|
|
960
|
+
assetName: params.assetName,
|
|
961
|
+
amountDisplay: params.amountDisplay,
|
|
962
|
+
to: review.destination,
|
|
963
|
+
amount: review.amount,
|
|
964
|
+
program: splMint,
|
|
965
|
+
memo: params.memo,
|
|
966
|
+
walletId: params.walletId ?? review.walletId,
|
|
967
|
+
walletName: params.walletName,
|
|
968
|
+
providerId: "local-socket-signer"
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
const existing = getWalletSendApprovalRequest({
|
|
972
|
+
requestId: review.requestId,
|
|
973
|
+
env
|
|
974
|
+
});
|
|
975
|
+
if (existing) {
|
|
976
|
+
if (canonicalApprovalJson(existing.payload) !== canonicalApprovalJson(payload)) {
|
|
977
|
+
throw new Error("signer review approval ID collides with different persisted metadata");
|
|
978
|
+
}
|
|
979
|
+
return existing;
|
|
980
|
+
}
|
|
981
|
+
return createWalletSendApprovalRequest({
|
|
982
|
+
requestId: review.requestId,
|
|
983
|
+
expiresAt: review.expiresAt,
|
|
984
|
+
payload,
|
|
985
|
+
requestedBy: params.requestedBy ?? "signer-review",
|
|
986
|
+
env
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
async function createOrExecuteWalletSend(params) {
|
|
990
|
+
const env = params.env ?? process.env;
|
|
991
|
+
const requestedBy = params.requestedBy?.trim() || "agent";
|
|
992
|
+
const cfg = params.runtimeConfig ?? loadConfig();
|
|
993
|
+
const effectiveConfig = applyWalletPolicyConfig({
|
|
994
|
+
config: params.config,
|
|
995
|
+
cfg,
|
|
420
996
|
env,
|
|
421
997
|
walletId: params.payload.walletId
|
|
422
998
|
});
|
|
423
|
-
const settlementRequestId = params.settlementContext?.taskId ? createRequestId() : undefined;
|
|
424
999
|
const resolvedMode = params.sendPath === "reviewed" ? "manual" : params.sendPath === "automation" ? "autonomous" : params.config.execution.mode === "autonomous" ? "autonomous" : "manual";
|
|
1000
|
+
const executionIntentId = params.executionIntentId?.trim();
|
|
1001
|
+
if (resolvedMode === "autonomous" && !executionIntentId) {
|
|
1002
|
+
return {
|
|
1003
|
+
ok: false,
|
|
1004
|
+
code: "wallet_execution_intent_required",
|
|
1005
|
+
message: "autonomous wallet sends require a stable caller-owned executionIntentId that is reused across retries"
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
const settlementRequestId = resolvedMode === "autonomous" && executionIntentId ? walletSendRequestId(executionIntentId) : params.settlementContext?.taskId ? createRequestId() : undefined;
|
|
425
1009
|
const settlementPayload = {
|
|
426
1010
|
...params.payload,
|
|
1011
|
+
...executionIntentId ? { executionIntentId } : {},
|
|
427
1012
|
providerId: params.providerIdOverride ?? params.payload.providerId ?? resolveWalletProviderId(cfg, env)
|
|
428
1013
|
};
|
|
429
1014
|
const walletRole = resolveWalletRoleForId({
|
|
@@ -431,8 +1016,18 @@ async function createOrExecuteWalletSend(params) {
|
|
|
431
1016
|
cfg,
|
|
432
1017
|
env
|
|
433
1018
|
});
|
|
434
|
-
|
|
435
|
-
|
|
1019
|
+
const satSweepValidation = validateSatMiningTokenSweep({
|
|
1020
|
+
cfg,
|
|
1021
|
+
env,
|
|
1022
|
+
walletId: params.payload.walletId,
|
|
1023
|
+
requestedBy,
|
|
1024
|
+
sendPath: params.sendPath,
|
|
1025
|
+
executionIntentId,
|
|
1026
|
+
authorization: params.satSweepAuthorization,
|
|
1027
|
+
payload: params.payload
|
|
1028
|
+
});
|
|
1029
|
+
if (resolvedMode === "autonomous" && walletRole === "mining" && !satSweepValidation.ok) {
|
|
1030
|
+
const message = satSweepValidation.message;
|
|
436
1031
|
if (settlementRequestId) {
|
|
437
1032
|
upsertSettlementLinkForPayload({
|
|
438
1033
|
requestId: settlementRequestId,
|
|
@@ -451,11 +1046,20 @@ async function createOrExecuteWalletSend(params) {
|
|
|
451
1046
|
requestId: settlementRequestId
|
|
452
1047
|
};
|
|
453
1048
|
}
|
|
1049
|
+
if (resolvedMode === "autonomous" && requestedBy.startsWith("sat-mining") && !satSweepValidation.ok) {
|
|
1050
|
+
return {
|
|
1051
|
+
ok: false,
|
|
1052
|
+
code: "wallet_role_not_allowed",
|
|
1053
|
+
message: satSweepValidation.message,
|
|
1054
|
+
requestId: settlementRequestId
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
454
1057
|
const providerResolution = resolveSendProviderForPayload({
|
|
455
1058
|
cfg,
|
|
456
1059
|
wallet: params.config,
|
|
457
1060
|
payload: params.payload,
|
|
458
1061
|
providerIdOverride: params.providerIdOverride,
|
|
1062
|
+
allowInteractiveBrowser: resolvedMode !== "autonomous",
|
|
459
1063
|
env
|
|
460
1064
|
});
|
|
461
1065
|
if (!providerResolution.ok) {
|
|
@@ -486,14 +1090,7 @@ async function createOrExecuteWalletSend(params) {
|
|
|
486
1090
|
sendPath: params.sendPath,
|
|
487
1091
|
payload: params.payload
|
|
488
1092
|
});
|
|
489
|
-
const skipSatMiningTokenCapRequirement =
|
|
490
|
-
cfg,
|
|
491
|
-
env,
|
|
492
|
-
walletId: params.payload.walletId,
|
|
493
|
-
requestedBy,
|
|
494
|
-
sendPath: params.sendPath,
|
|
495
|
-
payload: params.payload
|
|
496
|
-
});
|
|
1093
|
+
const skipSatMiningTokenCapRequirement = satSweepValidation.ok;
|
|
497
1094
|
if (resolvedMode === "autonomous" && selectedProviderId !== "local-socket-signer") {
|
|
498
1095
|
const message = "Autonomous wallet execution is restricted to local-socket-signer in the current self-hosted runtime";
|
|
499
1096
|
if (settlementRequestId) {
|
|
@@ -547,120 +1144,332 @@ async function createOrExecuteWalletSend(params) {
|
|
|
547
1144
|
};
|
|
548
1145
|
}
|
|
549
1146
|
if (resolvedMode !== "autonomous") {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
1147
|
+
const requestId = executionIntentId ? walletSendRequestId(executionIntentId) : createRequestId();
|
|
1148
|
+
let releaseManualExecution;
|
|
1149
|
+
if (executionIntentId) {
|
|
1150
|
+
const walletId = params.payload.walletId?.trim();
|
|
1151
|
+
if (!walletId) {
|
|
1152
|
+
return {
|
|
1153
|
+
ok: false,
|
|
1154
|
+
code: "wallet_execution_intent_required",
|
|
1155
|
+
message: "reviewed wallet sends with executionIntentId require an explicit walletId",
|
|
1156
|
+
requestId
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
const intentDigest = walletSendIntentDigest({
|
|
1160
|
+
version: 1,
|
|
1161
|
+
executionIntentId,
|
|
1162
|
+
requestedBy,
|
|
1163
|
+
sendPath: "reviewed",
|
|
1164
|
+
payload: settlementPayload,
|
|
1165
|
+
settlementContext: params.settlementContext
|
|
1166
|
+
});
|
|
1167
|
+
try {
|
|
1168
|
+
const begun = await beginWalletSendExecution({
|
|
1169
|
+
executionIntentId,
|
|
1170
|
+
intentDigest,
|
|
1171
|
+
walletId,
|
|
1172
|
+
providerId: selectedProviderId,
|
|
1173
|
+
env
|
|
1174
|
+
});
|
|
1175
|
+
if (begun.entry.state === "approval_pending") {
|
|
1176
|
+
const existing = getWalletSendApprovalRequest({
|
|
1177
|
+
requestId,
|
|
1178
|
+
env
|
|
1179
|
+
});
|
|
1180
|
+
if (existing) {
|
|
1181
|
+
return {
|
|
1182
|
+
ok: true,
|
|
1183
|
+
mode: "manual",
|
|
1184
|
+
request: existing
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
ok: false,
|
|
1189
|
+
code: "wallet_approval_reconciliation_required",
|
|
1190
|
+
message: "wallet approval identity exists but its approval record must be reconciled",
|
|
1191
|
+
requestId
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
releaseManualExecution = await claimWalletSendExecution(executionIntentId, env);
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
const existing = getWalletSendApprovalRequest({
|
|
1197
|
+
requestId,
|
|
1198
|
+
env
|
|
1199
|
+
});
|
|
1200
|
+
if (existing) {
|
|
1201
|
+
return {
|
|
1202
|
+
ok: true,
|
|
1203
|
+
mode: "manual",
|
|
1204
|
+
request: existing
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
return {
|
|
1208
|
+
ok: false,
|
|
1209
|
+
code: "wallet_execution_intent_collision",
|
|
1210
|
+
message: normalizeErrorMessage(error),
|
|
1211
|
+
requestId
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
try {
|
|
1216
|
+
let reviewedPayload = {
|
|
1217
|
+
...settlementPayload,
|
|
1218
|
+
providerId: selectedProviderId
|
|
1219
|
+
};
|
|
1220
|
+
if (selectedProviderId === "local-socket-signer" && !isSolanaSwapApprovalPayload(reviewedPayload) && !reviewedPayload.signerReviewId) {
|
|
1221
|
+
const walletId = reviewedPayload.walletId?.trim();
|
|
1222
|
+
const destination = reviewedPayload.to?.trim();
|
|
1223
|
+
const amount = reviewedPayload.amount?.trim();
|
|
1224
|
+
if (!walletId || !destination || !amount || !provider.prepareTypedTransferReview) {
|
|
1225
|
+
return {
|
|
1226
|
+
ok: false,
|
|
1227
|
+
code: "wallet_signer_review_required",
|
|
1228
|
+
message: "reviewed local-socket-signer sends require walletId, destination, amount, and exact signer review support",
|
|
1229
|
+
requestId
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
try {
|
|
1233
|
+
const review = await provider.prepareTypedTransferReview({
|
|
1234
|
+
walletId,
|
|
1235
|
+
requestId,
|
|
1236
|
+
destination,
|
|
1237
|
+
amount,
|
|
1238
|
+
...reviewedPayload.program?.trim() ? { mint: reviewedPayload.program.trim() } : {},
|
|
1239
|
+
...reviewedPayload.memo?.trim() ? { memo: reviewedPayload.memo.trim() } : {}
|
|
1240
|
+
});
|
|
1241
|
+
reviewedPayload = bindSignerReviewToWalletApprovalPayload({
|
|
1242
|
+
payload: reviewedPayload,
|
|
1243
|
+
review,
|
|
1244
|
+
role: walletRole
|
|
1245
|
+
});
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
return {
|
|
1248
|
+
ok: false,
|
|
1249
|
+
code: "wallet_signer_review_failed",
|
|
1250
|
+
message: normalizeErrorMessage(error),
|
|
1251
|
+
requestId
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
const existingRequest = getWalletSendApprovalRequest({
|
|
1256
|
+
requestId,
|
|
1257
|
+
env
|
|
1258
|
+
});
|
|
1259
|
+
if (existingRequest) {
|
|
1260
|
+
if (existingRequest.requestedBy !== requestedBy || canonicalApprovalJson(existingRequest.payload) !== canonicalApprovalJson(reviewedPayload)) {
|
|
1261
|
+
throw new Error("wallet approval request ID collides with a different immutable reviewed send");
|
|
1262
|
+
}
|
|
1263
|
+
if (executionIntentId) {
|
|
1264
|
+
await updateWalletSendExecution({
|
|
1265
|
+
executionIntentId,
|
|
1266
|
+
expectedStates: ["reserved", "approval_pending"],
|
|
1267
|
+
state: "approval_pending",
|
|
1268
|
+
patch: { approvalRequestId: existingRequest.id },
|
|
1269
|
+
env
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
return {
|
|
1273
|
+
ok: true,
|
|
1274
|
+
mode: "manual",
|
|
1275
|
+
request: existingRequest
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
const request = createWalletSendApprovalRequest({
|
|
1279
|
+
requestId,
|
|
1280
|
+
expiresAt: reviewedPayload.signerReviewExpiresAt,
|
|
1281
|
+
payload: reviewedPayload,
|
|
555
1282
|
requestedBy,
|
|
556
1283
|
settlementContext: params.settlementContext,
|
|
557
1284
|
simulation,
|
|
558
1285
|
approvalDiff: simulation.diff,
|
|
559
1286
|
env
|
|
560
|
-
})
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
const custodyGate = await enforceWalletCustodyForAutonomousSend({
|
|
564
|
-
wallet: effectiveConfig,
|
|
565
|
-
env,
|
|
566
|
-
cfg,
|
|
567
|
-
walletId: params.payload.walletId,
|
|
568
|
-
approvalToken: params.approvalToken,
|
|
569
|
-
approvalHost: params.approvalHost,
|
|
570
|
-
deviceShare: params.custodyDeviceShare
|
|
571
|
-
});
|
|
572
|
-
if (!custodyGate.ok) {
|
|
573
|
-
if (settlementRequestId) {
|
|
574
|
-
upsertSettlementLinkForPayload({
|
|
575
|
-
requestId: settlementRequestId,
|
|
576
|
-
payload: settlementPayload,
|
|
577
|
-
settlementContext: params.settlementContext,
|
|
578
|
-
mode: resolvedMode,
|
|
579
|
-
status: "failed",
|
|
580
|
-
reason: custodyGate.message,
|
|
581
|
-
env
|
|
582
1287
|
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
if (params.sendPath !== "automation" && custodyGate.custodyMode !== "split-key-active" && resolveWalletApprovalAuthMode(env, cfg) === "webauthn") {
|
|
592
|
-
const consumed = consumeWalletApprovalGrant({
|
|
593
|
-
host: params.approvalHost?.trim() || "127.0.0.1",
|
|
594
|
-
operation: "wallet.send",
|
|
595
|
-
token: params.approvalToken ?? "",
|
|
596
|
-
env,
|
|
597
|
-
cfg
|
|
598
|
-
});
|
|
599
|
-
if (!consumed.ok) {
|
|
600
|
-
if (settlementRequestId) {
|
|
601
|
-
upsertSettlementLinkForPayload({
|
|
602
|
-
requestId: settlementRequestId,
|
|
603
|
-
payload: settlementPayload,
|
|
604
|
-
settlementContext: params.settlementContext,
|
|
605
|
-
mode: resolvedMode,
|
|
606
|
-
status: "failed",
|
|
607
|
-
reason: consumed.message,
|
|
1288
|
+
if (executionIntentId) {
|
|
1289
|
+
await updateWalletSendExecution({
|
|
1290
|
+
executionIntentId,
|
|
1291
|
+
expectedStates: ["reserved"],
|
|
1292
|
+
state: "approval_pending",
|
|
1293
|
+
patch: { approvalRequestId: request.id },
|
|
608
1294
|
env
|
|
609
1295
|
});
|
|
610
1296
|
}
|
|
611
1297
|
return {
|
|
612
|
-
ok:
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
requestId: settlementRequestId
|
|
1298
|
+
ok: true,
|
|
1299
|
+
mode: "manual",
|
|
1300
|
+
request
|
|
616
1301
|
};
|
|
1302
|
+
} finally {
|
|
1303
|
+
await releaseManualExecution?.();
|
|
617
1304
|
}
|
|
618
1305
|
}
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1306
|
+
const walletId = params.payload.walletId?.trim();
|
|
1307
|
+
if (!walletId || !executionIntentId || !settlementRequestId) {
|
|
1308
|
+
return {
|
|
1309
|
+
ok: false,
|
|
1310
|
+
code: "wallet_execution_intent_required",
|
|
1311
|
+
message: "autonomous wallet sends require an explicit walletId and executionIntentId",
|
|
1312
|
+
requestId: settlementRequestId
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
const intentDigest = walletSendIntentDigest({
|
|
1316
|
+
version: 1,
|
|
1317
|
+
executionIntentId,
|
|
1318
|
+
requestedBy,
|
|
1319
|
+
sendPath: "automation",
|
|
1320
|
+
payload: settlementPayload,
|
|
1321
|
+
settlementContext: params.settlementContext,
|
|
1322
|
+
satSweepAuthorization: params.satSweepAuthorization
|
|
627
1323
|
});
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
});
|
|
639
|
-
}
|
|
1324
|
+
let begun;
|
|
1325
|
+
try {
|
|
1326
|
+
begun = await beginWalletSendExecution({
|
|
1327
|
+
executionIntentId,
|
|
1328
|
+
intentDigest,
|
|
1329
|
+
walletId,
|
|
1330
|
+
providerId: selectedProviderId,
|
|
1331
|
+
env
|
|
1332
|
+
});
|
|
1333
|
+
} catch (error) {
|
|
640
1334
|
return {
|
|
641
1335
|
ok: false,
|
|
642
|
-
code:
|
|
643
|
-
message:
|
|
1336
|
+
code: "wallet_execution_intent_collision",
|
|
1337
|
+
message: normalizeErrorMessage(error),
|
|
644
1338
|
requestId: settlementRequestId
|
|
645
1339
|
};
|
|
646
1340
|
}
|
|
1341
|
+
let releaseExecution;
|
|
647
1342
|
try {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1343
|
+
releaseExecution = await claimWalletSendExecution(executionIntentId, env);
|
|
1344
|
+
} catch {
|
|
1345
|
+
const current = getWalletSendExecution({
|
|
1346
|
+
executionIntentId,
|
|
1347
|
+
env
|
|
1348
|
+
}) ?? begun.entry;
|
|
1349
|
+
return autonomousResultFromExecution({
|
|
1350
|
+
entry: current,
|
|
1351
|
+
payload: settlementPayload
|
|
1352
|
+
}) ?? {
|
|
1353
|
+
ok: false,
|
|
1354
|
+
code: "wallet_send_in_progress",
|
|
1355
|
+
message: "wallet send is already executing; wait for its durable result",
|
|
1356
|
+
requestId: current.requestId
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
try {
|
|
1360
|
+
let current = getWalletSendExecution({
|
|
1361
|
+
executionIntentId,
|
|
1362
|
+
env
|
|
1363
|
+
}) ?? begun.entry;
|
|
1364
|
+
const terminal = autonomousResultFromExecution({
|
|
1365
|
+
entry: current,
|
|
1366
|
+
payload: settlementPayload
|
|
1367
|
+
});
|
|
1368
|
+
if (current.state === "executed" || current.state === "failed") {
|
|
1369
|
+
return terminal;
|
|
1370
|
+
}
|
|
1371
|
+
if (current.state === "executing" || current.state === "unknown") {
|
|
1372
|
+
const reconciliation = await reconcileTypedWalletSend({
|
|
1373
|
+
provider,
|
|
1374
|
+
walletId,
|
|
1375
|
+
requestId: current.requestId
|
|
1376
|
+
});
|
|
1377
|
+
if (reconciliation.state === "confirmed") {
|
|
1378
|
+
current = await updateWalletSendExecution({
|
|
1379
|
+
executionIntentId,
|
|
1380
|
+
expectedStates: ["executing", "unknown"],
|
|
1381
|
+
state: "executed",
|
|
1382
|
+
patch: {
|
|
1383
|
+
signature: reconciliation.signature,
|
|
1384
|
+
result: {
|
|
1385
|
+
chain: "solana",
|
|
1386
|
+
txHash: reconciliation.signature,
|
|
1387
|
+
metadata: reconciliation.metadata
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
env
|
|
1391
|
+
});
|
|
1392
|
+
return autonomousResultFromExecution({
|
|
1393
|
+
entry: current,
|
|
1394
|
+
payload: settlementPayload
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
if (reconciliation.state === "failed") {
|
|
1398
|
+
current = await updateWalletSendExecution({
|
|
1399
|
+
executionIntentId,
|
|
1400
|
+
expectedStates: ["executing", "unknown"],
|
|
1401
|
+
state: "failed",
|
|
1402
|
+
patch: { reason: reconciliation.reason },
|
|
1403
|
+
env
|
|
1404
|
+
});
|
|
1405
|
+
return autonomousResultFromExecution({
|
|
1406
|
+
entry: current,
|
|
1407
|
+
payload: settlementPayload
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
if (reconciliation.state === "unknown") {
|
|
1411
|
+
current = await updateWalletSendExecution({
|
|
1412
|
+
executionIntentId,
|
|
1413
|
+
expectedStates: ["executing", "unknown"],
|
|
1414
|
+
state: "unknown",
|
|
1415
|
+
patch: {
|
|
1416
|
+
reason: reconciliation.reason,
|
|
1417
|
+
signature: reconciliation.signature
|
|
1418
|
+
},
|
|
1419
|
+
env
|
|
1420
|
+
});
|
|
1421
|
+
return autonomousResultFromExecution({
|
|
1422
|
+
entry: current,
|
|
1423
|
+
payload: settlementPayload
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (begun.created) {
|
|
1428
|
+
const daily = enforceWalletDailyCap({
|
|
1429
|
+
config: effectiveConfig,
|
|
1430
|
+
chain: params.payload.chain,
|
|
1431
|
+
amount: params.payload.amount,
|
|
1432
|
+
program: params.payload.program,
|
|
1433
|
+
walletId: params.payload.walletId,
|
|
1434
|
+
env,
|
|
1435
|
+
skipNativeSolanaCaps
|
|
656
1436
|
});
|
|
1437
|
+
if (!daily.ok) {
|
|
1438
|
+
current = await updateWalletSendExecution({
|
|
1439
|
+
executionIntentId,
|
|
1440
|
+
expectedStates: ["reserved"],
|
|
1441
|
+
state: "failed",
|
|
1442
|
+
patch: { reason: daily.message ?? daily.code ?? "wallet daily cap exceeded" },
|
|
1443
|
+
env
|
|
1444
|
+
});
|
|
1445
|
+
return {
|
|
1446
|
+
ok: false,
|
|
1447
|
+
code: daily.code ?? "wallet_cap_daily_exceeded",
|
|
1448
|
+
message: daily.message ?? "wallet daily cap exceeded",
|
|
1449
|
+
requestId: current.requestId
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
657
1452
|
}
|
|
1453
|
+
current = await updateWalletSendExecution({
|
|
1454
|
+
executionIntentId,
|
|
1455
|
+
expectedStates: ["reserved", "executing"],
|
|
1456
|
+
state: "executing",
|
|
1457
|
+
env
|
|
1458
|
+
});
|
|
1459
|
+
upsertSettlementLinkForPayload({
|
|
1460
|
+
requestId: current.requestId,
|
|
1461
|
+
payload: settlementPayload,
|
|
1462
|
+
settlementContext: params.settlementContext,
|
|
1463
|
+
mode: resolvedMode,
|
|
1464
|
+
status: "pending",
|
|
1465
|
+
env
|
|
1466
|
+
});
|
|
658
1467
|
appendWalletAuditEntry({
|
|
659
1468
|
action: "send_requested",
|
|
660
1469
|
actor: requestedBy,
|
|
661
1470
|
details: buildWalletSendAuditDetails({
|
|
662
|
-
payload:
|
|
663
|
-
requestId:
|
|
1471
|
+
payload: settlementPayload,
|
|
1472
|
+
requestId: current.requestId,
|
|
664
1473
|
mode: resolvedMode,
|
|
665
1474
|
providerId: selectedProviderId,
|
|
666
1475
|
taskId: params.settlementContext?.taskId,
|
|
@@ -669,97 +1478,125 @@ async function createOrExecuteWalletSend(params) {
|
|
|
669
1478
|
}),
|
|
670
1479
|
env
|
|
671
1480
|
});
|
|
672
|
-
const sent = await
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1481
|
+
const sent = await executeWalletSendOnce({ execute: async () => await prepareAndSendProviderTransaction({
|
|
1482
|
+
provider,
|
|
1483
|
+
payload: settlementPayload,
|
|
1484
|
+
requestId: current.requestId
|
|
1485
|
+
}) });
|
|
1486
|
+
if (!sent.ok) {
|
|
1487
|
+
const reconciliation = await reconcileTypedWalletSend({
|
|
1488
|
+
provider,
|
|
1489
|
+
walletId,
|
|
1490
|
+
requestId: current.requestId
|
|
1491
|
+
});
|
|
1492
|
+
if (reconciliation.state === "confirmed") {
|
|
1493
|
+
current = await updateWalletSendExecution({
|
|
1494
|
+
executionIntentId,
|
|
1495
|
+
expectedStates: ["executing"],
|
|
1496
|
+
state: "executed",
|
|
1497
|
+
patch: {
|
|
1498
|
+
signature: reconciliation.signature,
|
|
1499
|
+
result: {
|
|
1500
|
+
chain: "solana",
|
|
1501
|
+
txHash: reconciliation.signature,
|
|
1502
|
+
metadata: reconciliation.metadata
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
env
|
|
1506
|
+
});
|
|
1507
|
+
} else if (reconciliation.state === "failed" || reconciliation.state === "not_found") {
|
|
1508
|
+
const reason = reconciliation.state === "failed" ? reconciliation.reason : normalizeErrorMessage(sent.error);
|
|
1509
|
+
current = await updateWalletSendExecution({
|
|
1510
|
+
executionIntentId,
|
|
1511
|
+
expectedStates: ["executing"],
|
|
1512
|
+
state: "failed",
|
|
1513
|
+
patch: { reason },
|
|
1514
|
+
env
|
|
1515
|
+
});
|
|
1516
|
+
} else {
|
|
1517
|
+
current = await updateWalletSendExecution({
|
|
1518
|
+
executionIntentId,
|
|
1519
|
+
expectedStates: ["executing"],
|
|
1520
|
+
state: "unknown",
|
|
1521
|
+
patch: {
|
|
1522
|
+
reason: reconciliation.reason,
|
|
1523
|
+
signature: reconciliation.signature
|
|
1524
|
+
},
|
|
1525
|
+
env
|
|
1526
|
+
});
|
|
687
1527
|
}
|
|
1528
|
+
} else {
|
|
1529
|
+
const tx = withSendAttemptMetadata(sent.tx, sent.attempts);
|
|
1530
|
+
current = await updateWalletSendExecution({
|
|
1531
|
+
executionIntentId,
|
|
1532
|
+
expectedStates: ["executing"],
|
|
1533
|
+
state: "executed",
|
|
1534
|
+
patch: {
|
|
1535
|
+
signature: tx.txHash,
|
|
1536
|
+
result: {
|
|
1537
|
+
chain: "solana",
|
|
1538
|
+
txHash: tx.txHash,
|
|
1539
|
+
...tx.signer ? { signer: tx.signer } : {},
|
|
1540
|
+
...tx.metadata ? { metadata: tx.metadata } : {}
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
env
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
const result = autonomousResultFromExecution({
|
|
1547
|
+
entry: current,
|
|
1548
|
+
payload: settlementPayload
|
|
688
1549
|
});
|
|
689
|
-
if (!
|
|
690
|
-
|
|
1550
|
+
if (!result?.ok) {
|
|
1551
|
+
appendWalletAuditEntry({
|
|
1552
|
+
action: "send_failed",
|
|
1553
|
+
actor: requestedBy,
|
|
1554
|
+
details: buildWalletSendAuditDetails({
|
|
1555
|
+
payload: settlementPayload,
|
|
1556
|
+
requestId: current.requestId,
|
|
1557
|
+
mode: resolvedMode,
|
|
1558
|
+
providerId: selectedProviderId,
|
|
1559
|
+
reason: current.reason
|
|
1560
|
+
}),
|
|
1561
|
+
env
|
|
1562
|
+
});
|
|
1563
|
+
markWalletSettlementLinkOutcome({
|
|
1564
|
+
requestId: current.requestId,
|
|
1565
|
+
status: current.state === "unknown" || current.state === "executing" ? "unknown" : "failed",
|
|
1566
|
+
reason: current.reason,
|
|
1567
|
+
env
|
|
1568
|
+
});
|
|
1569
|
+
return result;
|
|
691
1570
|
}
|
|
692
|
-
const tx = withSendAttemptMetadata(sent.tx, sent.attempts);
|
|
693
1571
|
appendWalletAuditEntry({
|
|
694
1572
|
action: "send_executed",
|
|
695
1573
|
actor: requestedBy,
|
|
696
1574
|
details: buildWalletSendAuditDetails({
|
|
697
|
-
payload:
|
|
698
|
-
requestId:
|
|
1575
|
+
payload: settlementPayload,
|
|
1576
|
+
requestId: current.requestId,
|
|
699
1577
|
mode: resolvedMode,
|
|
700
1578
|
taskId: params.settlementContext?.taskId,
|
|
701
1579
|
invoiceId: params.settlementContext?.invoiceId,
|
|
702
1580
|
senderHandle: params.settlementContext?.senderHandle,
|
|
703
1581
|
providerId: selectedProviderId,
|
|
704
|
-
txHash: tx.txHash,
|
|
705
|
-
attempts:
|
|
1582
|
+
txHash: result.tx.txHash,
|
|
1583
|
+
attempts: 1
|
|
706
1584
|
}),
|
|
707
1585
|
env
|
|
708
1586
|
});
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
txHash: tx.txHash,
|
|
714
|
-
env
|
|
715
|
-
});
|
|
716
|
-
await publishSettlementEvidenceForLink({
|
|
717
|
-
settlementLink,
|
|
718
|
-
env
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
return {
|
|
722
|
-
ok: true,
|
|
723
|
-
mode: "autonomous",
|
|
724
|
-
tx,
|
|
725
|
-
payload: params.payload,
|
|
726
|
-
requestId: settlementRequestId
|
|
727
|
-
};
|
|
728
|
-
} catch (err) {
|
|
729
|
-
const message = walletDiagnosticErrorString(err);
|
|
730
|
-
appendWalletAuditEntry({
|
|
731
|
-
action: "send_failed",
|
|
732
|
-
actor: requestedBy,
|
|
733
|
-
details: {
|
|
734
|
-
requestId: settlementRequestId,
|
|
735
|
-
mode: resolvedMode,
|
|
736
|
-
chain: params.payload.chain,
|
|
737
|
-
amount: params.payload.amount,
|
|
738
|
-
to: params.payload.to,
|
|
739
|
-
providerId: selectedProviderId,
|
|
740
|
-
walletId: params.payload.walletId,
|
|
741
|
-
walletName: params.payload.walletName,
|
|
742
|
-
taskId: params.settlementContext?.taskId,
|
|
743
|
-
invoiceId: params.settlementContext?.invoiceId,
|
|
744
|
-
senderHandle: params.settlementContext?.senderHandle,
|
|
745
|
-
reason: message
|
|
746
|
-
},
|
|
1587
|
+
const settlementLink = markWalletSettlementLinkOutcome({
|
|
1588
|
+
requestId: current.requestId,
|
|
1589
|
+
status: "executed",
|
|
1590
|
+
txHash: result.tx.txHash,
|
|
747
1591
|
env
|
|
748
1592
|
});
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
return {
|
|
758
|
-
ok: false,
|
|
759
|
-
code: "send_failed",
|
|
760
|
-
message,
|
|
761
|
-
requestId: settlementRequestId
|
|
762
|
-
};
|
|
1593
|
+
await publishSettlementEvidenceForLink({
|
|
1594
|
+
settlementLink,
|
|
1595
|
+
env
|
|
1596
|
+
});
|
|
1597
|
+
return result;
|
|
1598
|
+
} finally {
|
|
1599
|
+
await releaseExecution();
|
|
763
1600
|
}
|
|
764
1601
|
}
|
|
765
1602
|
function listWalletSendApprovalRequests(params) {
|
|
@@ -773,16 +1610,143 @@ function listWalletSendApprovalRequests(params) {
|
|
|
773
1610
|
const requests = [...file.requests].toReversed().filter((request) => status === "all" ? true : request.status === status).slice(0, limit);
|
|
774
1611
|
return requests;
|
|
775
1612
|
}
|
|
1613
|
+
function getWalletSendApprovalRequest(params) {
|
|
1614
|
+
const env = params.env ?? process.env;
|
|
1615
|
+
const file = loadFile(env);
|
|
1616
|
+
if (markExpired(file)) {
|
|
1617
|
+
saveFile(file, env);
|
|
1618
|
+
}
|
|
1619
|
+
return findRequest(file, params.requestId) ?? null;
|
|
1620
|
+
}
|
|
1621
|
+
async function markWalletSendRequestExecutedExternally(params) {
|
|
1622
|
+
const env = params.env ?? process.env;
|
|
1623
|
+
const file = loadFile(env);
|
|
1624
|
+
const request = findRequest(file, params.requestId);
|
|
1625
|
+
if (!request) {
|
|
1626
|
+
throw new Error("approval request not found");
|
|
1627
|
+
}
|
|
1628
|
+
if (request.status === "executed" && request.result?.txHash === params.txHash) {
|
|
1629
|
+
return request;
|
|
1630
|
+
}
|
|
1631
|
+
if (request.status !== "pending" && request.status !== "approved") {
|
|
1632
|
+
throw new Error(`approval request is ${request.status}`);
|
|
1633
|
+
}
|
|
1634
|
+
const actor = params.actor?.trim() || "control-ui";
|
|
1635
|
+
const settlementLinkBefore = getWalletSettlementLinkByRequestId({
|
|
1636
|
+
requestId: request.id,
|
|
1637
|
+
env
|
|
1638
|
+
});
|
|
1639
|
+
if (request.status === "pending") {
|
|
1640
|
+
request.status = "approved";
|
|
1641
|
+
request.approvedBy = actor;
|
|
1642
|
+
request.decisionAt = new Date().toISOString();
|
|
1643
|
+
appendWalletAuditEntry({
|
|
1644
|
+
action: "send_approved",
|
|
1645
|
+
actor,
|
|
1646
|
+
details: buildWalletSendAuditDetails({
|
|
1647
|
+
payload: request.payload,
|
|
1648
|
+
requestId: request.id,
|
|
1649
|
+
mode: "manual",
|
|
1650
|
+
providerId: request.payload.providerId,
|
|
1651
|
+
taskId: settlementLinkBefore?.taskId,
|
|
1652
|
+
invoiceId: settlementLinkBefore?.invoiceId,
|
|
1653
|
+
senderHandle: settlementLinkBefore?.senderHandle
|
|
1654
|
+
}),
|
|
1655
|
+
env
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
request.status = "executed";
|
|
1659
|
+
request.result = { txHash: params.txHash };
|
|
1660
|
+
request.reason = undefined;
|
|
1661
|
+
const settlementLink = markWalletSettlementLinkOutcome({
|
|
1662
|
+
requestId: request.id,
|
|
1663
|
+
status: "executed",
|
|
1664
|
+
txHash: params.txHash,
|
|
1665
|
+
env
|
|
1666
|
+
});
|
|
1667
|
+
appendWalletAuditEntry({
|
|
1668
|
+
action: "send_executed",
|
|
1669
|
+
actor,
|
|
1670
|
+
details: buildWalletSendAuditDetails({
|
|
1671
|
+
payload: request.payload,
|
|
1672
|
+
requestId: request.id,
|
|
1673
|
+
mode: "manual",
|
|
1674
|
+
providerId: request.payload.providerId,
|
|
1675
|
+
txHash: params.txHash,
|
|
1676
|
+
taskId: settlementLink?.taskId,
|
|
1677
|
+
invoiceId: settlementLink?.invoiceId,
|
|
1678
|
+
senderHandle: settlementLink?.senderHandle
|
|
1679
|
+
}),
|
|
1680
|
+
env
|
|
1681
|
+
});
|
|
1682
|
+
saveFile(file, env);
|
|
1683
|
+
syncApprovalTaskForRequest({
|
|
1684
|
+
request,
|
|
1685
|
+
settlementLink
|
|
1686
|
+
});
|
|
1687
|
+
await publishSettlementEvidenceForLink({
|
|
1688
|
+
settlementLink,
|
|
1689
|
+
env
|
|
1690
|
+
});
|
|
1691
|
+
return request;
|
|
1692
|
+
}
|
|
1693
|
+
async function markWalletSendRequestBroadcastUnknown(params) {
|
|
1694
|
+
const env = params.env ?? process.env;
|
|
1695
|
+
return await withApprovalMutationLock(env, () => {
|
|
1696
|
+
const file = loadFile(env);
|
|
1697
|
+
const request = findRequest(file, params.requestId);
|
|
1698
|
+
if (!request) {
|
|
1699
|
+
throw new Error("approval request not found");
|
|
1700
|
+
}
|
|
1701
|
+
if (request.status !== "pending" && request.status !== "executing" && request.status !== "approved" && request.status !== "unknown") {
|
|
1702
|
+
return request;
|
|
1703
|
+
}
|
|
1704
|
+
request.status = "unknown";
|
|
1705
|
+
request.approvedBy = params.actor?.trim() || "control-ui";
|
|
1706
|
+
request.decisionAt = request.decisionAt ?? new Date().toISOString();
|
|
1707
|
+
request.reason = params.reason;
|
|
1708
|
+
request.result = params.txHash ? {
|
|
1709
|
+
txHash: params.txHash,
|
|
1710
|
+
error: params.reason
|
|
1711
|
+
} : { error: params.reason };
|
|
1712
|
+
appendWalletAuditEntry({
|
|
1713
|
+
action: "send_failed",
|
|
1714
|
+
actor: request.approvedBy,
|
|
1715
|
+
details: buildWalletSendAuditDetails({
|
|
1716
|
+
payload: request.payload,
|
|
1717
|
+
requestId: request.id,
|
|
1718
|
+
mode: "manual",
|
|
1719
|
+
providerId: request.payload.providerId,
|
|
1720
|
+
txHash: params.txHash,
|
|
1721
|
+
reason: params.reason
|
|
1722
|
+
}),
|
|
1723
|
+
env
|
|
1724
|
+
});
|
|
1725
|
+
const settlementLink = markWalletSettlementLinkOutcome({
|
|
1726
|
+
requestId: request.id,
|
|
1727
|
+
status: "unknown",
|
|
1728
|
+
txHash: params.txHash,
|
|
1729
|
+
reason: params.reason,
|
|
1730
|
+
env
|
|
1731
|
+
});
|
|
1732
|
+
saveFile(file, env);
|
|
1733
|
+
syncApprovalTaskForRequest({
|
|
1734
|
+
request,
|
|
1735
|
+
...settlementLink ? { settlementLink } : {}
|
|
1736
|
+
});
|
|
1737
|
+
return request;
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
776
1740
|
function findRequest(file, requestId) {
|
|
777
1741
|
return file.requests.find((request) => request.id === requestId.trim());
|
|
778
1742
|
}
|
|
779
1743
|
async function approveWalletSendRequest(params) {
|
|
780
1744
|
const env = params.env ?? process.env;
|
|
781
|
-
|
|
1745
|
+
let file = loadFile(env);
|
|
782
1746
|
if (markExpired(file)) {
|
|
783
1747
|
saveFile(file, env);
|
|
784
1748
|
}
|
|
785
|
-
|
|
1749
|
+
let request = findRequest(file, params.requestId);
|
|
786
1750
|
if (!request) {
|
|
787
1751
|
return {
|
|
788
1752
|
ok: false,
|
|
@@ -790,7 +1754,11 @@ async function approveWalletSendRequest(params) {
|
|
|
790
1754
|
message: "approval request not found"
|
|
791
1755
|
};
|
|
792
1756
|
}
|
|
793
|
-
|
|
1757
|
+
const hasSignerReviewMetadata = hasSignerOwnedReviewMetadata(request.payload);
|
|
1758
|
+
const hasSignerOwnedReview = hasExactSignerOwnedReviewBinding(request);
|
|
1759
|
+
const canReconcileExpiredSignerReview = request.status === "expired" && hasSignerOwnedReview;
|
|
1760
|
+
const canRecoverClaimedExecution = request.status === "executing" || request.status === "unknown";
|
|
1761
|
+
if (request.status !== "pending" && !canReconcileExpiredSignerReview && !canRecoverClaimedExecution) {
|
|
794
1762
|
return {
|
|
795
1763
|
ok: false,
|
|
796
1764
|
code: "invalid_state",
|
|
@@ -802,16 +1770,43 @@ async function approveWalletSendRequest(params) {
|
|
|
802
1770
|
requestId: request.id,
|
|
803
1771
|
env
|
|
804
1772
|
});
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
request.
|
|
808
|
-
request.
|
|
1773
|
+
if (hasSignerReviewMetadata && !hasSignerOwnedReview) {
|
|
1774
|
+
request.status = "failed";
|
|
1775
|
+
request.reason = "signer-reviewed approval is missing its complete exact signer binding";
|
|
1776
|
+
request.result = { error: request.reason };
|
|
809
1777
|
request.decisionAt = new Date().toISOString();
|
|
810
1778
|
saveFile(file, env);
|
|
811
1779
|
syncApprovalTaskForRequest({
|
|
812
1780
|
request,
|
|
813
1781
|
settlementLink
|
|
814
1782
|
});
|
|
1783
|
+
appendWalletAuditEntry({
|
|
1784
|
+
action: "send_failed",
|
|
1785
|
+
actor: params.actor?.trim() || "operator",
|
|
1786
|
+
details: buildWalletSendAuditDetails({
|
|
1787
|
+
payload: request.payload,
|
|
1788
|
+
requestId: request.id,
|
|
1789
|
+
mode: "manual",
|
|
1790
|
+
taskId: settlementLink?.taskId,
|
|
1791
|
+
invoiceId: settlementLink?.invoiceId,
|
|
1792
|
+
senderHandle: settlementLink?.senderHandle,
|
|
1793
|
+
reason: request.reason
|
|
1794
|
+
}),
|
|
1795
|
+
env
|
|
1796
|
+
});
|
|
1797
|
+
return {
|
|
1798
|
+
ok: false,
|
|
1799
|
+
code: "wallet_signer_review_binding_incomplete",
|
|
1800
|
+
message: request.reason,
|
|
1801
|
+
request
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
if (isWalletSendApprovalExpired(request) && !hasSignerOwnedReview) {
|
|
1805
|
+
expireWalletSendApprovalRequest({
|
|
1806
|
+
request,
|
|
1807
|
+
settlementLink
|
|
1808
|
+
});
|
|
1809
|
+
saveFile(file, env);
|
|
815
1810
|
return {
|
|
816
1811
|
ok: false,
|
|
817
1812
|
code: "expired",
|
|
@@ -819,103 +1814,137 @@ async function approveWalletSendRequest(params) {
|
|
|
819
1814
|
request
|
|
820
1815
|
};
|
|
821
1816
|
}
|
|
822
|
-
if (isSolanaSwapApprovalPayload(request.payload)) {
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
config: params.config,
|
|
827
|
-
runtimeConfig: cfg,
|
|
828
|
-
providerIdOverride: params.providerIdOverride,
|
|
829
|
-
autonomous: false,
|
|
1817
|
+
if (isSolanaSwapApprovalPayload(request.payload) && !hasSignerOwnedReview) {
|
|
1818
|
+
const claim = await claimWalletSendApprovalExecution({
|
|
1819
|
+
requestId: request.id,
|
|
1820
|
+
actor: params.actor,
|
|
830
1821
|
env
|
|
831
1822
|
});
|
|
832
|
-
if (!
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1823
|
+
if (!claim.ok) {
|
|
1824
|
+
return {
|
|
1825
|
+
ok: false,
|
|
1826
|
+
code: claim.code,
|
|
1827
|
+
message: claim.message,
|
|
1828
|
+
...claim.request ? { request: claim.request } : {}
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
file = claim.file;
|
|
1832
|
+
request = claim.request;
|
|
1833
|
+
try {
|
|
1834
|
+
const cfg = loadConfig();
|
|
1835
|
+
const executed = await executeSolanaSwapApprovalPayload({
|
|
1836
|
+
payload: request.payload,
|
|
1837
|
+
config: params.config,
|
|
1838
|
+
runtimeConfig: cfg,
|
|
1839
|
+
providerIdOverride: params.providerIdOverride,
|
|
1840
|
+
autonomous: false,
|
|
1841
|
+
reviewAuthorization: params.reviewAuthorization,
|
|
1842
|
+
env
|
|
1843
|
+
});
|
|
1844
|
+
if (!executed.ok) {
|
|
1845
|
+
if (executed.code === "wallet_provider_ambiguous") {
|
|
1846
|
+
const ambiguousRequest = await markWalletSendRequestBroadcastUnknown({
|
|
1847
|
+
requestId: request.id,
|
|
1848
|
+
reason: executed.message,
|
|
1849
|
+
actor: params.actor,
|
|
1850
|
+
env
|
|
1851
|
+
});
|
|
1852
|
+
return {
|
|
1853
|
+
ok: false,
|
|
1854
|
+
code: executed.code,
|
|
1855
|
+
message: executed.message,
|
|
1856
|
+
request: ambiguousRequest
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
request.status = "failed";
|
|
1860
|
+
request.reason = executed.message;
|
|
1861
|
+
request.result = { error: request.reason };
|
|
1862
|
+
request.decisionAt = new Date().toISOString();
|
|
1863
|
+
saveFile(file, env);
|
|
1864
|
+
syncApprovalTaskForRequest({
|
|
1865
|
+
request,
|
|
1866
|
+
settlementLink
|
|
1867
|
+
});
|
|
1868
|
+
appendWalletAuditEntry({
|
|
1869
|
+
action: "send_failed",
|
|
1870
|
+
actor: params.actor?.trim() || "operator",
|
|
1871
|
+
details: {
|
|
1872
|
+
...buildWalletSendAuditDetails({
|
|
1873
|
+
payload: request.payload,
|
|
1874
|
+
requestId: request.id,
|
|
1875
|
+
mode: "manual",
|
|
1876
|
+
taskId: settlementLink?.taskId,
|
|
1877
|
+
invoiceId: settlementLink?.invoiceId,
|
|
1878
|
+
senderHandle: settlementLink?.senderHandle,
|
|
1879
|
+
reason: request.reason
|
|
1880
|
+
}),
|
|
1881
|
+
code: executed.code
|
|
1882
|
+
},
|
|
1883
|
+
env
|
|
1884
|
+
});
|
|
1885
|
+
return {
|
|
1886
|
+
ok: false,
|
|
1887
|
+
code: executed.code,
|
|
1888
|
+
message: executed.message,
|
|
1889
|
+
request
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
request.status = "approved";
|
|
1893
|
+
request.approvedBy = params.actor?.trim() || "operator";
|
|
836
1894
|
request.decisionAt = new Date().toISOString();
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
request,
|
|
840
|
-
|
|
1895
|
+
appendWalletAuditEntry({
|
|
1896
|
+
action: "send_approved",
|
|
1897
|
+
actor: request.approvedBy,
|
|
1898
|
+
details: buildWalletSendAuditDetails({
|
|
1899
|
+
payload: request.payload,
|
|
1900
|
+
requestId: request.id,
|
|
1901
|
+
mode: "manual",
|
|
1902
|
+
taskId: settlementLink?.taskId,
|
|
1903
|
+
invoiceId: settlementLink?.invoiceId,
|
|
1904
|
+
senderHandle: settlementLink?.senderHandle
|
|
1905
|
+
}),
|
|
1906
|
+
env
|
|
841
1907
|
});
|
|
1908
|
+
request.status = "executed";
|
|
1909
|
+
request.result = { txHash: executed.tx.txHash };
|
|
842
1910
|
appendWalletAuditEntry({
|
|
843
|
-
action: "
|
|
844
|
-
actor:
|
|
1911
|
+
action: "send_executed",
|
|
1912
|
+
actor: request.approvedBy,
|
|
845
1913
|
details: {
|
|
846
1914
|
...buildWalletSendAuditDetails({
|
|
847
|
-
payload:
|
|
1915
|
+
payload: {
|
|
1916
|
+
...request.payload,
|
|
1917
|
+
outAmount: executed.order.outAmount ?? request.payload.outAmount,
|
|
1918
|
+
outAmountDisplay: request.payload.outAmountDisplay,
|
|
1919
|
+
otherAmountThreshold: executed.order.otherAmountThreshold ?? request.payload.otherAmountThreshold,
|
|
1920
|
+
routeLabel: executed.order.routeLabel ?? request.payload.routeLabel,
|
|
1921
|
+
jupiterRequestId: executed.order.requestId ?? request.payload.jupiterRequestId
|
|
1922
|
+
},
|
|
848
1923
|
requestId: request.id,
|
|
849
1924
|
mode: "manual",
|
|
1925
|
+
providerId: request.payload.providerId,
|
|
1926
|
+
txHash: executed.tx.txHash,
|
|
850
1927
|
taskId: settlementLink?.taskId,
|
|
851
1928
|
invoiceId: settlementLink?.invoiceId,
|
|
852
|
-
senderHandle: settlementLink?.senderHandle
|
|
853
|
-
reason: request.reason
|
|
1929
|
+
senderHandle: settlementLink?.senderHandle
|
|
854
1930
|
}),
|
|
855
|
-
|
|
1931
|
+
swapProvider: "jupiter"
|
|
856
1932
|
},
|
|
857
1933
|
env
|
|
858
1934
|
});
|
|
1935
|
+
saveFile(file, env);
|
|
1936
|
+
syncApprovalTaskForRequest({
|
|
1937
|
+
request,
|
|
1938
|
+
settlementLink
|
|
1939
|
+
});
|
|
859
1940
|
return {
|
|
860
|
-
ok:
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
request
|
|
1941
|
+
ok: true,
|
|
1942
|
+
request,
|
|
1943
|
+
tx: executed.tx
|
|
864
1944
|
};
|
|
1945
|
+
} finally {
|
|
1946
|
+
await claim.release();
|
|
865
1947
|
}
|
|
866
|
-
request.status = "approved";
|
|
867
|
-
request.approvedBy = params.actor?.trim() || "operator";
|
|
868
|
-
request.decisionAt = new Date().toISOString();
|
|
869
|
-
appendWalletAuditEntry({
|
|
870
|
-
action: "send_approved",
|
|
871
|
-
actor: request.approvedBy,
|
|
872
|
-
details: buildWalletSendAuditDetails({
|
|
873
|
-
payload: request.payload,
|
|
874
|
-
requestId: request.id,
|
|
875
|
-
mode: "manual",
|
|
876
|
-
taskId: settlementLink?.taskId,
|
|
877
|
-
invoiceId: settlementLink?.invoiceId,
|
|
878
|
-
senderHandle: settlementLink?.senderHandle
|
|
879
|
-
}),
|
|
880
|
-
env
|
|
881
|
-
});
|
|
882
|
-
request.status = "executed";
|
|
883
|
-
request.result = { txHash: executed.tx.txHash };
|
|
884
|
-
appendWalletAuditEntry({
|
|
885
|
-
action: "send_executed",
|
|
886
|
-
actor: request.approvedBy,
|
|
887
|
-
details: {
|
|
888
|
-
...buildWalletSendAuditDetails({
|
|
889
|
-
payload: {
|
|
890
|
-
...request.payload,
|
|
891
|
-
outAmount: executed.order.outAmount ?? request.payload.outAmount,
|
|
892
|
-
outAmountDisplay: request.payload.outAmountDisplay,
|
|
893
|
-
otherAmountThreshold: executed.order.otherAmountThreshold ?? request.payload.otherAmountThreshold,
|
|
894
|
-
routeLabel: executed.order.routeLabel ?? request.payload.routeLabel,
|
|
895
|
-
jupiterRequestId: executed.order.requestId ?? request.payload.jupiterRequestId
|
|
896
|
-
},
|
|
897
|
-
requestId: request.id,
|
|
898
|
-
mode: "manual",
|
|
899
|
-
providerId: request.payload.providerId,
|
|
900
|
-
txHash: executed.tx.txHash,
|
|
901
|
-
taskId: settlementLink?.taskId,
|
|
902
|
-
invoiceId: settlementLink?.invoiceId,
|
|
903
|
-
senderHandle: settlementLink?.senderHandle
|
|
904
|
-
}),
|
|
905
|
-
swapProvider: "jupiter"
|
|
906
|
-
},
|
|
907
|
-
env
|
|
908
|
-
});
|
|
909
|
-
saveFile(file, env);
|
|
910
|
-
syncApprovalTaskForRequest({
|
|
911
|
-
request,
|
|
912
|
-
settlementLink
|
|
913
|
-
});
|
|
914
|
-
return {
|
|
915
|
-
ok: true,
|
|
916
|
-
request,
|
|
917
|
-
tx: executed.tx
|
|
918
|
-
};
|
|
919
1948
|
}
|
|
920
1949
|
const cfg = loadConfig();
|
|
921
1950
|
const providerResolution = resolveSendProviderForPayload({
|
|
@@ -978,263 +2007,444 @@ async function approveWalletSendRequest(params) {
|
|
|
978
2007
|
requestedBy: request.requestedBy,
|
|
979
2008
|
payload: request.payload
|
|
980
2009
|
});
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
request.status = "failed";
|
|
995
|
-
request.reason = failed?.detail ?? "wallet policy rejected";
|
|
996
|
-
request.result = { error: request.reason };
|
|
997
|
-
request.simulation = simulation;
|
|
998
|
-
request.approvalDiff = simulation.diff;
|
|
999
|
-
request.decisionAt = new Date().toISOString();
|
|
1000
|
-
saveFile(file, env);
|
|
1001
|
-
syncApprovalTaskForRequest({
|
|
1002
|
-
request,
|
|
1003
|
-
settlementLink
|
|
2010
|
+
const signerReviewId = request.payload.signerReviewId?.trim();
|
|
2011
|
+
const isSignerOwnedReview = selectedProviderId === "local-socket-signer" && Boolean(signerReviewId);
|
|
2012
|
+
if (!isSignerOwnedReview) {
|
|
2013
|
+
const simulation = simulateWalletPolicy({
|
|
2014
|
+
cfg,
|
|
2015
|
+
config: effectiveConfig,
|
|
2016
|
+
payload: request.payload,
|
|
2017
|
+
mode: "manual",
|
|
2018
|
+
source: request.requestedBy,
|
|
2019
|
+
requireDirectSigning: false,
|
|
2020
|
+
skipNativeSolanaCaps,
|
|
2021
|
+
requireSolanaTokenCap: Boolean(request.payload.program?.trim()),
|
|
2022
|
+
env
|
|
1004
2023
|
});
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
2024
|
+
if (!simulation.ok) {
|
|
2025
|
+
const failed = simulation.checks.find((check) => check.status === "fail");
|
|
2026
|
+
request.status = "failed";
|
|
2027
|
+
request.reason = failed?.detail ?? "wallet policy rejected";
|
|
2028
|
+
request.result = { error: request.reason };
|
|
2029
|
+
request.simulation = simulation;
|
|
2030
|
+
request.approvalDiff = simulation.diff;
|
|
2031
|
+
request.decisionAt = new Date().toISOString();
|
|
2032
|
+
saveFile(file, env);
|
|
2033
|
+
syncApprovalTaskForRequest({
|
|
2034
|
+
request,
|
|
2035
|
+
settlementLink
|
|
2036
|
+
});
|
|
2037
|
+
appendWalletAuditEntry({
|
|
2038
|
+
action: "send_failed",
|
|
2039
|
+
actor: params.actor?.trim() || "operator",
|
|
2040
|
+
details: {
|
|
2041
|
+
requestId: request.id,
|
|
2042
|
+
reason: request.reason,
|
|
2043
|
+
providerId: request.payload.providerId,
|
|
2044
|
+
walletId: request.payload.walletId,
|
|
2045
|
+
walletName: request.payload.walletName,
|
|
2046
|
+
taskId: settlementLink?.taskId,
|
|
2047
|
+
invoiceId: settlementLink?.invoiceId,
|
|
2048
|
+
senderHandle: settlementLink?.senderHandle
|
|
2049
|
+
},
|
|
2050
|
+
env
|
|
2051
|
+
});
|
|
2052
|
+
markWalletSettlementLinkOutcome({
|
|
1009
2053
|
requestId: request.id,
|
|
2054
|
+
status: "failed",
|
|
1010
2055
|
reason: request.reason,
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
status: "failed",
|
|
1023
|
-
reason: request.reason,
|
|
1024
|
-
env
|
|
1025
|
-
});
|
|
1026
|
-
return {
|
|
1027
|
-
ok: false,
|
|
1028
|
-
code: failed?.code ?? "policy_rejected",
|
|
1029
|
-
message: request.reason,
|
|
1030
|
-
request
|
|
1031
|
-
};
|
|
2056
|
+
env
|
|
2057
|
+
});
|
|
2058
|
+
return {
|
|
2059
|
+
ok: false,
|
|
2060
|
+
code: failed?.code ?? "policy_rejected",
|
|
2061
|
+
message: request.reason,
|
|
2062
|
+
request
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
request.simulation = simulation;
|
|
2066
|
+
request.approvalDiff = simulation.diff;
|
|
1032
2067
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
request,
|
|
1046
|
-
|
|
2068
|
+
if (isSignerOwnedReview && signerReviewId) {
|
|
2069
|
+
if (!provider.getSignerReview || !provider.executeSignerReview) {
|
|
2070
|
+
return {
|
|
2071
|
+
ok: false,
|
|
2072
|
+
code: "wallet_signer_review_required",
|
|
2073
|
+
message: "local-socket-signer does not expose exact review.get/review.execute support",
|
|
2074
|
+
request
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
let storedReview;
|
|
2078
|
+
try {
|
|
2079
|
+
storedReview = await provider.getSignerReview({
|
|
2080
|
+
walletId: request.payload.signerWalletId?.trim() || "",
|
|
2081
|
+
requestId: signerReviewId
|
|
1047
2082
|
});
|
|
2083
|
+
} catch (error) {
|
|
1048
2084
|
return {
|
|
1049
2085
|
ok: false,
|
|
1050
|
-
code:
|
|
1051
|
-
message:
|
|
2086
|
+
code: "wallet_signer_review_failed",
|
|
2087
|
+
message: normalizeErrorMessage(error),
|
|
1052
2088
|
request
|
|
1053
2089
|
};
|
|
1054
2090
|
}
|
|
1055
|
-
request.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
appendWalletAuditEntry({
|
|
1065
|
-
action: "send_failed",
|
|
1066
|
-
actor: params.actor?.trim() || "operator",
|
|
1067
|
-
details: {
|
|
1068
|
-
requestId: request.id,
|
|
1069
|
-
reason: request.reason,
|
|
1070
|
-
providerId: request.payload.providerId,
|
|
1071
|
-
walletId: request.payload.walletId,
|
|
1072
|
-
walletName: request.payload.walletName,
|
|
1073
|
-
taskId: settlementLink?.taskId,
|
|
1074
|
-
invoiceId: settlementLink?.invoiceId,
|
|
1075
|
-
senderHandle: settlementLink?.senderHandle
|
|
1076
|
-
},
|
|
1077
|
-
env
|
|
1078
|
-
});
|
|
1079
|
-
markWalletSettlementLinkOutcome({
|
|
2091
|
+
if (!signerReviewMatchesWalletApprovalPayload(storedReview, request.payload)) {
|
|
2092
|
+
return {
|
|
2093
|
+
ok: false,
|
|
2094
|
+
code: "wallet_signer_review_mismatch",
|
|
2095
|
+
message: "signer review does not match the exact persisted approval binding",
|
|
2096
|
+
request
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
const claim = await claimWalletSendApprovalExecution({
|
|
1080
2100
|
requestId: request.id,
|
|
1081
|
-
|
|
1082
|
-
|
|
2101
|
+
actor: params.actor,
|
|
2102
|
+
allowExpiredSignerReview: canReconcileExpiredSignerReview,
|
|
1083
2103
|
env
|
|
1084
2104
|
});
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
2105
|
+
if (!claim.ok) {
|
|
2106
|
+
return {
|
|
2107
|
+
ok: false,
|
|
2108
|
+
code: claim.code,
|
|
2109
|
+
message: claim.message,
|
|
2110
|
+
...claim.request ? { request: claim.request } : {}
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
file = claim.file;
|
|
2114
|
+
request = claim.request;
|
|
2115
|
+
try {
|
|
2116
|
+
if (storedReview.state === "prepared" && (request.status === "expired" || isWalletSendApprovalExpired(request))) {
|
|
2117
|
+
expireWalletSendApprovalRequest({
|
|
2118
|
+
request,
|
|
2119
|
+
settlementLink
|
|
2120
|
+
});
|
|
2121
|
+
saveFile(file, env);
|
|
2122
|
+
return {
|
|
2123
|
+
ok: false,
|
|
2124
|
+
code: "expired",
|
|
2125
|
+
message: "approval request expired before the signer completed it",
|
|
2126
|
+
request
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
if (storedReview.state === "prepared" && !params.reviewAuthorization) {
|
|
2130
|
+
if (claim.recovered && request.status === "executing") {
|
|
2131
|
+
await withApprovalMutationLock(env, () => {
|
|
2132
|
+
file = loadFile(env);
|
|
2133
|
+
const persisted = findRequest(file, claim.request.id);
|
|
2134
|
+
if (persisted?.status === "executing") {
|
|
2135
|
+
persisted.status = "pending";
|
|
2136
|
+
persisted.execution = undefined;
|
|
2137
|
+
persisted.reason = "previous execution stopped before the signer accepted it; a new WebAuthn approval is required";
|
|
2138
|
+
saveFile(file, env);
|
|
2139
|
+
request = persisted;
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
syncApprovalTaskForRequest({
|
|
2144
|
+
request,
|
|
2145
|
+
settlementLink
|
|
2146
|
+
});
|
|
2147
|
+
return {
|
|
2148
|
+
ok: false,
|
|
2149
|
+
code: "signer_webauthn_required",
|
|
2150
|
+
message: "reviewed signer execution requires a signer-owned WebAuthn proof",
|
|
2151
|
+
request
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
const executionAuthorization = storedReview.state === "prepared" ? params.reviewAuthorization : undefined;
|
|
2155
|
+
let executed;
|
|
2156
|
+
try {
|
|
2157
|
+
executed = await provider.executeSignerReview({
|
|
2158
|
+
walletId: request.payload.signerWalletId?.trim() || "",
|
|
2159
|
+
requestId: signerReviewId,
|
|
2160
|
+
authorization: executionAuthorization
|
|
2161
|
+
});
|
|
2162
|
+
} catch (error) {
|
|
2163
|
+
const message = `signer execution result is unknown; reconcile the durable signer request before retrying: ${normalizeErrorMessage(error)}`;
|
|
2164
|
+
const unknownRequest = await markWalletSendRequestBroadcastUnknown({
|
|
2165
|
+
requestId: request.id,
|
|
2166
|
+
reason: message,
|
|
2167
|
+
actor: params.actor,
|
|
2168
|
+
env
|
|
2169
|
+
});
|
|
2170
|
+
return {
|
|
2171
|
+
ok: false,
|
|
2172
|
+
code: "wallet_provider_ambiguous",
|
|
2173
|
+
message,
|
|
2174
|
+
request: unknownRequest
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
if (executed.review.state !== "signed" || !signerReviewMatchesWalletApprovalPayload(executed.review, request.payload) || executed.operation.requestId !== signerReviewId || executed.operation.walletId !== request.payload.signerWalletId?.trim() || executed.operation.intentDigest !== request.payload.signerIntentDigest?.trim() || executed.operation.policyHash !== request.payload.signerPolicyHash?.trim() || executed.operation.asset !== request.payload.signerAsset?.trim() || executed.operation.amount !== request.payload.signerAmount?.trim() || !sameOptionalSignerValue(executed.operation.transactionDigest, request.payload.signerTransactionDigest) || !sameOptionalSignerValue(executed.signer, request.payload.signerWalletPublicKey) || executed.review.artifactKind === "domain-separated-message" && executed.signatureBase64 !== executed.operation.signature || executionAuthorization && executed.operation.authorizationProof !== executionAuthorization.proof.proofId) {
|
|
2178
|
+
const message = "signer execution returned a mismatched result; preserve it as unknown and reconcile before retrying";
|
|
2179
|
+
const unknownRequest = await markWalletSendRequestBroadcastUnknown({
|
|
2180
|
+
requestId: request.id,
|
|
2181
|
+
txHash: executed.operation.signature,
|
|
2182
|
+
reason: message,
|
|
2183
|
+
actor: params.actor,
|
|
2184
|
+
env
|
|
2185
|
+
});
|
|
2186
|
+
return {
|
|
2187
|
+
ok: false,
|
|
2188
|
+
code: "wallet_signer_review_mismatch",
|
|
2189
|
+
message,
|
|
2190
|
+
request: unknownRequest
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
const operation = executed.operation;
|
|
2194
|
+
if (operation.state === "broadcast" || operation.state === "unknown") {
|
|
2195
|
+
const message = `signer operation ${operation.requestId} is ${operation.state}; reconcile signature before any new attempt`;
|
|
2196
|
+
const ambiguousRequest = await markWalletSendRequestBroadcastUnknown({
|
|
2197
|
+
requestId: request.id,
|
|
2198
|
+
txHash: operation.signature,
|
|
2199
|
+
reason: message,
|
|
2200
|
+
actor: params.actor,
|
|
2201
|
+
env
|
|
2202
|
+
});
|
|
2203
|
+
return {
|
|
2204
|
+
ok: false,
|
|
2205
|
+
code: "wallet_provider_ambiguous",
|
|
2206
|
+
message,
|
|
2207
|
+
request: ambiguousRequest
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
if (operation.state !== "confirmed" || !operation.signature) {
|
|
2211
|
+
request.status = "failed";
|
|
2212
|
+
request.reason = operation.error ?? `signer operation ended in state=${operation.state}`;
|
|
2213
|
+
request.result = { error: request.reason };
|
|
2214
|
+
request.decisionAt = new Date().toISOString();
|
|
2215
|
+
saveFile(file, env);
|
|
2216
|
+
syncApprovalTaskForRequest({
|
|
2217
|
+
request,
|
|
2218
|
+
settlementLink
|
|
2219
|
+
});
|
|
2220
|
+
return {
|
|
2221
|
+
ok: false,
|
|
2222
|
+
code: "wallet_signer_review_failed",
|
|
2223
|
+
message: request.reason,
|
|
2224
|
+
request
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
let signerCompletionWarning;
|
|
2228
|
+
if (executed.review.intentType === "federation.bondChallenge") {
|
|
2229
|
+
if (!executed.signatureBase64) {
|
|
2230
|
+
const message = "federation signer review completed without its exact signature artifact; reconcile before retrying";
|
|
2231
|
+
const unknownRequest = await markWalletSendRequestBroadcastUnknown({
|
|
2232
|
+
requestId: request.id,
|
|
2233
|
+
txHash: operation.signature,
|
|
2234
|
+
reason: message,
|
|
2235
|
+
actor: params.actor,
|
|
2236
|
+
env
|
|
2237
|
+
});
|
|
2238
|
+
return {
|
|
2239
|
+
ok: false,
|
|
2240
|
+
code: "wallet_signer_review_mismatch",
|
|
2241
|
+
message,
|
|
2242
|
+
request: unknownRequest
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
try {
|
|
2246
|
+
const federation = await import("../federation/auto-connect.js");
|
|
2247
|
+
const proof = await federation.persistFederationBondProofFromSignerReview({
|
|
2248
|
+
review: executed.review,
|
|
2249
|
+
signatureBase64: executed.signatureBase64,
|
|
2250
|
+
walletId: request.payload.walletId?.trim() || executed.review.walletId,
|
|
2251
|
+
env
|
|
2252
|
+
});
|
|
2253
|
+
try {
|
|
2254
|
+
await federation.submitFederationBondProof({
|
|
2255
|
+
env,
|
|
2256
|
+
proof
|
|
2257
|
+
});
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
signerCompletionWarning = `signature completed; federation proof submission remains pending: ${normalizeErrorMessage(error)}`;
|
|
2260
|
+
}
|
|
2261
|
+
} catch (error) {
|
|
2262
|
+
const message = `signature completed but its federation proof could not be persisted safely: ${normalizeErrorMessage(error)}`;
|
|
2263
|
+
const unknownRequest = await markWalletSendRequestBroadcastUnknown({
|
|
2264
|
+
requestId: request.id,
|
|
2265
|
+
txHash: operation.signature,
|
|
2266
|
+
reason: message,
|
|
2267
|
+
actor: params.actor,
|
|
2268
|
+
env
|
|
2269
|
+
});
|
|
2270
|
+
return {
|
|
2271
|
+
ok: false,
|
|
2272
|
+
code: "wallet_signer_review_failed",
|
|
2273
|
+
message,
|
|
2274
|
+
request: unknownRequest
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
request.status = "approved";
|
|
2279
|
+
request.approvedBy = params.actor?.trim() || "operator";
|
|
2280
|
+
request.decisionAt = new Date().toISOString();
|
|
2281
|
+
request.reason = undefined;
|
|
2282
|
+
appendWalletAuditEntry({
|
|
2283
|
+
action: "send_approved",
|
|
2284
|
+
actor: request.approvedBy,
|
|
2285
|
+
details: buildWalletSendAuditDetails({
|
|
2286
|
+
payload: request.payload,
|
|
2287
|
+
requestId: request.id,
|
|
2288
|
+
mode: "manual",
|
|
2289
|
+
providerId: selectedProviderId,
|
|
2290
|
+
taskId: settlementLink?.taskId,
|
|
2291
|
+
invoiceId: settlementLink?.invoiceId,
|
|
2292
|
+
senderHandle: settlementLink?.senderHandle
|
|
2293
|
+
}),
|
|
2294
|
+
env
|
|
2295
|
+
});
|
|
2296
|
+
request.status = "executed";
|
|
2297
|
+
request.result = {
|
|
2298
|
+
txHash: operation.signature,
|
|
2299
|
+
...signerCompletionWarning ? { error: signerCompletionWarning } : {}
|
|
2300
|
+
};
|
|
2301
|
+
appendWalletAuditEntry({
|
|
2302
|
+
action: "send_executed",
|
|
2303
|
+
actor: request.approvedBy,
|
|
2304
|
+
details: {
|
|
2305
|
+
...buildWalletSendAuditDetails({
|
|
2306
|
+
payload: claim.request.payload,
|
|
2307
|
+
requestId: claim.request.id,
|
|
2308
|
+
mode: "manual",
|
|
2309
|
+
providerId: selectedProviderId,
|
|
2310
|
+
txHash: operation.signature,
|
|
2311
|
+
taskId: settlementLink?.taskId,
|
|
2312
|
+
invoiceId: settlementLink?.invoiceId,
|
|
2313
|
+
senderHandle: settlementLink?.senderHandle
|
|
2314
|
+
}),
|
|
2315
|
+
...signerCompletionWarning ? { warning: signerCompletionWarning } : {}
|
|
2316
|
+
},
|
|
2317
|
+
env
|
|
2318
|
+
});
|
|
2319
|
+
saveFile(file, env);
|
|
2320
|
+
syncApprovalTaskForRequest({
|
|
2321
|
+
request,
|
|
2322
|
+
settlementLink
|
|
2323
|
+
});
|
|
2324
|
+
const updatedSettlement = markWalletSettlementLinkOutcome({
|
|
2325
|
+
requestId: request.id,
|
|
2326
|
+
status: "executed",
|
|
2327
|
+
txHash: operation.signature,
|
|
2328
|
+
env
|
|
2329
|
+
});
|
|
2330
|
+
await publishSettlementEvidenceForLink({
|
|
2331
|
+
settlementLink: updatedSettlement,
|
|
2332
|
+
env
|
|
2333
|
+
});
|
|
2334
|
+
return {
|
|
2335
|
+
ok: true,
|
|
2336
|
+
request,
|
|
2337
|
+
tx: {
|
|
2338
|
+
ok: true,
|
|
2339
|
+
chain: "solana",
|
|
2340
|
+
txHash: operation.signature,
|
|
2341
|
+
signer: executed.signer,
|
|
2342
|
+
metadata: {
|
|
2343
|
+
provider: "local-socket-signer",
|
|
2344
|
+
signerProtocol: 2,
|
|
2345
|
+
signerOperationState: operation.state,
|
|
2346
|
+
signerIntentDigest: operation.intentDigest,
|
|
2347
|
+
signerTransactionDigest: operation.transactionDigest
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
};
|
|
2351
|
+
} finally {
|
|
2352
|
+
await claim.release();
|
|
2353
|
+
}
|
|
1091
2354
|
}
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
program: request.payload.program,
|
|
1097
|
-
walletId: request.payload.walletId,
|
|
1098
|
-
env,
|
|
1099
|
-
skipNativeSolanaCaps
|
|
2355
|
+
const claim = await claimWalletSendApprovalExecution({
|
|
2356
|
+
requestId: request.id,
|
|
2357
|
+
actor: params.actor,
|
|
2358
|
+
env
|
|
1100
2359
|
});
|
|
1101
|
-
if (!
|
|
1102
|
-
request.status = "failed";
|
|
1103
|
-
request.reason = daily.message ?? daily.code ?? "wallet daily cap exceeded";
|
|
1104
|
-
request.result = { error: request.reason };
|
|
1105
|
-
request.decisionAt = new Date().toISOString();
|
|
1106
|
-
saveFile(file, env);
|
|
1107
|
-
syncApprovalTaskForRequest({
|
|
1108
|
-
request,
|
|
1109
|
-
settlementLink
|
|
1110
|
-
});
|
|
1111
|
-
appendWalletAuditEntry({
|
|
1112
|
-
action: "send_failed",
|
|
1113
|
-
actor: params.actor?.trim() || "operator",
|
|
1114
|
-
details: {
|
|
1115
|
-
requestId: request.id,
|
|
1116
|
-
reason: request.reason,
|
|
1117
|
-
providerId: request.payload.providerId,
|
|
1118
|
-
walletId: request.payload.walletId,
|
|
1119
|
-
walletName: request.payload.walletName,
|
|
1120
|
-
taskId: settlementLink?.taskId,
|
|
1121
|
-
invoiceId: settlementLink?.invoiceId,
|
|
1122
|
-
senderHandle: settlementLink?.senderHandle
|
|
1123
|
-
},
|
|
1124
|
-
env
|
|
1125
|
-
});
|
|
1126
|
-
markWalletSettlementLinkOutcome({
|
|
1127
|
-
requestId: request.id,
|
|
1128
|
-
status: "failed",
|
|
1129
|
-
reason: request.reason,
|
|
1130
|
-
env
|
|
1131
|
-
});
|
|
2360
|
+
if (!claim.ok) {
|
|
1132
2361
|
return {
|
|
1133
2362
|
ok: false,
|
|
1134
|
-
code:
|
|
1135
|
-
message:
|
|
1136
|
-
request
|
|
2363
|
+
code: claim.code,
|
|
2364
|
+
message: claim.message,
|
|
2365
|
+
...claim.request ? { request: claim.request } : {}
|
|
1137
2366
|
};
|
|
1138
2367
|
}
|
|
1139
|
-
|
|
1140
|
-
request
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
2368
|
+
file = claim.file;
|
|
2369
|
+
request = claim.request;
|
|
2370
|
+
try {
|
|
2371
|
+
if (claim.recovered && selectedProviderId === "wallet-standard") {
|
|
2372
|
+
const message = "Wallet Standard execution cannot be server-reconciled; preserve this approval as unknown until its exact on-chain signature is supplied";
|
|
2373
|
+
const unknownRequest = await markWalletSendRequestBroadcastUnknown({
|
|
2374
|
+
requestId: request.id,
|
|
2375
|
+
reason: message,
|
|
2376
|
+
actor: params.actor,
|
|
2377
|
+
env
|
|
2378
|
+
});
|
|
2379
|
+
return {
|
|
2380
|
+
ok: false,
|
|
2381
|
+
code: "wallet_provider_ambiguous",
|
|
2382
|
+
message,
|
|
2383
|
+
request: unknownRequest
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
const daily = claim.recovered ? { ok: true } : enforceWalletDailyCap({
|
|
2387
|
+
config: effectiveConfig,
|
|
1151
2388
|
chain: request.payload.chain,
|
|
1152
2389
|
amount: request.payload.amount,
|
|
1153
|
-
|
|
2390
|
+
program: request.payload.program,
|
|
1154
2391
|
walletId: request.payload.walletId,
|
|
1155
|
-
walletName: request.payload.walletName,
|
|
1156
|
-
taskId: settlementLink?.taskId,
|
|
1157
|
-
invoiceId: settlementLink?.invoiceId,
|
|
1158
|
-
senderHandle: settlementLink?.senderHandle
|
|
1159
|
-
},
|
|
1160
|
-
env
|
|
1161
|
-
});
|
|
1162
|
-
try {
|
|
1163
|
-
const sent = await executeWalletSendWithRetry({
|
|
1164
2392
|
env,
|
|
1165
|
-
|
|
1166
|
-
if (custodyGate.custodyMode === "split-key-active" && custodyGate.session) {
|
|
1167
|
-
const guarded = await withWalletCustodySigningMaterial({
|
|
1168
|
-
sessionId: custodyGate.session.id,
|
|
1169
|
-
host: custodyGate.session.host,
|
|
1170
|
-
handler: async () => await provider.sendTx(request.payload)
|
|
1171
|
-
});
|
|
1172
|
-
if (!guarded.ok) {
|
|
1173
|
-
throw new Error(guarded.message);
|
|
1174
|
-
}
|
|
1175
|
-
return guarded.value;
|
|
1176
|
-
}
|
|
1177
|
-
return await provider.sendTx(request.payload);
|
|
1178
|
-
}
|
|
2393
|
+
skipNativeSolanaCaps
|
|
1179
2394
|
});
|
|
1180
|
-
if (!
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
2395
|
+
if (!daily.ok) {
|
|
2396
|
+
request.status = "failed";
|
|
2397
|
+
request.reason = daily.message ?? daily.code ?? "wallet daily cap exceeded";
|
|
2398
|
+
request.result = { error: request.reason };
|
|
2399
|
+
request.decisionAt = new Date().toISOString();
|
|
2400
|
+
saveFile(file, env);
|
|
2401
|
+
syncApprovalTaskForRequest({
|
|
2402
|
+
request,
|
|
2403
|
+
settlementLink
|
|
2404
|
+
});
|
|
2405
|
+
appendWalletAuditEntry({
|
|
2406
|
+
action: "send_failed",
|
|
2407
|
+
actor: params.actor?.trim() || "operator",
|
|
2408
|
+
details: {
|
|
2409
|
+
requestId: request.id,
|
|
2410
|
+
reason: request.reason,
|
|
2411
|
+
providerId: request.payload.providerId,
|
|
2412
|
+
walletId: request.payload.walletId,
|
|
2413
|
+
walletName: request.payload.walletName,
|
|
2414
|
+
taskId: settlementLink?.taskId,
|
|
2415
|
+
invoiceId: settlementLink?.invoiceId,
|
|
2416
|
+
senderHandle: settlementLink?.senderHandle
|
|
2417
|
+
},
|
|
2418
|
+
env
|
|
2419
|
+
});
|
|
2420
|
+
markWalletSettlementLinkOutcome({
|
|
1201
2421
|
requestId: request.id,
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
2422
|
+
status: "failed",
|
|
2423
|
+
reason: request.reason,
|
|
2424
|
+
env
|
|
2425
|
+
});
|
|
2426
|
+
return {
|
|
2427
|
+
ok: false,
|
|
2428
|
+
code: "daily_cap_rejected",
|
|
2429
|
+
message: request.reason,
|
|
2430
|
+
request
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
request.status = "approved";
|
|
2434
|
+
request.approvedBy = params.actor?.trim() || "operator";
|
|
2435
|
+
request.decisionAt = new Date().toISOString();
|
|
1212
2436
|
syncApprovalTaskForRequest({
|
|
1213
2437
|
request,
|
|
1214
2438
|
settlementLink
|
|
1215
2439
|
});
|
|
1216
|
-
return {
|
|
1217
|
-
ok: true,
|
|
1218
|
-
request,
|
|
1219
|
-
tx
|
|
1220
|
-
};
|
|
1221
|
-
} catch (err) {
|
|
1222
|
-
request.status = "failed";
|
|
1223
|
-
request.reason = walletDiagnosticErrorString(err);
|
|
1224
|
-
request.result = { error: request.reason };
|
|
1225
|
-
markWalletSettlementLinkOutcome({
|
|
1226
|
-
requestId: request.id,
|
|
1227
|
-
status: "failed",
|
|
1228
|
-
reason: request.reason,
|
|
1229
|
-
env
|
|
1230
|
-
});
|
|
1231
2440
|
appendWalletAuditEntry({
|
|
1232
|
-
action: "
|
|
2441
|
+
action: "send_approved",
|
|
1233
2442
|
actor: request.approvedBy,
|
|
1234
2443
|
details: {
|
|
1235
2444
|
requestId: request.id,
|
|
1236
|
-
|
|
1237
|
-
|
|
2445
|
+
chain: request.payload.chain,
|
|
2446
|
+
amount: request.payload.amount,
|
|
2447
|
+
providerId: selectedProviderId,
|
|
1238
2448
|
walletId: request.payload.walletId,
|
|
1239
2449
|
walletName: request.payload.walletName,
|
|
1240
2450
|
taskId: settlementLink?.taskId,
|
|
@@ -1243,17 +2453,109 @@ async function approveWalletSendRequest(params) {
|
|
|
1243
2453
|
},
|
|
1244
2454
|
env
|
|
1245
2455
|
});
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
2456
|
+
const claimedPayload = claim.request.payload;
|
|
2457
|
+
const claimedRequestId = claim.request.id;
|
|
2458
|
+
try {
|
|
2459
|
+
const sent = await executeWalletSendOnce({ execute: async () => await prepareAndSendProviderTransaction({
|
|
2460
|
+
provider,
|
|
2461
|
+
payload: claimedPayload,
|
|
2462
|
+
requestId: claimedRequestId
|
|
2463
|
+
}) });
|
|
2464
|
+
if (!sent.ok) {
|
|
2465
|
+
throw sent.error;
|
|
2466
|
+
}
|
|
2467
|
+
const tx = withSendAttemptMetadata(sent.tx, sent.attempts);
|
|
2468
|
+
request.status = "executed";
|
|
2469
|
+
request.result = { txHash: tx.txHash };
|
|
2470
|
+
const settlementLink = markWalletSettlementLinkOutcome({
|
|
2471
|
+
requestId: request.id,
|
|
2472
|
+
status: "executed",
|
|
2473
|
+
txHash: tx.txHash,
|
|
2474
|
+
env
|
|
2475
|
+
});
|
|
2476
|
+
await publishSettlementEvidenceForLink({
|
|
2477
|
+
settlementLink,
|
|
2478
|
+
env
|
|
2479
|
+
});
|
|
2480
|
+
appendWalletAuditEntry({
|
|
2481
|
+
action: "send_executed",
|
|
2482
|
+
actor: request.approvedBy,
|
|
2483
|
+
details: buildWalletSendAuditDetails({
|
|
2484
|
+
payload: request.payload,
|
|
2485
|
+
requestId: request.id,
|
|
2486
|
+
txHash: tx.txHash,
|
|
2487
|
+
providerId: selectedProviderId,
|
|
2488
|
+
taskId: settlementLink?.taskId,
|
|
2489
|
+
invoiceId: settlementLink?.invoiceId,
|
|
2490
|
+
senderHandle: settlementLink?.senderHandle,
|
|
2491
|
+
attempts: sent.attempts
|
|
2492
|
+
}),
|
|
2493
|
+
env
|
|
2494
|
+
});
|
|
2495
|
+
saveFile(file, env);
|
|
2496
|
+
syncApprovalTaskForRequest({
|
|
2497
|
+
request,
|
|
2498
|
+
settlementLink
|
|
2499
|
+
});
|
|
2500
|
+
return {
|
|
2501
|
+
ok: true,
|
|
2502
|
+
request,
|
|
2503
|
+
tx
|
|
2504
|
+
};
|
|
2505
|
+
} catch (err) {
|
|
2506
|
+
if (err instanceof WalletProviderError && err.code === "wallet_provider_ambiguous") {
|
|
2507
|
+
const message = `${err.message}; reconcile the durable provider review before any retry`;
|
|
2508
|
+
const unknownRequest = await markWalletSendRequestBroadcastUnknown({
|
|
2509
|
+
requestId: request.id,
|
|
2510
|
+
reason: message,
|
|
2511
|
+
actor: params.actor,
|
|
2512
|
+
env
|
|
2513
|
+
});
|
|
2514
|
+
return {
|
|
2515
|
+
ok: false,
|
|
2516
|
+
code: err.code,
|
|
2517
|
+
message,
|
|
2518
|
+
request: unknownRequest
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
request.status = "failed";
|
|
2522
|
+
request.reason = walletDiagnosticErrorString(err);
|
|
2523
|
+
request.result = { error: request.reason };
|
|
2524
|
+
markWalletSettlementLinkOutcome({
|
|
2525
|
+
requestId: request.id,
|
|
2526
|
+
status: "failed",
|
|
2527
|
+
reason: request.reason,
|
|
2528
|
+
env
|
|
2529
|
+
});
|
|
2530
|
+
appendWalletAuditEntry({
|
|
2531
|
+
action: "send_failed",
|
|
2532
|
+
actor: request.approvedBy,
|
|
2533
|
+
details: {
|
|
2534
|
+
requestId: request.id,
|
|
2535
|
+
reason: request.reason,
|
|
2536
|
+
providerId: request.payload.providerId,
|
|
2537
|
+
walletId: request.payload.walletId,
|
|
2538
|
+
walletName: request.payload.walletName,
|
|
2539
|
+
taskId: settlementLink?.taskId,
|
|
2540
|
+
invoiceId: settlementLink?.invoiceId,
|
|
2541
|
+
senderHandle: settlementLink?.senderHandle
|
|
2542
|
+
},
|
|
2543
|
+
env
|
|
2544
|
+
});
|
|
2545
|
+
saveFile(file, env);
|
|
2546
|
+
syncApprovalTaskForRequest({
|
|
2547
|
+
request,
|
|
2548
|
+
settlementLink
|
|
2549
|
+
});
|
|
2550
|
+
return {
|
|
2551
|
+
ok: false,
|
|
2552
|
+
code: "send_failed",
|
|
2553
|
+
message: request.reason,
|
|
2554
|
+
request
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
} finally {
|
|
2558
|
+
await claim.release();
|
|
1257
2559
|
}
|
|
1258
2560
|
}
|
|
1259
2561
|
function rejectWalletSendRequest(params) {
|
|
@@ -1315,4 +2617,4 @@ function rejectWalletSendRequest(params) {
|
|
|
1315
2617
|
}
|
|
1316
2618
|
|
|
1317
2619
|
//#endregion
|
|
1318
|
-
export { approveWalletSendRequest, createOrExecuteWalletSend, createWalletSendApprovalRequest, listWalletSendApprovalRequests, rejectWalletSendRequest, sanitizeWalletSendApprovalPayload, sanitizeWalletSendApprovalRequest, wallet_send_approvals_exports };
|
|
2620
|
+
export { approveWalletSendRequest, bindSignerReviewToWalletApprovalPayload, createOrExecuteWalletSend, createSignerReviewApprovalRequest, createWalletSendApprovalRequest, getWalletSendApprovalRequest, listWalletSendApprovalRequests, markWalletSendRequestBroadcastUnknown, markWalletSendRequestExecutedExternally, rejectWalletSendRequest, sanitizeWalletSendApprovalPayload, sanitizeWalletSendApprovalRequest, signerReviewBindingMatchesWalletApprovalPayload, signerReviewMatchesWalletApprovalPayload, wallet_send_approvals_exports };
|