@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,1836 +0,0 @@
1
- import { ensureWalletStateDir, resolveWalletRuntimeConfig } from "./wallet-runtime-config.js";
2
- import { readWalletProviderRegistry, resolveWalletUserRole } from "./wallet-provider-registry.js";
3
- import { tryResolveSatRuntimeIds } from "../config/sat-runtime-ids.js";
4
- import { applyWalletPolicyConfig, resolveWalletRolePolicyProfile } from "./wallet-policy.js";
5
- import { walletDiagnosticErrorMessage } from "./wallet-redaction.js";
6
- import { resolveWalletProviderId } from "./wallet-provider-resolver.js";
7
- import { consumeWalletApprovalGrant, resolveWalletApprovalAuthMode } from "./wallet-approval-auth.js";
8
- import { lockLocalSignerCustody, unlockLocalSignerCustody } from "./local-socket-signer-custody.js";
9
- import syncFs from "node:fs";
10
- import path from "node:path";
11
- import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
12
-
13
- //#region src/wallet/wallet-custody.ts
14
- const DEFAULT_UNLOCK_TTL_SECONDS = 15 * 60;
15
- const MAX_UNLOCK_TTL_SECONDS = 60 * 60;
16
- const MANUAL_UNLOCK_EXPIRES_AT = "9999-12-31T23:59:59.000Z";
17
- const CUSTODY_SECRET_BYTES = 32;
18
- const SOLANA_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
19
- const SOLANA_TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
20
- const SOLANA_ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
21
- const unlockMaterialBySession = new Map();
22
- function normalizeHost(raw) {
23
- const trimmed = raw.trim();
24
- if (!trimmed) {
25
- return "";
26
- }
27
- try {
28
- const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
29
- const parsed = new URL(withScheme);
30
- const hostname = parsed.hostname.toLowerCase();
31
- if (!parsed.port || parsed.port === "443") {
32
- return hostname;
33
- }
34
- return `${hostname}:${parsed.port}`;
35
- } catch {
36
- return "";
37
- }
38
- }
39
- function nowMs() {
40
- return Date.now();
41
- }
42
- function isNonNull(value) {
43
- return value !== null;
44
- }
45
- function sha256Hex(input) {
46
- return createHash("sha256").update(input).digest("hex");
47
- }
48
- function normalizeWalletIdForState(value) {
49
- const raw = String(value ?? "").trim().toLowerCase();
50
- if (!raw) {
51
- return "default";
52
- }
53
- const normalized = raw.replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
54
- return normalized || "default";
55
- }
56
- function normalizeChainList(value) {
57
- const seen = new Set();
58
- for (const entry of value) {
59
- if (entry === "solana") {
60
- seen.add(entry);
61
- }
62
- }
63
- return [...seen];
64
- }
65
- function normalizeAddressList(values) {
66
- const seen = new Set();
67
- for (const value of values) {
68
- const normalized = String(value ?? "").trim().toLowerCase();
69
- if (!normalized) {
70
- continue;
71
- }
72
- seen.add(normalized);
73
- }
74
- return [...seen];
75
- }
76
- function normalizeWalletCustodyRole(value) {
77
- return value === "mining" || value === "agent" || value === "vault" ? value : undefined;
78
- }
79
- function normalizeDeviceLabel(value) {
80
- const normalized = String(value ?? "").trim().replace(/\s+/g, " ");
81
- return normalized ? normalized.slice(0, 80) : undefined;
82
- }
83
- function createCustodyDeviceId() {
84
- return randomBytes(8).toString("hex");
85
- }
86
- function normalizeDeviceShareX(value) {
87
- return typeof value === "number" && Number.isInteger(value) && value >= 2 && value <= 255 && value !== 3 ? value : null;
88
- }
89
- function normalizeEnrolledDevice(raw) {
90
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
91
- return null;
92
- }
93
- const value = raw;
94
- const id = typeof value.id === "string" ? value.id.trim() : "";
95
- const shareX = normalizeDeviceShareX(value.shareX);
96
- const shareHash = typeof value.shareHash === "string" ? value.shareHash.trim() : "";
97
- const createdAt = typeof value.createdAt === "string" ? value.createdAt : "";
98
- if (!id || !shareX || !shareHash || !createdAt) {
99
- return null;
100
- }
101
- return {
102
- id,
103
- label: normalizeDeviceLabel(typeof value.label === "string" ? value.label : undefined),
104
- shareX,
105
- shareHash,
106
- createdAt,
107
- revokedAt: typeof value.revokedAt === "string" ? value.revokedAt : undefined
108
- };
109
- }
110
- function listActiveEnrolledDevices(shareState) {
111
- if (!shareState) {
112
- return [];
113
- }
114
- if (shareState.version === 3 && shareState.scheme === "shamir-2ofn-v1") {
115
- return shareState.devices.filter((device) => !device.revokedAt);
116
- }
117
- if (shareState.version === 2 && shareState.scheme === "shamir-2of3-v1") {
118
- return [{
119
- id: "primary",
120
- label: "Primary device",
121
- shareX: 2,
122
- shareHash: shareState.deviceShareHash,
123
- createdAt: shareState.createdAt
124
- }];
125
- }
126
- if (shareState.version === 1 && shareState.scheme === "xor-3of3-v1") {
127
- return [{
128
- id: "legacy-primary",
129
- label: "Primary device",
130
- shareX: 2,
131
- shareHash: shareState.deviceShareHash,
132
- createdAt: shareState.createdAt
133
- }];
134
- }
135
- return [];
136
- }
137
- function allocateNextDeviceShareX(devices) {
138
- const used = new Set(devices.map((device) => device.shareX));
139
- for (let candidate = 2; candidate <= 255; candidate += 1) {
140
- if (candidate === 3 || used.has(candidate)) {
141
- continue;
142
- }
143
- return candidate;
144
- }
145
- throw new Error("no available device-share slots remain for this wallet");
146
- }
147
- function resolveLegacyCustodyStatePath(env = process.env) {
148
- const walletPaths = ensureWalletStateDir(env);
149
- return path.join(walletPaths.rootDir, "wallet-custody-state.json");
150
- }
151
- function resolveLegacyCustodyShareStatePath(env = process.env) {
152
- const walletPaths = ensureWalletStateDir(env);
153
- return path.join(walletPaths.rootDir, "wallet-custody-shares.v1.json");
154
- }
155
- function resolveCustodyWalletDir(walletId, env = process.env) {
156
- const walletPaths = ensureWalletStateDir(env);
157
- return path.join(walletPaths.rootDir, "custody", normalizeWalletIdForState(walletId));
158
- }
159
- function resolveCustodyStatePath(walletId, env = process.env) {
160
- return path.join(resolveCustodyWalletDir(walletId, env), "state.json");
161
- }
162
- function resolveCustodyShareStatePath(walletId, env = process.env) {
163
- return path.join(resolveCustodyWalletDir(walletId, env), "shares.v1.json");
164
- }
165
- function locateCustodyStatePath(walletId, env = process.env) {
166
- const scopedPath = resolveCustodyStatePath(walletId, env);
167
- if (syncFs.existsSync(scopedPath)) {
168
- return scopedPath;
169
- }
170
- const legacyPath = resolveLegacyCustodyStatePath(env);
171
- return syncFs.existsSync(legacyPath) ? legacyPath : scopedPath;
172
- }
173
- function locateCustodyShareStatePath(walletId, env = process.env) {
174
- const scopedPath = resolveCustodyShareStatePath(walletId, env);
175
- if (syncFs.existsSync(scopedPath)) {
176
- return scopedPath;
177
- }
178
- const legacyPath = resolveLegacyCustodyShareStatePath(env);
179
- return syncFs.existsSync(legacyPath) ? legacyPath : scopedPath;
180
- }
181
- function listCustodyStateTargets(env = process.env) {
182
- const walletPaths = ensureWalletStateDir(env);
183
- const custodyRoot = path.join(walletPaths.rootDir, "custody");
184
- const targets = new Set();
185
- if (syncFs.existsSync(custodyRoot)) {
186
- for (const entry of syncFs.readdirSync(custodyRoot, { withFileTypes: true })) {
187
- if (entry.isDirectory() && entry.name.trim()) {
188
- targets.add(entry.name.trim());
189
- }
190
- }
191
- }
192
- const out = [...targets];
193
- if (syncFs.existsSync(resolveLegacyCustodyStatePath(env)) || out.length === 0) {
194
- out.push(undefined);
195
- }
196
- return out;
197
- }
198
- function readUnlockTtlSeconds(env = process.env, requestedTtlSeconds) {
199
- const raw = typeof requestedTtlSeconds === "number" && Number.isFinite(requestedTtlSeconds) ? requestedTtlSeconds : Number.parseInt(String(env.FASED_WALLET_CUSTODY_UNLOCK_TTL_SECONDS ?? ""), 10);
200
- if (!Number.isFinite(raw) || raw <= 0) {
201
- return DEFAULT_UNLOCK_TTL_SECONDS;
202
- }
203
- return Math.min(MAX_UNLOCK_TTL_SECONDS, Math.max(30, Math.floor(raw)));
204
- }
205
- function resolveUnlockExpiresAt(env = process.env, requestedTtlSeconds) {
206
- if (requestedTtlSeconds === 0) {
207
- return MANUAL_UNLOCK_EXPIRES_AT;
208
- }
209
- return new Date(nowMs() + readUnlockTtlSeconds(env, requestedTtlSeconds) * 1e3).toISOString();
210
- }
211
- function readBooleanFlag(env, key) {
212
- const value = String(env[key] ?? "").trim().toLowerCase();
213
- return value === "1" || value === "true" || value === "yes" || value === "on";
214
- }
215
- function normalizeWalletIdForCustodyEnv(value) {
216
- const raw = String(value ?? "").trim().toLowerCase();
217
- if (!raw) {
218
- return "default";
219
- }
220
- return raw.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "default";
221
- }
222
- function readCustodyWalletSet(env) {
223
- const out = new Set();
224
- for (const part of String(env.FASED_WALLET_CUSTODY_WALLETS ?? "").split(",")) {
225
- if (!part.trim()) {
226
- continue;
227
- }
228
- const normalized = normalizeWalletIdForCustodyEnv(part);
229
- if (normalized) {
230
- out.add(normalized);
231
- }
232
- }
233
- return out;
234
- }
235
- function decodeBase64Share(raw) {
236
- const value = raw.trim();
237
- if (!value) {
238
- return null;
239
- }
240
- for (const encoding of ["base64url", "base64"]) {
241
- try {
242
- const decoded = Buffer.from(value, encoding);
243
- if (decoded.length > 0) {
244
- return decoded;
245
- }
246
- } catch {}
247
- }
248
- return null;
249
- }
250
- function buffersEqualHex(leftHex, rightHex) {
251
- try {
252
- const left = Buffer.from(leftHex, "hex");
253
- const right = Buffer.from(rightHex, "hex");
254
- if (left.length === 0 || right.length === 0 || left.length !== right.length) {
255
- return false;
256
- }
257
- return timingSafeEqual(left, right);
258
- } catch {
259
- return false;
260
- }
261
- }
262
- function xorBuffers(buffers) {
263
- if (buffers.length === 0) {
264
- return Buffer.alloc(0);
265
- }
266
- const length = buffers[0].length;
267
- const out = Buffer.alloc(length, 0);
268
- for (const buffer of buffers) {
269
- if (buffer.length !== length) {
270
- throw new Error("buffer length mismatch");
271
- }
272
- for (let i = 0; i < length; i += 1) {
273
- out[i] ^= buffer[i];
274
- }
275
- }
276
- return out;
277
- }
278
- function gf256Multiply(left, right) {
279
- let a = left & 255;
280
- let b = right & 255;
281
- let out = 0;
282
- while (b > 0) {
283
- if (b & 1) {
284
- out ^= a;
285
- }
286
- const hiBit = a & 128;
287
- a = a << 1 & 255;
288
- if (hiBit) {
289
- a ^= 27;
290
- }
291
- b >>= 1;
292
- }
293
- return out & 255;
294
- }
295
- function gf256Pow(value, exponent) {
296
- let result = 1;
297
- let base = value & 255;
298
- let power = Math.max(0, Math.floor(exponent));
299
- while (power > 0) {
300
- if (power & 1) {
301
- result = gf256Multiply(result, base);
302
- }
303
- base = gf256Multiply(base, base);
304
- power >>= 1;
305
- }
306
- return result & 255;
307
- }
308
- function gf256Inverse(value) {
309
- if (value === 0) {
310
- throw new Error("cannot invert zero in GF(256)");
311
- }
312
- return gf256Pow(value, 254);
313
- }
314
- function gf256Divide(left, right) {
315
- if (left === 0) {
316
- return 0;
317
- }
318
- return gf256Multiply(left, gf256Inverse(right));
319
- }
320
- function encodeTypedCustodyShare(type, share) {
321
- return `${type === "device" ? "d" : "r"}.${share.toString("base64url")}`;
322
- }
323
- function decodeTypedCustodyShare(raw) {
324
- const value = raw.trim();
325
- if (!value) {
326
- return {
327
- type: "unknown",
328
- bytes: null
329
- };
330
- }
331
- const match = /^([dr])\.(.+)$/i.exec(value);
332
- if (!match) {
333
- return {
334
- type: "unknown",
335
- bytes: decodeBase64Share(value)
336
- };
337
- }
338
- return {
339
- type: match[1]?.toLowerCase() === "r" ? "recovery" : "device",
340
- bytes: decodeBase64Share(match[2] ?? "")
341
- };
342
- }
343
- function splitSecretShamir2of3(secret) {
344
- const slope = randomBytes(secret.length);
345
- const hostShare = deriveSecretShamirShare(secret, slope, 1);
346
- const deviceShare = deriveSecretShamirShare(secret, slope, 2);
347
- const recoveryShare = deriveSecretShamirShare(secret, slope, 3);
348
- slope.fill(0);
349
- return {
350
- hostShare,
351
- deviceShare,
352
- recoveryShare
353
- };
354
- }
355
- function deriveSecretShamirShare(secret, slope, x) {
356
- if (secret.length !== slope.length) {
357
- throw new Error("share length mismatch");
358
- }
359
- const share = Buffer.alloc(secret.length, 0);
360
- for (let i = 0; i < secret.length; i += 1) {
361
- const s = secret[i] ?? 0;
362
- const m = slope[i] ?? 0;
363
- share[i] = s ^ gf256Multiply(m, x);
364
- }
365
- return share;
366
- }
367
- function splitSecretShamir2of3WithDeviceShare(secret, deviceShare) {
368
- if (secret.length !== deviceShare.length) {
369
- throw new Error("share length mismatch");
370
- }
371
- const slope = Buffer.alloc(secret.length, 0);
372
- for (let i = 0; i < secret.length; i += 1) {
373
- const s = secret[i] ?? 0;
374
- const d = deviceShare[i] ?? 0;
375
- slope[i] = gf256Divide(s ^ d, 2);
376
- }
377
- const hostShare = deriveSecretShamirShare(secret, slope, 1);
378
- const recoveryShare = deriveSecretShamirShare(secret, slope, 3);
379
- slope.fill(0);
380
- return {
381
- hostShare,
382
- recoveryShare
383
- };
384
- }
385
- function reconstructSecretShamir2ofN(params) {
386
- const { hostShare, otherShare, otherX } = params;
387
- if (hostShare.length !== otherShare.length) {
388
- throw new Error("share length mismatch");
389
- }
390
- const out = Buffer.alloc(hostShare.length, 0);
391
- const denominator = 1 ^ otherX;
392
- const weightHost = gf256Divide(otherX, denominator);
393
- const weightOther = gf256Divide(1, denominator);
394
- for (let i = 0; i < hostShare.length; i += 1) {
395
- const left = gf256Multiply(hostShare[i] ?? 0, weightHost);
396
- const right = gf256Multiply(otherShare[i] ?? 0, weightOther);
397
- out[i] = left ^ right;
398
- }
399
- return out;
400
- }
401
- function zeroizeBuffers(buffers) {
402
- for (const buffer of buffers) {
403
- if (!buffer || buffer.length === 0) {
404
- continue;
405
- }
406
- buffer.fill(0);
407
- }
408
- }
409
- function removeUnlockMaterial(sessionId) {
410
- const existing = unlockMaterialBySession.get(sessionId);
411
- if (!existing) {
412
- return;
413
- }
414
- unlockMaterialBySession.delete(sessionId);
415
- }
416
- function pruneUnlockMaterialStore(now = nowMs()) {
417
- for (const [sessionId, entry] of unlockMaterialBySession.entries()) {
418
- if (entry.expiresAtMs <= now) {
419
- removeUnlockMaterial(sessionId);
420
- }
421
- }
422
- }
423
- function setUnlockMaterial(params) {
424
- removeUnlockMaterial(params.sessionId);
425
- const expiresAtMs = Date.parse(params.expiresAt);
426
- unlockMaterialBySession.set(params.sessionId, {
427
- sessionId: params.sessionId,
428
- host: params.host,
429
- expiresAtMs: Number.isFinite(expiresAtMs) ? expiresAtMs : nowMs() + 3e4
430
- });
431
- }
432
- function hasActiveUnlockMaterial(params) {
433
- pruneUnlockMaterialStore();
434
- const entry = unlockMaterialBySession.get(params.sessionId);
435
- if (!entry) {
436
- return false;
437
- }
438
- if (entry.host !== params.host || entry.expiresAtMs <= nowMs()) {
439
- removeUnlockMaterial(params.sessionId);
440
- return false;
441
- }
442
- return true;
443
- }
444
- async function withWalletCustodySigningMaterial(params) {
445
- pruneUnlockMaterialStore();
446
- const entry = unlockMaterialBySession.get(params.sessionId);
447
- if (!entry || entry.host !== params.host || entry.expiresAtMs <= nowMs()) {
448
- removeUnlockMaterial(params.sessionId);
449
- return {
450
- ok: false,
451
- code: "custody_unlock_required",
452
- message: "custody unlock signing material is unavailable; re-run passkey unlock"
453
- };
454
- }
455
- try {
456
- const value = await params.handler({
457
- sessionId: entry.sessionId,
458
- host: entry.host
459
- });
460
- return {
461
- ok: true,
462
- value
463
- };
464
- } finally {}
465
- }
466
- function inferWalletChains(params) {
467
- try {
468
- const registry = readWalletProviderRegistry(params.env);
469
- const namedWallet = registry.wallets.find((entry) => entry.id === params.walletId);
470
- const inferred = normalizeChainList([namedWallet?.addresses?.solana ? "solana" : ""]);
471
- if (inferred.length > 0) {
472
- return inferred;
473
- }
474
- } catch {}
475
- return params.wallet.chains.length > 0 ? params.wallet.chains : ["solana"];
476
- }
477
- function resolveWalletCustodyScope(params) {
478
- const env = params.env ?? process.env;
479
- const wallet = params.wallet ?? resolveWalletRuntimeConfig(params.cfg ?? {}, env);
480
- const registry = readWalletProviderRegistry(env);
481
- const configuredMiningWalletId = typeof params.cfg?.plugins?.entries?.["sat-mining"]?.config?.walletId === "string" ? params.cfg.plugins.entries["sat-mining"]?.config?.walletId.trim() : "";
482
- const explicitWalletId = params.walletId?.trim() || "";
483
- const walletId = explicitWalletId || registry.defaultWalletId?.trim() || configuredMiningWalletId || registry.wallets.find((entry) => entry.providerId === "local-socket-signer")?.id || "default";
484
- const registryWallet = registry.wallets.find((entry) => entry.id === walletId);
485
- const walletPurpose = resolveWalletUserRole(registryWallet);
486
- const role = walletId === configuredMiningWalletId || walletPurpose === "mining" ? "mining" : walletId === registry.defaultWalletId || walletPurpose === "agent" ? "agent" : "vault";
487
- const effectiveWallet = applyWalletPolicyConfig({
488
- config: wallet,
489
- cfg: params.cfg,
490
- env,
491
- walletId
492
- });
493
- const roleProfile = resolveWalletRolePolicyProfile(role, env);
494
- const chains = role === "mining" ? ["solana"] : inferWalletChains({
495
- wallet: effectiveWallet,
496
- env,
497
- walletId
498
- });
499
- const allowPrograms = new Set(normalizeAddressList(effectiveWallet.policy.solana.allowPrograms));
500
- if (role === "mining") {
501
- const ids = tryResolveSatRuntimeIds(env);
502
- if (ids?.programId) {
503
- allowPrograms.add(ids.programId.toLowerCase());
504
- }
505
- } else if (chains.includes("solana")) {
506
- allowPrograms.add(SOLANA_TOKEN_PROGRAM_ID.toLowerCase());
507
- allowPrograms.add(SOLANA_TOKEN_2022_PROGRAM_ID.toLowerCase());
508
- allowPrograms.add(SOLANA_ASSOCIATED_TOKEN_PROGRAM_ID.toLowerCase());
509
- }
510
- for (const program of roleProfile.defaults.solana.allowPrograms) {
511
- allowPrograms.add(program.toLowerCase());
512
- }
513
- const solanaMaxPerTx = role === "mining" ? roleProfile.defaults.solana.maxPerTx : effectiveWallet.policy.solana.caps.maxPerTx.toString();
514
- const solanaMaxDaily = role === "mining" ? roleProfile.defaults.solana.maxDaily : effectiveWallet.policy.solana.caps.maxDaily.toString();
515
- return {
516
- walletId,
517
- role,
518
- chains,
519
- allowPrograms: [...allowPrograms],
520
- solana: {
521
- maxPerTx: solanaMaxPerTx,
522
- maxDaily: solanaMaxDaily
523
- }
524
- };
525
- }
526
- function loadShareState(walletId, env = process.env) {
527
- const filePath = locateCustodyShareStatePath(walletId, env);
528
- if (!syncFs.existsSync(filePath)) {
529
- return null;
530
- }
531
- try {
532
- const parsed = JSON.parse(syncFs.readFileSync(filePath, "utf8"));
533
- if (parsed.version === 1 && parsed.scheme === "xor-3of3-v1" && typeof parsed.secretBytes === "number" && parsed.secretBytes > 0 && typeof parsed.hotShare === "string" && typeof parsed.coldShare === "string" && typeof parsed.deviceShareHash === "string" && typeof parsed.secretChecksum === "string" && typeof parsed.createdAt === "string" && typeof parsed.updatedAt === "string") {
534
- const hot = decodeBase64Share(parsed.hotShare);
535
- const cold = decodeBase64Share(parsed.coldShare);
536
- if (!hot || !cold || hot.length !== parsed.secretBytes || cold.length !== parsed.secretBytes) {
537
- return null;
538
- }
539
- return {
540
- version: 1,
541
- scheme: "xor-3of3-v1",
542
- secretBytes: parsed.secretBytes,
543
- hotShare: parsed.hotShare,
544
- coldShare: parsed.coldShare,
545
- deviceShareHash: parsed.deviceShareHash,
546
- secretChecksum: parsed.secretChecksum,
547
- walletId: typeof parsed.walletId === "string" ? parsed.walletId.trim() || undefined : undefined,
548
- role: normalizeWalletCustodyRole(parsed.role),
549
- createdAt: parsed.createdAt,
550
- updatedAt: parsed.updatedAt
551
- };
552
- }
553
- if (parsed.version === 2 && parsed.scheme === "shamir-2of3-v1" && typeof parsed.secretBytes === "number" && parsed.secretBytes > 0 && typeof parsed.hostShare === "string" && typeof parsed.deviceShareHash === "string" && typeof parsed.recoveryShareHash === "string" && typeof parsed.secretChecksum === "string" && typeof parsed.createdAt === "string" && typeof parsed.updatedAt === "string") {
554
- const host = decodeBase64Share(parsed.hostShare);
555
- if (!host || host.length !== parsed.secretBytes) {
556
- return null;
557
- }
558
- return {
559
- version: 2,
560
- scheme: "shamir-2of3-v1",
561
- secretBytes: parsed.secretBytes,
562
- hostShare: parsed.hostShare,
563
- deviceShareHash: parsed.deviceShareHash,
564
- recoveryShareHash: parsed.recoveryShareHash,
565
- secretChecksum: parsed.secretChecksum,
566
- walletId: typeof parsed.walletId === "string" ? parsed.walletId.trim() || undefined : undefined,
567
- role: normalizeWalletCustodyRole(parsed.role),
568
- createdAt: parsed.createdAt,
569
- updatedAt: parsed.updatedAt
570
- };
571
- }
572
- if (parsed.version === 3 && parsed.scheme === "shamir-2ofn-v1" && typeof parsed.secretBytes === "number" && parsed.secretBytes > 0 && typeof parsed.hostShare === "string" && typeof parsed.recoveryShareHash === "string" && typeof parsed.secretChecksum === "string" && typeof parsed.createdAt === "string" && typeof parsed.updatedAt === "string" && Array.isArray(parsed.devices)) {
573
- const host = decodeBase64Share(parsed.hostShare);
574
- if (!host || host.length !== parsed.secretBytes) {
575
- return null;
576
- }
577
- const devices = (parsed.devices ?? []).map(normalizeEnrolledDevice).filter(isNonNull);
578
- return {
579
- version: 3,
580
- scheme: "shamir-2ofn-v1",
581
- secretBytes: parsed.secretBytes,
582
- hostShare: parsed.hostShare,
583
- recoveryShareHash: parsed.recoveryShareHash,
584
- secretChecksum: parsed.secretChecksum,
585
- devices,
586
- walletId: typeof parsed.walletId === "string" ? parsed.walletId.trim() || undefined : undefined,
587
- role: normalizeWalletCustodyRole(parsed.role),
588
- createdAt: parsed.createdAt,
589
- updatedAt: parsed.updatedAt
590
- };
591
- }
592
- return null;
593
- } catch {
594
- return null;
595
- }
596
- }
597
- function saveShareState(state, walletId, env = process.env) {
598
- const filePath = resolveCustodyShareStatePath(walletId, env);
599
- syncFs.mkdirSync(path.dirname(filePath), {
600
- recursive: true,
601
- mode: 448
602
- });
603
- syncFs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, {
604
- encoding: "utf8",
605
- mode: 384
606
- });
607
- try {
608
- syncFs.chmodSync(filePath, 384);
609
- } catch {}
610
- }
611
- function deleteWalletCustodyCeremony(params) {
612
- const env = params?.env ?? process.env;
613
- const scope = params?.wallet && params?.cfg ? resolveWalletCustodyScope({
614
- wallet: params.wallet,
615
- cfg: params.cfg,
616
- env,
617
- walletId: params.walletId
618
- }) : {
619
- walletId: params?.walletId?.trim() || "default",
620
- role: "vault"
621
- };
622
- const statePath = locateCustodyShareStatePath(scope.walletId, env);
623
- const existed = syncFs.existsSync(statePath);
624
- if (existed) {
625
- syncFs.rmSync(statePath, { force: true });
626
- try {
627
- syncFs.rmdirSync(path.dirname(statePath));
628
- } catch {}
629
- }
630
- return {
631
- ok: true,
632
- walletId: scope.walletId,
633
- removed: existed,
634
- statePath
635
- };
636
- }
637
- function encodeCustodyShareStateV3(params) {
638
- const secretBytes = params.shareState?.secretBytes && params.shareState.secretBytes > 0 ? params.shareState.secretBytes : params.hostShare.length;
639
- return {
640
- version: 3,
641
- scheme: "shamir-2ofn-v1",
642
- secretBytes,
643
- hostShare: params.hostShare.toString("base64url"),
644
- recoveryShareHash: params.recoveryShareHash,
645
- secretChecksum: params.secretChecksum,
646
- devices: params.devices.map((device) => ({
647
- id: device.id,
648
- label: normalizeDeviceLabel(device.label),
649
- shareX: device.shareX,
650
- shareHash: device.shareHash,
651
- createdAt: device.createdAt,
652
- revokedAt: device.revokedAt
653
- })),
654
- walletId: params.walletId,
655
- role: params.role,
656
- createdAt: params.createdAt,
657
- updatedAt: params.updatedAt
658
- };
659
- }
660
- function initializeWalletCustodyCeremony(params) {
661
- const env = params?.env ?? process.env;
662
- const scope = params?.wallet && params?.cfg ? resolveWalletCustodyScope({
663
- wallet: params.wallet,
664
- cfg: params.cfg,
665
- env,
666
- walletId: params.walletId
667
- }) : {
668
- walletId: params?.walletId?.trim() || "default",
669
- role: "vault"
670
- };
671
- const existing = loadShareState(scope.walletId, env);
672
- if (existing && !params?.force) {
673
- return {
674
- ok: false,
675
- code: "ceremony_exists",
676
- message: "wallet custody ceremony is already initialized",
677
- statePath: locateCustodyShareStatePath(scope.walletId, env)
678
- };
679
- }
680
- const secret = randomBytes(CUSTODY_SECRET_BYTES);
681
- let hostShare = null;
682
- let deviceShare = null;
683
- let recoveryShare = null;
684
- try {
685
- const requested = decodeTypedCustodyShare(params?.deviceShare ?? "");
686
- const requestedDeviceShare = requested.bytes;
687
- if (requestedDeviceShare && requestedDeviceShare.length !== CUSTODY_SECRET_BYTES) {
688
- return {
689
- ok: false,
690
- code: "invalid_device_share",
691
- message: `device share must be ${CUSTODY_SECRET_BYTES} bytes (base64/base64url)`
692
- };
693
- }
694
- if (requestedDeviceShare) {
695
- deviceShare = Buffer.from(requestedDeviceShare);
696
- const split = splitSecretShamir2of3WithDeviceShare(secret, deviceShare);
697
- hostShare = split.hostShare;
698
- recoveryShare = split.recoveryShare;
699
- } else {
700
- const split = splitSecretShamir2of3(secret);
701
- hostShare = split.hostShare;
702
- deviceShare = split.deviceShare;
703
- recoveryShare = split.recoveryShare;
704
- }
705
- const nowIso = new Date().toISOString();
706
- const record = encodeCustodyShareStateV3({
707
- shareState: existing,
708
- walletId: scope.walletId,
709
- role: scope.role,
710
- hostShare,
711
- recoveryShareHash: sha256Hex(recoveryShare),
712
- secretChecksum: sha256Hex(secret),
713
- createdAt: existing?.createdAt ?? nowIso,
714
- updatedAt: nowIso,
715
- devices: [{
716
- id: createCustodyDeviceId(),
717
- label: normalizeDeviceLabel(params?.deviceLabel) ?? "Primary device",
718
- shareX: 2,
719
- shareHash: sha256Hex(deviceShare),
720
- createdAt: nowIso
721
- }]
722
- });
723
- saveShareState(record, scope.walletId, env);
724
- return {
725
- ok: true,
726
- statePath: resolveCustodyShareStatePath(scope.walletId, env),
727
- scheme: record.scheme,
728
- walletId: scope.walletId,
729
- role: scope.role,
730
- deviceShare: encodeTypedCustodyShare("device", deviceShare),
731
- recoveryShare: encodeTypedCustodyShare("recovery", recoveryShare),
732
- secretBytes: CUSTODY_SECRET_BYTES,
733
- updatedAt: record.updatedAt
734
- };
735
- } finally {
736
- zeroizeBuffers([
737
- secret,
738
- hostShare,
739
- deviceShare,
740
- recoveryShare
741
- ]);
742
- }
743
- }
744
- function recoverWalletCustodyCeremony(params) {
745
- const env = params?.env ?? process.env;
746
- const scope = params?.wallet && params?.cfg ? resolveWalletCustodyScope({
747
- wallet: params.wallet,
748
- cfg: params.cfg,
749
- env,
750
- walletId: params.walletId
751
- }) : {
752
- walletId: params?.walletId?.trim() || "default",
753
- role: "vault"
754
- };
755
- const existing = loadShareState(scope.walletId, env);
756
- if (!existing) {
757
- return {
758
- ok: false,
759
- code: "custody_ceremony_required",
760
- message: "split-key custody ceremony is not initialized",
761
- statePath: locateCustodyShareStatePath(scope.walletId, env)
762
- };
763
- }
764
- const reconstructed = reconstructCustodySigningKey({
765
- env,
766
- recoveryShare: params?.recoveryShare,
767
- walletId: scope.walletId
768
- });
769
- if (!reconstructed.ok) {
770
- return reconstructed;
771
- }
772
- let deviceShare = null;
773
- let hostShare = null;
774
- let recoveryShare = null;
775
- try {
776
- const requested = decodeTypedCustodyShare(params?.deviceShare ?? "");
777
- const requestedDeviceShare = requested.bytes;
778
- if (requestedDeviceShare && requestedDeviceShare.length !== CUSTODY_SECRET_BYTES) {
779
- return {
780
- ok: false,
781
- code: "invalid_device_share",
782
- message: `device share must be ${CUSTODY_SECRET_BYTES} bytes (base64/base64url)`
783
- };
784
- }
785
- if (requestedDeviceShare) {
786
- deviceShare = Buffer.from(requestedDeviceShare);
787
- const split = splitSecretShamir2of3WithDeviceShare(reconstructed.signingKey, deviceShare);
788
- hostShare = split.hostShare;
789
- recoveryShare = split.recoveryShare;
790
- } else {
791
- const split = splitSecretShamir2of3(reconstructed.signingKey);
792
- hostShare = split.hostShare;
793
- deviceShare = split.deviceShare;
794
- recoveryShare = split.recoveryShare;
795
- }
796
- const updatedAt = new Date().toISOString();
797
- const record = encodeCustodyShareStateV3({
798
- shareState: existing,
799
- walletId: scope.walletId,
800
- role: scope.role,
801
- hostShare,
802
- recoveryShareHash: sha256Hex(recoveryShare),
803
- secretChecksum: sha256Hex(reconstructed.signingKey),
804
- createdAt: existing.createdAt,
805
- updatedAt,
806
- devices: [{
807
- id: createCustodyDeviceId(),
808
- label: normalizeDeviceLabel(params?.deviceLabel) ?? "Recovered device",
809
- shareX: 2,
810
- shareHash: sha256Hex(deviceShare),
811
- createdAt: updatedAt
812
- }]
813
- });
814
- saveShareState(record, scope.walletId, env);
815
- return {
816
- ok: true,
817
- statePath: resolveCustodyShareStatePath(scope.walletId, env),
818
- scheme: record.scheme,
819
- walletId: scope.walletId,
820
- role: scope.role,
821
- deviceShare: encodeTypedCustodyShare("device", deviceShare),
822
- recoveryShare: encodeTypedCustodyShare("recovery", recoveryShare),
823
- secretBytes: CUSTODY_SECRET_BYTES,
824
- updatedAt
825
- };
826
- } finally {
827
- zeroizeBuffers([
828
- reconstructed.signingKey,
829
- deviceShare,
830
- hostShare,
831
- recoveryShare
832
- ]);
833
- }
834
- }
835
- function enrollWalletCustodyDevice(params) {
836
- const env = params?.env ?? process.env;
837
- const scope = resolveWalletCustodyScope({
838
- wallet: params?.wallet,
839
- cfg: params?.cfg,
840
- env,
841
- walletId: params?.walletId
842
- });
843
- const shareState = loadShareState(scope.walletId, env);
844
- if (!shareState) {
845
- return {
846
- ok: false,
847
- code: "custody_ceremony_required",
848
- message: "split-key custody ceremony is not initialized"
849
- };
850
- }
851
- if (shareState.scheme === "xor-3of3-v1") {
852
- return {
853
- ok: false,
854
- code: "custody_scheme_unsupported",
855
- message: "legacy custody scheme does not support second-device enrollment; recover the wallet first"
856
- };
857
- }
858
- const reconstructed = reconstructCustodySigningKey({
859
- env,
860
- walletId: scope.walletId,
861
- deviceShare: params?.deviceShare,
862
- recoveryShare: params?.recoveryShare
863
- });
864
- if (!reconstructed.ok) {
865
- return reconstructed;
866
- }
867
- let hostShare = null;
868
- let slope = null;
869
- let newDeviceShare = null;
870
- try {
871
- hostShare = decodeBase64Share(shareState.hostShare);
872
- if (!hostShare || hostShare.length !== CUSTODY_SECRET_BYTES) {
873
- return {
874
- ok: false,
875
- code: "custody_shares_invalid",
876
- message: "stored split-key share payload is invalid"
877
- };
878
- }
879
- const activeDevices = listActiveEnrolledDevices(shareState);
880
- const shareX = allocateNextDeviceShareX(activeDevices);
881
- slope = Buffer.alloc(reconstructed.signingKey.length, 0);
882
- for (let i = 0; i < reconstructed.signingKey.length; i += 1) {
883
- slope[i] = (reconstructed.signingKey[i] ?? 0) ^ (hostShare[i] ?? 0);
884
- }
885
- newDeviceShare = deriveSecretShamirShare(reconstructed.signingKey, slope, shareX);
886
- const updatedAt = new Date().toISOString();
887
- const newDevice = {
888
- id: createCustodyDeviceId(),
889
- label: normalizeDeviceLabel(params?.label) ?? `Device ${activeDevices.length + 1}`,
890
- shareX,
891
- shareHash: sha256Hex(newDeviceShare),
892
- createdAt: updatedAt
893
- };
894
- const record = encodeCustodyShareStateV3({
895
- shareState,
896
- walletId: scope.walletId,
897
- role: scope.role,
898
- hostShare,
899
- recoveryShareHash: shareState.recoveryShareHash,
900
- secretChecksum: shareState.secretChecksum,
901
- createdAt: shareState.createdAt,
902
- updatedAt,
903
- devices: [...activeDevices, newDevice]
904
- });
905
- saveShareState(record, scope.walletId, env);
906
- return {
907
- ok: true,
908
- walletId: scope.walletId,
909
- role: scope.role,
910
- deviceId: newDevice.id,
911
- label: newDevice.label,
912
- deviceShare: encodeTypedCustodyShare("device", newDeviceShare),
913
- statePath: resolveCustodyShareStatePath(scope.walletId, env),
914
- updatedAt
915
- };
916
- } finally {
917
- zeroizeBuffers([
918
- reconstructed.signingKey,
919
- hostShare,
920
- slope,
921
- newDeviceShare
922
- ]);
923
- }
924
- }
925
- function revokeWalletCustodyDevice(params) {
926
- const env = params?.env ?? process.env;
927
- const scope = resolveWalletCustodyScope({
928
- wallet: params?.wallet,
929
- cfg: params?.cfg,
930
- env,
931
- walletId: params?.walletId
932
- });
933
- const shareState = loadShareState(scope.walletId, env);
934
- if (!shareState) {
935
- return {
936
- ok: false,
937
- code: "custody_ceremony_required",
938
- message: "split-key custody ceremony is not initialized"
939
- };
940
- }
941
- if (shareState.scheme === "xor-3of3-v1") {
942
- return {
943
- ok: false,
944
- code: "custody_scheme_unsupported",
945
- message: "legacy custody scheme does not support per-device revocation; recover the wallet first"
946
- };
947
- }
948
- const targetDeviceId = String(params?.deviceId ?? "").trim();
949
- if (!targetDeviceId) {
950
- return {
951
- ok: false,
952
- code: "custody_device_id_required",
953
- message: "device id is required"
954
- };
955
- }
956
- const reconstructed = reconstructCustodySigningKey({
957
- env,
958
- walletId: scope.walletId,
959
- deviceShare: params?.deviceShare,
960
- recoveryShare: params?.recoveryShare
961
- });
962
- if (!reconstructed.ok) {
963
- return reconstructed;
964
- }
965
- let hostShare = null;
966
- try {
967
- hostShare = decodeBase64Share(shareState.hostShare);
968
- if (!hostShare || hostShare.length !== CUSTODY_SECRET_BYTES) {
969
- return {
970
- ok: false,
971
- code: "custody_shares_invalid",
972
- message: "stored split-key share payload is invalid"
973
- };
974
- }
975
- const activeDevices = listActiveEnrolledDevices(shareState);
976
- const target = activeDevices.find((device) => device.id === targetDeviceId) ?? null;
977
- if (!target) {
978
- return {
979
- ok: false,
980
- code: "custody_device_not_found",
981
- message: "device is not enrolled for this wallet"
982
- };
983
- }
984
- if (activeDevices.length <= 1) {
985
- return {
986
- ok: false,
987
- code: "custody_last_device_forbidden",
988
- message: "cannot revoke the last enrolled device; recover the wallet instead"
989
- };
990
- }
991
- const updatedAt = new Date().toISOString();
992
- const record = encodeCustodyShareStateV3({
993
- shareState,
994
- walletId: scope.walletId,
995
- role: scope.role,
996
- hostShare,
997
- recoveryShareHash: shareState.recoveryShareHash,
998
- secretChecksum: shareState.secretChecksum,
999
- createdAt: shareState.createdAt,
1000
- updatedAt,
1001
- devices: activeDevices.filter((device) => device.id !== targetDeviceId)
1002
- });
1003
- saveShareState(record, scope.walletId, env);
1004
- return {
1005
- ok: true,
1006
- walletId: scope.walletId,
1007
- role: scope.role,
1008
- removedDeviceId: target.id,
1009
- removedDeviceLabel: target.label,
1010
- updatedAt
1011
- };
1012
- } finally {
1013
- zeroizeBuffers([reconstructed.signingKey, hostShare]);
1014
- }
1015
- }
1016
- function loadState(walletId, env = process.env) {
1017
- const filePath = locateCustodyStatePath(walletId, env);
1018
- if (!syncFs.existsSync(filePath)) {
1019
- return {
1020
- version: 1,
1021
- unlockSessions: []
1022
- };
1023
- }
1024
- try {
1025
- const parsed = JSON.parse(syncFs.readFileSync(filePath, "utf8"));
1026
- if (parsed.version !== 1 || !Array.isArray(parsed.unlockSessions)) {
1027
- return {
1028
- version: 1,
1029
- unlockSessions: []
1030
- };
1031
- }
1032
- return {
1033
- version: 1,
1034
- unlockSessions: parsed.unlockSessions.map((entry) => {
1035
- if (!entry || typeof entry !== "object") {
1036
- return null;
1037
- }
1038
- const value = entry;
1039
- if (typeof value.id !== "string" || typeof value.host !== "string" || typeof value.walletId !== "string" || typeof value.createdAt !== "string" || typeof value.expiresAt !== "string") {
1040
- return null;
1041
- }
1042
- const chains = normalizeChainList(Array.isArray(value.chains) ? (value.chains ?? []).map((entry) => String(entry)) : []);
1043
- const allowPrograms = normalizeAddressList(Array.isArray(value.allowPrograms) ? (value.allowPrograms ?? []).map((entry) => String(entry)) : []);
1044
- return {
1045
- id: value.id,
1046
- host: normalizeHost(value.host),
1047
- walletId: value.walletId.trim() || "default",
1048
- role: normalizeWalletCustodyRole(value.role) ?? "vault",
1049
- chains,
1050
- allowPrograms,
1051
- solanaMaxPerTx: typeof value.solanaMaxPerTx === "string" ? value.solanaMaxPerTx : "0",
1052
- solanaMaxDaily: typeof value.solanaMaxDaily === "string" ? value.solanaMaxDaily : "0",
1053
- createdAt: value.createdAt,
1054
- expiresAt: value.expiresAt,
1055
- lastUsedAt: typeof value.lastUsedAt === "string" ? value.lastUsedAt : undefined
1056
- };
1057
- }).filter(isNonNull)
1058
- };
1059
- } catch {
1060
- return {
1061
- version: 1,
1062
- unlockSessions: []
1063
- };
1064
- }
1065
- }
1066
- function saveState(state, walletId, env = process.env) {
1067
- const filePath = resolveCustodyStatePath(walletId, env);
1068
- syncFs.mkdirSync(path.dirname(filePath), {
1069
- recursive: true,
1070
- mode: 448
1071
- });
1072
- syncFs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, {
1073
- encoding: "utf8",
1074
- mode: 384
1075
- });
1076
- try {
1077
- syncFs.chmodSync(filePath, 384);
1078
- } catch {}
1079
- }
1080
- function pruneState(state) {
1081
- const now = nowMs();
1082
- const active = [];
1083
- let changed = false;
1084
- for (const entry of state.unlockSessions) {
1085
- const expiresAt = Date.parse(entry.expiresAt);
1086
- if (Number.isFinite(expiresAt) && expiresAt > now) {
1087
- active.push(entry);
1088
- continue;
1089
- }
1090
- removeUnlockMaterial(entry.id);
1091
- changed = true;
1092
- }
1093
- if (changed) {
1094
- state.unlockSessions = active;
1095
- }
1096
- return changed;
1097
- }
1098
- function getActiveUnlockSession(params) {
1099
- const env = params.env ?? process.env;
1100
- const host = normalizeHost(params.host);
1101
- if (!host) {
1102
- return null;
1103
- }
1104
- const state = loadState(params.walletId, env);
1105
- const changed = pruneState(state);
1106
- const active = state.unlockSessions.find((entry) => entry.host === host && entry.walletId === params.walletId) ?? null;
1107
- if (changed) {
1108
- saveState(state, params.walletId, env);
1109
- }
1110
- return active;
1111
- }
1112
- function touchUnlockSession(params) {
1113
- const env = params.env ?? process.env;
1114
- const state = loadState(params.walletId, env);
1115
- const changed = pruneState(state);
1116
- const session = state.unlockSessions.find((entry) => entry.id === params.sessionId) ?? null;
1117
- if (!session) {
1118
- if (changed) {
1119
- saveState(state, params.walletId, env);
1120
- }
1121
- removeUnlockMaterial(params.sessionId);
1122
- return null;
1123
- }
1124
- session.lastUsedAt = new Date().toISOString();
1125
- saveState(state, params.walletId, env);
1126
- return session;
1127
- }
1128
- function openUnlockSession(params) {
1129
- const env = params.env ?? process.env;
1130
- const state = loadState(params.scope.walletId, env);
1131
- pruneState(state);
1132
- const createdAt = new Date().toISOString();
1133
- const expiresAt = resolveUnlockExpiresAt(env, params.ttlSeconds);
1134
- const session = {
1135
- id: randomBytes(12).toString("hex"),
1136
- host: normalizeHost(params.host),
1137
- walletId: params.scope.walletId,
1138
- role: params.scope.role,
1139
- chains: params.scope.chains,
1140
- allowPrograms: params.scope.allowPrograms,
1141
- solanaMaxPerTx: params.scope.solana.maxPerTx,
1142
- solanaMaxDaily: params.scope.solana.maxDaily,
1143
- createdAt,
1144
- expiresAt,
1145
- lastUsedAt: createdAt
1146
- };
1147
- for (const existing of state.unlockSessions) {
1148
- if (existing.walletId === session.walletId) {
1149
- removeUnlockMaterial(existing.id);
1150
- }
1151
- }
1152
- state.unlockSessions = state.unlockSessions.filter((entry) => entry.walletId !== session.walletId);
1153
- state.unlockSessions.push(session);
1154
- saveState(state, params.scope.walletId, env);
1155
- return session;
1156
- }
1157
- function clearLocalUnlockSessions(params) {
1158
- const env = params?.env ?? process.env;
1159
- const requestedHost = params?.host?.trim() ? normalizeHost(params.host) : "";
1160
- const requestedWalletId = params?.walletId?.trim() || "";
1161
- if (params?.host?.trim() && !requestedHost) {
1162
- return {
1163
- ok: false,
1164
- code: "invalid_host",
1165
- message: "host is invalid for custody lock"
1166
- };
1167
- }
1168
- let removed = 0;
1169
- let remaining = 0;
1170
- const targets = requestedWalletId ? [requestedWalletId] : listCustodyStateTargets(env);
1171
- for (const target of targets) {
1172
- const state = loadState(target, env);
1173
- const changedByPrune = pruneState(state);
1174
- const before = state.unlockSessions.length;
1175
- if (!requestedHost && !requestedWalletId) {
1176
- for (const session of state.unlockSessions) {
1177
- removeUnlockMaterial(session.id);
1178
- }
1179
- state.unlockSessions = [];
1180
- } else {
1181
- state.unlockSessions = state.unlockSessions.filter((session) => {
1182
- if (requestedHost && session.host !== requestedHost) {
1183
- return true;
1184
- }
1185
- if (requestedWalletId && session.walletId !== requestedWalletId) {
1186
- return true;
1187
- }
1188
- removeUnlockMaterial(session.id);
1189
- return false;
1190
- });
1191
- }
1192
- removed += before - state.unlockSessions.length;
1193
- remaining += state.unlockSessions.length;
1194
- if (changedByPrune || before !== state.unlockSessions.length) {
1195
- saveState(state, target, env);
1196
- }
1197
- }
1198
- return {
1199
- ok: true,
1200
- host: requestedHost || undefined,
1201
- walletId: requestedWalletId || undefined,
1202
- removed,
1203
- remaining
1204
- };
1205
- }
1206
- function recoverWalletCustodyPassphrase(params) {
1207
- const reconstructed = reconstructCustodySigningKey({
1208
- env: params?.env ?? process.env,
1209
- deviceShare: params?.deviceShare,
1210
- recoveryShare: params?.recoveryShare,
1211
- walletId: params?.walletId
1212
- });
1213
- if (!reconstructed.ok) {
1214
- return reconstructed;
1215
- }
1216
- try {
1217
- return {
1218
- ok: true,
1219
- passphrase: Buffer.from(reconstructed.signingKey).toString("base64url")
1220
- };
1221
- } finally {
1222
- zeroizeBuffers([reconstructed.signingKey]);
1223
- }
1224
- }
1225
- async function lockWalletCustodyUnlockSessions(params) {
1226
- const env = params?.env ?? process.env;
1227
- const localResult = clearLocalUnlockSessions({
1228
- host: params?.host,
1229
- walletId: params?.walletId,
1230
- env
1231
- });
1232
- if (!localResult.ok) {
1233
- return localResult;
1234
- }
1235
- const splitKeyActive = String(env.FASED_WALLET_CUSTODY_MODE ?? "").trim().toLowerCase() === "split-key" && readBooleanFlag(env, "FASED_WALLET_CUSTODY_PASSKEY_CEREMONY") && readBooleanFlag(env, "FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION") && readBooleanFlag(env, "FASED_WALLET_CUSTODY_PHASE2_COMPLETE");
1236
- if (!splitKeyActive) {
1237
- return localResult;
1238
- }
1239
- try {
1240
- await lockLocalSignerCustody({
1241
- host: localResult.host,
1242
- walletId: localResult.walletId,
1243
- env
1244
- });
1245
- return localResult;
1246
- } catch (err) {
1247
- return {
1248
- ok: false,
1249
- code: "custody_signer_lock_failed",
1250
- message: walletDiagnosticErrorMessage(err),
1251
- host: localResult.host,
1252
- walletId: localResult.walletId,
1253
- removed: localResult.removed,
1254
- remaining: localResult.remaining
1255
- };
1256
- }
1257
- }
1258
- function reconstructCustodySigningKey(params) {
1259
- const env = params.env ?? process.env;
1260
- const shareState = loadShareState(params.walletId, env);
1261
- if (!shareState) {
1262
- return {
1263
- ok: false,
1264
- code: "custody_ceremony_required",
1265
- message: "split-key custody ceremony is not initialized"
1266
- };
1267
- }
1268
- let hot = null;
1269
- let cold = null;
1270
- let host = null;
1271
- let providedShare = null;
1272
- let secret = null;
1273
- try {
1274
- if (shareState.scheme === "xor-3of3-v1") {
1275
- hot = decodeBase64Share(shareState.hotShare);
1276
- cold = decodeBase64Share(shareState.coldShare);
1277
- providedShare = decodeTypedCustodyShare(params.deviceShare?.trim() || String(env.FASED_WALLET_CUSTODY_DEVICE_SHARE ?? "").trim()).bytes;
1278
- if (!hot || !cold || hot.length !== shareState.secretBytes || cold.length !== shareState.secretBytes) {
1279
- return {
1280
- ok: false,
1281
- code: "custody_shares_invalid",
1282
- message: "stored split-key share payload is invalid"
1283
- };
1284
- }
1285
- if (!providedShare || providedShare.length !== shareState.secretBytes) {
1286
- return {
1287
- ok: false,
1288
- code: "custody_device_share_required",
1289
- message: "device share is required to unlock split-key custody"
1290
- };
1291
- }
1292
- const deviceHash = sha256Hex(providedShare);
1293
- if (!buffersEqualHex(deviceHash, shareState.deviceShareHash)) {
1294
- return {
1295
- ok: false,
1296
- code: "custody_device_share_mismatch",
1297
- message: "device share does not match split-key ceremony state"
1298
- };
1299
- }
1300
- secret = xorBuffers([
1301
- hot,
1302
- cold,
1303
- providedShare
1304
- ]);
1305
- } else {
1306
- host = decodeBase64Share(shareState.hostShare);
1307
- const provided = decodeTypedCustodyShare(params.deviceShare?.trim() || params.recoveryShare?.trim() || String(env.FASED_WALLET_CUSTODY_DEVICE_SHARE ?? "").trim());
1308
- providedShare = provided.bytes;
1309
- if (!host || host.length !== shareState.secretBytes) {
1310
- return {
1311
- ok: false,
1312
- code: "custody_shares_invalid",
1313
- message: "stored split-key share payload is invalid"
1314
- };
1315
- }
1316
- if (!providedShare || providedShare.length !== shareState.secretBytes) {
1317
- return {
1318
- ok: false,
1319
- code: "custody_device_share_required",
1320
- message: "device share or recovery share is required to unlock split-key custody"
1321
- };
1322
- }
1323
- const providedHash = sha256Hex(providedShare);
1324
- const activeDevices = listActiveEnrolledDevices(shareState);
1325
- const matchingDevice = shareState.version === 3 ? activeDevices.find((device) => buffersEqualHex(providedHash, device.shareHash)) ?? null : buffersEqualHex(providedHash, shareState.deviceShareHash) ? activeDevices[0] ?? null : null;
1326
- const matchesRecoveryShare = buffersEqualHex(providedHash, shareState.recoveryShareHash);
1327
- const shareType = matchesRecoveryShare ? "recovery" : matchingDevice ? "device" : null;
1328
- if (!shareType) {
1329
- return {
1330
- ok: false,
1331
- code: "custody_device_share_mismatch",
1332
- message: "provided custody share does not match device or recovery share state"
1333
- };
1334
- }
1335
- if (provided.type !== "unknown" && provided.type !== shareType) {
1336
- return {
1337
- ok: false,
1338
- code: "custody_device_share_mismatch",
1339
- message: "provided custody share type does not match stored ceremony state"
1340
- };
1341
- }
1342
- secret = reconstructSecretShamir2ofN({
1343
- hostShare: host,
1344
- otherShare: providedShare,
1345
- otherX: shareType === "recovery" ? 3 : matchingDevice?.shareX ?? 2
1346
- });
1347
- }
1348
- if (!buffersEqualHex(sha256Hex(secret), shareState.secretChecksum)) {
1349
- return {
1350
- ok: false,
1351
- code: "custody_share_integrity_failed",
1352
- message: "split-key reconstruction failed integrity check"
1353
- };
1354
- }
1355
- return {
1356
- ok: true,
1357
- signingKey: Buffer.from(secret)
1358
- };
1359
- } finally {
1360
- zeroizeBuffers([
1361
- hot,
1362
- cold,
1363
- host,
1364
- providedShare,
1365
- secret
1366
- ]);
1367
- }
1368
- }
1369
- async function activateWalletCustodyUnlockSession(params) {
1370
- const env = params.env ?? process.env;
1371
- const host = normalizeHost(params.host);
1372
- if (!host) {
1373
- return {
1374
- ok: false,
1375
- code: "invalid_host",
1376
- message: "host is required for custody unlock"
1377
- };
1378
- }
1379
- const consumed = consumeWalletApprovalGrant({
1380
- host,
1381
- operation: "wallet.custody-unlock",
1382
- token: params.approvalToken,
1383
- env,
1384
- cfg: params.cfg
1385
- });
1386
- if (!consumed.ok) {
1387
- return consumed;
1388
- }
1389
- const scope = params.wallet && params.cfg ? resolveWalletCustodyScope({
1390
- wallet: params.wallet,
1391
- cfg: params.cfg,
1392
- env,
1393
- walletId: params.walletId
1394
- }) : {
1395
- walletId: params.walletId?.trim() || "default",
1396
- role: "vault",
1397
- chains: ["solana"],
1398
- allowPrograms: [],
1399
- solana: {
1400
- maxPerTx: "0",
1401
- maxDaily: "0"
1402
- }
1403
- };
1404
- const status = params.wallet ? readWalletCustodyStatus({
1405
- wallet: params.wallet,
1406
- env,
1407
- cfg: params.cfg,
1408
- walletId: scope.walletId
1409
- }) : null;
1410
- let custodyPassphrase = "";
1411
- if (status?.mode === "split-key-active") {
1412
- const reconstructed = recoverWalletCustodyPassphrase({
1413
- env,
1414
- deviceShare: params.deviceShare,
1415
- walletId: scope.walletId
1416
- });
1417
- if (!reconstructed.ok) {
1418
- return reconstructed;
1419
- }
1420
- custodyPassphrase = reconstructed.passphrase;
1421
- }
1422
- const session = openUnlockSession({
1423
- host,
1424
- env,
1425
- scope,
1426
- ttlSeconds: params.ttlSeconds
1427
- });
1428
- if (custodyPassphrase) {
1429
- try {
1430
- await unlockLocalSignerCustody({
1431
- sessionId: session.id,
1432
- host: session.host,
1433
- walletId: session.walletId,
1434
- role: session.role,
1435
- chains: session.chains,
1436
- allowPrograms: session.allowPrograms,
1437
- expiresAt: session.expiresAt,
1438
- passphrase: custodyPassphrase,
1439
- solanaMaxPerTx: session.solanaMaxPerTx,
1440
- solanaMaxDaily: session.solanaMaxDaily,
1441
- env
1442
- });
1443
- } catch (err) {
1444
- clearLocalUnlockSessions({
1445
- host: session.host,
1446
- walletId: session.walletId,
1447
- env
1448
- });
1449
- return {
1450
- ok: false,
1451
- code: "custody_signer_unlock_failed",
1452
- message: walletDiagnosticErrorMessage(err)
1453
- };
1454
- }
1455
- }
1456
- setUnlockMaterial({
1457
- sessionId: session.id,
1458
- host: session.host,
1459
- expiresAt: session.expiresAt
1460
- });
1461
- return {
1462
- ok: true,
1463
- session
1464
- };
1465
- }
1466
- async function refreshWalletCustodyUnlockSession(params) {
1467
- const env = params.env ?? process.env;
1468
- const host = normalizeHost(params.host);
1469
- if (!host) {
1470
- return {
1471
- ok: false,
1472
- code: "invalid_host",
1473
- message: "host is required for custody refresh"
1474
- };
1475
- }
1476
- const scope = params.wallet && params.cfg ? resolveWalletCustodyScope({
1477
- wallet: params.wallet,
1478
- cfg: params.cfg,
1479
- env,
1480
- walletId: params.walletId
1481
- }) : {
1482
- walletId: params.walletId?.trim() || "default",
1483
- role: "vault",
1484
- chains: ["solana"],
1485
- allowPrograms: [],
1486
- solana: {
1487
- maxPerTx: "0",
1488
- maxDaily: "0"
1489
- }
1490
- };
1491
- const active = getActiveUnlockSession({
1492
- host,
1493
- walletId: scope.walletId,
1494
- env
1495
- });
1496
- if (!active) {
1497
- return {
1498
- ok: false,
1499
- code: "custody_unlock_required",
1500
- message: `split-key custody for wallet ${scope.walletId} is locked`
1501
- };
1502
- }
1503
- const status = params.wallet ? readWalletCustodyStatus({
1504
- wallet: params.wallet,
1505
- env,
1506
- cfg: params.cfg,
1507
- walletId: scope.walletId
1508
- }) : null;
1509
- let custodyPassphrase = "";
1510
- if (status?.mode === "split-key-active") {
1511
- const reconstructed = recoverWalletCustodyPassphrase({
1512
- env,
1513
- deviceShare: params.deviceShare,
1514
- walletId: scope.walletId
1515
- });
1516
- if (!reconstructed.ok) {
1517
- return reconstructed;
1518
- }
1519
- custodyPassphrase = reconstructed.passphrase;
1520
- }
1521
- const session = openUnlockSession({
1522
- host,
1523
- env,
1524
- scope,
1525
- ttlSeconds: params.ttlSeconds
1526
- });
1527
- if (custodyPassphrase) {
1528
- try {
1529
- await unlockLocalSignerCustody({
1530
- sessionId: session.id,
1531
- host: session.host,
1532
- walletId: session.walletId,
1533
- role: session.role,
1534
- chains: session.chains,
1535
- allowPrograms: session.allowPrograms,
1536
- expiresAt: session.expiresAt,
1537
- passphrase: custodyPassphrase,
1538
- solanaMaxPerTx: session.solanaMaxPerTx,
1539
- solanaMaxDaily: session.solanaMaxDaily,
1540
- env
1541
- });
1542
- } catch (err) {
1543
- clearLocalUnlockSessions({
1544
- host: session.host,
1545
- walletId: session.walletId,
1546
- env
1547
- });
1548
- return {
1549
- ok: false,
1550
- code: "custody_signer_unlock_failed",
1551
- message: walletDiagnosticErrorMessage(err)
1552
- };
1553
- }
1554
- }
1555
- setUnlockMaterial({
1556
- sessionId: session.id,
1557
- host: session.host,
1558
- expiresAt: session.expiresAt
1559
- });
1560
- return {
1561
- ok: true,
1562
- session
1563
- };
1564
- }
1565
- async function enforceWalletCustodyForAutonomousSend(params) {
1566
- const env = params.env ?? process.env;
1567
- const scope = resolveWalletCustodyScope({
1568
- wallet: params.wallet,
1569
- cfg: params.cfg,
1570
- env,
1571
- walletId: params.walletId
1572
- });
1573
- const status = readWalletCustodyStatus({
1574
- wallet: params.wallet,
1575
- env,
1576
- cfg: params.cfg,
1577
- walletId: scope.walletId
1578
- });
1579
- if (status.mode === "single-key") {
1580
- return {
1581
- ok: true,
1582
- custodyMode: status.mode
1583
- };
1584
- }
1585
- if (status.mode !== "split-key-active") {
1586
- return {
1587
- ok: false,
1588
- code: "custody_phase2_incomplete",
1589
- message: "split-key custody is configured but phase-2 controls are incomplete; autonomous signing is blocked"
1590
- };
1591
- }
1592
- const runtimeGuard = enforceSplitKeyRuntimeGuard({
1593
- wallet: params.wallet,
1594
- cfg: params.cfg,
1595
- env
1596
- });
1597
- if (!runtimeGuard.ok) {
1598
- return runtimeGuard;
1599
- }
1600
- if (resolveWalletApprovalAuthMode(env, params.cfg) !== "webauthn") {
1601
- return {
1602
- ok: false,
1603
- code: "custody_webauthn_required",
1604
- message: "split-key custody requires wallet approval auth mode=webauthn"
1605
- };
1606
- }
1607
- const host = normalizeHost(params.approvalHost?.trim() || "127.0.0.1");
1608
- const active = getActiveUnlockSession({
1609
- host,
1610
- walletId: scope.walletId,
1611
- env
1612
- });
1613
- if (active) {
1614
- touchUnlockSession({
1615
- sessionId: active.id,
1616
- walletId: scope.walletId,
1617
- env
1618
- });
1619
- if (!hasActiveUnlockMaterial({
1620
- sessionId: active.id,
1621
- host
1622
- })) {
1623
- return {
1624
- ok: false,
1625
- code: "custody_unlock_required",
1626
- message: "split-key unlock session exists without ephemeral signing material; unlock custody again"
1627
- };
1628
- }
1629
- return {
1630
- ok: true,
1631
- custodyMode: status.mode,
1632
- session: active
1633
- };
1634
- }
1635
- const token = params.approvalToken?.trim() ?? "";
1636
- if (!token) {
1637
- return {
1638
- ok: false,
1639
- code: "custody_unlock_required",
1640
- message: "split-key custody requires an active unlock session; provide a passkey approval token for operation wallet.custody-unlock"
1641
- };
1642
- }
1643
- const activated = await activateWalletCustodyUnlockSession({
1644
- host,
1645
- approvalToken: token,
1646
- env,
1647
- cfg: params.cfg,
1648
- wallet: params.wallet,
1649
- walletId: scope.walletId,
1650
- deviceShare: params.deviceShare
1651
- });
1652
- if (!activated.ok) {
1653
- return activated;
1654
- }
1655
- return {
1656
- ok: true,
1657
- custodyMode: status.mode,
1658
- session: activated.session
1659
- };
1660
- }
1661
- function enforceSplitKeyRuntimeGuard(params) {
1662
- const env = params.env ?? process.env;
1663
- if (!params.cfg) {
1664
- return {
1665
- ok: false,
1666
- code: "custody_config_required",
1667
- message: "split-key custody requires runtime config to verify signer backend"
1668
- };
1669
- }
1670
- const providerId = resolveWalletProviderId(params.cfg, env);
1671
- if (providerId !== "local-socket-signer") {
1672
- return {
1673
- ok: false,
1674
- code: "custody_provider_unsupported",
1675
- message: "split-key custody requires local-socket-signer"
1676
- };
1677
- }
1678
- if (!params.wallet.enabled) {
1679
- return {
1680
- ok: false,
1681
- code: "custody_stack_unconfigured",
1682
- message: "split-key custody requires wallet runtime to be enabled"
1683
- };
1684
- }
1685
- return { ok: true };
1686
- }
1687
- function readWalletCustodyStatus(params) {
1688
- const env = params.env ?? process.env;
1689
- const scope = resolveWalletCustodyScope({
1690
- wallet: params.wallet,
1691
- cfg: params.cfg,
1692
- env,
1693
- walletId: params.walletId
1694
- });
1695
- const requestedSplitKey = String(env.FASED_WALLET_CUSTODY_MODE ?? "").trim().toLowerCase() === "split-key";
1696
- const passkeyCeremonyEnabled = readBooleanFlag(env, "FASED_WALLET_CUSTODY_PASSKEY_CEREMONY");
1697
- const ephemeralReconstructionEnabled = readBooleanFlag(env, "FASED_WALLET_CUSTODY_EPHEMERAL_RECONSTRUCTION");
1698
- const phase2CompleteFlag = readBooleanFlag(env, "FASED_WALLET_CUSTODY_PHASE2_COMPLETE");
1699
- const shareState = loadShareState(scope.walletId, env);
1700
- const ceremonyInitialized = Boolean(shareState);
1701
- const custodyWallets = readCustodyWalletSet(env);
1702
- const roleAllowsSplitKey = scope.role === "vault";
1703
- const splitKeyEnabled = requestedSplitKey && roleAllowsSplitKey && (custodyWallets.size === 0 ? ceremonyInitialized : custodyWallets.has(normalizeWalletIdForCustodyEnv(scope.walletId)));
1704
- const splitKeyActive = splitKeyEnabled && phase2CompleteFlag && passkeyCeremonyEnabled && ephemeralReconstructionEnabled && ceremonyInitialized;
1705
- const complete = splitKeyActive;
1706
- const activeUnlock = getActiveUnlockSession({
1707
- host: String(params.approvalHost ?? env.FASED_WALLET_CUSTODY_ACTIVE_HOST ?? env.FASED_A2A_ORIGIN ?? "127.0.0.1"),
1708
- walletId: scope.walletId,
1709
- env
1710
- });
1711
- const notes = [];
1712
- if (splitKeyEnabled && !splitKeyActive) {
1713
- notes.push("split-key custody is configured but full runtime prerequisites are not complete.");
1714
- }
1715
- if (!splitKeyEnabled) {
1716
- notes.push("single-key custody active (phase-2 split-key disabled).");
1717
- }
1718
- if (splitKeyEnabled && !passkeyCeremonyEnabled) {
1719
- notes.push("split-key mode enabled without passkey ceremony flag.");
1720
- }
1721
- if (splitKeyEnabled && !ephemeralReconstructionEnabled) {
1722
- notes.push("split-key mode enabled without ephemeral reconstruction flag.");
1723
- }
1724
- if (splitKeyEnabled && !phase2CompleteFlag) {
1725
- notes.push("split-key mode enabled without phase-2 completion flag.");
1726
- }
1727
- if (splitKeyEnabled && !ceremonyInitialized) {
1728
- notes.push(`split-key ceremony is not initialized for wallet ${scope.walletId}; run \`fased wallet custody-init --wallet ${scope.walletId}\` to generate host/device/recovery shares.`);
1729
- }
1730
- if (splitKeyActive && activeUnlock) {
1731
- notes.push(`split-key custody unlock session is active for wallet ${scope.walletId}.`);
1732
- if (!hasActiveUnlockMaterial({
1733
- sessionId: activeUnlock.id,
1734
- host: activeUnlock.host
1735
- })) {
1736
- notes.push("unlock session exists but ephemeral signing material is missing; unlock again.");
1737
- }
1738
- }
1739
- return {
1740
- mode: splitKeyActive ? "split-key-active" : splitKeyEnabled ? "split-key-scaffold" : "single-key",
1741
- target: {
1742
- walletId: scope.walletId,
1743
- role: scope.role
1744
- },
1745
- scope: {
1746
- chains: scope.chains,
1747
- allowPrograms: scope.allowPrograms,
1748
- solana: scope.solana
1749
- },
1750
- unlock: {
1751
- active: Boolean(activeUnlock),
1752
- sessionId: activeUnlock?.id,
1753
- host: activeUnlock?.host,
1754
- expiresAt: activeUnlock?.expiresAt
1755
- },
1756
- phase2: {
1757
- complete,
1758
- splitKeyEnabled,
1759
- passkeyCeremonyEnabled,
1760
- ephemeralReconstructionEnabled,
1761
- notes
1762
- },
1763
- ceremony: {
1764
- initialized: ceremonyInitialized,
1765
- scheme: shareState?.scheme,
1766
- secretBytes: shareState?.secretBytes,
1767
- devices: listActiveEnrolledDevices(shareState).map((device) => ({
1768
- id: device.id,
1769
- label: device.label,
1770
- createdAt: device.createdAt,
1771
- revokedAt: device.revokedAt
1772
- })),
1773
- updatedAt: shareState?.updatedAt,
1774
- path: locateCustodyShareStatePath(scope.walletId, env)
1775
- }
1776
- };
1777
- }
1778
- function listSplitKeyWalletCustodyStatuses(params) {
1779
- const env = params.env ?? process.env;
1780
- const walletIds = new Set();
1781
- const registry = readWalletProviderRegistry(env);
1782
- const registryWalletIds = new Set(registry.wallets.map((wallet) => wallet.id.trim()).filter(Boolean));
1783
- const normalizedRegistryWalletIds = new Set([...registryWalletIds].map((walletId) => normalizeWalletIdForCustodyEnv(walletId)));
1784
- const hasNamedWalletRegistry = registry.wallets.length > 0;
1785
- const configuredMiningWalletId = typeof params.cfg?.plugins?.entries?.["sat-mining"]?.config?.walletId === "string" ? params.cfg.plugins.entries["sat-mining"]?.config?.walletId.trim() : "";
1786
- const custodyWallets = readCustodyWalletSet(env);
1787
- for (const wallet of registry.wallets) {
1788
- if (wallet.id.trim()) {
1789
- walletIds.add(wallet.id.trim());
1790
- }
1791
- }
1792
- if (registry.defaultWalletId?.trim()) {
1793
- walletIds.add(registry.defaultWalletId.trim());
1794
- }
1795
- for (const raw of String(env.FASED_WALLET_CUSTODY_WALLETS ?? "").split(",")) {
1796
- if (raw.trim()) {
1797
- walletIds.add(raw.trim());
1798
- }
1799
- }
1800
- for (const target of listCustodyStateTargets(env)) {
1801
- walletIds.add(target);
1802
- }
1803
- if (walletIds.size === 0) {
1804
- walletIds.add(undefined);
1805
- }
1806
- const seen = new Set();
1807
- const out = [];
1808
- for (const walletId of walletIds) {
1809
- const walletIdValue = walletId?.trim() ?? "";
1810
- const normalizedTarget = normalizeWalletIdForCustodyEnv(walletId);
1811
- const shareState = loadShareState(walletId, env);
1812
- const isCurrentWallet = walletId === undefined || registryWalletIds.has(walletIdValue) || normalizedRegistryWalletIds.has(normalizedTarget) || registry.defaultWalletId?.trim() === walletIdValue || configuredMiningWalletId === walletIdValue || normalizeWalletIdForCustodyEnv(configuredMiningWalletId) === normalizedTarget || normalizedTarget === "default" || normalizedTarget === "vault" || !hasNamedWalletRegistry && (custodyWallets.has(normalizedTarget) || shareState?.role !== undefined);
1813
- if (!isCurrentWallet) {
1814
- continue;
1815
- }
1816
- const status = readWalletCustodyStatus({
1817
- wallet: params.wallet,
1818
- cfg: params.cfg,
1819
- env,
1820
- walletId
1821
- });
1822
- if (status.mode === "single-key") {
1823
- continue;
1824
- }
1825
- const key = normalizeWalletIdForState(status.target.walletId);
1826
- if (seen.has(key)) {
1827
- continue;
1828
- }
1829
- seen.add(key);
1830
- out.push(status);
1831
- }
1832
- return out;
1833
- }
1834
-
1835
- //#endregion
1836
- export { activateWalletCustodyUnlockSession, deleteWalletCustodyCeremony, enforceWalletCustodyForAutonomousSend, enrollWalletCustodyDevice, initializeWalletCustodyCeremony, listSplitKeyWalletCustodyStatuses, lockWalletCustodyUnlockSessions, readWalletCustodyStatus, recoverWalletCustodyCeremony, recoverWalletCustodyPassphrase, refreshWalletCustodyUnlockSession, revokeWalletCustodyDevice, withWalletCustodySigningMaterial };