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