@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
@@ -1,41 +1,26 @@
1
1
  import { defaultRuntime } from "../runtime.js";
2
2
  import { loadConfig, writeConfigFile } from "../config/io.js";
3
3
  import "../config/config.js";
4
- import { ensureWalletStateDir, resolveLocalSignerBackendSocketPath, resolveLocalSignerMaterialRootDir, resolveLocalSignerSidecarPaths, resolveLocalSignerSocketPath, resolveWalletRuntimeConfig } from "../wallet/wallet-runtime-config.js";
5
- import { normalizeWalletUserRole, readWalletProviderRegistry, resolveWalletUserRole, setDefaultWallet, setNamedWalletRole, upsertNamedWallet } from "../wallet/wallet-provider-registry.js";
6
- import { applyWalletPolicyConfig, resolveWalletRoleForId } from "../wallet/wallet-policy.js";
4
+ import { hasLegacyEmbeddedKeystoreConfig, hasLegacyEmbeddedKeystoreMaterialHint, throwLegacyEmbeddedKeystoreMigrationRequired } from "../wallet/legacy-embedded-keystore.js";
5
+ import { ensureWalletStateDir, isLocalSignerExternallyManaged, resolveLocalSignerControlSocketPath, resolveLocalSignerMasterKeyPath, resolveLocalSignerSidecarPaths, resolveLocalSignerSocketPath, resolveLocalSignerStateDbPath } from "../wallet/wallet-runtime-config.js";
6
+ import { normalizeWalletUserRole, readWalletProviderRegistry, resolveWalletUserRole, setDefaultWallet, setNamedWalletRole, setWalletProviderEnabled, upsertNamedWallet } from "../wallet/wallet-provider-registry.js";
7
7
  import { buildWalletProviderCapabilityMatrix } from "../wallet/wallet-provider-capabilities.js";
8
8
  import { redactWalletDiagnosticText } from "../wallet/wallet-redaction.js";
9
+ import { callLocalSocketSigner, probeLocalSocketSignerHealth, requireLocalSocketSignerPath } from "../wallet/providers/local-socket-signer-adapter.js";
9
10
  import { readWalletProviderSecretStatus, saveWalletProviderSecret } from "../wallet/wallet-secrets-store.js";
10
11
  import { createWalletProviderAdapter, resolveWalletProviderId } from "../wallet/wallet-provider-resolver.js";
11
- import { readWalletApprovalAuthSnapshot } from "../wallet/wallet-approval-auth.js";
12
- import { deleteWalletCustodyCeremony, initializeWalletCustodyCeremony, listSplitKeyWalletCustodyStatuses, lockWalletCustodyUnlockSessions, readWalletCustodyStatus, recoverWalletCustodyPassphrase } from "../wallet/wallet-custody.js";
13
12
  import { readWalletStatusSnapshot, resolveWalletConfigForRuntime } from "../wallet/wallet-status.js";
14
- import { startLocalSocketSignerBroker } from "../wallet/local-socket-signer-broker.js";
13
+ import { createLockedSignerOwnedWallet } from "../wallet/local-socket-signer-lifecycle.js";
14
+ import { normalizeNativeSignerWalletId } from "../wallet/native-signer-wallet-id.js";
15
+ import { configureSignerOwnedWalletNetwork } from "../wallet/signer-network-admin.js";
15
16
  import { listWalletInboundEvents, pollWalletInboundEvents, reconcileWalletInboundEvents } from "../wallet/wallet-inbound-events.js";
16
17
  import { buildWalletCanaryReport, runWalletProviderCanaryReport } from "../wallet/wallet-canary.js";
17
- import { resolveSignerdBinaryPath, restartLocalSocketSigner } from "../wizard/onboarding.wallet.js";
18
+ import { installSignerdBinary, resolveSignerdBinaryPath, restartLocalSocketSigner, writeLocalSignerEnvFile } from "../wizard/onboarding.wallet.js";
18
19
  import syncFs from "node:fs";
19
20
  import path from "node:path";
20
- import { createCipheriv, createDecipheriv, createPrivateKey, createPublicKey, randomBytes, scryptSync } from "node:crypto";
21
21
  import { createInterface } from "node:readline/promises";
22
22
 
23
23
  //#region src/commands/wallet.ts
24
- const PRIVATE_KEY_PRINT_CONFIRMATION = "SHOW PRIVATE KEY";
25
- const KEYSTORE_EXPORT_CONFIRMATION = "EXPORT KEYSTORE";
26
- function normalizeDangerousConfirmation(value) {
27
- return String(value ?? "").trim();
28
- }
29
- function requirePrivateKeyPrintConfirmation(value) {
30
- if (normalizeDangerousConfirmation(value) !== PRIVATE_KEY_PRINT_CONFIRMATION) {
31
- throw new Error(`Printing a private key requires explicit confirmation. Re-run with confirmation text "${PRIVATE_KEY_PRINT_CONFIRMATION}".`);
32
- }
33
- }
34
- function requireKeystoreSecretExportConfirmation(value) {
35
- if (normalizeDangerousConfirmation(value) !== KEYSTORE_EXPORT_CONFIRMATION) {
36
- throw new Error(`Including encrypted keystore material requires explicit confirmation. Re-run with confirmation text "${KEYSTORE_EXPORT_CONFIRMATION}".`);
37
- }
38
- }
39
24
  function resolveConfiguredMiningWalletId(cfg) {
40
25
  const config = cfg.plugins?.entries?.["sat-mining"]?.config;
41
26
  if (!config || typeof config !== "object" || Array.isArray(config)) {
@@ -48,55 +33,16 @@ function normalizeWalletRoleForCli(value) {
48
33
  const role = normalizeWalletUserRole(value);
49
34
  return role === "agent" || role === "vault" ? role : undefined;
50
35
  }
51
- function resolveLegacyLocalSignerEmbeddedScope(params) {
52
- const effectiveEnv = {
53
- ...params.env,
54
- ...params.cfg.env?.vars
55
- };
56
- const walletId = params.walletId?.trim() || undefined;
57
- const keystorePath = resolveEmbeddedKeystorePathForChain(effectiveEnv, params.chain, undefined, walletId);
58
- const rpcUrl = resolveRpcUrlForChain(effectiveEnv, params.chain, walletId);
59
- return {
60
- effectiveEnv,
61
- keystorePath,
62
- rpcUrl
63
- };
64
- }
65
- function createLegacyLocalSignerEmbeddedAdapter(params) {
66
- const { effectiveEnv, keystorePath, rpcUrl } = resolveLegacyLocalSignerEmbeddedScope(params);
67
- const scopedEnv = {
68
- ...effectiveEnv,
69
- FASED_WALLET_KEYSTORE_PATH: keystorePath,
70
- FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL: rpcUrl,
71
- FASED_WALLET_RPC_URL: rpcUrl
72
- };
73
- const scopedCfg = {
74
- ...params.cfg,
75
- wallet: {
76
- ...params.cfg.wallet,
77
- keystore: {
78
- ...params.cfg.wallet?.keystore,
79
- path: keystorePath
80
- }
81
- }
82
- };
83
- return createWalletProviderAdapter({
84
- cfg: scopedCfg,
85
- wallet: {
86
- ...resolveWalletConfigForRuntime(scopedCfg, scopedEnv),
87
- chains: [params.chain]
88
- },
89
- env: scopedEnv,
90
- providerIdOverride: "embedded-keystore"
91
- });
36
+ function createLegacyLocalSignerEmbeddedAdapter() {
37
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy embedded adapter construction requested");
92
38
  }
93
39
  function parseWalletProviderId(input) {
94
40
  switch ((input ?? "").trim()) {
95
- case "embedded-keystore":
41
+ case "embedded-keystore": throwLegacyEmbeddedKeystoreMigrationRequired("legacy wallet provider requested");
96
42
  case "local-socket-signer":
97
43
  case "alchemy":
98
- case "turnkey":
99
- case "privy": return input;
44
+ case "turnkey": return input;
45
+ case "privy": throw new Error("Privy wallet creation and signing are unavailable.");
100
46
  default: return undefined;
101
47
  }
102
48
  }
@@ -127,22 +73,7 @@ function isWalletRuntime(value) {
127
73
  return value === "external-docker" || value === "external-custom";
128
74
  }
129
75
  function throwLegacyDockerSignerRemoved(command) {
130
- throw new Error(`${command} is no longer supported. Use \`fased wallet keystore ...\` or hosted providers (turnkey/privy/alchemy).`);
131
- }
132
- function resolveEmbeddedKeystorePath(env, explicit) {
133
- const custom = explicit?.trim() || String(env.FASED_WALLET_KEYSTORE_PATH ?? "").trim();
134
- if (custom) {
135
- return path.resolve(custom);
136
- }
137
- const paths = ensureWalletStateDir(env);
138
- return path.join(paths.rootDir, "keystore.v1.enc");
139
- }
140
- function normalizeWalletIdForFilename(walletId) {
141
- const raw = String(walletId ?? "").trim().toLowerCase();
142
- if (!raw) {
143
- return undefined;
144
- }
145
- return raw.replace(/[^a-z0-9_-]+/g, "-");
76
+ throw new Error(`${command} is no longer supported. Use the native fased-signerd, Wallet Standard hardware custody, or Turnkey.`);
146
77
  }
147
78
  function normalizeWalletIdForEnvSuffix(walletId) {
148
79
  const raw = String(walletId ?? "").trim().toLowerCase();
@@ -172,7 +103,7 @@ function setConfigEnvVar(cfg, key, value) {
172
103
  };
173
104
  }
174
105
  const JUPITER_API_KEY_ENV = "FASED_JUPITER_API_KEY";
175
- const JUPITER_TRIGGER_API_BASE_URL_ENV = "FASED_JUPITER_TRIGGER_API_BASE_URL";
106
+ const LEGACY_JUPITER_TRIGGER_API_BASE_URL_ENV = "FASED_JUPITER_TRIGGER_API_BASE_URL";
176
107
  function resolveConfiguredJupiterApiKey(cfg, env) {
177
108
  return String(env[JUPITER_API_KEY_ENV] ?? cfg.env?.vars?.[JUPITER_API_KEY_ENV] ?? env.JUPITER_API_KEY ?? cfg.env?.vars?.JUPITER_API_KEY ?? "").trim();
178
109
  }
@@ -236,31 +167,30 @@ async function promptCliSecret(question, fallback = "") {
236
167
  }
237
168
  async function walletLimitOrdersConfigureCommand(runtime = defaultRuntime, options = {}) {
238
169
  if (options.enable && options.disable) {
239
- throw new Error("Use either --enable or --disable for Jupiter wallet-action support, not both.");
170
+ throw new Error("Use either --enable or --disable for Jupiter Swap API access, not both.");
240
171
  }
241
172
  const env = process.env;
242
173
  let cfg = loadConfig();
243
174
  if (options.disable) {
244
175
  cfg = setConfigEnvVar(cfg, JUPITER_API_KEY_ENV, undefined);
245
- cfg = setConfigEnvVar(cfg, JUPITER_TRIGGER_API_BASE_URL_ENV, undefined);
176
+ cfg = setConfigEnvVar(cfg, LEGACY_JUPITER_TRIGGER_API_BASE_URL_ENV, undefined);
246
177
  await writeConfigFile(cfg, { envSnapshotForRestore: process.env });
247
178
  delete env[JUPITER_API_KEY_ENV];
248
- delete env[JUPITER_TRIGGER_API_BASE_URL_ENV];
179
+ delete env[LEGACY_JUPITER_TRIGGER_API_BASE_URL_ENV];
249
180
  if (options.json) {
250
181
  runtime.log(JSON.stringify({
251
182
  ok: true,
252
183
  enabled: false
253
184
  }, null, 2));
254
185
  } else {
255
- runtime.log("Jupiter wallet-action support disabled. Other approved wallet actions can still use the normal wallet flow.");
186
+ runtime.log("Gateway Jupiter Swap API access disabled. Signer-owned Trigger configuration is unchanged.");
256
187
  }
257
188
  return;
258
189
  }
259
190
  const existingKey = resolveConfiguredJupiterApiKey(cfg, env);
260
- const existingBaseUrl = String(env[JUPITER_TRIGGER_API_BASE_URL_ENV] ?? cfg.env?.vars?.[JUPITER_TRIGGER_API_BASE_URL_ENV] ?? "").trim();
261
- let shouldEnable = options.enable === true || Boolean(options.jupiterApiKey?.trim()) || Boolean(options.jupiterTriggerApiBaseUrl?.trim());
191
+ let shouldEnable = options.enable === true || Boolean(options.jupiterApiKey?.trim());
262
192
  if (!shouldEnable && !options.nonInteractive) {
263
- const answer = await promptCliText("Enable Jupiter support for policy-gated Agent wallet actions? [y/N]", existingKey ? "y" : "n");
193
+ const answer = await promptCliText("Enable Gateway Jupiter Swap API access? Trigger credentials remain signer-owned. [y/N]", existingKey ? "y" : "n");
264
194
  shouldEnable = answer.trim().toLowerCase().startsWith("y");
265
195
  }
266
196
  if (!shouldEnable) {
@@ -271,36 +201,35 @@ async function walletLimitOrdersConfigureCommand(runtime = defaultRuntime, optio
271
201
  configured: Boolean(existingKey)
272
202
  }, null, 2));
273
203
  } else {
274
- runtime.log(existingKey ? "Jupiter wallet-action support already configured." : "Jupiter wallet-action support not configured.");
204
+ runtime.log(existingKey ? "Gateway Jupiter Swap API access already configured." : "Gateway Jupiter Swap API access not configured.");
275
205
  }
276
206
  return;
277
207
  }
278
208
  let apiKey = String(options.jupiterApiKey ?? "").trim() || existingKey;
279
209
  if (!apiKey && !options.nonInteractive) {
280
- apiKey = await promptCliSecret("Jupiter API key");
210
+ apiKey = await promptCliSecret("Jupiter Swap API key");
281
211
  }
282
212
  if (!apiKey) {
283
- throw new Error("Jupiter wallet-action support requires a Jupiter API key. Pass --jupiter-api-key or set FASED_JUPITER_API_KEY.");
213
+ throw new Error("Jupiter swap crafting requires an API key. Pass --jupiter-api-key or set FASED_JUPITER_API_KEY.");
284
214
  }
285
- const triggerApiBaseUrl = String(options.jupiterTriggerApiBaseUrl ?? "").trim() || existingBaseUrl;
286
215
  cfg = setConfigEnvVar(cfg, JUPITER_API_KEY_ENV, apiKey);
287
- if (triggerApiBaseUrl) {
288
- cfg = setConfigEnvVar(cfg, JUPITER_TRIGGER_API_BASE_URL_ENV, triggerApiBaseUrl);
289
- env[JUPITER_TRIGGER_API_BASE_URL_ENV] = triggerApiBaseUrl;
290
- }
216
+ cfg = setConfigEnvVar(cfg, LEGACY_JUPITER_TRIGGER_API_BASE_URL_ENV, undefined);
291
217
  await writeConfigFile(cfg, { envSnapshotForRestore: process.env });
292
218
  env[JUPITER_API_KEY_ENV] = apiKey;
219
+ delete env[LEGACY_JUPITER_TRIGGER_API_BASE_URL_ENV];
293
220
  if (options.json) {
294
221
  runtime.log(JSON.stringify({
295
222
  ok: true,
296
223
  enabled: true,
297
224
  jupiterApiKeyConfigured: true,
298
- triggerApiBaseUrl: triggerApiBaseUrl || undefined
225
+ scope: "gateway-jupiter-swap-only",
226
+ triggerCredentials: "signer-owned"
299
227
  }, null, 2));
300
228
  return;
301
229
  }
302
- runtime.log("Jupiter wallet-action support enabled for Agent wallet actions.");
303
- runtime.log(`Stored ${JUPITER_API_KEY_ENV} in local config env vars.`);
230
+ runtime.log("Gateway Jupiter Swap API access enabled for Agent wallet swap crafting.");
231
+ runtime.log(`Stored ${JUPITER_API_KEY_ENV} in local config env vars for swaps only.`);
232
+ runtime.log("Jupiter Trigger credentials and production API routing are owned by fased-signerd and are not configured in Gateway.");
304
233
  }
305
234
  function normalizeWalletChainsForSigner(chains) {
306
235
  const out = new Set();
@@ -324,91 +253,6 @@ function resolveLocalSignerChainsEnvValue(cfg, env) {
324
253
  }
325
254
  return normalizeWalletChainsForSigner(cfg.wallet?.runtime?.chains).join(",");
326
255
  }
327
- function collectLocalSignerExportEnv(cfg, env) {
328
- const effectiveEnv = {
329
- ...env,
330
- ...cfg.env?.vars
331
- };
332
- const exportablePrefixes = [
333
- "FASED_WALLET_SOLANA_KEYSTORE_PATH",
334
- "FASED_WALLET_SOLANA_RPC_URL",
335
- "FASED_WALLET_RPC_URL",
336
- "FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL"
337
- ];
338
- const out = {};
339
- for (const [key, rawValue] of Object.entries(effectiveEnv)) {
340
- if (!exportablePrefixes.some((prefix) => key === prefix || key.startsWith(`${prefix}__`))) {
341
- continue;
342
- }
343
- const value = String(rawValue ?? "").trim();
344
- if (!value) {
345
- continue;
346
- }
347
- out[key] = value;
348
- }
349
- return out;
350
- }
351
- function walletPolicyEnvSuffix(walletId) {
352
- return walletId.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
353
- }
354
- function addLocalSignerPolicyEnv(out, params) {
355
- const key = (name) => `${name}${params.prefix}`;
356
- out[key("FASED_WALLET_LOCAL_SIGNER_ROLE")] = params.role;
357
- out[key("FASED_WALLET_LOCAL_SIGNER_DIRECT_SIGNING")] = params.policy.directSigning ? "1" : "0";
358
- out[key("FASED_WALLET_LOCAL_SIGNER_CAPS_ENABLED")] = params.policy.capsEnabled ? "1" : "0";
359
- out[key("FASED_WALLET_LOCAL_SIGNER_SOLANA_MAX_PER_TX")] = params.policy.solana.caps.maxPerTx.toString();
360
- out[key("FASED_WALLET_LOCAL_SIGNER_SOLANA_MAX_DAILY")] = params.policy.solana.caps.maxDaily.toString();
361
- if (params.policy.solana.allowPrograms.length > 0) {
362
- out[key("FASED_WALLET_LOCAL_SIGNER_SOLANA_ALLOW_PROGRAMS")] = params.policy.solana.allowPrograms.join(",");
363
- }
364
- }
365
- function collectLocalSignerPolicyExportEnv(cfg, env) {
366
- const out = {};
367
- const registry = readWalletProviderRegistry(env);
368
- const localSignerWallets = registry.wallets.filter((wallet) => wallet.providerId === "local-socket-signer" && wallet.id.trim());
369
- if (localSignerWallets.length === 0) {
370
- return out;
371
- }
372
- const baseRuntime = resolveWalletRuntimeConfig(cfg, env);
373
- for (const wallet of localSignerWallets) {
374
- const role = resolveWalletRoleForId({
375
- walletId: wallet.id,
376
- cfg,
377
- env
378
- });
379
- const runtime = applyWalletPolicyConfig({
380
- config: baseRuntime,
381
- cfg,
382
- env,
383
- walletId: wallet.id
384
- });
385
- const suffix = walletPolicyEnvSuffix(wallet.id);
386
- if (!suffix) {
387
- continue;
388
- }
389
- addLocalSignerPolicyEnv(out, {
390
- prefix: `__${suffix}`,
391
- role,
392
- policy: runtime.policy
393
- });
394
- if (registry.defaultWalletId === wallet.id || localSignerWallets.length === 1) {
395
- addLocalSignerPolicyEnv(out, {
396
- prefix: "",
397
- role,
398
- policy: runtime.policy
399
- });
400
- }
401
- }
402
- return out;
403
- }
404
- function keystoreEnvKeyFor(chain, walletId) {
405
- void chain;
406
- const suffix = walletIdEnvSuffix(walletId);
407
- if (suffix) {
408
- return `FASED_WALLET_SOLANA_KEYSTORE_PATH__${suffix}`;
409
- }
410
- return "FASED_WALLET_SOLANA_KEYSTORE_PATH";
411
- }
412
256
  function rpcEnvKeyFor(chain, walletId) {
413
257
  void chain;
414
258
  const suffix = walletIdEnvSuffix(walletId);
@@ -417,6 +261,10 @@ function rpcEnvKeyFor(chain, walletId) {
417
261
  }
418
262
  return "FASED_WALLET_SOLANA_RPC_URL";
419
263
  }
264
+ function fallbackRpcEnvKeyFor(walletId) {
265
+ const suffix = walletIdEnvSuffix(walletId);
266
+ return suffix ? `FASED_WALLET_SOLANA_WRITE_RPC_FALLBACK_URL__${suffix}` : "FASED_WALLET_SOLANA_WRITE_RPC_FALLBACK_URL";
267
+ }
420
268
  function resolveRpcUrlForChain(env, chain, walletId, explicit) {
421
269
  const explicitValue = explicit?.trim();
422
270
  if (explicitValue) {
@@ -427,579 +275,34 @@ function resolveRpcUrlForChain(env, chain, walletId, explicit) {
427
275
  const perChainKey = "FASED_WALLET_SOLANA_RPC_URL";
428
276
  const scopedOrChain = (perWalletKey ? String(env[perWalletKey] ?? "").trim() : "") || String(env[perChainKey] ?? "").trim();
429
277
  void chain;
430
- return scopedOrChain || String(env.FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL ?? "").trim();
431
- }
432
- function defaultKeystoreFilenameFor(chain, walletId) {
433
- void chain;
434
- const normalized = normalizeWalletIdForFilename(walletId);
435
- if (!normalized || normalized === "default") {
436
- return "keystore-solana.v1.enc";
437
- }
438
- return `keystore-solana-${normalized}.v1.enc`;
439
- }
440
- function resolveEmbeddedKeystorePathForChain(env, chain, explicit, walletId) {
441
- const explicitPath = explicit?.trim();
442
- if (explicitPath) {
443
- return path.resolve(explicitPath);
444
- }
445
- const suffix = walletIdEnvSuffix(walletId);
446
- const perChainEnvKey = "FASED_WALLET_SOLANA_KEYSTORE_PATH";
447
- const perWalletEnvKey = suffix ? `FASED_WALLET_SOLANA_KEYSTORE_PATH__${suffix}` : undefined;
448
- const scopedPath = perWalletEnvKey ? String(env[perWalletEnvKey] ?? "").trim() : "";
449
- if (scopedPath) {
450
- return path.resolve(scopedPath);
451
- }
452
- const normalizedWalletId = normalizeWalletIdForFilename(walletId);
453
- if (normalizedWalletId && normalizedWalletId !== "default") {
454
- return path.join(resolveLocalSignerMaterialRootDir(env), defaultKeystoreFilenameFor(chain, walletId));
455
- }
456
- const genericPath = String(env[perChainEnvKey] ?? "").trim() || String(env.FASED_WALLET_KEYSTORE_PATH ?? "").trim();
457
- if (genericPath) {
458
- return path.resolve(genericPath);
459
- }
460
- return path.join(resolveLocalSignerMaterialRootDir(env), defaultKeystoreFilenameFor(chain, walletId));
461
- }
462
- const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
463
- const ED25519_PKCS8_SEED_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
464
- const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
465
- function decodeBase58(input) {
466
- const text = input.trim();
467
- if (!text) {
468
- return new Uint8Array();
469
- }
470
- let num = 0n;
471
- for (const ch of text) {
472
- const idx = BASE58_ALPHABET.indexOf(ch);
473
- if (idx < 0) {
474
- throw new Error(`invalid base58 character: ${ch}`);
475
- }
476
- num = num * 58n + BigInt(idx);
477
- }
478
- const bytes = [];
479
- while (num > 0n) {
480
- bytes.push(Number(num % 256n));
481
- num /= 256n;
482
- }
483
- bytes.reverse();
484
- let leadingZeros = 0;
485
- for (const ch of text) {
486
- if (ch === "1") {
487
- leadingZeros += 1;
488
- } else {
489
- break;
490
- }
491
- }
492
- return Uint8Array.from([...Array.from({ length: leadingZeros }, () => 0), ...bytes]);
493
- }
494
- function encodeBase58(input) {
495
- if (input.length === 0) {
496
- return "";
497
- }
498
- let num = 0n;
499
- for (const b of input) {
500
- num = (num << 8n) + BigInt(b);
501
- }
502
- let encoded = "";
503
- while (num > 0n) {
504
- const idx = Number(num % 58n);
505
- encoded = BASE58_ALPHABET[idx] + encoded;
506
- num /= 58n;
507
- }
508
- let leadingZeros = 0;
509
- for (const b of input) {
510
- if (b === 0) {
511
- leadingZeros += 1;
512
- } else {
513
- break;
514
- }
515
- }
516
- return `${"1".repeat(leadingZeros)}${encoded}`;
517
- }
518
- function deriveEd25519PublicKeyFromSeed(seed) {
519
- if (seed.length !== 32) {
520
- throw new Error(`invalid Ed25519 seed length: ${seed.length} (expected 32)`);
521
- }
522
- const privateKey = createPrivateKey({
523
- key: Buffer.concat([ED25519_PKCS8_SEED_PREFIX, Buffer.from(seed)]),
524
- format: "der",
525
- type: "pkcs8"
526
- });
527
- const spki = Buffer.from(createPublicKey(privateKey).export({
528
- format: "der",
529
- type: "spki"
530
- }));
531
- if (spki.length !== ED25519_SPKI_PREFIX.length + 32 || !spki.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)) {
532
- throw new Error("failed to derive Ed25519 public key");
533
- }
534
- return Uint8Array.from(spki.subarray(ED25519_SPKI_PREFIX.length));
535
- }
536
- function normalizeSolanaSecretKey(secret) {
537
- if (secret.length === 64) {
538
- return secret;
539
- }
540
- if (secret.length === 32) {
541
- const publicKey = deriveEd25519PublicKeyFromSeed(secret);
542
- return Uint8Array.from([...secret, ...publicKey]);
543
- }
544
- throw new Error(`invalid Solana secret key length: ${secret.length} (expected 32 or 64)`);
545
- }
546
- function solanaAddressFromSecretKey(secretKey) {
547
- if (secretKey.length !== 64) {
548
- throw new Error(`invalid Solana secret key length: ${secretKey.length} (expected 64)`);
549
- }
550
- return encodeBase58(secretKey.slice(32));
551
- }
552
- async function callRpcMethod(rpcUrl, method, params) {
553
- const response = await fetch(rpcUrl, {
554
- method: "POST",
555
- headers: { "content-type": "application/json" },
556
- body: JSON.stringify({
557
- jsonrpc: "2.0",
558
- id: `${method}-${Date.now()}`,
559
- method,
560
- params
561
- })
562
- });
563
- if (!response.ok) {
564
- throw new Error(`rpc ${method} http ${response.status}`);
565
- }
566
- const payload = await response.json();
567
- if (payload.error) {
568
- throw new Error(payload.error.message || `rpc ${method} error`);
569
- }
570
- if (!payload.result || typeof payload.result !== "object") {
571
- throw new Error(`rpc ${method} missing result`);
572
- }
573
- return payload.result;
574
- }
575
- function parseSolanaSecretKey(raw) {
576
- const text = raw.trim();
577
- if (!text) {
578
- throw new Error("missing Solana private key");
579
- }
580
- if (text.startsWith("[")) {
581
- const parsed = JSON.parse(text);
582
- if (!Array.isArray(parsed)) {
583
- throw new Error("invalid Solana key JSON");
584
- }
585
- const bytes = Uint8Array.from(parsed.map((v) => {
586
- const n = Number(v);
587
- if (!Number.isInteger(n) || n < 0 || n > 255) {
588
- throw new Error("invalid Solana secret key byte");
589
- }
590
- return n;
591
- }));
592
- return normalizeSolanaSecretKey(bytes);
593
- }
594
- const normalized = text.replace(/^0x/i, "");
595
- if (/^[0-9a-fA-F]+$/.test(normalized) && normalized.length % 2 === 0) {
596
- const bytes = Uint8Array.from(Buffer.from(normalized, "hex"));
597
- if (bytes.length === 32 || bytes.length === 64) {
598
- return normalizeSolanaSecretKey(bytes);
599
- }
600
- }
601
- try {
602
- const bytes = Uint8Array.from(Buffer.from(text, "base64"));
603
- if (bytes.length === 32 || bytes.length === 64) {
604
- return normalizeSolanaSecretKey(bytes);
605
- }
606
- } catch {}
607
- try {
608
- const bytes = Uint8Array.from(Buffer.from(text, "base64url"));
609
- if (bytes.length === 32 || bytes.length === 64) {
610
- return normalizeSolanaSecretKey(bytes);
611
- }
612
- } catch {}
613
- try {
614
- const bytes = decodeBase58(text);
615
- if (bytes.length === 64) {
616
- return normalizeSolanaSecretKey(bytes);
617
- }
618
- } catch {}
619
- throw new Error("Unsupported Solana secret key format. Use JSON byte array [32/64 bytes], base64/base64url, base58 (64-byte secret), or hex.");
620
- }
621
- function encryptSolanaKeypairEnvelope(params) {
622
- const salt = randomBytes(16);
623
- const iv = randomBytes(12);
624
- const key = scryptSync(params.passphrase, salt, 32);
625
- const cipher = createCipheriv("aes-256-gcm", key, iv);
626
- const ciphertext = Buffer.concat([cipher.update(Buffer.from(params.secretKey)), cipher.final()]);
627
- const authTag = cipher.getAuthTag();
628
- return {
629
- kind: "fased-solana-keypair",
630
- version: 1,
631
- kdf: "scrypt",
632
- cipher: "aes-256-gcm",
633
- salt: salt.toString("base64url"),
634
- iv: iv.toString("base64url"),
635
- authTag: authTag.toString("base64url"),
636
- ciphertext: ciphertext.toString("base64url"),
637
- publicKey: params.publicKey
638
- };
639
- }
640
- function parseSolanaKeystoreEnvelope(raw) {
641
- try {
642
- const parsed = JSON.parse(raw);
643
- if (parsed.kind !== "fased-solana-keypair" || parsed.version !== 1 || parsed.kdf !== "scrypt" || parsed.cipher !== "aes-256-gcm") {
644
- return null;
645
- }
646
- if (typeof parsed.salt !== "string" || typeof parsed.iv !== "string" || typeof parsed.authTag !== "string" || typeof parsed.ciphertext !== "string" || typeof parsed.publicKey !== "string") {
647
- return null;
648
- }
649
- return parsed;
650
- } catch {
651
- return null;
652
- }
653
- }
654
- function decryptSolanaKeypairEnvelope(envelope, passphrase) {
655
- const salt = Buffer.from(envelope.salt, "base64url");
656
- const iv = Buffer.from(envelope.iv, "base64url");
657
- const authTag = Buffer.from(envelope.authTag, "base64url");
658
- const ciphertext = Buffer.from(envelope.ciphertext, "base64url");
659
- const key = scryptSync(passphrase, salt, 32);
660
- const decipher = createDecipheriv("aes-256-gcm", key, iv);
661
- decipher.setAuthTag(authTag);
662
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
663
- if (plaintext.length !== 64) {
664
- throw new Error(`invalid decrypted Solana secret key length: ${plaintext.length}`);
665
- }
666
- return Uint8Array.from(plaintext);
667
- }
668
- function detectEmbeddedKeystoreType(raw) {
669
- if (parseSolanaKeystoreEnvelope(raw)) {
670
- return "solana-envelope";
671
- }
672
- return "unknown";
673
- }
674
- function resolveKeystorePassphrase(optionsPassphrase, env) {
675
- if (optionsPassphrase?.trim()) {
676
- return optionsPassphrase.trim();
677
- }
678
- const file = String(env.FASED_WALLET_PASSPHRASE_FILE ?? "").trim();
679
- if (file && syncFs.existsSync(file)) {
680
- return syncFs.readFileSync(file, "utf8").trim();
681
- }
682
- const managedFile = path.join(resolveLocalSignerMaterialRootDir(env), "passphrase");
683
- if (syncFs.existsSync(managedFile)) {
684
- return syncFs.readFileSync(managedFile, "utf8").trim();
685
- }
686
- return String(env.FASED_WALLET_PASSPHRASE ?? "").trim();
687
- }
688
- function resolvePassphraseFilePath(env, explicit) {
689
- const provided = explicit?.trim() || String(env.FASED_WALLET_PASSPHRASE_FILE ?? "").trim();
690
- if (provided) {
691
- return path.resolve(provided);
692
- }
693
- return path.join(resolveLocalSignerMaterialRootDir(env), "passphrase");
694
- }
695
- function resolveLocalSignerPassphraseSource(env) {
696
- const configuredFile = String(env.FASED_WALLET_PASSPHRASE_FILE ?? "").trim();
697
- const candidates = [configuredFile ? path.resolve(configuredFile) : "", path.join(resolveLocalSignerMaterialRootDir(env), "passphrase")].filter(Boolean);
698
- for (const candidate of candidates) {
699
- try {
700
- const value = syncFs.readFileSync(candidate, "utf8").trim();
701
- if (value) {
702
- return {
703
- kind: "file",
704
- path: candidate,
705
- value
706
- };
707
- }
708
- } catch {}
709
- }
710
- const explicit = String(env.FASED_WALLET_PASSPHRASE ?? "").trim();
711
- if (explicit) {
712
- return {
713
- kind: "value",
714
- value: explicit
715
- };
716
- }
717
- return { kind: "none" };
718
- }
719
- function renderLocalSignerPassphraseEnvLines(env) {
720
- if (String(env.FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER ?? "").trim()) {
721
- const file = String(env.FASED_WALLET_PASSPHRASE_FILE ?? "").trim() || path.join(resolveLocalSignerMaterialRootDir(env), "passphrase");
722
- return [`export FASED_WALLET_PASSPHRASE_FILE="${file.replaceAll("\"", "\\\"")}"`];
723
- }
724
- const source = resolveLocalSignerPassphraseSource(env);
725
- if (source.kind === "file") {
726
- return [`export FASED_WALLET_PASSPHRASE_FILE="${source.path.replaceAll("\"", "\\\"")}"`];
727
- }
728
- if (source.kind === "value") {
729
- return [`export FASED_WALLET_PASSPHRASE="${source.value.replaceAll("\"", "\\\"")}"`];
730
- }
731
- return [];
732
- }
733
- function ensureLocalSignerPassphraseForSetup(runtime, env) {
734
- const existing = resolveLocalSignerPassphraseSource(env);
735
- if (existing.kind === "file") {
736
- env.FASED_WALLET_PASSPHRASE_FILE = existing.path;
737
- delete env.FASED_WALLET_PASSPHRASE;
738
- return existing.value;
739
- }
740
- if (existing.kind === "value") {
741
- env.FASED_WALLET_PASSPHRASE = existing.value;
742
- delete env.FASED_WALLET_PASSPHRASE_FILE;
743
- return existing.value;
744
- }
745
- const generated = randomBytes(24).toString("base64url");
746
- const passphraseFile = path.join(resolveLocalSignerMaterialRootDir(env), "passphrase");
747
- writePassphraseFile(passphraseFile, generated);
748
- env.FASED_WALLET_PASSPHRASE_FILE = passphraseFile;
749
- delete env.FASED_WALLET_PASSPHRASE;
750
- runtime.log("No wallet passphrase configured. Creating a managed signer passphrase file (0600).");
751
- return generated;
752
- }
753
- function ensureLocalSignerPassphraseFileForCustodyDisable(params) {
754
- const effectiveEnv = {
755
- ...params.env,
756
- ...params.cfg.env?.vars
757
- };
758
- const existing = resolveLocalSignerPassphraseSource(effectiveEnv);
759
- let passphrase = "";
760
- let passphraseFile = "";
761
- if (existing.kind === "file") {
762
- passphrase = existing.value;
763
- passphraseFile = existing.path;
764
- } else {
765
- passphrase = existing.kind === "value" && existing.value.trim() ? existing.value.trim() : randomBytes(24).toString("base64url");
766
- passphraseFile = resolvePassphraseFilePath(effectiveEnv);
767
- writePassphraseFile(passphraseFile, passphrase);
768
- }
769
- let nextCfg = setConfigEnvVar(params.cfg, "FASED_WALLET_PASSPHRASE_FILE", passphraseFile);
770
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_PASSPHRASE", undefined);
771
- return {
772
- passphrase,
773
- passphraseFile,
774
- cfg: nextCfg
775
- };
776
- }
777
- function writePassphraseFile(filePath, passphrase) {
778
- syncFs.mkdirSync(path.dirname(filePath), {
779
- recursive: true,
780
- mode: 448
781
- });
782
- syncFs.writeFileSync(filePath, `${passphrase}\n`, {
783
- encoding: "utf8",
784
- mode: 384
785
- });
786
- try {
787
- syncFs.chmodSync(filePath, 384);
788
- } catch {}
789
- }
790
- function walletIdsMatchForEnvSuffix(left, right) {
791
- const normalizedLeft = (left?.trim() || "default").toLowerCase();
792
- const normalizedRight = (right?.trim() || "default").toLowerCase();
793
- return normalizedLeft === normalizedRight || normalizeWalletIdForEnvSuffix(normalizedLeft) === normalizeWalletIdForEnvSuffix(normalizedRight);
794
- }
795
- function normalizeCustodyWalletId(walletId) {
796
- return normalizeWalletIdForEnvSuffix(walletId?.trim() || "default") || "default";
797
- }
798
- function parseCustodyWalletSet(env) {
799
- const out = new Set();
800
- for (const part of String(env.FASED_WALLET_CUSTODY_WALLETS ?? "").split(",")) {
801
- if (!part.trim()) {
802
- continue;
803
- }
804
- const normalized = normalizeCustodyWalletId(part);
805
- if (normalized) {
806
- out.add(normalized);
807
- }
808
- }
809
- return out;
810
- }
811
- function formatCustodyWalletSet(values) {
812
- return [...values].filter(Boolean).toSorted().join(",");
813
- }
814
- function collectLocalSignerKeystoreTargets(cfg, env) {
815
- const effectiveEnv = {
816
- ...env,
817
- ...cfg.env?.vars
818
- };
819
- const targets = new Map();
820
- const materialRoot = resolveLocalSignerMaterialRootDir(effectiveEnv);
821
- const addTarget = (chain, rawPath, walletId) => {
822
- const candidate = rawPath?.trim();
823
- if (!candidate) {
824
- return;
825
- }
826
- const resolved = path.resolve(candidate);
827
- if (!syncFs.existsSync(resolved)) {
828
- return;
829
- }
830
- const existing = targets.get(resolved);
831
- if (existing) {
832
- return;
833
- }
834
- targets.set(resolved, {
835
- chain,
836
- walletId,
837
- path: resolved,
838
- label: walletId ? `${chain}.${walletId}` : `${chain}.default`
839
- });
840
- };
841
- const addDefaultChainTarget = (chain) => {
842
- addTarget(chain, resolveEmbeddedKeystorePathForChain(effectiveEnv, chain), "default");
843
- };
844
- addDefaultChainTarget("solana");
845
- for (const [key, rawValue] of Object.entries(effectiveEnv)) {
846
- const value = String(rawValue ?? "").trim();
847
- if (!value) {
848
- continue;
849
- }
850
- if (key.startsWith("FASED_WALLET_SOLANA_KEYSTORE_PATH__")) {
851
- addTarget("solana", value, key.slice("FASED_WALLET_SOLANA_KEYSTORE_PATH__".length).toLowerCase());
852
- }
853
- }
854
- if (syncFs.existsSync(materialRoot)) {
855
- for (const entry of syncFs.readdirSync(materialRoot, { withFileTypes: true })) {
856
- if (!entry.isFile()) {
857
- continue;
858
- }
859
- const name = entry.name;
860
- let chain = null;
861
- let walletId;
862
- if (/^keystore-solana(?:-(.+))?\.v1\.enc$/i.test(name)) {
863
- chain = "solana";
864
- walletId = /^keystore-solana-(.+)\.v1\.enc$/i.exec(name)?.[1];
865
- }
866
- if (!chain) {
867
- continue;
868
- }
869
- addTarget(chain, path.join(materialRoot, name), walletId);
870
- }
871
- }
872
- return [...targets.values()].toSorted((left, right) => left.label.localeCompare(right.label));
873
- }
874
- async function reencryptLocalSignerKeystore(params) {
875
- const raw = syncFs.readFileSync(params.target.path, "utf8");
876
- const detected = detectEmbeddedKeystoreType(raw);
877
- if (detected === "solana-envelope") {
878
- const envelope = parseSolanaKeystoreEnvelope(raw);
879
- if (!envelope) {
880
- throw new Error(`invalid Solana keystore envelope: ${params.target.path}`);
881
- }
882
- const secretKey = decryptSolanaKeypairEnvelope(envelope, params.oldPassphrase);
883
- const nextEnvelope = encryptSolanaKeypairEnvelope({
884
- secretKey,
885
- passphrase: params.newPassphrase,
886
- publicKey: envelope.publicKey
887
- });
888
- syncFs.writeFileSync(params.target.path, `${JSON.stringify(nextEnvelope, null, 2)}\n`, {
889
- encoding: "utf8",
890
- mode: 384
891
- });
892
- return;
893
- }
894
- throw new Error(`unsupported keystore format for ${params.target.path}`);
895
- }
896
- function clearConfiguredSignerPassphrase(cfg) {
897
- let nextCfg = setConfigEnvVar(cfg, "FASED_WALLET_PASSPHRASE", undefined);
898
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_PASSPHRASE_FILE", undefined);
899
- return nextCfg;
900
- }
901
- function removeLocalSignerPassphraseFileIfManaged(env) {
902
- const configured = String(env.FASED_WALLET_PASSPHRASE_FILE ?? "").trim();
903
- if (!configured) {
904
- return { removed: false };
905
- }
906
- const resolved = path.resolve(configured);
907
- const materialRoot = resolveLocalSignerMaterialRootDir(env);
908
- const withinMaterialRoot = resolved === materialRoot || resolved.startsWith(`${materialRoot}${path.sep}`);
909
- if (!withinMaterialRoot || !syncFs.existsSync(resolved)) {
910
- return {
911
- removed: false,
912
- path: resolved
913
- };
914
- }
915
- syncFs.rmSync(resolved, { force: true });
916
- return {
917
- removed: true,
918
- path: resolved
919
- };
920
- }
921
- function writeLocalSignerEnvFile(cfg, env) {
922
- const effectiveEnv = {
923
- ...env,
924
- ...cfg.env?.vars
925
- };
926
- const socketPath = resolveLocalSignerSocketPath(effectiveEnv);
927
- const backendSocketPath = resolveLocalSignerBackendSocketPath(effectiveEnv);
928
- const materialRoot = resolveLocalSignerMaterialRootDir(effectiveEnv);
929
- const signerBinPath = resolveSignerdBinaryPath(effectiveEnv);
930
- const signerEnvPath = path.resolve(ensureWalletStateDir(env).rootDir, "signer.env");
931
- const custodyKeys = [
932
- "FASED_WALLET_CUSTODY_MODE",
933
- "FASED_WALLET_CUSTODY_WALLETS",
934
- "FASED_WALLET_CUSTODY_PASSKEY_CEREMONY",
935
- "FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION",
936
- "FASED_WALLET_CUSTODY_PHASE2_COMPLETE"
937
- ];
938
- const custodyEnvLines = custodyKeys.map((key) => [key, String(effectiveEnv[key] ?? "").trim()]).filter(([, value]) => Boolean(value)).map(([key, value]) => `export ${key}="${value}"`);
939
- const signerEnvLines = [
940
- `export FASED_WALLET_LOCAL_SIGNER_SOCKET="${socketPath}"`,
941
- ...backendSocketPath !== socketPath ? [`export FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET="${backendSocketPath}"`] : [],
942
- ...materialRoot !== ensureWalletStateDir(env).rootDir ? [`export FASED_WALLET_SIGNER_STATE_DIR="${materialRoot}"`] : [],
943
- `export FASED_WALLET_CHAINS="${resolveLocalSignerChainsEnvValue(cfg, effectiveEnv)}"`,
944
- ...renderLocalSignerPassphraseEnvLines(effectiveEnv),
945
- ...Object.entries({
946
- ...collectLocalSignerExportEnv(cfg, effectiveEnv),
947
- ...collectLocalSignerPolicyExportEnv(cfg, effectiveEnv)
948
- }).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `export ${key}="${value.replaceAll("\"", "\\\"")}"`),
949
- ...custodyEnvLines,
950
- "",
951
- `"${signerBinPath}" --socket "\${FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET:-$FASED_WALLET_LOCAL_SIGNER_SOCKET}"`
952
- ];
953
- syncFs.mkdirSync(path.dirname(signerEnvPath), {
954
- recursive: true,
955
- mode: 448
956
- });
957
- syncFs.writeFileSync(signerEnvPath, `${signerEnvLines.join("\n")}\n`, {
958
- encoding: "utf8",
959
- mode: 384
960
- });
961
- }
962
- function ensureEmbeddedProviderConfig(cfg, params) {
963
- let nextCfg = ensureWalletMaterialConfig(cfg, params);
964
- const chain = params.chain ?? "solana";
965
- const nextChains = new Set(nextCfg.wallet?.keystore?.chainSupport ?? []);
966
- nextChains.add(chain);
967
- nextCfg = {
968
- ...nextCfg,
969
- wallet: {
970
- ...nextCfg.wallet,
971
- provider: {
972
- ...nextCfg.wallet?.provider,
973
- id: "embedded-keystore"
974
- },
975
- keystore: {
976
- ...nextCfg.wallet?.keystore,
977
- enabled: true,
978
- path: params.keystorePath,
979
- chainSupport: nextChains.size > 0 ? [...nextChains] : ["solana"]
980
- },
981
- runtime: {
982
- ...cfg.wallet?.runtime,
983
- enabled: false
984
- }
985
- }
986
- };
987
- return nextCfg;
988
- }
989
- function ensureWalletMaterialConfig(cfg, params) {
990
- const chain = params.chain ?? "solana";
991
- let nextCfg = setConfigEnvVar(cfg, keystoreEnvKeyFor(chain, params.walletId), params.keystorePath);
992
- if (params.rpcUrl?.trim()) {
993
- nextCfg = setConfigEnvVar(nextCfg, rpcEnvKeyFor(chain, params.walletId), params.rpcUrl);
994
- }
995
- return nextCfg;
278
+ return scopedOrChain || String(env.FASED_WALLET_RPC_URL ?? "").trim();
996
279
  }
997
280
  function ensureLocalSignerProviderConfig(cfg, env, socketPath) {
998
281
  const effectiveSocketPath = socketPath?.trim() || resolveLocalSignerSocketPath(env);
999
282
  let nextCfg = setConfigEnvVar(cfg, "FASED_WALLET_LOCAL_SIGNER_SOCKET", effectiveSocketPath);
1000
- const backendSocketPath = resolveLocalSignerBackendSocketPath(env);
1001
- if (backendSocketPath !== effectiveSocketPath) {
1002
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET", backendSocketPath);
283
+ const hosted = String(env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
284
+ if (hosted) {
285
+ for (const key of [
286
+ "FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET",
287
+ "FASED_WALLET_LOCAL_SIGNER_CONTROL_SOCKET",
288
+ "FASED_WALLET_LOCAL_SIGNER_STATE_DB",
289
+ "FASED_WALLET_LOCAL_SIGNER_MASTER_KEY",
290
+ "FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER",
291
+ "FASED_WALLET_LOCAL_SIGNER_BIN",
292
+ "FASED_WALLET_SIGNER_STATE_DIR",
293
+ "FASED_WALLET_PASSPHRASE_FILE"
294
+ ]) {
295
+ nextCfg = setConfigEnvVar(nextCfg, key, undefined);
296
+ }
297
+ }
298
+ const controlSocketPath = resolveLocalSignerControlSocketPath(env);
299
+ const stateDbPath = resolveLocalSignerStateDbPath(env);
300
+ const masterKeyPath = resolveLocalSignerMasterKeyPath(env);
301
+ nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET", undefined);
302
+ if (!hosted) {
303
+ nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_LOCAL_SIGNER_CONTROL_SOCKET", controlSocketPath);
304
+ nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_LOCAL_SIGNER_STATE_DB", stateDbPath);
305
+ nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_LOCAL_SIGNER_MASTER_KEY", masterKeyPath);
1003
306
  }
1004
307
  nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CHAINS", resolveLocalSignerChainsEnvValue(nextCfg, env));
1005
308
  return {
@@ -1022,7 +325,6 @@ function ensureLocalSignerProviderConfig(cfg, env, socketPath) {
1022
325
  async function configureLocalSignerMode(runtime, options, env) {
1023
326
  const cfg = loadConfig();
1024
327
  const socketPath = resolveLocalSignerSocketPath(env);
1025
- const backendSocketPath = resolveLocalSignerBackendSocketPath(env);
1026
328
  const nextCfg = ensureLocalSignerProviderConfig(cfg, env, socketPath);
1027
329
  if (!options.noProviderIdUpdate) {
1028
330
  await writeConfigFile(nextCfg);
@@ -1032,43 +334,21 @@ async function configureLocalSignerMode(runtime, options, env) {
1032
334
  }
1033
335
  runtime.log("Signer mode: local native signer");
1034
336
  runtime.log(`Signer socket: ${socketPath}`);
1035
- const signerEnvPath = path.resolve(ensureWalletStateDir(process.env).rootDir, "signer.env");
1036
- const materialRoot = resolveLocalSignerMaterialRootDir(env);
1037
337
  const effectiveEnv = {
1038
338
  ...env,
1039
339
  ...nextCfg.env?.vars
1040
340
  };
1041
- const signerBinPath = resolveSignerdBinaryPath(effectiveEnv);
1042
- const signerEnvLines = [
1043
- `export FASED_WALLET_LOCAL_SIGNER_SOCKET="${socketPath}"`,
1044
- ...backendSocketPath !== socketPath ? [`export FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET="${backendSocketPath}"`] : [],
1045
- ...materialRoot !== ensureWalletStateDir(env).rootDir ? [`export FASED_WALLET_SIGNER_STATE_DIR="${materialRoot}"`] : [],
1046
- `export FASED_WALLET_CHAINS="${resolveLocalSignerChainsEnvValue(nextCfg, env)}"`,
1047
- ...renderLocalSignerPassphraseEnvLines(effectiveEnv),
1048
- ...Object.entries({
1049
- ...collectLocalSignerExportEnv(nextCfg, env),
1050
- ...collectLocalSignerPolicyExportEnv(nextCfg, env)
1051
- }).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `export ${key}="${value.replaceAll("\"", "\\\"")}"`),
1052
- "",
1053
- `"${signerBinPath}" --socket "\${FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET:-$FASED_WALLET_LOCAL_SIGNER_SOCKET}"`
1054
- ];
1055
- syncFs.mkdirSync(path.dirname(signerEnvPath), {
1056
- recursive: true,
1057
- mode: 448
1058
- });
1059
- syncFs.writeFileSync(signerEnvPath, `${signerEnvLines.join("\n")}\n`, {
1060
- encoding: "utf8",
1061
- mode: 384
1062
- });
1063
- runtime.log("Recommended environment exports (set before starting fased-signerd):");
1064
- for (const line of signerEnvLines.slice(0, Math.min(8, signerEnvLines.length - 2))) {
1065
- runtime.log(` ${line}`);
341
+ const hosted = String(env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
342
+ if (!hosted) {
343
+ const signerEnvPath = writeLocalSignerEnvFile({
344
+ config: nextCfg,
345
+ env: effectiveEnv
346
+ });
347
+ runtime.log(`Signer environment written: ${signerEnvPath} (mode 600)`);
348
+ runtime.log(`Start signer with the generated same-user signer environment.`);
349
+ } else {
350
+ runtime.log("Signer lifecycle is owned by the root-managed fased-signerd service.");
1066
351
  }
1067
- runtime.log(`Env file written: ${signerEnvPath} (mode 600)`);
1068
- runtime.log(` source "${signerEnvPath}"`);
1069
- runtime.log("");
1070
- runtime.log("Start signer:");
1071
- runtime.log(` "${signerBinPath}" --socket "$FASED_WALLET_LOCAL_SIGNER_SOCKET"`);
1072
352
  runtime.log("");
1073
353
  runtime.log("Then verify:");
1074
354
  runtime.log(" fased wallet signer doctor --json");
@@ -1084,21 +364,161 @@ async function configureLocalSignerMode(runtime, options, env) {
1084
364
  }
1085
365
  }
1086
366
  }
367
+ function findNativeSignerWalletIdCollision(wallets, friendlyWalletId, signerWalletId) {
368
+ return wallets.find((entry) => {
369
+ if (entry.id === friendlyWalletId || entry.providerId !== "local-socket-signer") {
370
+ return false;
371
+ }
372
+ const signerWalletIdMetadata = entry.metadata?.signerWalletId;
373
+ const registeredSignerWalletId = typeof signerWalletIdMetadata === "string" ? signerWalletIdMetadata.trim() : "";
374
+ return (registeredSignerWalletId || normalizeNativeSignerWalletId(entry.id)) === signerWalletId;
375
+ });
376
+ }
377
+ async function createSignerOwnedWalletForSetup(params) {
378
+ if (params.chain !== "solana") {
379
+ throw new Error("fased-signerd protocol v2 currently supports Solana wallet creation only");
380
+ }
381
+ const walletId = params.walletId?.trim() || params.role;
382
+ const hosted = String(params.env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
383
+ let cfg = ensureLocalSignerProviderConfig(loadConfig(), params.env);
384
+ if (!hosted) {
385
+ cfg = setConfigEnvVar(cfg, rpcEnvKeyFor(params.chain, walletId), params.rpcUrl);
386
+ }
387
+ await writeConfigFile(cfg);
388
+ const mergedEnv = {
389
+ ...params.env,
390
+ ...cfg.env?.vars
391
+ };
392
+ const socketPath = resolveLocalSignerSocketPath(mergedEnv);
393
+ const expectedSignerWalletId = normalizeNativeSignerWalletId(walletId);
394
+ const existingSignerIdCollision = findNativeSignerWalletIdCollision(readWalletProviderRegistry(params.env).wallets, walletId, expectedSignerWalletId);
395
+ if (existingSignerIdCollision) {
396
+ throw new Error(`native signer wallet ID ${expectedSignerWalletId} is already registered as ${existingSignerIdCollision.id}; choose a distinct wallet ID`);
397
+ }
398
+ if (!hosted) {
399
+ writeLocalSignerEnvFile({
400
+ config: cfg,
401
+ env: mergedEnv
402
+ });
403
+ const signerBinPath = resolveSignerdBinaryPath(mergedEnv);
404
+ if (!syncFs.existsSync(signerBinPath)) {
405
+ installSignerdBinary(signerBinPath);
406
+ }
407
+ await restartLocalSocketSigner(undefined, mergedEnv);
408
+ }
409
+ let result;
410
+ try {
411
+ result = await createLockedSignerOwnedWallet({
412
+ socketPath,
413
+ walletId,
414
+ role: params.role,
415
+ allowExisting: Boolean(params.options.force)
416
+ });
417
+ } catch (error) {
418
+ if (hosted) {
419
+ throw new Error(`root-managed hosted signer wallet creation failed: ${error instanceof Error ? error.message : String(error)}. Repair only from the provider root console with the exact tagged, attested Hosting release; never run the app checkout with sudo.`, { cause: error });
420
+ }
421
+ throw error;
422
+ }
423
+ const signerWalletId = String(result.wallet.walletId ?? "").trim();
424
+ if (!signerWalletId || signerWalletId !== expectedSignerWalletId || result.policy.walletId && result.policy.walletId !== signerWalletId) {
425
+ throw new Error(`native signer returned an unexpected wallet ID; requested=${walletId} expected=${expectedSignerWalletId} returned=${signerWalletId || "missing"}`);
426
+ }
427
+ const signerIdCollision = findNativeSignerWalletIdCollision(readWalletProviderRegistry(params.env).wallets, walletId, signerWalletId);
428
+ if (signerIdCollision) {
429
+ throw new Error(`native signer wallet ID ${signerWalletId} is already registered as ${signerIdCollision.id}; choose a distinct wallet ID`);
430
+ }
431
+ const fallbackRpcUrl = hosted ? "" : String(mergedEnv[fallbackRpcEnvKeyFor(walletId)] ?? mergedEnv.FASED_WALLET_SOLANA_RPC_FALLBACK_URL ?? "").trim();
432
+ const network = configureSignerOwnedWalletNetwork({
433
+ walletId: signerWalletId,
434
+ primaryRpcUrl: params.rpcUrl,
435
+ fallbackRpcUrl: fallbackRpcUrl || undefined,
436
+ env: mergedEnv,
437
+ signerBinPath: hosted ? undefined : resolveSignerdBinaryPath(mergedEnv),
438
+ controlSocketPath: hosted ? undefined : resolveLocalSignerControlSocketPath(mergedEnv)
439
+ });
440
+ await writeConfigFile(cfg);
441
+ const wallet = upsertNamedWallet({
442
+ walletId,
443
+ name: params.options.walletName || "Wallet",
444
+ providerId: "local-socket-signer",
445
+ addresses: { solana: result.wallet.publicKey },
446
+ metadata: {
447
+ role: params.role,
448
+ purpose: params.role,
449
+ keyAuthority: "signer-owned-v2",
450
+ signerWalletId,
451
+ policyHash: result.policy.hash,
452
+ policyVersion: result.policy.version,
453
+ policyState: "locked",
454
+ networkHash: network.hash,
455
+ networkVersion: network.version,
456
+ networkReady: network.ready
457
+ },
458
+ env: params.env
459
+ });
460
+ if (params.role === "agent") {
461
+ setDefaultWallet({
462
+ walletId: wallet.id,
463
+ env: params.env
464
+ });
465
+ }
466
+ if (params.options.json) {
467
+ params.runtime.log(JSON.stringify({
468
+ ok: true,
469
+ provider: "local-socket-signer",
470
+ chain: params.chain,
471
+ walletId: wallet.id,
472
+ signerWalletId,
473
+ address: result.wallet.publicKey,
474
+ policyHash: result.policy.hash,
475
+ policyVersion: result.policy.version,
476
+ policyState: "locked",
477
+ networkVersion: network.version,
478
+ networkReady: network.ready,
479
+ ...network.rootAdminRequired ? {
480
+ networkActivation: "root-admin-required",
481
+ rootCommand: network.rootCommand
482
+ } : {}
483
+ }, null, 2));
484
+ } else {
485
+ params.runtime.log(`Signer wallet ID: ${signerWalletId}`);
486
+ params.runtime.log(`${params.chain.toUpperCase()} address: ${result.wallet.publicKey}`);
487
+ if (!params.options.noSignerHints) {
488
+ params.runtime.log("Signer-owned wallet created with a deny-all policy. Configure explicit operations, destinations, assets, and positive caps before sending.");
489
+ if (network.rootAdminRequired && network.rootCommand) {
490
+ params.runtime.log("Hosted wallet remains fail-closed until a root administrator activates its RPC network from a root-owned file:");
491
+ params.runtime.log(network.rootCommand);
492
+ }
493
+ }
494
+ }
495
+ return result;
496
+ }
1087
497
  async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
1088
498
  const env = process.env;
1089
499
  const interactive = !options.nonInteractive;
1090
- if (options.role && !normalizeWalletRoleForCli(options.role)) {
1091
- throw new Error("wallet role must be agent or vault. Use the Mining page/command for SAT mining.");
500
+ const currentConfig = loadConfig();
501
+ const currentRegistry = readWalletProviderRegistry(env);
502
+ if (hasLegacyEmbeddedKeystoreConfig(currentConfig, {
503
+ ...env,
504
+ ...currentConfig.env?.vars
505
+ }) || hasLegacyEmbeddedKeystoreMaterialHint({
506
+ ...env,
507
+ ...currentConfig.env?.vars
508
+ }) || currentRegistry.providers["embedded-keystore"]?.enabled || currentRegistry.wallets.some((wallet) => wallet.providerId === "embedded-keystore")) {
509
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy wallet setup state detected");
510
+ }
511
+ if (options.role && !normalizeWalletUserRole(options.role)) {
512
+ throw new Error("wallet role must be agent, mining, or vault");
1092
513
  }
1093
514
  const configureLimitOrdersIfRequested = async () => {
1094
- if (!options.enableLimitOrders && !options.disableLimitOrders && !options.jupiterApiKey && !options.jupiterTriggerApiBaseUrl) {
515
+ if (!options.enableLimitOrders && !options.disableLimitOrders && !options.jupiterApiKey) {
1095
516
  return;
1096
517
  }
1097
518
  await walletLimitOrdersConfigureCommand(runtime, {
1098
519
  enable: Boolean(options.enableLimitOrders || options.jupiterApiKey),
1099
520
  disable: Boolean(options.disableLimitOrders),
1100
521
  jupiterApiKey: options.jupiterApiKey,
1101
- jupiterTriggerApiBaseUrl: options.jupiterTriggerApiBaseUrl,
1102
522
  nonInteractive: options.nonInteractive,
1103
523
  json: options.json
1104
524
  });
@@ -1170,90 +590,18 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
1170
590
  let mode = options.mode;
1171
591
  if (!mode) {
1172
592
  runtime.log("Wallet setup modes:");
1173
- runtime.log(" embedded-create Create a new self-hosted wallet (default)");
1174
- runtime.log(" embedded-import Import an existing Solana key (advanced)");
593
+ runtime.log(" local-signer-create Create a signer-owned Solana wallet (default)");
594
+ runtime.log(" local-signer-import Show the native signer-only import command");
1175
595
  runtime.log(" turnkey Configure hosted provider (Turnkey)");
1176
596
  runtime.log(" alchemy Configure hosted provider (Alchemy)");
1177
- runtime.log(" privy Configure hosted provider (Privy)");
1178
- const picked = await prompt("Choose wallet setup mode", "embedded-create");
1179
- mode = picked === "embedded" || picked === "embedded-create" || picked === "embedded-import" || picked === "local-signer-create" || picked === "local-signer-import" || picked === "local-signer" || picked === "turnkey" || picked === "alchemy" || picked === "privy" ? picked : "embedded-create";
597
+ const picked = await prompt("Choose wallet setup mode", "local-signer-create");
598
+ mode = picked === "local-signer-create" || picked === "local-signer-import" || picked === "local-signer" || picked === "turnkey" || picked === "alchemy" ? picked : "local-signer-create";
1180
599
  }
1181
- if (mode === "embedded") {
1182
- mode = "embedded-create";
600
+ if (mode === "embedded" || mode === "embedded-create" || mode === "embedded-import") {
601
+ throwLegacyEmbeddedKeystoreMigrationRequired(`wallet setup mode ${mode} is unavailable`);
1183
602
  }
1184
- if (mode !== "embedded-create" && mode !== "embedded-import" && mode !== "local-signer-create" && mode !== "local-signer-import" && mode !== "local-signer" && mode !== "turnkey" && mode !== "alchemy" && mode !== "privy") {
1185
- throw new Error(`Unsupported wallet setup mode: ${String(mode)}. ` + "Use one of: embedded-create, embedded-import, local-signer-create, local-signer-import, local-signer, turnkey, alchemy, privy.");
1186
- }
1187
- if (mode === "embedded-create") {
1188
- const chain = options.chain ?? "solana";
1189
- const walletId = options.walletId ?? ((await prompt("Wallet id (optional, e.g. agent/mining/vault)", "")).trim() || undefined);
1190
- const rpcUrlFallback = resolveRpcUrlForChain(env, chain, walletId, options.rpcUrl);
1191
- const rpcUrl = (await prompt(`${chain.toUpperCase()} RPC URL (required for balances/readiness/send)`, rpcUrlFallback)).trim();
1192
- if (!rpcUrl) {
1193
- throw new Error(`${chain.toUpperCase()} RPC URL is required for self-hosted wallet setup. ` + "Pass --rpc-url or set FASED_WALLET_<CHAIN>_RPC_URL.");
1194
- }
1195
- const showPrivateKeyOnce = typeof options.showPrivateKeyOnce === "boolean" ? options.showPrivateKeyOnce : !options.nonInteractive && (await prompt("Show private key once for backup now? (dangerous, shoulder-surf risk) [y/N]", "n")).trim().toLowerCase().startsWith("y");
1196
- if (showPrivateKeyOnce && typeof options.showPrivateKeyOnce === "boolean") {
1197
- requirePrivateKeyPrintConfirmation(options.confirmPrivateKeyPrint);
1198
- }
1199
- const privateKeyPrintConfirmation = showPrivateKeyOnce ? options.confirmPrivateKeyPrint ?? (typeof options.showPrivateKeyOnce === "boolean" ? undefined : PRIVATE_KEY_PRINT_CONFIRMATION) : undefined;
1200
- if (!String(env.FASED_WALLET_PASSPHRASE_FILE ?? env.FASED_WALLET_PASSPHRASE ?? "").trim()) {
1201
- runtime.log("No wallet passphrase configured. Creating a local passphrase file (0600)...");
1202
- await walletKeystorePassphraseInitCommand(runtime, {
1203
- force: false,
1204
- json: false
1205
- });
1206
- }
1207
- await walletKeystoreInitCommand(runtime, {
1208
- chain,
1209
- walletId,
1210
- name: options.walletName,
1211
- rpcUrl,
1212
- showPrivateKeyOnce,
1213
- confirmPrivateKeyPrint: privateKeyPrintConfirmation,
1214
- force: Boolean(options.force),
1215
- json: Boolean(options.json),
1216
- role: options.role
1217
- });
1218
- if (!options.noSignerHints) {
1219
- runtime.log("Embedded wallet created.");
1220
- }
1221
- await configureLimitOrdersIfRequested();
1222
- return;
1223
- }
1224
- if (mode === "embedded-import") {
1225
- const chain = options.chain ?? "solana";
1226
- const walletId = options.walletId ?? ((await prompt("Wallet id (optional, e.g. agent/mining/vault)", "")).trim() || undefined);
1227
- const rpcUrlFallback = resolveRpcUrlForChain(env, chain, walletId, options.rpcUrl);
1228
- const rpcUrl = (await prompt(`${chain.toUpperCase()} RPC URL (required for balances/readiness/send)`, rpcUrlFallback)).trim();
1229
- if (!rpcUrl) {
1230
- throw new Error(`${chain.toUpperCase()} RPC URL is required for self-hosted wallet setup. ` + "Pass --rpc-url or set FASED_WALLET_<CHAIN>_RPC_URL.");
1231
- }
1232
- const privateKey = options.privateKey ?? await prompt("Paste Solana private key (base58/json/base64/hex)");
1233
- if (!privateKey) {
1234
- throw new Error("Import mode requires a private key. " + "Pass --private-key (and optional --wallet-id) or set FASED_WALLET_PRIVATE_KEY for non-interactive runs.");
1235
- }
1236
- if (!String(env.FASED_WALLET_PASSPHRASE_FILE ?? env.FASED_WALLET_PASSPHRASE ?? "").trim()) {
1237
- runtime.log("No wallet passphrase configured. Creating a local passphrase file (0600)...");
1238
- await walletKeystorePassphraseInitCommand(runtime, {
1239
- force: false,
1240
- json: false
1241
- });
1242
- }
1243
- await walletKeystoreImportCommand(runtime, {
1244
- chain,
1245
- walletId,
1246
- name: options.walletName,
1247
- privateKey,
1248
- rpcUrl,
1249
- json: Boolean(options.json),
1250
- role: options.role
1251
- });
1252
- if (!options.noSignerHints) {
1253
- runtime.log("Embedded wallet imported.");
1254
- }
1255
- await configureLimitOrdersIfRequested();
1256
- return;
603
+ if (mode !== "local-signer-create" && mode !== "local-signer-import" && mode !== "local-signer" && mode !== "turnkey" && mode !== "alchemy" && mode !== "privy") {
604
+ throw new Error(`Unsupported wallet setup mode: ${String(mode)}. ` + "Use one of: local-signer-create, local-signer-import, local-signer, turnkey, alchemy. Privy is unavailable.");
1257
605
  }
1258
606
  if (mode === "local-signer-create") {
1259
607
  const chain = options.chain ?? "solana";
@@ -1263,64 +611,51 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
1263
611
  if (!rpcUrl) {
1264
612
  throw new Error(`${chain.toUpperCase()} RPC URL is required for self-hosted wallet setup. ` + "Pass --rpc-url or set FASED_WALLET_<CHAIN>_RPC_URL.");
1265
613
  }
1266
- const showPrivateKeyOnce = typeof options.showPrivateKeyOnce === "boolean" ? options.showPrivateKeyOnce : !options.nonInteractive && (await prompt("Show private key once for backup now? (dangerous, shoulder-surf risk) [y/N]", "n")).trim().toLowerCase().startsWith("y");
1267
- if (showPrivateKeyOnce && typeof options.showPrivateKeyOnce === "boolean") {
1268
- requirePrivateKeyPrintConfirmation(options.confirmPrivateKeyPrint);
1269
- }
1270
- const privateKeyPrintConfirmation = showPrivateKeyOnce ? options.confirmPrivateKeyPrint ?? (typeof options.showPrivateKeyOnce === "boolean" ? undefined : PRIVATE_KEY_PRINT_CONFIRMATION) : undefined;
1271
- ensureLocalSignerPassphraseForSetup(runtime, env);
1272
- await walletKeystoreInitCommand(runtime, {
614
+ const role = normalizeWalletUserRole(options.role) ?? (walletId?.trim().toLowerCase() === "mining" ? "mining" : walletId?.trim().toLowerCase() === "vault" ? "vault" : "agent");
615
+ await createSignerOwnedWalletForSetup({
616
+ runtime,
617
+ options,
618
+ env,
1273
619
  chain,
1274
620
  walletId,
1275
- name: options.walletName,
1276
621
  rpcUrl,
1277
- showPrivateKeyOnce,
1278
- confirmPrivateKeyPrint: privateKeyPrintConfirmation,
1279
- force: Boolean(options.force),
1280
- json: Boolean(options.json),
1281
- skipProviderConfig: true,
1282
- providerIdForRegistry: "local-socket-signer",
1283
- suppressExtraLogs: Boolean(options.noSignerHints),
1284
- role: options.role
622
+ role
1285
623
  });
1286
- await configureLocalSignerMode(runtime, options, env);
1287
- if (!options.noSignerHints) {
1288
- runtime.log("Self-hosted wallet created for local native signer.");
624
+ if (!options.noSignerHints && !options.json) {
625
+ runtime.log("Signer-owned wallet created in fased-signerd.");
1289
626
  }
1290
627
  await configureLimitOrdersIfRequested();
1291
628
  return;
1292
629
  }
1293
630
  if (mode === "local-signer-import") {
1294
- const chain = options.chain ?? "solana";
1295
- const walletId = options.walletId ?? ((await prompt("Wallet id (optional, e.g. agent/mining/vault)", "")).trim() || undefined);
1296
- const rpcUrlFallback = resolveRpcUrlForChain(env, chain, walletId, options.rpcUrl);
1297
- const rpcUrl = (await prompt(`${chain.toUpperCase()} RPC URL (required for balances/readiness/send)`, rpcUrlFallback)).trim();
1298
- if (!rpcUrl) {
1299
- throw new Error(`${chain.toUpperCase()} RPC URL is required for self-hosted wallet setup. ` + "Pass --rpc-url or set FASED_WALLET_<CHAIN>_RPC_URL.");
1300
- }
1301
- const privateKey = options.privateKey ?? await prompt("Paste Solana private key (base58/json/base64/hex)");
1302
- if (!privateKey) {
1303
- throw new Error("Import mode requires a private key. " + "Pass --private-key (and optional --wallet-id) or set FASED_WALLET_PRIVATE_KEY for non-interactive runs.");
1304
- }
1305
- ensureLocalSignerPassphraseForSetup(runtime, env);
1306
- await walletKeystoreImportCommand(runtime, {
1307
- chain,
1308
- walletId,
1309
- name: options.walletName,
1310
- privateKey,
1311
- rpcUrl,
1312
- json: Boolean(options.json),
1313
- force: Boolean(options.force),
1314
- skipProviderConfig: true,
1315
- providerIdForRegistry: "local-socket-signer",
1316
- suppressExtraLogs: Boolean(options.noSignerHints),
1317
- role: options.role
1318
- });
1319
- await configureLocalSignerMode(runtime, options, env);
1320
- if (!options.noSignerHints) {
1321
- runtime.log("Self-hosted wallet imported for local native signer.");
1322
- }
1323
- await configureLimitOrdersIfRequested();
631
+ const walletId = normalizeNativeSignerWalletId(options.walletId ?? "agent");
632
+ const role = normalizeWalletUserRole(options.role) ?? (walletId === "mining" ? "mining" : walletId === "vault" ? "vault" : "agent");
633
+ const hosted = String(env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
634
+ const config = loadConfig();
635
+ const effectiveEnv = {
636
+ ...env,
637
+ ...config.env?.vars
638
+ };
639
+ if (!hosted) {
640
+ writeLocalSignerEnvFile({
641
+ config,
642
+ env: effectiveEnv
643
+ });
644
+ const signerBinPath = resolveSignerdBinaryPath(effectiveEnv);
645
+ if (!syncFs.existsSync(signerBinPath)) {
646
+ installSignerdBinary(signerBinPath);
647
+ }
648
+ await restartLocalSocketSigner(undefined, effectiveEnv);
649
+ }
650
+ const signerBinary = hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(effectiveEnv);
651
+ const controlSocket = hosted ? "/run/fased-signerd/control.sock" : resolveLocalSignerControlSocketPath(effectiveEnv);
652
+ runtime.log("Wallet import is native-signer-only; Fased Node will not read the key or passphrase.");
653
+ runtime.log("Solana CLI keypair import:");
654
+ runtime.log(`chmod 600 /absolute/path/to/solana-keypair.json && ${signerBinary} admin wallet import --control-socket ${controlSocket} --wallet-id ${walletId} --locked-role ${role} < /absolute/path/to/solana-keypair.json`);
655
+ runtime.log("Legacy encrypted Fased keystore migration:");
656
+ runtime.log(`chmod 600 /absolute/path/to/keystore-solana.v1.enc /absolute/path/to/passphrase && ${signerBinary} admin wallet import-legacy --control-socket ${controlSocket} --wallet-id ${walletId} --locked-role ${role} --keystore-path /absolute/path/to/keystore-solana.v1.enc --passphrase-path /absolute/path/to/passphrase`);
657
+ runtime.log(`After verifying the returned public address, run: fased wallet finalize-legacy-migration --wallet-id ${options.walletId ?? walletId}`);
658
+ runtime.log("Never put a private key or passphrase in the dashboard, Gateway, chat, argv, or an environment variable.");
1324
659
  return;
1325
660
  }
1326
661
  if (mode === "local-signer") {
@@ -1335,8 +670,11 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
1335
670
  if (providerId === "turnkey") {
1336
671
  runtime.log("Turnkey readiness validation will run after save.");
1337
672
  }
1338
- if (providerId === "alchemy" || providerId === "privy") {
1339
- const apiKey = (options.apiKey ?? options.privateKey ?? env[providerId === "alchemy" ? "ALCHEMY_API_KEY" : "PRIVY_API_KEY"] ?? await prompt(`${providerId} API key`, "")).trim();
673
+ if (providerId === "privy") {
674
+ throw new Error("Privy wallet creation and signing are not implemented. Fased will not save credentials for an unavailable provider.");
675
+ }
676
+ if (providerId === "alchemy") {
677
+ const apiKey = (options.apiKey ?? env.ALCHEMY_API_KEY ?? await prompt(`${providerId} API key`, "")).trim();
1340
678
  if (!apiKey) {
1341
679
  throw new Error(`${providerId} setup requires API key.`);
1342
680
  }
@@ -1348,24 +686,25 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
1348
686
  runtime.log("See docs: docs/plugins/crypto/wallet-production-flow.md");
1349
687
  return;
1350
688
  }
1351
- const apiPublicKey = (options.turnkeyApiPublicKey ?? env.TURNKEY_API_PUBLIC_KEY ?? await prompt("Turnkey API public key", "")).trim();
1352
- const apiPrivateKey = (options.turnkeyApiPrivateKey ?? env.TURNKEY_API_PRIVATE_KEY ?? await promptSecret("Turnkey API private key", "")).trim();
1353
- const organizationId = (options.turnkeyOrganizationId ?? env.TURNKEY_ORGANIZATION_ID ?? await prompt("Turnkey organization ID (optional)", "")).trim();
1354
- const baseUrl = (options.turnkeyBaseUrl ?? env.TURNKEY_BASE_URL ?? await prompt("Turnkey base URL (optional)", "")).trim();
1355
- const stamp = (options.turnkeyStamp ?? env.TURNKEY_STAMP ?? env.FASED_WALLET_TURNKEY_STAMP ?? await prompt("Turnkey signing stamp (optional, required for live send)", "")).trim();
1356
- if (!apiPublicKey || !apiPrivateKey) {
1357
- throw new Error("Turnkey setup requires API public key and API private key. " + "Set TURNKEY_API_PUBLIC_KEY and TURNKEY_API_PRIVATE_KEY " + "or pass --turnkey-api-public-key/--turnkey-api-private-key for non-interactive use.");
1358
- }
1359
- const values = [`apiPublicKey=${apiPublicKey}`, `apiPrivateKey=${apiPrivateKey}`];
1360
- if (organizationId) {
1361
- values.push(`organizationId=${organizationId}`);
1362
- }
689
+ const apiPublicKey = (options.turnkeyApiPublicKey ?? env.TURNKEY_API_PUBLIC_KEY ?? env.FASED_WALLET_TURNKEY_API_PUBLIC_KEY ?? await prompt("Turnkey API public key", "")).trim();
690
+ const apiPrivateKey = (options.turnkeyApiPrivateKey ?? env.TURNKEY_API_PRIVATE_KEY ?? env.FASED_WALLET_TURNKEY_API_PRIVATE_KEY ?? await promptSecret("Turnkey API private key", "")).trim();
691
+ const organizationId = (options.turnkeyOrganizationId ?? env.TURNKEY_ORGANIZATION_ID ?? env.FASED_WALLET_TURNKEY_ORGANIZATION_ID ?? await prompt("Turnkey organization ID", "")).trim();
692
+ const policyId = (options.turnkeyPolicyId ?? env.TURNKEY_POLICY_ID ?? env.FASED_WALLET_TURNKEY_POLICY_ID ?? await prompt("Turnkey policy ID for this dedicated API user", "")).trim();
693
+ const baseUrl = (options.turnkeyBaseUrl ?? env.TURNKEY_BASE_URL ?? env.FASED_WALLET_TURNKEY_BASE_URL ?? await prompt("Turnkey base URL (optional)", "")).trim();
694
+ const turnkeyRpcUrl = (options.rpcUrl ?? env.FASED_WALLET_TURNKEY_RPC_URL ?? env.FASED_WALLET_SOLANA_RPC_URL ?? await prompt("Solana RPC URL", "")).trim();
695
+ if (!apiPublicKey || !apiPrivateKey || !organizationId || !policyId || !turnkeyRpcUrl) {
696
+ throw new Error("Turnkey setup requires a dedicated API public/private key, organization ID, policy ID, and Solana RPC URL. " + "Pass --turnkey-api-public-key, --turnkey-api-private-key, --turnkey-organization-id, " + "--turnkey-policy-id, and --rpc-url for non-interactive use.");
697
+ }
698
+ const values = [
699
+ `apiPublicKey=${apiPublicKey}`,
700
+ `apiPrivateKey=${apiPrivateKey}`,
701
+ `organizationId=${organizationId}`,
702
+ `policyId=${policyId}`,
703
+ `rpcUrl=${turnkeyRpcUrl}`
704
+ ];
1363
705
  if (baseUrl) {
1364
706
  values.push(`baseUrl=${baseUrl}`);
1365
707
  }
1366
- if (stamp) {
1367
- values.push(`stamp=${stamp}`);
1368
- }
1369
708
  await walletProviderConfigureCommand(runtime, {
1370
709
  providerId: "turnkey",
1371
710
  values,
@@ -1387,8 +726,8 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
1387
726
  runtime.log(` Health: ${health.ok ? "ok" : "fail"}${health.details ? ` — ${health.details}` : ""}`);
1388
727
  runtime.log(` Chains: ${caps.supportedChains.join(", ") || "(none)"} · prepare=${String(caps.operations.prepare)} send=${String(caps.operations.send)}`);
1389
728
  runtime.log(` Ready: ${health.ok && caps.operations.prepare && caps.operations.send ? "yes" : "needs fixes"}`);
1390
- if (!caps.operations.prepare || !caps.operations.send) {
1391
- runtime.log(" Turnkey signing is not ready. Add signing stamp: `fased wallet provider configure turnkey stamp=<STAMP>`");
729
+ if (!health.ok || !caps.operations.prepare || !caps.operations.send) {
730
+ runtime.log(" Turnkey is not ready. Verify the dedicated API user's policy scope, organization/policy IDs, RPC URL, and connectivity.");
1392
731
  }
1393
732
  if (options.nonInteractive && !(health.ok && caps.operations.prepare && caps.operations.send)) {
1394
733
  throw new Error("Turnkey provider is not ready after configuration.");
@@ -1434,545 +773,129 @@ async function walletRoleSetCommand(runtime = defaultRuntime, options) {
1434
773
  });
1435
774
  const wallet = registry.wallets.find((entry) => entry.id === walletId);
1436
775
  if (role === "agent" && options.primary) {
1437
- setDefaultWallet({
1438
- walletId,
1439
- env: process.env
1440
- });
1441
- }
1442
- const updatedRegistry = readWalletProviderRegistry(process.env);
1443
- const primary = updatedRegistry.defaultWalletId === walletId;
1444
- if (options.json) {
1445
- runtime.log(JSON.stringify({
1446
- ok: true,
1447
- walletId,
1448
- walletName: wallet?.name,
1449
- role,
1450
- primary
1451
- }, null, 2));
1452
- return;
1453
- }
1454
- runtime.log(`${wallet?.name ?? walletId} (${walletId}) set to ${role === "agent" ? "Agent wallet" : "Vault wallet"}${primary ? " and primary Agent fallback" : ""}.`);
1455
- }
1456
- async function walletStatusCommand(runtime = defaultRuntime, options = {}) {
1457
- const status = await readWalletStatusSnapshot();
1458
- if (options.json) {
1459
- runtime.log(JSON.stringify({
1460
- ok: true,
1461
- status
1462
- }, null, 2));
1463
- return;
1464
- }
1465
- runtime.log(`Wallet: ${status.enabled ? "enabled" : "disabled"} (${status.mode})`);
1466
- runtime.log(`Runtime source: ${status.runtime}`);
1467
- runtime.log(`Service: ${status.service.host}:${status.service.port} healthy=${String(status.service.healthy)}`);
1468
- runtime.log(`Startup state: ${status.startupState}`);
1469
- runtime.log(`Auth state: ${status.authState}`);
1470
- runtime.log(`Auth mode: ${status.authMode}`);
1471
- runtime.log(`Auth source: ${status.authSource}`);
1472
- runtime.log(`Settlement: ${status.settlement.class} realChainReady=${String(status.settlement.realChainReady)}`);
1473
- runtime.log(`Execution mode: ${status.policy.executionMode}`);
1474
- runtime.log(`Chains: ${status.chains.join(", ")}`);
1475
- runtime.log(`Tool access: ${status.policy.toolAccessMode}`);
1476
- runtime.log(`Automated execution: ${status.policy.directSigning ? "enabled" : "disabled"}`);
1477
- runtime.log(`Approval auth: ${status.approvalAuth.mode} ready=${String(status.approvalAuth.ready)} passkeys=${status.approvalAuth.passkeyCount}`);
1478
- runtime.log(`State path: ${status.paths.rootDir}`);
1479
- if (status.stack) {
1480
- runtime.log(`Docker stack: configured=${String(status.stack.configured)} healthy=${String(status.stack.healthy)} runningServices=${status.stack.runningServices}`);
1481
- }
1482
- if (status.addresses?.solana) {
1483
- runtime.log(`Address: ${status.addresses.solana}`);
1484
- }
1485
- if (status.error) {
1486
- runtime.log(`Status warning: ${status.error}`);
1487
- }
1488
- }
1489
- async function walletKeystoreInitCommand(runtime = defaultRuntime, options = {}) {
1490
- const env = process.env;
1491
- if (options.role && !normalizeWalletRoleForCli(options.role)) {
1492
- throw new Error("wallet role must be agent or vault. Use the Mining page/command for SAT mining.");
1493
- }
1494
- const passphrase = resolveKeystorePassphrase(options.passphrase, env);
1495
- if (!passphrase) {
1496
- throw new Error("Missing keystore passphrase. Set FASED_WALLET_PASSPHRASE(_FILE) or pass --passphrase.");
1497
- }
1498
- if (options.showPrivateKeyOnce) {
1499
- requirePrivateKeyPrintConfirmation(options.confirmPrivateKeyPrint);
1500
- }
1501
- const chain = options.chain ?? "solana";
1502
- const outPath = resolveEmbeddedKeystorePathForChain(env, chain, options.out, options.walletId);
1503
- if (syncFs.existsSync(outPath) && !options.force) {
1504
- throw new Error(`Keystore already exists: ${outPath}`);
1505
- }
1506
- syncFs.mkdirSync(path.dirname(outPath), {
1507
- recursive: true,
1508
- mode: 448
1509
- });
1510
- let addressOut = "";
1511
- let privateKeyOut = "";
1512
- const seed = randomBytes(32);
1513
- const publicKeyBytes = deriveEd25519PublicKeyFromSeed(seed);
1514
- const secretKey = Uint8Array.from([...seed, ...publicKeyBytes]);
1515
- const publicKey = encodeBase58(publicKeyBytes);
1516
- const envelope = encryptSolanaKeypairEnvelope({
1517
- secretKey,
1518
- passphrase,
1519
- publicKey
1520
- });
1521
- syncFs.writeFileSync(outPath, `${JSON.stringify(envelope, null, 2)}\n`, {
1522
- encoding: "utf8",
1523
- mode: 384
1524
- });
1525
- addressOut = publicKey;
1526
- privateKeyOut = Buffer.from(secretKey).toString("base64");
1527
- try {
1528
- syncFs.chmodSync(outPath, 384);
1529
- } catch {}
1530
- const cfg = options.skipProviderConfig ? ensureWalletMaterialConfig(loadConfig(), {
1531
- keystorePath: outPath,
1532
- rpcUrl: options.rpcUrl,
1533
- chain,
1534
- walletId: options.walletId
1535
- }) : ensureEmbeddedProviderConfig(loadConfig(), {
1536
- keystorePath: outPath,
1537
- rpcUrl: options.rpcUrl,
1538
- chain,
1539
- walletId: options.walletId
1540
- });
1541
- await writeConfigFile(cfg);
1542
- if (options.json) {
1543
- const payload = {
1544
- ok: true,
1545
- provider: options.providerIdForRegistry ?? "embedded-keystore",
1546
- chain,
1547
- keystorePath: outPath,
1548
- address: addressOut,
1549
- warning: "Store the passphrase securely; it is not stored in fased config."
1550
- };
1551
- if (options.showPrivateKeyOnce) {
1552
- payload.privateKey = privateKeyOut;
1553
- payload.warning = "Private key shown once in JSON output; move to offline backup immediately.";
1554
- }
1555
- runtime.log(JSON.stringify(payload, null, 2));
1556
- return;
1557
- }
1558
- runtime.log(`${chain.toUpperCase()} address: ${addressOut}`);
1559
- if (options.showPrivateKeyOnce) {
1560
- runtime.log(`PRIVATE KEY (shown once): ${privateKeyOut}`);
1561
- }
1562
- if (options.skipProviderConfig && !options.suppressExtraLogs) {
1563
- runtime.log(`Self-hosted signer keystore created: ${outPath}`);
1564
- }
1565
- try {
1566
- const role = normalizeWalletRoleForCli(options.role);
1567
- const wallet = upsertNamedWallet({
1568
- walletId: options.walletId?.trim() || undefined,
1569
- name: options.name || "Wallet",
1570
- providerId: options.providerIdForRegistry ?? "embedded-keystore",
1571
- addresses: { solana: addressOut },
1572
- metadata: role ? {
1573
- role,
1574
- purpose: role
1575
- } : undefined,
1576
- env
1577
- });
1578
- if (role === "agent") {
1579
- setDefaultWallet({
1580
- walletId: wallet.id,
1581
- env
1582
- });
1583
- }
1584
- } catch (err) {
1585
- runtime.log(`Warning: failed to register wallet in UI registry: ${err instanceof Error ? err.message : String(err)}`);
1586
- }
1587
- }
1588
- async function walletKeystoreImportCommand(runtime = defaultRuntime, options = {}) {
1589
- const env = process.env;
1590
- if (options.role && !normalizeWalletRoleForCli(options.role)) {
1591
- throw new Error("wallet role must be agent or vault. Use the Mining page/command for SAT mining.");
1592
- }
1593
- const passphrase = resolveKeystorePassphrase(options.passphrase, env);
1594
- if (!passphrase) {
1595
- throw new Error("Missing keystore passphrase. Set FASED_WALLET_PASSPHRASE(_FILE) or pass --passphrase.");
1596
- }
1597
- const chain = options.chain ?? "solana";
1598
- const privateKey = (options.privateKey ?? String(env.FASED_WALLET_PRIVATE_KEY ?? "")).trim();
1599
- if (!privateKey) {
1600
- throw new Error("Missing private key. Pass --private-key or set FASED_WALLET_PRIVATE_KEY.");
1601
- }
1602
- const outPath = resolveEmbeddedKeystorePathForChain(env, chain, options.out, options.walletId);
1603
- if (syncFs.existsSync(outPath) && !options.force) {
1604
- throw new Error(`Keystore already exists: ${outPath}`);
1605
- }
1606
- syncFs.mkdirSync(path.dirname(outPath), {
1607
- recursive: true,
1608
- mode: 448
1609
- });
1610
- let addressOut = "";
1611
- const secretKey = parseSolanaSecretKey(privateKey);
1612
- const publicKey = solanaAddressFromSecretKey(secretKey);
1613
- const envelope = encryptSolanaKeypairEnvelope({
1614
- secretKey,
1615
- passphrase,
1616
- publicKey
1617
- });
1618
- syncFs.writeFileSync(outPath, `${JSON.stringify(envelope, null, 2)}\n`, {
1619
- encoding: "utf8",
1620
- mode: 384
1621
- });
1622
- addressOut = publicKey;
1623
- try {
1624
- syncFs.chmodSync(outPath, 384);
1625
- } catch {}
1626
- const cfg = options.skipProviderConfig ? ensureWalletMaterialConfig(loadConfig(), {
1627
- keystorePath: outPath,
1628
- rpcUrl: options.rpcUrl,
1629
- chain,
1630
- walletId: options.walletId
1631
- }) : ensureEmbeddedProviderConfig(loadConfig(), {
1632
- keystorePath: outPath,
1633
- rpcUrl: options.rpcUrl,
1634
- chain,
1635
- walletId: options.walletId
1636
- });
1637
- await writeConfigFile(cfg);
1638
- if (options.json) {
1639
- runtime.log(JSON.stringify({
1640
- ok: true,
1641
- provider: options.providerIdForRegistry ?? "embedded-keystore",
1642
- chain,
1643
- keystorePath: outPath,
1644
- address: addressOut
1645
- }, null, 2));
1646
- return;
1647
- }
1648
- if (!options.skipProviderConfig || !options.suppressExtraLogs) {
1649
- runtime.log(options.skipProviderConfig ? `Self-hosted signer keystore imported: ${outPath}` : `Embedded keystore imported: ${outPath}`);
1650
- }
1651
- runtime.log(`${chain.toUpperCase()} address: ${addressOut}`);
1652
- if (options.walletId) {
1653
- runtime.log(`Signer env hint: export FASED_WALLET_SOLANA_KEYSTORE_PATH__${walletIdEnvSuffix(options.walletId)}="${outPath}"`);
1654
- }
1655
- if (!options.skipProviderConfig) {
1656
- runtime.log("Configured wallet.provider.id=embedded-keystore and disabled wallet.runtime.");
1657
- }
1658
- try {
1659
- const role = normalizeWalletRoleForCli(options.role);
1660
- const wallet = upsertNamedWallet({
1661
- walletId: options.walletId?.trim() || undefined,
1662
- name: options.name || "Wallet",
1663
- providerId: options.providerIdForRegistry ?? "embedded-keystore",
1664
- addresses: { solana: addressOut },
1665
- metadata: role ? {
1666
- role,
1667
- purpose: role
1668
- } : undefined,
1669
- env
1670
- });
1671
- if (role === "agent") {
1672
- setDefaultWallet({
1673
- walletId: wallet.id,
1674
- env
1675
- });
1676
- }
1677
- } catch (err) {
1678
- runtime.log(`Warning: failed to register wallet in UI registry: ${err instanceof Error ? err.message : String(err)}`);
1679
- }
1680
- }
1681
- async function walletKeystoreStatusCommand(runtime = defaultRuntime, options = {}) {
1682
- const env = process.env;
1683
- const cfg = loadConfig();
1684
- const chain = options.chain ?? "solana";
1685
- const keystorePath = resolveEmbeddedKeystorePathForChain(env, chain, cfg.wallet?.keystore?.path, options.walletId);
1686
- const exists = syncFs.existsSync(keystorePath);
1687
- const providerId = cfg.wallet?.provider?.id;
1688
- let address;
1689
- let unlocked = false;
1690
- let error;
1691
- let detectedType = "missing";
1692
- if (exists) {
1693
- const passphrase = resolveKeystorePassphrase(undefined, env);
1694
- const raw = syncFs.readFileSync(keystorePath, "utf8");
1695
- detectedType = detectEmbeddedKeystoreType(raw);
1696
- if (passphrase) {
1697
- try {
1698
- if (detectedType === "solana-envelope") {
1699
- const envelope = parseSolanaKeystoreEnvelope(raw);
1700
- if (!envelope) {
1701
- throw new Error("invalid Solana keystore envelope");
1702
- }
1703
- decryptSolanaKeypairEnvelope(envelope, passphrase);
1704
- address = envelope.publicKey;
1705
- unlocked = true;
1706
- } else {
1707
- throw new Error("unsupported Solana keystore envelope");
1708
- }
1709
- } catch (err) {
1710
- error = String(err);
1711
- }
1712
- }
1713
- }
1714
- const payload = {
1715
- ok: true,
1716
- provider: providerId,
1717
- keystore: {
1718
- path: keystorePath,
1719
- exists,
1720
- type: detectedType,
1721
- unlocked,
1722
- address,
1723
- passphraseConfigured: Boolean(String(env.FASED_WALLET_PASSPHRASE_FILE ?? "").trim() || String(env.FASED_WALLET_PASSPHRASE ?? "").trim()),
1724
- rpcUrlConfigured: Boolean(String(env.FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL ?? "").trim() || String(env.FASED_WALLET_RPC_URL ?? "").trim()),
1725
- providerReady: Boolean(providerId === "embedded-keystore") && exists && Boolean(String(env.FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL ?? "").trim() || String(env.FASED_WALLET_RPC_URL ?? "").trim()),
1726
- error
1727
- }
1728
- };
1729
- if (options.json) {
1730
- runtime.log(JSON.stringify(payload, null, 2));
1731
- return;
1732
- }
1733
- runtime.log(`Provider: ${String(providerId ?? "unset")}`);
1734
- runtime.log(`Keystore: ${keystorePath} (${exists ? "exists" : "missing"})`);
1735
- runtime.log(`Type: ${detectedType}`);
1736
- runtime.log(`Unlocked: ${unlocked ? "yes" : "no"}`);
1737
- runtime.log(`RPC URL configured: ${payload.keystore.rpcUrlConfigured ? "yes" : "no (set FASED_WALLET_[EMBEDDED_KEYSTORE_]RPC_URL)"}`);
1738
- if (address) {
1739
- runtime.log(`Address: ${address}`);
1740
- }
1741
- if (error) {
1742
- runtime.log(`Error: ${error}`);
1743
- }
1744
- }
1745
- async function walletKeystoreValidateCommand(runtime = defaultRuntime, options = {}) {
1746
- const env = process.env;
1747
- const cfg = loadConfig();
1748
- const chainForPath = options.chain ?? "solana";
1749
- const keystorePath = resolveEmbeddedKeystorePathForChain(env, chainForPath, cfg.wallet?.keystore?.path, options.walletId);
1750
- const passphrase = resolveKeystorePassphrase(undefined, env);
1751
- const rpcUrl = String(env.FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL ?? "").trim() || String(env.FASED_WALLET_RPC_URL ?? "").trim();
1752
- const result = {
1753
- ok: false,
1754
- provider: "embedded-keystore",
1755
- checks: []
1756
- };
1757
- let raw = "";
1758
- let detectedType = "missing";
1759
- if (!syncFs.existsSync(keystorePath)) {
1760
- result.checks.push({
1761
- id: "keystore.exists",
1762
- ok: false,
1763
- message: `missing: ${keystorePath}`
1764
- });
1765
- } else {
1766
- raw = syncFs.readFileSync(keystorePath, "utf8");
1767
- detectedType = detectEmbeddedKeystoreType(raw);
1768
- result.checks.push({
1769
- id: "keystore.exists",
1770
- ok: true,
1771
- message: `${keystorePath} (${detectedType})`
1772
- });
1773
- }
1774
- if (!passphrase) {
1775
- result.checks.push({
1776
- id: "keystore.passphrase",
1777
- ok: false,
1778
- message: "missing passphrase (set FASED_WALLET_PASSPHRASE or FASED_WALLET_PASSPHRASE_FILE)"
1779
- });
1780
- } else {
1781
- result.checks.push({
1782
- id: "keystore.passphrase",
1783
- ok: true,
1784
- message: "configured"
776
+ setDefaultWallet({
777
+ walletId,
778
+ env: process.env
1785
779
  });
1786
780
  }
1787
- if (!rpcUrl) {
1788
- result.checks.push({
1789
- id: "rpc.url",
1790
- ok: false,
1791
- message: "missing RPC URL (set FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL or FASED_WALLET_RPC_URL)"
1792
- });
1793
- } else {
1794
- result.checks.push({
1795
- id: "rpc.url",
781
+ const updatedRegistry = readWalletProviderRegistry(process.env);
782
+ const primary = updatedRegistry.defaultWalletId === walletId;
783
+ if (options.json) {
784
+ runtime.log(JSON.stringify({
1796
785
  ok: true,
1797
- message: "configured"
1798
- });
1799
- }
1800
- if (result.checks.some((check) => !check.ok)) {
1801
- if (options.json) {
1802
- runtime.log(JSON.stringify(result, null, 2));
1803
- return;
1804
- }
1805
- for (const check of result.checks) {
1806
- runtime.log(`${check.ok ? "✓" : "✗"} ${check.id}: ${check.message}`);
1807
- }
1808
- throw new Error("embedded keystore validation failed");
1809
- }
1810
- try {
1811
- if (detectedType === "solana-envelope") {
1812
- const envelope = parseSolanaKeystoreEnvelope(raw);
1813
- if (!envelope) {
1814
- throw new Error("invalid Solana keystore envelope");
1815
- }
1816
- const secretKey = decryptSolanaKeypairEnvelope(envelope, passphrase);
1817
- const address = solanaAddressFromSecretKey(secretKey);
1818
- result.chain = "solana";
1819
- result.address = address;
1820
- result.checks.push({
1821
- id: "keystore.decrypt",
1822
- ok: true,
1823
- message: `unlocked (${address})`
1824
- });
1825
- const latest = await callRpcMethod(rpcUrl, "getLatestBlockhash", [{ commitment: "finalized" }]);
1826
- const latestValue = latest.value;
1827
- const blockhash = latestValue && typeof latestValue === "object" && typeof latestValue.blockhash === "string" ? latestValue.blockhash : "unknown";
1828
- result.checks.push({
1829
- id: "rpc.connect",
1830
- ok: true,
1831
- message: `latestBlockhash=${String(blockhash).slice(0, 12)}…`
1832
- });
1833
- const balance = await callRpcMethod(rpcUrl, "getBalance", [address, { commitment: "finalized" }]);
1834
- const balanceValue = balance.value;
1835
- const lamports = typeof balanceValue === "number" || typeof balanceValue === "string" ? String(balanceValue) : "unknown";
1836
- result.checks.push({
1837
- id: "rpc.balance",
1838
- ok: true,
1839
- message: `lamports=${lamports}`
1840
- });
1841
- result.ok = result.checks.every((check) => check.ok);
1842
- } else {
1843
- throw new Error("unsupported Solana keystore envelope");
1844
- }
1845
- } catch (err) {
1846
- result.error = String(err);
1847
- result.checks.push({
1848
- id: "validate.error",
1849
- ok: false,
1850
- message: String(err)
1851
- });
1852
- result.ok = false;
786
+ walletId,
787
+ walletName: wallet?.name,
788
+ role,
789
+ primary
790
+ }, null, 2));
791
+ return;
1853
792
  }
793
+ runtime.log(`${wallet?.name ?? walletId} (${walletId}) set to ${role === "agent" ? "Agent wallet" : "Vault wallet"}${primary ? " and primary Agent fallback" : ""}.`);
794
+ }
795
+ async function walletStatusCommand(runtime = defaultRuntime, options = {}) {
796
+ const status = await readWalletStatusSnapshot();
1854
797
  if (options.json) {
1855
- runtime.log(JSON.stringify(result, null, 2));
798
+ runtime.log(JSON.stringify({
799
+ ok: true,
800
+ status
801
+ }, null, 2));
1856
802
  return;
1857
803
  }
1858
- for (const check of result.checks) {
1859
- runtime.log(`${check.ok ? "✓" : "✗"} ${check.id}: ${check.message}`);
804
+ runtime.log(`Wallet: ${status.enabled ? "enabled" : "disabled"} (${status.mode})`);
805
+ runtime.log(`Runtime source: ${status.runtime}`);
806
+ runtime.log(`Service: ${status.service.host}:${status.service.port} healthy=${String(status.service.healthy)}`);
807
+ runtime.log(`Startup state: ${status.startupState}`);
808
+ runtime.log(`Auth state: ${status.authState}`);
809
+ runtime.log(`Auth mode: ${status.authMode}`);
810
+ runtime.log(`Auth source: ${status.authSource}`);
811
+ runtime.log(`Settlement: ${status.settlement.class} realChainReady=${String(status.settlement.realChainReady)}`);
812
+ runtime.log(`Execution mode: ${status.policy.executionMode}`);
813
+ runtime.log(`Chains: ${status.chains.join(", ")}`);
814
+ runtime.log(`Tool access: ${status.policy.toolAccessMode}`);
815
+ runtime.log(`Automated execution: ${status.policy.directSigning ? "enabled" : "disabled"}`);
816
+ runtime.log(`Approval auth: ${status.approvalAuth.mode} ready=${String(status.approvalAuth.ready)} passkeys=${status.approvalAuth.passkeyCount}`);
817
+ runtime.log(`State path: ${status.paths.rootDir}`);
818
+ if (status.stack) {
819
+ runtime.log(`Docker stack: configured=${String(status.stack.configured)} healthy=${String(status.stack.healthy)} runningServices=${status.stack.runningServices}`);
820
+ }
821
+ if (status.addresses?.solana) {
822
+ runtime.log(`Address: ${status.addresses.solana}`);
1860
823
  }
1861
- if (!result.ok) {
1862
- throw new Error("embedded keystore validation failed");
824
+ if (status.error) {
825
+ runtime.log(`Status warning: ${status.error}`);
1863
826
  }
1864
827
  }
828
+ async function walletKeystoreInitCommand(runtime = defaultRuntime, options = {}) {
829
+ void runtime;
830
+ void options;
831
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy keystore CLI command is unavailable");
832
+ }
833
+ async function walletKeystoreImportCommand(runtime = defaultRuntime, options = {}) {
834
+ void runtime;
835
+ void options;
836
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy keystore CLI command is unavailable");
837
+ }
838
+ async function walletKeystoreStatusCommand(runtime = defaultRuntime, options = {}) {
839
+ void runtime;
840
+ void options;
841
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy keystore CLI command is unavailable");
842
+ }
843
+ async function walletKeystoreValidateCommand(runtime = defaultRuntime, options = {}) {
844
+ void runtime;
845
+ void options;
846
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy keystore CLI command is unavailable");
847
+ }
1865
848
  async function walletKeystorePassphraseInitCommand(runtime = defaultRuntime, options = {}) {
1866
- const env = process.env ?? process.env;
1867
- const outPath = resolvePassphraseFilePath(env, options.out);
1868
- if (syncFs.existsSync(outPath) && !options.force) {
1869
- throw new Error(`Passphrase file already exists: ${outPath} (use --force to overwrite)`);
1870
- }
1871
- const bytes = typeof options.length === "number" && Number.isFinite(options.length) && options.length >= 16 ? Math.floor(options.length) : 24;
1872
- const passphrase = randomBytes(bytes).toString("base64url");
1873
- writePassphraseFile(outPath, passphrase);
1874
- const payload = {
1875
- ok: true,
1876
- path: outPath,
1877
- bytes,
1878
- envExport: `export FASED_WALLET_PASSPHRASE_FILE=${outPath}`
1879
- };
1880
- runtime.log(options.json ? JSON.stringify(payload, null, 2) : `Passphrase file written: ${outPath} (mode 600)`);
1881
- if (!options.json) {
1882
- runtime.log(`Set env: export FASED_WALLET_PASSPHRASE_FILE=${outPath}`);
1883
- }
849
+ void runtime;
850
+ void options;
851
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy keystore CLI command is unavailable");
1884
852
  }
1885
853
  async function walletKeystorePassphraseRotateCommand(runtime = defaultRuntime, options = {}) {
1886
- const env = process.env ?? process.env;
1887
- const cfg = loadConfig();
1888
- const keystorePath = resolveEmbeddedKeystorePath(env, cfg.wallet?.keystore?.path);
1889
- const passphraseFile = resolvePassphraseFilePath(env, options.file);
1890
- if (!syncFs.existsSync(keystorePath)) {
1891
- throw new Error(`Keystore missing: ${keystorePath}`);
1892
- }
1893
- const oldPassphrase = (options.oldPassphrase ?? (syncFs.existsSync(passphraseFile) ? syncFs.readFileSync(passphraseFile, "utf8") : "")).trim() || String(env.FASED_WALLET_PASSPHRASE ?? "").trim();
1894
- if (!oldPassphrase) {
1895
- throw new Error("Missing old passphrase. Provide --old-passphrase or configure passphrase file/env.");
1896
- }
1897
- const newPassphrase = (options.newPassphrase ?? "").trim() || randomBytes(24).toString("base64url");
1898
- const raw = syncFs.readFileSync(keystorePath, "utf8");
1899
- const detected = detectEmbeddedKeystoreType(raw);
1900
- if (detected === "solana-envelope") {
1901
- const envelope = parseSolanaKeystoreEnvelope(raw);
1902
- if (!envelope) {
1903
- throw new Error("Invalid Solana keystore envelope");
1904
- }
1905
- const secretKey = decryptSolanaKeypairEnvelope(envelope, oldPassphrase);
1906
- const nextEnvelope = encryptSolanaKeypairEnvelope({
1907
- secretKey,
1908
- passphrase: newPassphrase,
1909
- publicKey: envelope.publicKey
1910
- });
1911
- syncFs.writeFileSync(keystorePath, `${JSON.stringify(nextEnvelope, null, 2)}\n`, {
1912
- encoding: "utf8",
1913
- mode: 384
1914
- });
1915
- } else {
1916
- throw new Error("unsupported Solana keystore envelope");
1917
- }
1918
- writePassphraseFile(passphraseFile, newPassphrase);
1919
- const payload = {
1920
- ok: true,
1921
- keystorePath,
1922
- passphraseFile
1923
- };
1924
- runtime.log(options.json ? JSON.stringify(payload, null, 2) : `Rotated keystore passphrase and updated ${passphraseFile}`);
854
+ void runtime;
855
+ void options;
856
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy keystore CLI command is unavailable");
1925
857
  }
1926
858
  async function walletKeystoreExportCommand(runtime = defaultRuntime, options = {}) {
1927
- if (options.includeSecret) {
1928
- if (!options.json) {
1929
- throw new Error("Including encrypted keystore material requires --json.");
1930
- }
1931
- requireKeystoreSecretExportConfirmation(options.confirmIncludeSecret);
1932
- }
1933
- const env = process.env ?? process.env;
1934
- const cfg = loadConfig();
1935
- const keystorePath = resolveEmbeddedKeystorePath(env, cfg.wallet?.keystore?.path);
1936
- if (!syncFs.existsSync(keystorePath)) {
1937
- throw new Error(`Keystore missing: ${keystorePath}`);
1938
- }
1939
- const raw = syncFs.readFileSync(keystorePath, "utf8");
1940
- const detected = detectEmbeddedKeystoreType(raw);
1941
- const outPath = options.out?.trim() ? path.resolve(options.out.trim()) : undefined;
1942
- const payload = {
1943
- ok: true,
1944
- keystorePath,
1945
- type: detected,
1946
- exportedAt: new Date().toISOString()
1947
- };
1948
- if (options.includeSecret) {
1949
- payload.keystore = raw;
1950
- }
1951
- if (outPath) {
1952
- syncFs.mkdirSync(path.dirname(outPath), {
1953
- recursive: true,
1954
- mode: 448
1955
- });
1956
- syncFs.writeFileSync(outPath, raw, {
1957
- encoding: "utf8",
1958
- mode: 384
1959
- });
1960
- payload.outputPath = outPath;
1961
- }
1962
- if (!options.json && !outPath && !options.includeSecret) {
1963
- runtime.log(`Keystore export info: type=${detected} path=${keystorePath}`);
1964
- runtime.log("Use --out <path> to write a backup copy, or --include-secret --json to print (dangerous).");
1965
- return;
1966
- }
1967
- runtime.log(JSON.stringify(payload, null, 2));
859
+ void runtime;
860
+ void options;
861
+ throwLegacyEmbeddedKeystoreMigrationRequired("legacy keystore CLI command is unavailable");
1968
862
  }
1969
863
  async function walletProviderConfigureCommand(runtime, options) {
1970
864
  const providerId = options.providerId;
865
+ if (providerId === "privy") {
866
+ throw new Error("Privy wallet creation and signing are unavailable. No Privy credentials or provider selection were saved.");
867
+ }
1971
868
  const cfg = loadConfig();
1972
869
  const credentials = parseCredentialPairs(options.values);
1973
870
  if (Object.keys(credentials).length === 0) {
1974
871
  throw new Error("no credentials supplied; pass one or more --set key=value");
1975
872
  }
873
+ if (providerId === "turnkey") {
874
+ const allowed = new Set([
875
+ "apiPublicKey",
876
+ "apiPrivateKey",
877
+ "organizationId",
878
+ "policyId",
879
+ "baseUrl",
880
+ "rpcUrl",
881
+ "defaultSolanaAddress",
882
+ "providerWalletId"
883
+ ]);
884
+ const unsupported = Object.keys(credentials).find((field) => !allowed.has(field));
885
+ if (unsupported) {
886
+ throw new Error(`unsupported Turnkey credential field: ${unsupported}`);
887
+ }
888
+ const missing = [
889
+ "apiPublicKey",
890
+ "apiPrivateKey",
891
+ "organizationId",
892
+ "policyId",
893
+ "rpcUrl"
894
+ ].filter((field) => !credentials[field]?.trim());
895
+ if (missing.length > 0) {
896
+ throw new Error(`Turnkey credentials are incomplete; missing ${missing.join(", ")}`);
897
+ }
898
+ }
1976
899
  const saved = saveWalletProviderSecret({
1977
900
  providerId,
1978
901
  credentials
@@ -2009,6 +932,11 @@ async function walletProviderConfigureCommand(runtime, options) {
2009
932
  }, process.env);
2010
933
  }
2011
934
  await writeConfigFile(nextCfg, { envSnapshotForRestore: process.env });
935
+ setWalletProviderEnabled({
936
+ providerId,
937
+ enabled: true,
938
+ env: process.env
939
+ });
2012
940
  const status = readWalletProviderSecretStatus(providerId, process.env);
2013
941
  if (options.json) {
2014
942
  console.log(JSON.stringify({
@@ -2093,29 +1021,6 @@ async function walletSignerServeCommand(runtime = defaultRuntime, options = {})
2093
1021
  void runtime;
2094
1022
  throw new Error(`legacy Node signer has been removed; run native Go signer instead: ${suggested} --socket "${socketPath}"${options.readOnly ? " --read-only" : ""}`);
2095
1023
  }
2096
- async function walletSignerBrokerCommand(runtime = defaultRuntime, options = {}) {
2097
- void runtime;
2098
- const env = process.env ?? process.env;
2099
- const socketPath = options.socketPath?.trim() || resolveLocalSignerSocketPath(env);
2100
- const backendSocketPath = options.backendSocketPath?.trim() || resolveLocalSignerBackendSocketPath(env);
2101
- const sidecarPaths = resolveLocalSignerSidecarPaths(socketPath);
2102
- const pidFile = options.pidFile?.trim() || sidecarPaths.pidPath;
2103
- const auditLog = options.auditLog?.trim() || sidecarPaths.auditPath;
2104
- const broker = await startLocalSocketSignerBroker({
2105
- socketPath,
2106
- backendSocketPath,
2107
- pidFile,
2108
- auditLog,
2109
- readOnly: Boolean(options.readOnly)
2110
- });
2111
- await new Promise((resolve, reject) => {
2112
- process.once("SIGINT", resolve);
2113
- process.once("SIGTERM", resolve);
2114
- process.once("uncaughtException", reject);
2115
- }).finally(async () => {
2116
- await broker.close();
2117
- });
2118
- }
2119
1024
  async function collectWalletSignerDoctorReport(env = process.env, options = {}) {
2120
1025
  const cfg = options.config ?? loadConfig();
2121
1026
  const effectiveEnv = {
@@ -2123,8 +1028,9 @@ async function collectWalletSignerDoctorReport(env = process.env, options = {})
2123
1028
  ...cfg.env?.vars
2124
1029
  };
2125
1030
  const socketPath = options.socketPath?.trim() || String(effectiveEnv.FASED_WALLET_LOCAL_SIGNER_SOCKET ?? "").trim() || path.join(ensureWalletStateDir(effectiveEnv).rootDir, "local-signer.sock");
1031
+ const hostingSigner = String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting" || socketPath === "/run/fased-signerd/app.sock";
1032
+ const expectedSocketMode = hostingSigner ? 432 : 384;
2126
1033
  const { pidPath, auditPath } = resolveLocalSignerSidecarPaths(socketPath);
2127
- const wallet = resolveWalletConfigForRuntime(cfg, effectiveEnv);
2128
1034
  const checks = [];
2129
1035
  const push = (check, ok, detail) => checks.push({
2130
1036
  check,
@@ -2136,161 +1042,93 @@ async function collectWalletSignerDoctorReport(env = process.env, options = {})
2136
1042
  const providerRegistry = readWalletProviderRegistry(effectiveEnv);
2137
1043
  const providerWallets = providerRegistry.wallets.filter((entry) => entry.providerId === providerId);
2138
1044
  const localSignerSetupPending = isLocalSigner && providerWallets.length === 0;
1045
+ let localSignerHealth;
2139
1046
  const isNotFoundError = (err) => err?.code === "ENOENT";
2140
1047
  if (isLocalSigner) {
1048
+ const externallyManaged = isLocalSignerExternallyManaged(effectiveEnv);
2141
1049
  try {
2142
1050
  const st = syncFs.statSync(socketPath);
2143
1051
  push("socket.exists", st.isSocket?.() ?? true, socketPath);
2144
1052
  try {
2145
1053
  const mode = st.mode & 511;
2146
- push("socket.mode", mode === 384, `mode=${mode.toString(8)}`);
1054
+ push("socket.mode", mode === expectedSocketMode, `mode=${mode.toString(8)} expected=${expectedSocketMode.toString(8)}`);
2147
1055
  } catch {}
2148
1056
  } catch (err) {
2149
1057
  push("socket.exists", localSignerSetupPending && isNotFoundError(err), localSignerSetupPending && isNotFoundError(err) ? "Configure" : String(err));
2150
1058
  }
2151
- try {
2152
- const pidRaw = syncFs.readFileSync(pidPath, "utf8").trim();
2153
- const pid = Number.parseInt(pidRaw, 10);
2154
- if (Number.isFinite(pid) && pid > 1) {
2155
- try {
2156
- process.kill(pid, 0);
2157
- push("pid.alive", true, `pid=${pid}`);
2158
- } catch (err) {
2159
- push("pid.alive", false, `pid=${pid} ${String(err)}`);
1059
+ if (externallyManaged) {
1060
+ push("pid.alive", true, "lifecycle=external; process health is verified over the socket");
1061
+ } else {
1062
+ try {
1063
+ const pidRaw = syncFs.readFileSync(pidPath, "utf8").trim();
1064
+ const pid = Number.parseInt(pidRaw, 10);
1065
+ if (Number.isFinite(pid) && pid > 1) {
1066
+ try {
1067
+ process.kill(pid, 0);
1068
+ push("pid.alive", true, `pid=${pid}`);
1069
+ } catch (err) {
1070
+ push("pid.alive", false, `pid=${pid} ${String(err)}`);
1071
+ }
1072
+ } else {
1073
+ push("pid.alive", false, "invalid pid file");
2160
1074
  }
2161
- } else {
2162
- push("pid.alive", false, "invalid pid file");
1075
+ } catch (err) {
1076
+ push("pid.alive", localSignerSetupPending && isNotFoundError(err), localSignerSetupPending && isNotFoundError(err) ? "Configure" : String(err));
2163
1077
  }
2164
- } catch (err) {
2165
- push("pid.alive", localSignerSetupPending && isNotFoundError(err), localSignerSetupPending && isNotFoundError(err) ? "Configure" : String(err));
2166
1078
  }
2167
- try {
2168
- const st = syncFs.statSync(auditPath);
2169
- push("audit.exists", true, `bytes=${st.size}`);
2170
- } catch (err) {
2171
- push("audit.exists", localSignerSetupPending && isNotFoundError(err), localSignerSetupPending && isNotFoundError(err) ? "Configure" : String(err));
1079
+ if (externallyManaged) {
1080
+ push("audit.exists", true, "lifecycle=external; audit state is signer-owned");
1081
+ } else {
1082
+ try {
1083
+ const st = syncFs.statSync(auditPath);
1084
+ push("audit.exists", true, `bytes=${st.size}`);
1085
+ } catch (err) {
1086
+ push("audit.exists", localSignerSetupPending && isNotFoundError(err), localSignerSetupPending && isNotFoundError(err) ? "Configure" : String(err));
1087
+ }
2172
1088
  }
2173
1089
  if (localSignerSetupPending) {
2174
1090
  push("socket.health", true, "Configure");
2175
1091
  } else {
2176
1092
  try {
2177
- const signerHealth = await createWalletProviderAdapter({
2178
- cfg,
2179
- wallet,
2180
- env: effectiveEnv,
2181
- providerIdOverride: "local-socket-signer"
2182
- }).health();
2183
- push("socket.health", signerHealth.ok, signerHealth.details);
1093
+ localSignerHealth = await probeLocalSocketSignerHealth(socketPath);
1094
+ push("socket.health", localSignerHealth.ok, localSignerHealth.details);
2184
1095
  } catch (err) {
2185
1096
  push("socket.health", false, String(err));
2186
1097
  }
2187
1098
  }
2188
1099
  }
2189
- const providerDefaultWallet = providerRegistry.defaultWalletId ? providerWallets.find((entry) => entry.id === providerRegistry.defaultWalletId) : undefined;
2190
- const passphrase = String(effectiveEnv.FASED_WALLET_PASSPHRASE ?? "").trim() || (() => {
2191
- const p = String(effectiveEnv.FASED_WALLET_PASSPHRASE_FILE ?? "").trim() || path.join(resolveLocalSignerMaterialRootDir(effectiveEnv), "passphrase");
2192
- if (!p) {
2193
- return "";
2194
- }
2195
- try {
2196
- return syncFs.readFileSync(p, "utf8").trim();
2197
- } catch {
2198
- return "";
2199
- }
2200
- })();
2201
- const inspectKeystore = (label, keystorePath) => {
2202
- try {
2203
- const raw = syncFs.readFileSync(keystorePath, "utf8");
2204
- const kind = detectEmbeddedKeystoreType(raw);
2205
- if (kind === "unknown") {
2206
- push(`keystore.file.${label}`, false, `${keystorePath} type=unknown (expected fased-solana-keypair envelope)`);
2207
- } else {
2208
- push(`keystore.file.${label}`, true, `${keystorePath} type=${kind}`);
2209
- }
2210
- if (!passphrase) {
2211
- push(`keystore.passphrase.${label}`, false, "missing FASED_WALLET_PASSPHRASE(_FILE)");
2212
- } else if (kind === "solana-envelope") {
2213
- try {
2214
- const envv = parseSolanaKeystoreEnvelope(raw);
2215
- if (!envv) {
2216
- throw new Error("invalid solana envelope");
2217
- }
2218
- void decryptSolanaKeypairEnvelope(envv, passphrase);
2219
- push(`keystore.decrypt.${label}`, true, "solana envelope decrypt ok");
2220
- } catch (err) {
2221
- push(`keystore.decrypt.${label}`, false, String(err));
2222
- }
2223
- } else {
2224
- push(`keystore.decrypt.${label}`, false, "unsupported keystore format");
2225
- }
2226
- } catch (err) {
2227
- push(`keystore.file.${label}`, false, String(err));
2228
- }
2229
- };
2230
- const listWalletIds = (prefix) => {
2231
- const out = new Set();
2232
- for (const [key, value] of Object.entries(effectiveEnv)) {
2233
- if (!key.startsWith(prefix)) {
2234
- continue;
2235
- }
2236
- if (typeof value !== "string" || !value.trim()) {
2237
- continue;
2238
- }
2239
- const walletId = key.slice(prefix.length).trim().toLowerCase();
2240
- if (walletId) {
2241
- out.add(walletId);
2242
- }
2243
- }
2244
- return [...out].toSorted();
1100
+ const signerHealthy = checks.find((entry) => entry.check === "socket.health")?.ok === true;
1101
+ const registrySolanaWalletIds = providerWallets.filter((entry) => Boolean(entry.addresses?.solana)).map((entry) => entry.id.trim()).filter(Boolean).toSorted();
1102
+ push("wallets.configured.solana", true, registrySolanaWalletIds.length > 0 ? registrySolanaWalletIds.join(",") : "none");
1103
+ const resolveChainRpcConfigured = (walletId) => {
1104
+ const suffix = walletIdEnvSuffix(walletId);
1105
+ return Boolean((suffix ? String(effectiveEnv[`FASED_WALLET_SOLANA_RPC_URL__${suffix}`] ?? "").trim() : "") || String(effectiveEnv.FASED_WALLET_SOLANA_RPC_URL ?? "").trim() || String(effectiveEnv.FASED_WALLET_RPC_URL ?? "").trim());
2245
1106
  };
2246
- const solanaWalletIds = listWalletIds("FASED_WALLET_SOLANA_KEYSTORE_PATH__");
2247
- const registrySolanaWalletIds = providerWallets.filter((wallet) => Boolean(wallet.addresses?.solana)).map((wallet) => wallet.id.trim().toLowerCase()).filter(Boolean);
2248
- const configuredSolanaWallets = new Set([...solanaWalletIds, ...registrySolanaWalletIds]);
2249
- push("wallets.configured.solana", true, configuredSolanaWallets.size ? [...configuredSolanaWallets].join(",") : "default-only (or fallback vars)");
2250
- const shouldInspectDefaultWallet = () => {
2251
- const perChainKey = "FASED_WALLET_SOLANA_KEYSTORE_PATH";
2252
- const explicitChainPath = String(effectiveEnv[perChainKey] ?? "").trim() || String(effectiveEnv.FASED_WALLET_KEYSTORE_PATH ?? "").trim();
2253
- if (explicitChainPath) {
2254
- return true;
2255
- }
2256
- if (providerId !== "local-socket-signer" && typeof cfg.wallet?.keystore?.path === "string" && cfg.wallet.keystore.path.trim()) {
2257
- return true;
2258
- }
2259
- const defaultPath = resolveEmbeddedKeystorePathForChain(effectiveEnv, "solana");
2260
- if (syncFs.existsSync(defaultPath)) {
2261
- return true;
2262
- }
2263
- if (providerDefaultWallet?.addresses?.solana) {
2264
- return true;
1107
+ const signerNetworks = localSignerHealth?.network?.wallets ?? [];
1108
+ const networkForWallet = (walletId) => signerNetworks.find((entry) => entry.walletId.trim().toLowerCase() === walletId.trim().toLowerCase());
1109
+ if (isLocalSigner && registrySolanaWalletIds.length > 0) {
1110
+ const signerNetworkReady = localSignerHealth?.network?.ready === true && registrySolanaWalletIds.every((walletId) => networkForWallet(walletId)?.ready === true);
1111
+ push("rpc.configured.solana", signerNetworkReady, signerNetworkReady ? "signer-owned network ready" : localSignerHealth?.ok ? "signer-owned network pending" : "signer health unavailable");
1112
+ }
1113
+ if (isLocalSigner && localSignerHealth?.ok && localSignerHealth.jupiter) {
1114
+ push("jupiter.trigger.configured", true, localSignerHealth.jupiter.triggerConfigured ? "configured in native signer" : "not configured (optional; swaps and transfers remain available)");
1115
+ push("jupiter.execution.mode", localSignerHealth.jupiter.liveEnabled !== true, localSignerHealth.jupiter.liveEnabled === true ? "live execution enabled for qualification only; this release remains preview-only" : "preview-only; signer rejects Jupiter and Trigger execution");
1116
+ }
1117
+ if (isLocalSigner && localSignerHealth?.ok && localSignerHealth.state?.capacities) {
1118
+ for (const [label, capacity] of Object.entries(localSignerHealth.state.capacities).toSorted(([left], [right]) => left.localeCompare(right))) {
1119
+ push(`state.capacity.${label}`, !capacity.warning, `${capacity.used}/${capacity.maximum} records; warning=${capacity.warnAt}`);
1120
+ }
1121
+ }
1122
+ for (const walletId of registrySolanaWalletIds) {
1123
+ push(`keystore.file.solana.${walletId}`, signerHealthy, signerHealthy ? "signer-owned encrypted state; normal Fased Node key-handling path disabled (Local same-user isolation is not a hard boundary)" : "native signer is not healthy");
1124
+ push(`keystore.decrypt.solana.${walletId}`, signerHealthy, signerHealthy ? "native signer handles decryption; normal Fased Node decryption path disabled (Local same-user isolation is not a hard boundary)" : "native signer is not healthy");
1125
+ if (isLocalSigner) {
1126
+ const network = networkForWallet(walletId);
1127
+ push(`rpc.configured.solana.${walletId}`, network?.ready === true, network?.ready ? `signer-owned network ready (version=${network.version})` : network?.configured ? "signer-owned network is not ready" : "signer-owned network is not configured");
1128
+ } else {
1129
+ const rpcConfigured = resolveChainRpcConfigured(walletId);
1130
+ push(`rpc.configured.solana.${walletId}`, rpcConfigured, rpcConfigured ? "configured" : "missing");
2265
1131
  }
2266
- return false;
2267
- };
2268
- const solanaWalletSet = new Set([
2269
- ...shouldInspectDefaultWallet() ? ["default"] : [],
2270
- ...solanaWalletIds,
2271
- ...registrySolanaWalletIds
2272
- ]);
2273
- const resolveChainKeystorePath = (walletId) => {
2274
- const explicitFallback = providerId !== "local-socket-signer" && walletId === "default" ? cfg.wallet?.keystore?.path : undefined;
2275
- return resolveEmbeddedKeystorePathForChain(effectiveEnv, "solana", explicitFallback, walletId);
2276
- };
2277
- for (const walletId of [...solanaWalletSet].toSorted()) {
2278
- inspectKeystore(`solana.${walletId}`, resolveChainKeystorePath(walletId));
2279
- }
2280
- const resolveChainRpcUrl = (walletId) => {
2281
- const suffix = walletId.toUpperCase();
2282
- const perWalletKey = `FASED_WALLET_SOLANA_RPC_URL__${suffix}`;
2283
- const perChainKey = "FASED_WALLET_SOLANA_RPC_URL";
2284
- return String(effectiveEnv[perWalletKey] ?? "").trim() || String(effectiveEnv[perChainKey] ?? "").trim() || String(effectiveEnv.FASED_WALLET_EMBEDDED_KEYSTORE_RPC_URL ?? "").trim();
2285
- };
2286
- const solanaRpcChecks = [...solanaWalletSet].toSorted().map((walletId) => resolveChainRpcUrl(walletId));
2287
- const solanaRpcUrl = solanaRpcChecks.find(Boolean) ?? "";
2288
- if (solanaWalletSet.size > 0 || String(effectiveEnv.FASED_WALLET_SOLANA_RPC_URL ?? "").trim()) {
2289
- push("rpc.configured.solana", Boolean(solanaRpcUrl), solanaRpcUrl || "missing");
2290
- }
2291
- for (const walletId of [...solanaWalletSet].toSorted()) {
2292
- const u = resolveChainRpcUrl(walletId);
2293
- push(`rpc.configured.solana.${walletId}`, Boolean(u), u || "missing");
2294
1132
  }
2295
1133
  const ok = checks.every((c) => {
2296
1134
  if (c.ok) {
@@ -2304,12 +1142,22 @@ async function collectWalletSignerDoctorReport(env = process.env, options = {})
2304
1142
  }
2305
1143
  return false;
2306
1144
  });
1145
+ const signer = localSignerHealth?.ok ? {
1146
+ ...localSignerHealth.jupiter ? { jupiter: localSignerHealth.jupiter } : {},
1147
+ ...localSignerHealth.webAuthn ? { webAuthn: {
1148
+ configured: localSignerHealth.webAuthn.configured,
1149
+ credentialCount: localSignerHealth.webAuthn.credentialCount,
1150
+ credentialVersion: localSignerHealth.webAuthn.credentialVersion,
1151
+ ready: localSignerHealth.webAuthn.ready
1152
+ } } : {}
1153
+ } : undefined;
2307
1154
  return {
2308
1155
  ok,
2309
1156
  socketPath,
2310
1157
  pidPath,
2311
1158
  auditPath,
2312
- checks
1159
+ checks,
1160
+ ...signer ? { signer } : {}
2313
1161
  };
2314
1162
  }
2315
1163
  async function walletSignerDoctorCommand(runtime = defaultRuntime, options = {}) {
@@ -2325,317 +1173,131 @@ async function walletSignerDoctorCommand(runtime = defaultRuntime, options = {})
2325
1173
  }
2326
1174
  }
2327
1175
  async function walletRotateKeysCommand(runtime = defaultRuntime, options = {}) {
2328
- const cfg = loadConfig();
2329
- const resolved = resolveWalletConfigForRuntime(cfg, process.env);
2330
- if (!resolved.enabled) {
2331
- throw new Error("wallet is disabled");
2332
- }
2333
- const provider = createWalletProviderAdapter({
2334
- cfg,
2335
- wallet: resolved,
2336
- env: process.env
2337
- });
2338
- if (!provider.capabilities.supportsRotateKeys || !provider.rotateKeys) {
2339
- throw new Error(`provider ${provider.id} does not support key rotation`);
2340
- }
2341
- const result = await provider.rotateKeys();
2342
- if (options.json) {
2343
- runtime.log(JSON.stringify(result, null, 2));
2344
- } else {
2345
- runtime.log("Wallet keys rotated.");
2346
- if (result.addresses?.solana) {
2347
- runtime.log(`Address: ${result.addresses.solana}`);
2348
- }
2349
- }
1176
+ void runtime;
1177
+ void options;
1178
+ throw new Error("Gateway wallet key rotation was removed. For signer-owned encryption rotation, run `fased-signerd admin wallet reencrypt --control-socket <absolute-control.sock> --wallet-id <wallet-id>` as the signer/control-socket owner. Rotate Turnkey or hardware custody only in its provider/wallet authority surface.");
2350
1179
  }
2351
- async function initializeWalletCustodyForWallet(params) {
2352
- const env = params.env ?? process.env;
2353
- const cfg = params.cfg ?? loadConfig();
2354
- const walletCfg = resolveWalletConfigForRuntime(cfg, env);
2355
- const providerId = resolveWalletProviderId(cfg, env);
2356
- if (providerId !== "local-socket-signer") {
2357
- throw new Error("split-key custody currently requires local-socket-signer");
2358
- }
2359
- const approvalAuth = readWalletApprovalAuthSnapshot(env, cfg);
2360
- if (approvalAuth.mode !== "webauthn" || approvalAuth.passkeyCount < 1) {
2361
- throw new Error("split-key custody requires wallet approval auth mode=webauthn with at least one enrolled passkey");
2362
- }
2363
- const oldPassphrase = resolveKeystorePassphrase(undefined, env);
2364
- if (!oldPassphrase) {
2365
- throw new Error("split-key custody migration requires the current keystore passphrase to be configured before initialization");
2366
- }
2367
- const allTargets = collectLocalSignerKeystoreTargets(cfg, env);
2368
- const distinctWalletIds = [...new Set(allTargets.map((target) => target.walletId || "default"))];
2369
- const requestedWalletId = params.walletId?.trim() || "";
2370
- if (!requestedWalletId && distinctWalletIds.length > 1) {
2371
- throw new Error(`multiple local-signer wallets found (${distinctWalletIds.join(", ")}); re-run with --wallet <walletId>`);
2372
- }
2373
- const resolvedWalletId = requestedWalletId || distinctWalletIds[0] || "default";
2374
- const registry = readWalletProviderRegistry(env);
2375
- const registryWallet = registry.wallets.find((wallet) => walletIdsMatchForEnvSuffix(wallet.id, resolvedWalletId));
2376
- const walletRole = resolveWalletUserRole(registryWallet) ?? (walletIdsMatchForEnvSuffix(registry.defaultWalletId, resolvedWalletId) ? "agent" : undefined);
2377
- if (walletRole !== "vault") {
2378
- throw new Error("split-key wallet security can only be enabled for Vault wallets. Agent wallets use automation, caps, and passkey-reviewed manual sends; Mining wallets use SAT mining/sweep policy. Create or select a Vault wallet before enabling wallet security.");
2379
- }
2380
- const targets = allTargets.filter((target) => walletIdsMatchForEnvSuffix(target.walletId, resolvedWalletId));
2381
- if (targets.length === 0) {
2382
- throw new Error(`no self-hosted local-signer keystores were found to migrate for ${resolvedWalletId}`);
2383
- }
2384
- const result = initializeWalletCustodyCeremony({
2385
- env,
2386
- force: Boolean(params.force),
2387
- deviceShare: params.deviceShare,
2388
- walletId: resolvedWalletId,
2389
- wallet: walletCfg,
2390
- cfg
2391
- });
2392
- if (!result.ok) {
2393
- throw new Error(result.message);
2394
- }
2395
- const recovered = recoverWalletCustodyPassphrase({
2396
- env,
2397
- deviceShare: result.deviceShare,
2398
- walletId: resolvedWalletId
2399
- });
2400
- if (!recovered.ok) {
2401
- throw new Error(recovered.message);
2402
- }
2403
- for (const target of targets) {
2404
- await reencryptLocalSignerKeystore({
2405
- target,
2406
- oldPassphrase,
2407
- newPassphrase: recovered.passphrase
2408
- });
2409
- }
2410
- const custodyWallets = parseCustodyWalletSet({
2411
- ...env,
2412
- ...cfg.env?.vars
2413
- });
2414
- custodyWallets.add(normalizeCustodyWalletId(resolvedWalletId));
2415
- const allSignerWalletsMigrated = allTargets.every((target) => custodyWallets.has(normalizeCustodyWalletId(target.walletId)));
2416
- let nextCfg = allSignerWalletsMigrated ? clearConfiguredSignerPassphrase(cfg) : cfg;
2417
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_MODE", "split-key");
2418
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_WALLETS", formatCustodyWalletSet(custodyWallets));
2419
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_PASSKEY_CEREMONY", "1");
2420
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION", "1");
2421
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_PHASE2_COMPLETE", "1");
2422
- await writeConfigFile(nextCfg);
2423
- process.env.FASED_WALLET_CUSTODY_MODE = "split-key";
2424
- process.env.FASED_WALLET_CUSTODY_WALLETS = formatCustodyWalletSet(custodyWallets);
2425
- process.env.FASED_WALLET_CUSTODY_PASSKEY_CEREMONY = "1";
2426
- process.env.FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION = "1";
2427
- process.env.FASED_WALLET_CUSTODY_PHASE2_COMPLETE = "1";
2428
- let removedPassphraseFile = { removed: false };
2429
- if (allSignerWalletsMigrated) {
2430
- delete process.env.FASED_WALLET_PASSPHRASE;
2431
- removedPassphraseFile = removeLocalSignerPassphraseFileIfManaged(env);
2432
- delete process.env.FASED_WALLET_PASSPHRASE_FILE;
2433
- }
2434
- writeLocalSignerEnvFile(nextCfg, process.env);
2435
- let signerRestarted = true;
2436
- let signerRestartError;
2437
- try {
2438
- await restartLocalSocketSigner(undefined, process.env);
2439
- } catch (err) {
2440
- signerRestarted = false;
2441
- signerRestartError = err instanceof Error ? err.message : String(err);
2442
- }
2443
- return {
2444
- ok: true,
2445
- scheme: result.scheme,
2446
- statePath: result.statePath,
2447
- walletId: result.walletId,
2448
- role: result.role,
2449
- deviceShare: result.deviceShare,
2450
- recoveryShare: result.recoveryShare,
2451
- secretBytes: result.secretBytes,
2452
- updatedAt: result.updatedAt,
2453
- migratedKeystores: targets.map((target) => ({
2454
- chain: target.chain,
2455
- walletId: target.walletId,
2456
- path: target.path
2457
- })),
2458
- walletProvider: providerId,
2459
- executionMode: walletCfg.execution.mode,
2460
- removedManagedPassphraseFile: removedPassphraseFile.removed,
2461
- removedManagedPassphraseFilePath: removedPassphraseFile.path,
2462
- signerRestarted,
2463
- signerRestartError
2464
- };
1180
+ function isLegacyWalletMaterialEnvKey(key) {
1181
+ return key === "FASED_WALLET_KEYSTORE_PATH" || key === "FASED_WALLET_PASSPHRASE" || key === "FASED_WALLET_PASSPHRASE_FILE" || key === "FASED_WALLET_PRIVATE_KEY" || key === "FASED_WALLET_SOLANA_KEYSTORE_PATH" || key.startsWith("FASED_WALLET_SOLANA_KEYSTORE_PATH__");
2465
1182
  }
2466
- async function disableWalletCustodyForWallet(params) {
2467
- const env = params.env ?? process.env;
2468
- const cfg = params.cfg ?? loadConfig();
1183
+ async function walletLegacyMigrationFinalizeCommand(runtime = defaultRuntime, options) {
1184
+ const walletId = options.walletId.trim();
1185
+ if (!walletId || !/^[a-zA-Z0-9_-]+$/.test(walletId)) {
1186
+ throw new Error("walletId must contain only letters, numbers, hyphens, or underscores");
1187
+ }
1188
+ const cfg = loadConfig();
2469
1189
  const effectiveEnv = {
2470
- ...env,
1190
+ ...process.env,
2471
1191
  ...cfg.env?.vars
2472
1192
  };
2473
- const walletCfg = resolveWalletConfigForRuntime(cfg, env);
2474
- const providerId = resolveWalletProviderId(cfg, env);
2475
- if (providerId !== "local-socket-signer") {
2476
- throw new Error("wallet security disable currently requires local-socket-signer");
2477
- }
2478
- const requestedWalletId = params.walletId?.trim() || "";
2479
- if (!requestedWalletId) {
2480
- throw new Error("wallet id is required to disable wallet security");
2481
- }
2482
- const status = readWalletCustodyStatus({
2483
- cfg,
2484
- env: effectiveEnv,
2485
- wallet: walletCfg,
2486
- walletId: requestedWalletId
2487
- });
2488
- if (status.mode === "single-key") {
2489
- throw new Error(`wallet security is not enabled for ${requestedWalletId}`);
2490
- }
2491
- const resolvedWalletId = status.target.walletId || requestedWalletId;
2492
- const allTargets = collectLocalSignerKeystoreTargets(cfg, env);
2493
- const targets = allTargets.filter((target) => walletIdsMatchForEnvSuffix(target.walletId, resolvedWalletId));
2494
- if (targets.length === 0) {
2495
- throw new Error(`no local-signer keystore was found for ${resolvedWalletId}`);
2496
- }
2497
- const recovered = recoverWalletCustodyPassphrase({
2498
- env: effectiveEnv,
2499
- walletId: resolvedWalletId,
2500
- deviceShare: params.deviceShare,
2501
- recoveryShare: params.recoveryShare
1193
+ const socketPath = requireLocalSocketSignerPath(effectiveEnv);
1194
+ const capabilities = await callLocalSocketSigner(socketPath, { op: "v2.capabilities" });
1195
+ if (capabilities.ready !== true || capabilities.capabilities?.protocol?.current !== 2 || !capabilities.capabilities?.features?.includes("signerOwnedKeys")) {
1196
+ throw new Error("legacy migration finalization requires a ready protocol-v2 signer-owned wallet");
1197
+ }
1198
+ const nativeWallet = await callLocalSocketSigner(socketPath, {
1199
+ op: "v2.wallet.get",
1200
+ walletId
2502
1201
  });
2503
- if (!recovered.ok) {
2504
- throw new Error(recovered.message);
1202
+ const expectedSignerWalletId = normalizeNativeSignerWalletId(walletId);
1203
+ const signerWalletId = String(nativeWallet.walletId ?? "").trim();
1204
+ const publicKey = String(nativeWallet.publicKey ?? "").trim();
1205
+ if (!publicKey || signerWalletId !== expectedSignerWalletId) {
1206
+ throw new Error("protocol-v2 signer did not return the requested signer-owned wallet");
2505
1207
  }
2506
- const passphraseSource = ensureLocalSignerPassphraseFileForCustodyDisable({
2507
- cfg,
2508
- env
2509
- });
2510
- let nextCfg = passphraseSource.cfg;
2511
- for (const target of targets) {
2512
- await reencryptLocalSignerKeystore({
2513
- target,
2514
- oldPassphrase: recovered.passphrase,
2515
- newPassphrase: passphraseSource.passphrase
2516
- });
1208
+ const registry = readWalletProviderRegistry(effectiveEnv);
1209
+ const legacyWallet = registry.wallets.find((wallet) => wallet.id === walletId);
1210
+ const signerIdCollision = findNativeSignerWalletIdCollision(registry.wallets, walletId, signerWalletId);
1211
+ if (signerIdCollision) {
1212
+ throw new Error(`native signer wallet ID ${signerWalletId} is already registered as ${signerIdCollision.id}; refusing legacy migration finalization`);
2517
1213
  }
2518
- const remainingCustodyWallets = new Set();
2519
- for (const walletStatus of listSplitKeyWalletCustodyStatuses({
2520
- wallet: walletCfg,
2521
- cfg,
2522
- env: effectiveEnv
2523
- })) {
2524
- if (!walletIdsMatchForEnvSuffix(walletStatus.target.walletId, resolvedWalletId)) {
2525
- remainingCustodyWallets.add(normalizeCustodyWalletId(walletStatus.target.walletId));
1214
+ if (legacyWallet && legacyWallet.providerId !== "embedded-keystore") {
1215
+ if (legacyWallet.providerId !== "local-socket-signer") {
1216
+ throw new Error(`wallet ${walletId} is registered to ${legacyWallet.providerId}, not the legacy provider`);
1217
+ }
1218
+ const registeredAddress = legacyWallet.addresses?.solana?.trim();
1219
+ if (registeredAddress && registeredAddress !== publicKey) {
1220
+ throw new Error(`signer public key mismatch for ${walletId}; registry=${registeredAddress} signer=${publicKey}`);
1221
+ }
1222
+ const registeredSignerWalletId = legacyWallet.metadata?.signerWalletId;
1223
+ if (typeof registeredSignerWalletId === "string" && registeredSignerWalletId.trim() && registeredSignerWalletId.trim() !== signerWalletId) {
1224
+ throw new Error(`signer wallet ID mismatch for ${walletId}; registry=${registeredSignerWalletId.trim()} signer=${signerWalletId}`);
2526
1225
  }
2527
1226
  }
2528
- if (remainingCustodyWallets.size > 0) {
2529
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_MODE", "split-key");
2530
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_WALLETS", formatCustodyWalletSet(remainingCustodyWallets));
2531
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_PASSKEY_CEREMONY", "1");
2532
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION", "1");
2533
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_PHASE2_COMPLETE", "1");
2534
- } else {
2535
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_MODE", undefined);
2536
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_WALLETS", undefined);
2537
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_PASSKEY_CEREMONY", undefined);
2538
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION", undefined);
2539
- nextCfg = setConfigEnvVar(nextCfg, "FASED_WALLET_CUSTODY_PHASE2_COMPLETE", undefined);
2540
- }
2541
- await writeConfigFile(nextCfg);
2542
- process.env.FASED_WALLET_PASSPHRASE_FILE = passphraseSource.passphraseFile;
2543
- delete process.env.FASED_WALLET_PASSPHRASE;
2544
- if (remainingCustodyWallets.size > 0) {
2545
- process.env.FASED_WALLET_CUSTODY_MODE = "split-key";
2546
- process.env.FASED_WALLET_CUSTODY_WALLETS = formatCustodyWalletSet(remainingCustodyWallets);
2547
- process.env.FASED_WALLET_CUSTODY_PASSKEY_CEREMONY = "1";
2548
- process.env.FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION = "1";
2549
- process.env.FASED_WALLET_CUSTODY_PHASE2_COMPLETE = "1";
2550
- } else {
2551
- delete process.env.FASED_WALLET_CUSTODY_MODE;
2552
- delete process.env.FASED_WALLET_CUSTODY_WALLETS;
2553
- delete process.env.FASED_WALLET_CUSTODY_PASSKEY_CEREMONY;
2554
- delete process.env.FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION;
2555
- delete process.env.FASED_WALLET_CUSTODY_PHASE2_COMPLETE;
2556
- }
2557
- const deletedCeremony = deleteWalletCustodyCeremony({
2558
- env: effectiveEnv,
2559
- cfg,
2560
- wallet: walletCfg,
2561
- walletId: resolvedWalletId
1227
+ const legacyAddress = legacyWallet?.addresses?.solana?.trim();
1228
+ if (legacyAddress && legacyAddress !== publicKey) {
1229
+ throw new Error(`signer public key mismatch for ${walletId}; registry=${legacyAddress} signer=${publicKey}`);
1230
+ }
1231
+ upsertNamedWallet({
1232
+ walletId,
1233
+ name: options.walletName?.trim() || legacyWallet?.name || "Wallet",
1234
+ providerId: "local-socket-signer",
1235
+ addresses: { solana: publicKey },
1236
+ metadata: {
1237
+ ...legacyWallet?.metadata,
1238
+ keyAuthority: "signer-owned-v2",
1239
+ signerWalletId,
1240
+ migratedFromProviderId: "embedded-keystore",
1241
+ migratedAt: new Date().toISOString()
1242
+ },
1243
+ env: effectiveEnv
2562
1244
  });
2563
- const locked = await lockWalletCustodyUnlockSessions({
2564
- env: process.env,
2565
- walletId: resolvedWalletId
1245
+ setWalletProviderEnabled({
1246
+ providerId: "local-socket-signer",
1247
+ enabled: true,
1248
+ env: effectiveEnv
2566
1249
  });
2567
- writeLocalSignerEnvFile(nextCfg, process.env);
2568
- let signerRestarted = true;
2569
- let signerRestartError;
2570
- try {
2571
- await restartLocalSocketSigner(undefined, process.env);
2572
- } catch (err) {
2573
- signerRestarted = false;
2574
- signerRestartError = err instanceof Error ? err.message : String(err);
1250
+ const afterRegistry = readWalletProviderRegistry(effectiveEnv);
1251
+ const remainingLegacyWallets = afterRegistry.wallets.filter((wallet) => wallet.providerId === "embedded-keystore");
1252
+ if (remainingLegacyWallets.length === 0) {
1253
+ setWalletProviderEnabled({
1254
+ providerId: "embedded-keystore",
1255
+ enabled: false,
1256
+ env: effectiveEnv
1257
+ });
1258
+ const vars = { ...cfg.env?.vars };
1259
+ for (const key of Object.keys(vars)) {
1260
+ if (isLegacyWalletMaterialEnvKey(key)) {
1261
+ delete vars[key];
1262
+ }
1263
+ }
1264
+ for (const key of Object.keys(process.env)) {
1265
+ if (isLegacyWalletMaterialEnvKey(key)) {
1266
+ delete process.env[key];
1267
+ }
1268
+ }
1269
+ await writeConfigFile({
1270
+ ...cfg,
1271
+ env: {
1272
+ ...cfg.env,
1273
+ vars
1274
+ },
1275
+ wallet: {
1276
+ ...cfg.wallet,
1277
+ provider: {
1278
+ ...cfg.wallet?.provider,
1279
+ id: "local-socket-signer"
1280
+ },
1281
+ keystore: undefined
1282
+ }
1283
+ }, { envSnapshotForRestore: process.env });
2575
1284
  }
2576
- return {
1285
+ const result = {
2577
1286
  ok: true,
2578
- walletId: resolvedWalletId,
2579
- migratedKeystores: targets.map((target) => ({
2580
- chain: target.chain,
2581
- walletId: target.walletId,
2582
- path: target.path
2583
- })),
2584
- remainingCustodyWallets: formatCustodyWalletSet(remainingCustodyWallets),
2585
- passphraseFile: passphraseSource.passphraseFile,
2586
- custodyStateRemoved: deletedCeremony.removed,
2587
- custodyStatePath: deletedCeremony.statePath,
2588
- unlockSessionsCleared: locked.ok ? locked.removed : 0,
2589
- unlockSessionClearError: locked.ok ? undefined : locked.message,
2590
- signerRestarted,
2591
- signerRestartError
1287
+ walletId,
1288
+ signerWalletId,
1289
+ publicKey,
1290
+ alreadyFinalized: legacyWallet?.providerId === "local-socket-signer" && legacyWallet.addresses?.solana?.trim() === publicKey,
1291
+ remainingLegacyWalletIds: remainingLegacyWallets.map((wallet) => wallet.id)
2592
1292
  };
2593
- }
2594
- async function walletCustodyInitCommand(runtime = defaultRuntime, options = {}) {
2595
- const result = await initializeWalletCustodyForWallet({
2596
- walletId: options.walletId,
2597
- deviceShare: options.deviceShare,
2598
- force: options.force,
2599
- env: process.env
2600
- });
2601
1293
  if (options.json) {
2602
1294
  runtime.log(JSON.stringify(result, null, 2));
2603
- return;
2604
- }
2605
- runtime.log("Wallet split-key custody ceremony initialized.");
2606
- runtime.log(`Wallet: ${result.walletId}`);
2607
- runtime.log(`Scheme: ${result.scheme}`);
2608
- runtime.log(`State: ${result.statePath}`);
2609
- runtime.log(`Secret bytes: ${result.secretBytes}`);
2610
- runtime.log(`Migrated keystores: ${result.migratedKeystores.length}`);
2611
- if (result.removedManagedPassphraseFile) {
2612
- runtime.log(`Removed managed passphrase file: ${result.removedManagedPassphraseFilePath}`);
2613
- } else if (result.removedManagedPassphraseFilePath) {
2614
- runtime.log(`Passphrase file still exists outside signer state dir; remove it manually: ${result.removedManagedPassphraseFilePath}`);
2615
- }
2616
- runtime.log("Signer env updated and local signer restarted in split-key custody mode.");
2617
- runtime.log("Device share (store offline, never commit):");
2618
- runtime.log(result.deviceShare);
2619
- runtime.log("Recovery share (store offline separately; use with host share if the device is lost):");
2620
- runtime.log(result.recoveryShare);
2621
- }
2622
- async function walletCustodyLockCommand(runtime = defaultRuntime, options = {}) {
2623
- const result = await lockWalletCustodyUnlockSessions({
2624
- env: process.env,
2625
- host: options.host,
2626
- walletId: options.walletId
2627
- });
2628
- if (!result.ok) {
2629
- throw new Error(result.message);
2630
- }
2631
- if (options.json) {
2632
- runtime.log(JSON.stringify({
2633
- ok: true,
2634
- result
2635
- }, null, 2));
2636
- return;
1295
+ } else {
1296
+ runtime.log(`Verified and finalized signer-owned migration for ${walletId}; signer wallet ID: ${signerWalletId} (${publicKey}).`);
1297
+ if (remainingLegacyWallets.length > 0) {
1298
+ runtime.log(`Remaining legacy wallets: ${remainingLegacyWallets.map((wallet) => wallet.id).join(", ")}`);
1299
+ }
2637
1300
  }
2638
- runtime.log(`Custody unlock sessions locked: removed=${result.removed} remaining=${result.remaining}${result.host ? ` host=${result.host}` : ""}${result.walletId ? ` wallet=${result.walletId}` : ""}`);
2639
1301
  }
2640
1302
  async function walletStackInstallCommand(runtime = defaultRuntime, options = {}) {
2641
1303
  throwLegacyDockerSignerRemoved("wallet stack install");
@@ -2740,12 +1402,7 @@ async function walletCanaryCommand(runtime = defaultRuntime, options = {}) {
2740
1402
  };
2741
1403
  if (options.executeProviderE2E) {
2742
1404
  const requestedProviders = (options.providers ?? []).map((entry) => entry.trim()).filter(Boolean);
2743
- const allowed = new Set([
2744
- "embedded-keystore",
2745
- "alchemy",
2746
- "turnkey",
2747
- "privy"
2748
- ]);
1405
+ const allowed = new Set(["alchemy", "turnkey"]);
2749
1406
  const providers = requestedProviders.filter((entry) => allowed.has(entry));
2750
1407
  const providerE2E = await runWalletProviderCanaryReport({
2751
1408
  cfg,
@@ -2852,4 +1509,4 @@ async function walletInboundReconcileCommand(runtime = defaultRuntime, options =
2852
1509
  }
2853
1510
 
2854
1511
  //#endregion
2855
- export { collectWalletSignerDoctorReport, disableWalletCustodyForWallet, initializeWalletCustodyForWallet, walletCanaryCommand, walletCustodyInitCommand, walletCustodyLockCommand, walletInboundListCommand, walletInboundPollCommand, walletInboundReconcileCommand, walletKeystoreExportCommand, walletKeystoreImportCommand, walletKeystoreInitCommand, walletKeystorePassphraseInitCommand, walletKeystorePassphraseRotateCommand, walletKeystoreStatusCommand, walletKeystoreValidateCommand, walletLimitOrdersConfigureCommand, walletMigrateCommand, walletPolicyProfileApplyCommand, walletProviderConfigureCommand, walletRoleSetCommand, walletRotateKeysCommand, walletSetupCommand, walletSignerBrokerCommand, walletSignerDoctorCommand, walletSignerServeCommand, walletStatusCommand };
1512
+ export { collectWalletSignerDoctorReport, walletCanaryCommand, walletInboundListCommand, walletInboundPollCommand, walletInboundReconcileCommand, walletLegacyMigrationFinalizeCommand, walletLimitOrdersConfigureCommand, walletMigrateCommand, walletPolicyProfileApplyCommand, walletProviderConfigureCommand, walletRoleSetCommand, walletRotateKeysCommand, walletSetupCommand, walletSignerDoctorCommand, walletSignerServeCommand, walletStatusCommand };