@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.
Files changed (514) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +38 -30
  3. package/config/dependency-ownership.json +1 -1
  4. package/config/signer-policies/README.md +49 -0
  5. package/config/signer-policies/agent.json.template +32 -0
  6. package/config/signer-policies/mining.json.template +69 -0
  7. package/config/signer-policies/network.json.template +5 -0
  8. package/config/signer-policies/vault.json.template +70 -0
  9. package/config/signer-protocol-v2.json +76 -0
  10. package/config/typescript-noemit-baseline.json +680 -0
  11. package/dist/acp/client.js +2 -2
  12. package/dist/acp/secret-file.js +2 -2
  13. package/dist/agents/agent-scope.js +2 -2
  14. package/dist/agents/apply-patch.js +8 -8
  15. package/dist/agents/auth-profiles/paths.js +2 -2
  16. package/dist/agents/auth-profiles/store.js +269 -33
  17. package/dist/agents/bash-tools.exec.js +10 -4
  18. package/dist/agents/bash-tools.shared.js +2 -2
  19. package/dist/agents/cli-credentials.js +4 -4
  20. package/dist/agents/current-model-catalog.js +3 -3
  21. package/dist/agents/docs-path.js +3 -3
  22. package/dist/agents/embedded-pi-mcp.js +4 -4
  23. package/dist/agents/identity-avatar.js +3 -3
  24. package/dist/agents/identity-file.js +2 -2
  25. package/dist/agents/memory-search.js +7 -1
  26. package/dist/agents/model-catalog-access.js +1 -2
  27. package/dist/agents/model-catalog-snapshot.js +1 -1
  28. package/dist/agents/model-catalog-source.js +1 -1
  29. package/dist/agents/model-metadata.js +1 -1
  30. package/dist/agents/model-selection.js +2 -28
  31. package/dist/agents/models-config.providers.js +6 -6
  32. package/dist/agents/openai-codex-app-server.js +5 -5
  33. package/dist/agents/pi-embedded-runner/compact.js +15 -7
  34. package/dist/agents/pi-embedded-runner/model.js +1 -1
  35. package/dist/agents/pi-embedded-runner/run/attempt.js +24 -13
  36. package/dist/agents/pi-embedded-runner/run.js +2 -0
  37. package/dist/agents/pi-embedded-runner/tool-split.js +40 -2
  38. package/dist/agents/pi-embedded-subscribe.raw-stream.js +3 -3
  39. package/dist/agents/pi-extensions/compaction-safeguard.js +3 -3
  40. package/dist/agents/pi-model-discovery.js +6 -6
  41. package/dist/agents/pi-project-settings.js +3 -3
  42. package/dist/agents/pi-tool-definition-adapter.js +16 -1
  43. package/dist/agents/pi-tools.js +2 -1
  44. package/dist/agents/sandbox/config.js +1 -1
  45. package/dist/agents/sandbox/context.js +27 -2
  46. package/dist/agents/sandbox/fs-bridge.js +2 -2
  47. package/dist/agents/session-dirs.js +4 -4
  48. package/dist/agents/session-write-lock.js +13 -13
  49. package/dist/agents/shell-utils.js +5 -5
  50. package/dist/agents/skills/bundled-dir.js +4 -4
  51. package/dist/agents/skills/env-overrides.js +38 -28
  52. package/dist/agents/skills/local-loader.js +6 -6
  53. package/dist/agents/skills/plugin-skills.js +2 -2
  54. package/dist/agents/skills/trust.js +93 -0
  55. package/dist/agents/skills/workspace.js +141 -13
  56. package/dist/agents/skills-clawhub.js +36 -11
  57. package/dist/agents/skills-install-download.js +7 -7
  58. package/dist/agents/skills-install-plan.js +2 -2
  59. package/dist/agents/skills-install.js +2 -2
  60. package/dist/agents/skills-marketplace-scan.js +49 -0
  61. package/dist/agents/skills-status.js +2 -2
  62. package/dist/agents/subagent-depth.js +2 -2
  63. package/dist/agents/system-prompt-params.js +2 -2
  64. package/dist/agents/tools/marketplace-offer-draft-tool.js +1 -1
  65. package/dist/agents/tools/mining-tool.js +1 -1
  66. package/dist/agents/tools/session-status-tool.js +1 -1
  67. package/dist/agents/tools/wallet-action-tool.js +172 -36
  68. package/dist/agents/tools/wallet-skill-policy.js +65 -23
  69. package/dist/agents/tools/wallet-tool.js +7 -11
  70. package/dist/agents/workspace.js +22 -22
  71. package/dist/auto-reply/reply/agent-runner-execution.js +2 -2
  72. package/dist/auto-reply/reply/agent-runner.js +2 -2
  73. package/dist/auto-reply/reply/commands-export-session.js +7 -7
  74. package/dist/auto-reply/reply/directive-handling.fast-lane.js +2 -1
  75. package/dist/auto-reply/reply/directive-handling.impl.js +2 -1
  76. package/dist/auto-reply/reply/directive-handling.model.js +2 -1
  77. package/dist/auto-reply/reply/directive-handling.persist.js +2 -2
  78. package/dist/auto-reply/reply/get-reply-directives-apply.js +2 -0
  79. package/dist/auto-reply/reply/get-reply-inline-actions.js +25 -1
  80. package/dist/auto-reply/reply/model-selection.js +7 -4
  81. package/dist/auto-reply/reply/post-compaction-audit.js +3 -3
  82. package/dist/auto-reply/reply/post-compaction-context.js +3 -3
  83. package/dist/auto-reply/reply/session-reset-model.js +4 -4
  84. package/dist/auto-reply/reply/session.js +3 -3
  85. package/dist/auto-reply/skill-commands.js +3 -3
  86. package/dist/auto-reply/status.js +6 -6
  87. package/dist/brand.js +1 -1
  88. package/dist/browser/chrome.executables.js +3 -3
  89. package/dist/browser/chrome.js +3 -3
  90. package/dist/browser/chrome.profile-decoration.js +6 -6
  91. package/dist/browser/profiles-service.js +2 -2
  92. package/dist/browser/server-context.js +2 -2
  93. package/dist/browser/trash.js +4 -4
  94. package/dist/build-info.json +3 -3
  95. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  96. package/dist/canvas-host/server.js +4 -4
  97. package/dist/capabilities/catalog.js +3 -3
  98. package/dist/channel-web.js +2 -2
  99. package/dist/channels/plugins/catalog.js +3 -3
  100. package/dist/cli/browser-cli-extension.js +6 -6
  101. package/dist/cli/command-format.js +2 -2
  102. package/dist/cli/cron-cli/register.cron-simple.js +2 -2
  103. package/dist/cli/dns-cli.js +9 -9
  104. package/dist/cli/gateway-cli/dev.js +5 -5
  105. package/dist/cli/gateway-cli/run.js +10 -2
  106. package/dist/cli/hooks-cli.js +5 -5
  107. package/dist/cli/lightweight/gateway-runtime-probe.js +2 -2
  108. package/dist/cli/memory-cli.js +7 -7
  109. package/dist/cli/nodes-media-utils.js +2 -2
  110. package/dist/cli/plugins-cli.js +2 -2
  111. package/dist/cli/program/command-registry.js +1 -1
  112. package/dist/cli/program/register.onboard.js +2 -3
  113. package/dist/cli/program/register.wallet.js +16 -123
  114. package/dist/cli/providers-cli.js +1 -1
  115. package/dist/cli/sat-cli.js +129 -2
  116. package/dist/cli/secrets-cli.js +3 -3
  117. package/dist/cli/update-cli/update-command.js +260 -11
  118. package/dist/cli/windows-argv.js +2 -2
  119. package/dist/commands/auth-choice.apply.api-providers.js +3 -3
  120. package/dist/commands/dashboard-link.js +3 -3
  121. package/dist/commands/doctor-config-flow.js +1 -1
  122. package/dist/commands/doctor-install.js +5 -5
  123. package/dist/commands/doctor-legacy-config.js +2 -13
  124. package/dist/commands/doctor-memory-search.js +2 -2
  125. package/dist/commands/doctor-platform-notes.js +2 -2
  126. package/dist/commands/doctor-sandbox.js +2 -2
  127. package/dist/commands/doctor-state-integrity.js +23 -23
  128. package/dist/commands/doctor-workspace.js +3 -3
  129. package/dist/commands/doctor.js +3 -3
  130. package/dist/commands/managed-up.js +2 -2
  131. package/dist/commands/model-picker.js +6 -6
  132. package/dist/commands/models/auth.js +1 -1
  133. package/dist/commands/onboard-auth.config-gateways.js +1 -1
  134. package/dist/commands/onboard-auth.credentials.js +35 -11
  135. package/dist/commands/onboard-non-interactive/local/wallet-config.js +28 -27
  136. package/dist/commands/onboard-non-interactive/local.js +5 -1
  137. package/dist/commands/onboarding/plugin-install.js +2 -2
  138. package/dist/commands/sessions-cleanup.js +2 -2
  139. package/dist/commands/signal-install.js +6 -6
  140. package/dist/commands/status-all/channels.js +2 -2
  141. package/dist/commands/wallet.js +570 -1913
  142. package/dist/config/cache-utils.js +2 -2
  143. package/dist/config/config.js +2 -2
  144. package/dist/config/includes.js +5 -5
  145. package/dist/config/io.js +66 -4
  146. package/dist/config/paths.js +3 -3
  147. package/dist/config/sat-runtime-ids.js +3 -3
  148. package/dist/config/schema.help.js +8 -7
  149. package/dist/config/schema.labels.js +5 -4
  150. package/dist/config/sessions/disk-budget.js +6 -6
  151. package/dist/config/sessions/paths.js +2 -2
  152. package/dist/config/sessions/store.js +17 -17
  153. package/dist/config/sessions/transcript.js +4 -4
  154. package/dist/config/talk.js +2 -2
  155. package/dist/config/zod-schema.agent-runtime.js +1 -0
  156. package/dist/config/zod-schema.js +5 -0
  157. package/dist/control-ui/assets/{app-BXA4gPvN.js → app-bR-fO5ty.js} +1511 -1555
  158. package/dist/control-ui/assets/{config-CU5Zi8Dh.js → config-LeImukCW.js} +10 -10
  159. package/dist/control-ui/assets/{federation-D10Q4eP_.js → federation-DgA9e-Pn.js} +364 -422
  160. package/dist/control-ui/assets/{index-D2QbJhVg.js → index-ckCxHwjy.js} +3 -3
  161. package/dist/control-ui/assets/{mining-BdhMn18r.js → mining-BgHFh0ei.js} +134 -114
  162. package/dist/control-ui/assets/mining-commit-DFpB_4JN.js +2 -0
  163. package/dist/control-ui/assets/{open-external-url-DwSiwMPl.js → open-external-url-ulF1B-sA.js} +3 -3
  164. package/dist/control-ui/assets/{providers-QMn4w2_f.js → providers-CIx9fxc6.js} +2 -2
  165. package/dist/control-ui/assets/{wallet-C2g294QT.js → wallet-DvStR_AT.js} +259 -377
  166. package/dist/control-ui/index.html +1 -1
  167. package/dist/control-ui/version.json +1 -1
  168. package/dist/cron/service/store.js +2 -2
  169. package/dist/cron/store.js +6 -6
  170. package/dist/daemon/systemd-system.js +60 -54
  171. package/dist/discord/monitor/thread-bindings.state.js +2 -2
  172. package/dist/extensions/sat-mining/src/protocol-contract.js +43 -3
  173. package/dist/extensions/sat-mining/src/rpc-read.js +235 -67
  174. package/dist/extensions/sat-mining/src/sat-bond-layout.js +14 -11
  175. package/dist/extensions/sat-mining/src/signer-codec-manifest.js +406 -0
  176. package/dist/extensions/sat-mining/src/solana-submit.js +1038 -397
  177. package/dist/extensions/sat-mining/src/submission-ledger.js +349 -0
  178. package/dist/federation/auto-connect.js +98 -8
  179. package/dist/federation/marketplace-capability-adapter.js +33 -7
  180. package/dist/federation/marketplace-delivery-outbox.js +196 -0
  181. package/dist/federation/marketplace-delivery.js +351 -215
  182. package/dist/federation/marketplace-direct-payment.js +272 -141
  183. package/dist/federation/marketplace-escrow.js +607 -249
  184. package/dist/federation/marketplace-settlement-ledger.js +352 -0
  185. package/dist/federation/offers.js +9 -7
  186. package/dist/federation/peer-auth-v2.js +529 -0
  187. package/dist/gateway/a2a-http.js +425 -96
  188. package/dist/gateway/a2a-settlement.js +259 -153
  189. package/dist/gateway/a2a-task-store.js +524 -0
  190. package/dist/gateway/auth.js +38 -2
  191. package/dist/gateway/config-reload.js +46 -33
  192. package/dist/gateway/control-ui-boot-check.js +6 -6
  193. package/dist/gateway/control-ui-login.js +7 -7
  194. package/dist/gateway/control-ui.js +9 -9
  195. package/dist/gateway/durable-capacity.js +31 -0
  196. package/dist/gateway/federation-http.js +540 -135
  197. package/dist/gateway/federation-marketplace-run-store.js +397 -0
  198. package/dist/gateway/federation-marketplace.js +535 -230
  199. package/dist/gateway/hooks-mapping.js +4 -4
  200. package/dist/gateway/mutating-admin-rpc-rate-limit.js +5 -0
  201. package/dist/gateway/security-path.js +1 -1
  202. package/dist/gateway/server-discovery.js +2 -2
  203. package/dist/gateway/server-http.js +1088 -1209
  204. package/dist/gateway/server-methods/chat.js +3 -3
  205. package/dist/gateway/server-methods/models.js +1 -1
  206. package/dist/gateway/server-methods/secrets.js +40 -0
  207. package/dist/gateway/server-methods/sessions.js +6 -6
  208. package/dist/gateway/server-methods/usage.js +2 -2
  209. package/dist/gateway/server-methods.js +1 -2
  210. package/dist/gateway/server-reload-handlers.js +196 -66
  211. package/dist/gateway/server-runtime-config.js +1 -1
  212. package/dist/gateway/server-secrets-runtime.js +288 -0
  213. package/dist/gateway/server.impl.js +73 -42
  214. package/dist/gateway/session-compaction-checkpoints.js +4 -4
  215. package/dist/gateway/session-utils.fs.js +24 -24
  216. package/dist/gateway/session-utils.js +6 -6
  217. package/dist/gateway/sessions-patch.js +3 -5
  218. package/dist/hooks/bundled-dir.js +4 -4
  219. package/dist/hooks/gmail-setup-utils.js +6 -6
  220. package/dist/hooks/import-url.js +2 -2
  221. package/dist/hooks/loader.js +4 -4
  222. package/dist/hooks/workspace.js +7 -7
  223. package/dist/index.js +4 -2
  224. package/dist/infra/archive.js +17 -17
  225. package/dist/infra/boundary-file-read.js +2 -2
  226. package/dist/infra/boundary-path.js +15 -15
  227. package/dist/infra/brew.js +2 -2
  228. package/dist/infra/control-ui-assets.js +14 -14
  229. package/dist/infra/device-auth-store.js +8 -8
  230. package/dist/infra/device-identity.js +8 -8
  231. package/dist/infra/dotenv.js +2 -2
  232. package/dist/infra/exec-approvals.js +8 -8
  233. package/dist/infra/exec-command-resolution.js +4 -4
  234. package/dist/infra/exec-safe-bin-trust.js +2 -2
  235. package/dist/infra/fased-root.js +5 -5
  236. package/dist/infra/fasedhub.js +64 -1
  237. package/dist/infra/fs-safe.js +14 -14
  238. package/dist/infra/gateway-lock.js +11 -11
  239. package/dist/infra/git-commit.js +5 -5
  240. package/dist/infra/git-root.js +4 -4
  241. package/dist/infra/hosted-runtime-artifact.js +3 -3
  242. package/dist/infra/is-main.js +2 -2
  243. package/dist/infra/json-file.js +7 -7
  244. package/dist/infra/local-source-paired-update.js +538 -0
  245. package/dist/infra/managed-runtime-bootstrap.js +6 -6
  246. package/dist/infra/outbound/delivery-queue.js +16 -16
  247. package/dist/infra/path-env.js +3 -3
  248. package/dist/infra/ports-lsof.js +4 -4
  249. package/dist/infra/provider-usage.auth.js +3 -3
  250. package/dist/infra/restart.js +41 -10
  251. package/dist/infra/safe-open-sync.js +2 -2
  252. package/dist/infra/session-cost-usage.js +9 -9
  253. package/dist/infra/shell-env.js +2 -2
  254. package/dist/infra/state-migrations.fs.js +6 -6
  255. package/dist/infra/state-migrations.js +23 -23
  256. package/dist/infra/tmp-fased-dir.js +6 -6
  257. package/dist/infra/update-runner.js +96 -0
  258. package/dist/infra/widearea-dns.js +3 -3
  259. package/dist/infra/wsl.js +2 -2
  260. package/dist/light-update-status.js +11 -11
  261. package/dist/line/accounts.js +2 -2
  262. package/dist/line/download.js +2 -2
  263. package/dist/logging/config.js +3 -3
  264. package/dist/logging/logger.js +7 -7
  265. package/dist/managed/federation.js +3 -3
  266. package/dist/managed/tunnel.js +3 -3
  267. package/dist/media/store.js +13 -13
  268. package/dist/media-understanding/runner.js +3 -3
  269. package/dist/memory/backend-config.js +2 -1
  270. package/dist/memory/embeddings.js +2 -2
  271. package/dist/memory/internal.js +10 -10
  272. package/dist/memory/manager-sync-ops.js +7 -7
  273. package/dist/memory/repair-executor.js +17 -17
  274. package/dist/memory/session-files.js +16 -2
  275. package/dist/node-host/invoke-system-run-plan.js +8 -8
  276. package/dist/node-host/invoke.js +2 -2
  277. package/dist/node-host/runner.js +3 -3
  278. package/dist/pairing/pairing-store.js +3 -3
  279. package/dist/plugin-sdk/{accounts-DxblobYM.js → accounts-Brn3-uWN.js} +1 -1
  280. package/dist/plugin-sdk/{actions-D4UNimWa.js → actions-DRu135h0.js} +2 -2
  281. package/dist/plugin-sdk/{active-listener-CFuy106s.js → active-listener-B_NMtgPL.js} +1 -1
  282. package/dist/plugin-sdk/{audio-preflight-Bv0hUOWL.js → audio-preflight-BYxAt775.js} +27 -27
  283. package/dist/plugin-sdk/{audit-Cs0K7LBp.js → audit-C5d2z5U1.js} +1 -1
  284. package/dist/plugin-sdk/{channel-access-D5aHoTY8.js → channel-access-CcFRdRPO.js} +1 -1
  285. package/dist/plugin-sdk/{channel-access-configure-Dvch9mMQ.js → channel-access-configure-DaI5wevh.js} +1 -1
  286. package/dist/plugin-sdk/channel-plugin-common.d.ts +3 -3
  287. package/dist/plugin-sdk/channel-plugin-common.js +4 -4
  288. package/dist/plugin-sdk/{chunk-B0FUGc5A.js → chunk-CyoyqpbG.js} +1 -1
  289. package/dist/plugin-sdk/{chunk-items-Bk--NU3E.js → chunk-items-DIRHG2gJ.js} +1 -1
  290. package/dist/plugin-sdk/command-status.d.ts +2 -2
  291. package/dist/plugin-sdk/command-status.js +27 -27
  292. package/dist/plugin-sdk/{commands-registry-DmDRhRAU.js → commands-registry-BfqdEWdF.js} +2 -2
  293. package/dist/plugin-sdk/{common-CfUOowOp.js → common-HCcXU-NR.js} +1 -1
  294. package/dist/plugin-sdk/{config-mxULv0fQ.js → config-DlWVeLFB.js} +7113 -6877
  295. package/dist/plugin-sdk/{config-schema-CGW6J3Pv.js → config-schema-BOT7kHb6.js} +6779 -3696
  296. package/dist/plugin-sdk/device-pair.d.ts +1 -1
  297. package/dist/plugin-sdk/discord.d.ts +5 -5
  298. package/dist/plugin-sdk/discord.js +19 -19
  299. package/dist/plugin-sdk/file-lock.js +35 -4
  300. package/dist/plugin-sdk/{filesystem-manager-ZoHU8oA7.js → filesystem-manager-DoI8IBNF.js} +1 -1
  301. package/dist/plugin-sdk/{group-mentions-BHRff6j8.d.ts → group-mentions-q3hUTCt7.d.ts} +1 -1
  302. package/dist/plugin-sdk/{handle-action-DbVCF9HZ.js → handle-action-hvwapMKg.js} +13 -13
  303. package/dist/plugin-sdk/{helpers-BXbdgs5H.js → helpers-DMBU4oTo.js} +1 -1
  304. package/dist/plugin-sdk/{image-ops-D7jPxaph.js → image-ops-DQi0n_Tt.js} +1 -1
  305. package/dist/plugin-sdk/index.d.ts +9 -27
  306. package/dist/plugin-sdk/index.js +29 -29
  307. package/dist/plugin-sdk/json-store.js +6 -6
  308. package/dist/plugin-sdk/{login-BRxww6nv.js → login-Ca583Okc.js} +3 -3
  309. package/dist/plugin-sdk/{login-qr-DJXsk9XH.js → login-qr-CGciBSNP.js} +3 -3
  310. package/dist/plugin-sdk/{manager-Y2S_5IlI.js → manager-BegWxNYX.js} +4 -4
  311. package/dist/plugin-sdk/{markdown-tables-DHLFQqaF.js → markdown-tables-DCOXEUfZ.js} +1 -1
  312. package/dist/plugin-sdk/{media-Czo4carg.js → media-CAdQF5TG.js} +47 -47
  313. package/dist/plugin-sdk/{memory-search-DTjIMw8J.js → memory-search-CrFPOeTH.js} +4 -1
  314. package/dist/plugin-sdk/{monitor-B9Cmt_Je.js → monitor-BCFDaqJ-.js} +31 -31
  315. package/dist/plugin-sdk/{monitor-B71iwAgC.js → monitor-BgbjRiPM.js} +32 -32
  316. package/dist/plugin-sdk/{openresponses-http-Du4GMmwo.js → openresponses-http-DFf87SMM.js} +27 -27
  317. package/dist/plugin-sdk/{optional-runtime-dependency-DdMbxS24.js → optional-runtime-dependency-0dznm3ZO.js} +1 -1
  318. package/dist/plugin-sdk/{outbound-Db8mRN6P.js → outbound-BgVDdHxx.js} +6 -6
  319. package/dist/plugin-sdk/{outbound-attachment-BqFjcypb.js → outbound-attachment-QqTnIhZL.js} +2 -2
  320. package/dist/plugin-sdk/{pairing-message-BMkkS038.d.ts → pairing-message-CS-9BHug.d.ts} +2 -2
  321. package/dist/plugin-sdk/{pairing-message-B3GC_7CD.js → pairing-message-D0CoLHRJ.js} +1 -1
  322. package/dist/plugin-sdk/{pairing-store-CQikgIbw.js → pairing-store-BChahgI2.js} +2 -2
  323. package/dist/plugin-sdk/{pi-model-discovery-CY3m5zJP.js → pi-model-discovery-BX_JLcEJ.js} +1 -1
  324. package/dist/plugin-sdk/{pi-model-discovery-runtime-DSh1T2pn.js → pi-model-discovery-runtime-DxTMz_XF.js} +2 -2
  325. package/dist/plugin-sdk/provider-web-search-config-contract.d.ts +2 -2
  326. package/dist/plugin-sdk/{pw-ai-AFUqn-tQ.js → pw-ai-natjh3rm.js} +3 -3
  327. package/dist/plugin-sdk/{qmd-manager-DSyx77KY.js → qmd-manager-CgRaTxUq.js} +1 -1
  328. package/dist/plugin-sdk/{query-expansion-CAI2kKLQ.js → query-expansion-BG3FgzF7.js} +14 -3
  329. package/dist/plugin-sdk/{replies-Fzh9PhXl.js → replies-ChYo5ZQM.js} +2 -2
  330. package/dist/plugin-sdk/{resolve-outbound-target-KpF3co6T.js → resolve-outbound-target-D-KGgHJZ.js} +1 -1
  331. package/dist/plugin-sdk/{resolve-route-CDl74O19.js → resolve-route-C8tvQm8Z.js} +1 -1
  332. package/dist/plugin-sdk/{retry-policy-D4uiqUOG.js → retry-policy-C6SjKjQA.js} +1 -1
  333. package/dist/plugin-sdk/{runtime-Bb6KRnCC.js → runtime-2B9_WKoI.js} +8 -3
  334. package/dist/plugin-sdk/{runtime-dependency-DLDmB94f.js → runtime-dependency-DczkvPxn.js} +2 -2
  335. package/dist/plugin-sdk/sat-runtime.d.ts +549 -309
  336. package/dist/plugin-sdk/sat-runtime.js +29 -29
  337. package/dist/plugin-sdk/{send-C4k4JuJN.js → send-B6KkGKZD.js} +6 -6
  338. package/dist/plugin-sdk/{send-D-uDKGIq.js → send-CFzGKJXo.js} +6 -6
  339. package/dist/plugin-sdk/{send-Du8ykP1k.js → send-DCO441U7.js} +8 -8
  340. package/dist/plugin-sdk/{send-5nFktR1n.js → send-gDoGA-7e.js} +5 -5
  341. package/dist/plugin-sdk/{send-Bfzf11VY.js → send-q9HXrSwK.js} +7 -7
  342. package/dist/plugin-sdk/{session-CS-Td9EM.js → session-DHD4ZKyG.js} +2 -2
  343. package/dist/plugin-sdk/{skill-commands-CFAlXWHk.js → skill-commands-FKEPLS7T.js} +201 -26
  344. package/dist/plugin-sdk/{slack-D_bbE6Yd.js → slack-BUTt8cxK.js} +39 -39
  345. package/dist/plugin-sdk/{slack-actions-BcMkNv1Z.js → slack-actions-DhbMcp8M.js} +11 -11
  346. package/dist/plugin-sdk/slack.d.ts +5 -5
  347. package/dist/plugin-sdk/slack.js +9 -9
  348. package/dist/plugin-sdk/{sticker-cache-Dqo9nRhi.js → sticker-cache-D0wHVqjt.js} +3 -3
  349. package/dist/plugin-sdk/{tables-bFXyRpkx.js → tables-B3jr0Pvq.js} +1 -1
  350. package/dist/plugin-sdk/{targets-DH7a9lKB.js → targets-CtSbTxYX.js} +1 -1
  351. package/dist/plugin-sdk/{telegram-actions-Cnag7JTH.js → telegram-actions-CC_qN4v0.js} +30 -30
  352. package/dist/plugin-sdk/telegram.d.ts +5 -5
  353. package/dist/plugin-sdk/telegram.js +8 -8
  354. package/dist/plugin-sdk/{thread-bindings.lifecycle-CgOvvdw8.js → thread-bindings.lifecycle-QbkNl-m6.js} +3 -3
  355. package/dist/plugin-sdk/{thread-bindings.state-CQpk44Hq.js → thread-bindings.state-D9hg_M1c.js} +2 -2
  356. package/dist/plugin-sdk/tmp-fased-dir-xOTGlzBE.d.ts +486 -0
  357. package/dist/plugin-sdk/{types-CLIvQaVg.d.ts → types-C3avNytw.d.ts} +3 -4
  358. package/dist/plugin-sdk/{types-DyhXnYUr.d.ts → types-D0kDG8l0.d.ts} +3 -2
  359. package/dist/plugin-sdk/{types-BYDL6dQj.d.ts → types-DWfbSAaM.d.ts} +2 -2
  360. package/dist/plugin-sdk/{types.plugin-_fbu40Re.d.ts → types.plugin-BcSBq42x.d.ts} +8 -3
  361. package/dist/plugin-sdk/{web-BInKQkc4.js → web-BXsJ1vqe.js} +32 -32
  362. package/dist/plugin-sdk/{whatsapp-actions-BtSc2CBv.js → whatsapp-actions-Bj_rOncr.js} +13 -13
  363. package/dist/plugin-sdk/{whatsapp-heartbeat-Bg9TuYzl.js → whatsapp-heartbeat-B1WHkJIg.js} +2 -2
  364. package/dist/plugin-sdk/whatsapp.d.ts +5 -5
  365. package/dist/plugin-sdk/whatsapp.js +12 -12
  366. package/dist/plugin-sdk/{wsl-C4DGFkOd.js → wsl-J-HscWxS.js} +3 -3
  367. package/dist/plugins/bundled-dir.js +3 -3
  368. package/dist/plugins/discovery.js +11 -11
  369. package/dist/plugins/loader.js +13 -13
  370. package/dist/plugins/manifest-registry.js +2 -2
  371. package/dist/plugins/manifest.js +4 -4
  372. package/dist/plugins/path-safety.js +3 -3
  373. package/dist/plugins/status-cache.js +9 -9
  374. package/dist/plugins/update.js +3 -3
  375. package/dist/plugins/web-search-install-catalog.js +3 -3
  376. package/dist/providers/refresh.js +1 -1
  377. package/dist/providers/registry.js +1 -1
  378. package/dist/secrets/apply.js +17 -17
  379. package/dist/secrets/audit.js +11 -11
  380. package/dist/secrets/runtime.js +1 -1
  381. package/dist/secrets/shared.js +9 -9
  382. package/dist/security/scan-paths.js +2 -2
  383. package/dist/shared/config-eval.js +2 -2
  384. package/dist/shared/pid-alive.js +2 -2
  385. package/dist/tasks/standing-orders.js +5 -5
  386. package/dist/tasks/task-flow-registry.js +5 -5
  387. package/dist/tasks/task-registry.js +5 -5
  388. package/dist/tasks/workflow-definitions.js +6 -6
  389. package/dist/telegram/token.js +5 -5
  390. package/dist/utils.js +5 -5
  391. package/dist/wallet/external-submission-ledger.js +376 -0
  392. package/dist/wallet/legacy-embedded-keystore.js +40 -0
  393. package/dist/wallet/local-socket-signer-archive.js +30 -0
  394. package/dist/wallet/local-socket-signer-lifecycle.js +97 -0
  395. package/dist/wallet/local-socket-signer-policy.js +166 -0
  396. package/dist/wallet/local-socket-signer-protocol.js +561 -118
  397. package/dist/wallet/native-signer-wallet-id.js +25 -0
  398. package/dist/wallet/providers/alchemy-adapter.js +3 -0
  399. package/dist/wallet/providers/local-socket-signer-adapter.js +382 -107
  400. package/dist/wallet/providers/turnkey-adapter.js +292 -34
  401. package/dist/wallet/providers/wallet-standard-adapter.js +100 -0
  402. package/dist/wallet/signer-network-admin.js +184 -0
  403. package/dist/wallet/signer-protocol-v2.generated.js +90 -0
  404. package/dist/wallet/solana-bond-signing.js +157 -69
  405. package/dist/wallet/solana-reviewed-transaction.js +375 -0
  406. package/dist/wallet/solana-swap.js +520 -64
  407. package/dist/wallet/solana-transaction-inspection.js +106 -4
  408. package/dist/wallet/solana-trigger.js +658 -320
  409. package/dist/wallet/turnkey-reviewed-state.js +370 -0
  410. package/dist/wallet/wallet-approval-auth.js +20 -31
  411. package/dist/wallet/wallet-atomic-state.js +53 -0
  412. package/dist/wallet/wallet-audit-log.js +5 -5
  413. package/dist/wallet/wallet-canary.js +0 -1
  414. package/dist/wallet/wallet-inbound-events.js +19 -14
  415. package/dist/wallet/wallet-observability.js +5 -5
  416. package/dist/wallet/wallet-policy-simulation.js +1 -1
  417. package/dist/wallet/wallet-policy.js +95 -41
  418. package/dist/wallet/wallet-provider-capabilities.js +28 -4
  419. package/dist/wallet/wallet-provider-registry.js +64 -51
  420. package/dist/wallet/wallet-provider-resolver.js +62 -63
  421. package/dist/wallet/wallet-runtime-config.js +31 -44
  422. package/dist/wallet/wallet-secrets-store.js +44 -22
  423. package/dist/wallet/wallet-send-approvals.js +1894 -592
  424. package/dist/wallet/wallet-send-execution-ledger.js +292 -0
  425. package/dist/wallet/wallet-settlement-links.js +17 -16
  426. package/dist/wallet/wallet-standard-review.js +353 -0
  427. package/dist/wallet/wallet-status.js +0 -9
  428. package/dist/wallet/wallet-task-ledger.js +16 -2
  429. package/dist/web/accounts.js +3 -3
  430. package/dist/web/auth-store.js +18 -18
  431. package/dist/web/auto-reply/heartbeat-runner.js +1 -1
  432. package/dist/web/auto-reply/monitor.js +2 -2
  433. package/dist/web/session.js +4 -4
  434. package/dist/wizard/host-security-capability.js +15 -24
  435. package/dist/wizard/onboarding.finalize.js +63 -184
  436. package/dist/wizard/onboarding.host-security.js +118 -1368
  437. package/dist/wizard/onboarding.js +180 -242
  438. package/dist/wizard/onboarding.wallet.js +323 -829
  439. package/extensions/acpx/package.json +1 -1
  440. package/extensions/bluebubbles/package.json +1 -1
  441. package/extensions/copilot-proxy/package.json +1 -1
  442. package/extensions/diagnostics-otel/package.json +1 -1
  443. package/extensions/fased-federation/package.json +1 -1
  444. package/extensions/google-gemini-cli-auth/package.json +1 -1
  445. package/extensions/imessage/package.json +1 -1
  446. package/extensions/irc/package.json +1 -1
  447. package/extensions/line/package.json +1 -1
  448. package/extensions/llm-task/package.json +1 -1
  449. package/extensions/lobster/package.json +1 -1
  450. package/extensions/matrix/CHANGELOG.md +18 -0
  451. package/extensions/matrix/package.json +1 -1
  452. package/extensions/mattermost/package.json +1 -1
  453. package/extensions/memory-core/package.json +2 -2
  454. package/extensions/memory-lancedb/package.json +1 -1
  455. package/extensions/minimax-portal-auth/package.json +1 -1
  456. package/extensions/msteams/CHANGELOG.md +18 -0
  457. package/extensions/msteams/package.json +1 -1
  458. package/extensions/nextcloud-talk/package.json +1 -1
  459. package/extensions/nostr/CHANGELOG.md +18 -0
  460. package/extensions/nostr/package.json +1 -1
  461. package/extensions/open-prose/package.json +1 -1
  462. package/extensions/sat-mining/index.ts +498 -153
  463. package/extensions/sat-mining/package.json +1 -1
  464. package/extensions/sat-mining/signer-codec-schema.v1.json +374 -0
  465. package/extensions/sat-mining/src/audit-store.ts +50 -5
  466. package/extensions/sat-mining/src/claim-service.ts +85 -14
  467. package/extensions/sat-mining/src/config.ts +9 -0
  468. package/extensions/sat-mining/src/cycle-progress.ts +16 -8
  469. package/extensions/sat-mining/src/epoch-service.ts +9 -5
  470. package/extensions/sat-mining/src/gateway-runner.ts +21 -1
  471. package/extensions/sat-mining/src/protocol-contract.ts +45 -1
  472. package/extensions/sat-mining/src/recovery-service.ts +312 -0
  473. package/extensions/sat-mining/src/round-watcher.ts +391 -81
  474. package/extensions/sat-mining/src/rpc-read.ts +330 -89
  475. package/extensions/sat-mining/src/runtime.ts +14 -0
  476. package/extensions/sat-mining/src/sat-bond-layout.ts +6 -0
  477. package/extensions/sat-mining/src/signer-codec-manifest.ts +429 -0
  478. package/extensions/sat-mining/src/solana-submit.ts +1199 -428
  479. package/extensions/sat-mining/src/submission-ledger.ts +433 -0
  480. package/extensions/signal/package.json +1 -1
  481. package/extensions/synology-chat/package.json +1 -1
  482. package/extensions/tlon/package.json +1 -1
  483. package/extensions/twitch/CHANGELOG.md +18 -0
  484. package/extensions/twitch/package.json +1 -1
  485. package/extensions/voice-call/CHANGELOG.md +18 -0
  486. package/extensions/voice-call/package.json +1 -1
  487. package/extensions/zalo/CHANGELOG.md +18 -0
  488. package/extensions/zalo/package.json +1 -1
  489. package/extensions/zalouser/CHANGELOG.md +18 -0
  490. package/extensions/zalouser/package.json +1 -1
  491. package/install.sh +4955 -0
  492. package/package.json +24 -5
  493. package/scripts/fased-host-updater.mjs +1421 -0
  494. package/scripts/fased-host-updaterctl.mjs +186 -0
  495. package/scripts/fased-managed-service.sh +36 -0
  496. package/scripts/fased-managed-updater.mjs +3065 -156
  497. package/scripts/fased-signer-enroll-hosting.sh +275 -0
  498. package/scripts/fased-signer-network-hosting.sh +143 -0
  499. package/scripts/fased-signer-owner-policy.mjs +1430 -0
  500. package/scripts/fased-signer-policy-hosting.sh +50 -0
  501. package/scripts/fased-signer-policy-local.sh +26 -0
  502. package/scripts/hosted-release-manifest.mjs +185 -0
  503. package/scripts/install-fased-signerd.sh +217 -0
  504. package/scripts/install-managed-runtime.mjs +212 -10
  505. package/scripts/managed-runtime-layout.mjs +75 -3
  506. package/scripts/migrate-hosted-signer-v2.mjs +58 -0
  507. package/scripts/start-managed.sh +169 -360
  508. package/dist/control-ui/assets/mining-commit-R-6nu87p.js +0 -2
  509. package/dist/plugin-sdk/tmp-fased-dir-BdFQvrvw.d.ts +0 -77
  510. package/dist/wallet/local-socket-signer-broker.js +0 -224
  511. package/dist/wallet/local-socket-signer-custody.js +0 -42
  512. package/dist/wallet/providers/embedded-keystore-adapter.js +0 -402
  513. package/dist/wallet/providers/privy-adapter.js +0 -80
  514. 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 fs from "node:fs";
20
- import { randomBytes } from "node:crypto";
21
- import { setTimeout } from "node:timers/promises";
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 DEFAULT_SEND_RETRY_ATTEMPTS = 3;
48
- const DEFAULT_SEND_RETRY_DELAY_MS = 750;
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 isSatMiningTokenSweep(params) {
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 false;
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 || !ids?.mintAddress || program !== ids.mintAddress) {
70
- return false;
71
- }
72
- if (params.sendPath !== "automation" || String(params.requestedBy ?? "").trim() !== "sat-mining") {
73
- return false;
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
- return resolveWalletRoleForId({
76
- walletId: params.walletId,
247
+ const destination = resolveConfiguredSatSweepDestination({
77
248
  cfg: params.cfg,
249
+ walletId,
78
250
  env: params.env
79
- }) === "mining";
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
- if (!providerSupportsChainOperation({
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 executeWalletSendWithRetry(params) {
284
- const env = params.env ?? process.env;
285
- const maxAttempts = resolveSendRetryAttempts(env);
286
- const baseDelayMs = resolveSendRetryDelayMs(env);
287
- let attempts = 0;
288
- while (attempts < maxAttempts) {
289
- attempts += 1;
290
- try {
291
- const tx = await params.execute();
292
- return {
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
- tx,
295
- attempts
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 (!fs.existsSync(paths.sendApprovalsPath)) {
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(fs.readFileSync(paths.sendApprovalsPath, "utf8"));
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
- return {
335
- version: 1,
336
- requests: []
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
- fs.writeFileSync(paths.sendApprovalsPath, `${JSON.stringify(file, null, 2)}\n`, {
342
- encoding: "utf8",
343
- mode: 384
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
- fs.chmodSync(paths.sendApprovalsPath, 384);
347
- } catch {}
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
- const exp = Date.parse(request.expiresAt);
357
- if (Number.isFinite(exp) && exp <= now) {
358
- request.status = "expired";
359
- request.decisionAt = new Date(now).toISOString();
360
- request.reason = "expired";
361
- syncApprovalTaskForRequest({ request });
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
- saveFile(file, env);
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
- async function createOrExecuteWalletSend(params) {
948
+ function createSignerReviewApprovalRequest(params) {
414
949
  const env = params.env ?? process.env;
415
- const requestedBy = params.requestedBy?.trim() || "agent";
416
- const cfg = params.runtimeConfig ?? loadConfig();
417
- const effectiveConfig = applyWalletPolicyConfig({
418
- config: params.config,
419
- cfg,
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
- if (resolvedMode === "autonomous" && walletRole === "mining" && requestedBy !== "sat-mining") {
435
- const message = "Mining wallets are limited to mining operations and SAT sweep automation";
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 = isSatMiningTokenSweep({
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
- return {
551
- ok: true,
552
- mode: "manual",
553
- request: createWalletSendApprovalRequest({
554
- payload: params.payload,
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
- return {
585
- ok: false,
586
- code: custodyGate.code,
587
- message: custodyGate.message,
588
- requestId: settlementRequestId
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: false,
613
- code: consumed.code,
614
- message: consumed.message,
615
- requestId: settlementRequestId
1298
+ ok: true,
1299
+ mode: "manual",
1300
+ request
616
1301
  };
1302
+ } finally {
1303
+ await releaseManualExecution?.();
617
1304
  }
618
1305
  }
619
- const daily = enforceWalletDailyCap({
620
- config: effectiveConfig,
621
- chain: params.payload.chain,
622
- amount: params.payload.amount,
623
- program: params.payload.program,
624
- walletId: params.payload.walletId,
625
- env,
626
- skipNativeSolanaCaps
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
- if (!daily.ok) {
629
- if (settlementRequestId) {
630
- upsertSettlementLinkForPayload({
631
- requestId: settlementRequestId,
632
- payload: settlementPayload,
633
- settlementContext: params.settlementContext,
634
- mode: resolvedMode,
635
- status: "failed",
636
- reason: daily.message ?? daily.code ?? "wallet daily cap exceeded",
637
- env
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: daily.code ?? "wallet_cap_daily_exceeded",
643
- message: daily.message ?? "wallet daily cap exceeded",
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
- if (settlementRequestId) {
649
- upsertSettlementLinkForPayload({
650
- requestId: settlementRequestId,
651
- payload: settlementPayload,
652
- settlementContext: params.settlementContext,
653
- mode: resolvedMode,
654
- status: "pending",
655
- env
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: params.payload,
663
- requestId: settlementRequestId,
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 executeWalletSendWithRetry({
673
- env,
674
- execute: async () => {
675
- if (custodyGate.custodyMode === "split-key-active" && custodyGate.session) {
676
- const guarded = await withWalletCustodySigningMaterial({
677
- sessionId: custodyGate.session.id,
678
- host: custodyGate.session.host,
679
- handler: async () => await provider.sendTx(params.payload)
680
- });
681
- if (!guarded.ok) {
682
- throw new Error(guarded.message);
683
- }
684
- return guarded.value;
685
- }
686
- return await provider.sendTx(params.payload);
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 (!sent.ok) {
690
- throw new Error(`${normalizeErrorMessage(sent.error)} (attempts=${sent.attempts})`);
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: params.payload,
698
- requestId: settlementRequestId,
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: sent.attempts
1582
+ txHash: result.tx.txHash,
1583
+ attempts: 1
706
1584
  }),
707
1585
  env
708
1586
  });
709
- if (settlementRequestId) {
710
- const settlementLink = markWalletSettlementLinkOutcome({
711
- requestId: settlementRequestId,
712
- status: "executed",
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
- if (settlementRequestId) {
750
- markWalletSettlementLinkOutcome({
751
- requestId: settlementRequestId,
752
- status: "failed",
753
- reason: message,
754
- env
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
- const file = loadFile(env);
1745
+ let file = loadFile(env);
782
1746
  if (markExpired(file)) {
783
1747
  saveFile(file, env);
784
1748
  }
785
- const request = findRequest(file, params.requestId);
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
- if (request.status !== "pending") {
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
- const expiredAt = Date.parse(request.expiresAt);
806
- if (Number.isFinite(expiredAt) && expiredAt <= nowMs()) {
807
- request.status = "expired";
808
- request.reason = "expired";
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 cfg = loadConfig();
824
- const executed = await executeSolanaSwapApprovalPayload({
825
- payload: request.payload,
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 (!executed.ok) {
833
- request.status = "failed";
834
- request.reason = executed.message;
835
- request.result = { error: request.reason };
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
- saveFile(file, env);
838
- syncApprovalTaskForRequest({
839
- request,
840
- settlementLink
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: "send_failed",
844
- actor: params.actor?.trim() || "operator",
1911
+ action: "send_executed",
1912
+ actor: request.approvedBy,
845
1913
  details: {
846
1914
  ...buildWalletSendAuditDetails({
847
- payload: request.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
- code: executed.code
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: false,
861
- code: executed.code,
862
- message: executed.message,
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 simulation = simulateWalletPolicy({
982
- cfg,
983
- config: effectiveConfig,
984
- payload: request.payload,
985
- mode: "manual",
986
- source: request.requestedBy,
987
- requireDirectSigning: false,
988
- skipNativeSolanaCaps,
989
- requireSolanaTokenCap: Boolean(request.payload.program?.trim()),
990
- env
991
- });
992
- if (!simulation.ok) {
993
- const failed = simulation.checks.find((check) => check.status === "fail");
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
- appendWalletAuditEntry({
1006
- action: "send_failed",
1007
- actor: params.actor?.trim() || "operator",
1008
- details: {
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
- providerId: request.payload.providerId,
1012
- walletId: request.payload.walletId,
1013
- walletName: request.payload.walletName,
1014
- taskId: settlementLink?.taskId,
1015
- invoiceId: settlementLink?.invoiceId,
1016
- senderHandle: settlementLink?.senderHandle
1017
- },
1018
- env
1019
- });
1020
- markWalletSettlementLinkOutcome({
1021
- requestId: request.id,
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
- request.simulation = simulation;
1034
- request.approvalDiff = simulation.diff;
1035
- const custodyGate = await enforceWalletCustodyForAutonomousSend({
1036
- wallet: params.config,
1037
- cfg,
1038
- env,
1039
- walletId: request.payload.walletId,
1040
- approvalHost: params.approvalHost
1041
- });
1042
- if (!custodyGate.ok) {
1043
- if (custodyGate.code === "custody_unlock_required") {
1044
- syncApprovalTaskForRequest({
1045
- request,
1046
- settlementLink
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: custodyGate.code,
1051
- message: custodyGate.message,
2086
+ code: "wallet_signer_review_failed",
2087
+ message: normalizeErrorMessage(error),
1052
2088
  request
1053
2089
  };
1054
2090
  }
1055
- request.status = "failed";
1056
- request.reason = custodyGate.message;
1057
- request.result = { error: request.reason };
1058
- request.decisionAt = new Date().toISOString();
1059
- saveFile(file, env);
1060
- syncApprovalTaskForRequest({
1061
- request,
1062
- settlementLink
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
- status: "failed",
1082
- reason: request.reason,
2101
+ actor: params.actor,
2102
+ allowExpiredSignerReview: canReconcileExpiredSignerReview,
1083
2103
  env
1084
2104
  });
1085
- return {
1086
- ok: false,
1087
- code: custodyGate.code,
1088
- message: request.reason,
1089
- request
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 daily = enforceWalletDailyCap({
1093
- config: effectiveConfig,
1094
- chain: request.payload.chain,
1095
- amount: request.payload.amount,
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 (!daily.ok) {
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: "daily_cap_rejected",
1135
- message: request.reason,
1136
- request
2363
+ code: claim.code,
2364
+ message: claim.message,
2365
+ ...claim.request ? { request: claim.request } : {}
1137
2366
  };
1138
2367
  }
1139
- request.status = "approved";
1140
- request.approvedBy = params.actor?.trim() || "operator";
1141
- request.decisionAt = new Date().toISOString();
1142
- syncApprovalTaskForRequest({
1143
- request,
1144
- settlementLink
1145
- });
1146
- appendWalletAuditEntry({
1147
- action: "send_approved",
1148
- actor: request.approvedBy,
1149
- details: {
1150
- requestId: request.id,
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
- providerId: selectedProviderId,
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
- execute: async () => {
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 (!sent.ok) {
1181
- throw new Error(`${normalizeErrorMessage(sent.error)} (attempts=${sent.attempts})`);
1182
- }
1183
- const tx = withSendAttemptMetadata(sent.tx, sent.attempts);
1184
- request.status = "executed";
1185
- request.result = { txHash: tx.txHash };
1186
- const settlementLink = markWalletSettlementLinkOutcome({
1187
- requestId: request.id,
1188
- status: "executed",
1189
- txHash: tx.txHash,
1190
- env
1191
- });
1192
- await publishSettlementEvidenceForLink({
1193
- settlementLink,
1194
- env
1195
- });
1196
- appendWalletAuditEntry({
1197
- action: "send_executed",
1198
- actor: request.approvedBy,
1199
- details: buildWalletSendAuditDetails({
1200
- payload: request.payload,
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
- txHash: tx.txHash,
1203
- providerId: selectedProviderId,
1204
- taskId: settlementLink?.taskId,
1205
- invoiceId: settlementLink?.invoiceId,
1206
- senderHandle: settlementLink?.senderHandle,
1207
- attempts: sent.attempts
1208
- }),
1209
- env
1210
- });
1211
- saveFile(file, env);
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: "send_failed",
2441
+ action: "send_approved",
1233
2442
  actor: request.approvedBy,
1234
2443
  details: {
1235
2444
  requestId: request.id,
1236
- reason: request.reason,
1237
- providerId: request.payload.providerId,
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
- saveFile(file, env);
1247
- syncApprovalTaskForRequest({
1248
- request,
1249
- settlementLink
1250
- });
1251
- return {
1252
- ok: false,
1253
- code: "send_failed",
1254
- message: request.reason,
1255
- request
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 };