@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.
- package/CHANGELOG.md +32 -0
- package/README.md +38 -30
- package/config/dependency-ownership.json +1 -1
- package/config/signer-policies/README.md +49 -0
- package/config/signer-policies/agent.json.template +32 -0
- package/config/signer-policies/mining.json.template +69 -0
- package/config/signer-policies/network.json.template +5 -0
- package/config/signer-policies/vault.json.template +70 -0
- package/config/signer-protocol-v2.json +76 -0
- package/config/typescript-noemit-baseline.json +680 -0
- package/dist/agents/auth-profiles/store.js +267 -31
- package/dist/agents/bash-tools.exec.js +10 -4
- package/dist/agents/current-model-catalog.js +3 -3
- package/dist/agents/memory-search.js +7 -1
- package/dist/agents/model-catalog-access.js +1 -2
- package/dist/agents/model-catalog-snapshot.js +1 -1
- package/dist/agents/model-catalog-source.js +1 -1
- package/dist/agents/model-metadata.js +1 -1
- package/dist/agents/model-selection.js +2 -28
- package/dist/agents/models-config.providers.js +6 -6
- package/dist/agents/pi-embedded-runner/compact.js +15 -7
- package/dist/agents/pi-embedded-runner/model.js +1 -1
- package/dist/agents/pi-embedded-runner/run/attempt.js +24 -13
- package/dist/agents/pi-embedded-runner/run.js +2 -0
- package/dist/agents/pi-embedded-runner/tool-split.js +40 -2
- package/dist/agents/pi-tool-definition-adapter.js +16 -1
- package/dist/agents/pi-tools.js +2 -1
- package/dist/agents/sandbox/config.js +1 -1
- package/dist/agents/sandbox/context.js +27 -2
- package/dist/agents/skills/env-overrides.js +38 -28
- package/dist/agents/skills/trust.js +93 -0
- package/dist/agents/skills/workspace.js +128 -0
- package/dist/agents/skills-clawhub.js +36 -11
- package/dist/agents/skills-marketplace-scan.js +49 -0
- package/dist/agents/tools/marketplace-offer-draft-tool.js +1 -1
- package/dist/agents/tools/mining-tool.js +1 -1
- package/dist/agents/tools/session-status-tool.js +1 -1
- package/dist/agents/tools/wallet-action-tool.js +172 -36
- package/dist/agents/tools/wallet-skill-policy.js +65 -23
- package/dist/agents/tools/wallet-tool.js +7 -11
- package/dist/auto-reply/reply/directive-handling.fast-lane.js +2 -1
- package/dist/auto-reply/reply/directive-handling.impl.js +2 -1
- package/dist/auto-reply/reply/directive-handling.model.js +2 -1
- package/dist/auto-reply/reply/directive-handling.persist.js +2 -2
- package/dist/auto-reply/reply/get-reply-directives-apply.js +2 -0
- package/dist/auto-reply/reply/get-reply-inline-actions.js +25 -1
- package/dist/auto-reply/reply/model-selection.js +7 -4
- package/dist/auto-reply/reply/session-reset-model.js +4 -4
- package/dist/brand.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/channel-web.js +2 -2
- package/dist/cli/gateway-cli/run.js +8 -0
- package/dist/cli/program/command-registry.js +1 -1
- package/dist/cli/program/register.onboard.js +2 -3
- package/dist/cli/program/register.wallet.js +16 -123
- package/dist/cli/providers-cli.js +1 -1
- package/dist/cli/sat-cli.js +129 -2
- package/dist/cli/update-cli/update-command.js +260 -11
- package/dist/commands/auth-choice.apply.api-providers.js +3 -3
- package/dist/commands/doctor-config-flow.js +1 -1
- package/dist/commands/doctor-legacy-config.js +2 -13
- package/dist/commands/model-picker.js +6 -6
- package/dist/commands/models/auth.js +1 -1
- package/dist/commands/onboard-auth.config-gateways.js +1 -1
- package/dist/commands/onboard-auth.credentials.js +32 -8
- package/dist/commands/onboard-non-interactive/local/wallet-config.js +28 -27
- package/dist/commands/onboard-non-interactive/local.js +5 -1
- package/dist/commands/wallet.js +568 -1911
- package/dist/config/config.js +2 -2
- package/dist/config/io.js +64 -2
- package/dist/config/schema.help.js +8 -7
- package/dist/config/schema.labels.js +5 -4
- package/dist/config/zod-schema.agent-runtime.js +1 -0
- package/dist/config/zod-schema.js +5 -0
- package/dist/control-ui/assets/{app-DLil9VO1.js → app-bR-fO5ty.js} +1511 -1555
- package/dist/control-ui/assets/{config-CU5Zi8Dh.js → config-LeImukCW.js} +10 -10
- package/dist/control-ui/assets/{federation-D10Q4eP_.js → federation-DgA9e-Pn.js} +364 -422
- package/dist/control-ui/assets/{index-DdE_NIXo.js → index-ckCxHwjy.js} +3 -3
- package/dist/control-ui/assets/{mining-BdhMn18r.js → mining-BgHFh0ei.js} +134 -114
- package/dist/control-ui/assets/mining-commit-DFpB_4JN.js +2 -0
- package/dist/control-ui/assets/{open-external-url-DwSiwMPl.js → open-external-url-ulF1B-sA.js} +3 -3
- package/dist/control-ui/assets/{providers-QMn4w2_f.js → providers-CIx9fxc6.js} +2 -2
- package/dist/control-ui/assets/{wallet-C2g294QT.js → wallet-DvStR_AT.js} +259 -377
- package/dist/control-ui/index.html +1 -1
- package/dist/control-ui/version.json +1 -1
- package/dist/daemon/systemd-system.js +56 -50
- package/dist/extensions/sat-mining/src/protocol-contract.js +43 -3
- package/dist/extensions/sat-mining/src/rpc-read.js +235 -67
- package/dist/extensions/sat-mining/src/sat-bond-layout.js +5 -2
- package/dist/extensions/sat-mining/src/signer-codec-manifest.js +406 -0
- package/dist/extensions/sat-mining/src/solana-submit.js +1038 -397
- package/dist/extensions/sat-mining/src/submission-ledger.js +349 -0
- package/dist/federation/auto-connect.js +98 -8
- package/dist/federation/marketplace-capability-adapter.js +33 -7
- package/dist/federation/marketplace-delivery-outbox.js +196 -0
- package/dist/federation/marketplace-delivery.js +351 -215
- package/dist/federation/marketplace-direct-payment.js +272 -141
- package/dist/federation/marketplace-escrow.js +607 -249
- package/dist/federation/marketplace-settlement-ledger.js +352 -0
- package/dist/federation/offers.js +9 -7
- package/dist/federation/peer-auth-v2.js +529 -0
- package/dist/gateway/a2a-http.js +425 -96
- package/dist/gateway/a2a-settlement.js +259 -153
- package/dist/gateway/a2a-task-store.js +524 -0
- package/dist/gateway/auth.js +38 -2
- package/dist/gateway/config-reload.js +46 -33
- package/dist/gateway/durable-capacity.js +31 -0
- package/dist/gateway/federation-http.js +540 -135
- package/dist/gateway/federation-marketplace-run-store.js +397 -0
- package/dist/gateway/federation-marketplace.js +535 -230
- package/dist/gateway/mutating-admin-rpc-rate-limit.js +5 -0
- package/dist/gateway/security-path.js +1 -1
- package/dist/gateway/server-http.js +1084 -1205
- package/dist/gateway/server-methods/models.js +1 -1
- package/dist/gateway/server-methods/secrets.js +40 -0
- package/dist/gateway/server-methods.js +1 -2
- package/dist/gateway/server-reload-handlers.js +196 -66
- package/dist/gateway/server-runtime-config.js +1 -1
- package/dist/gateway/server-secrets-runtime.js +288 -0
- package/dist/gateway/server.impl.js +73 -42
- package/dist/gateway/sessions-patch.js +3 -5
- package/dist/index.js +4 -2
- package/dist/infra/fasedhub.js +64 -1
- package/dist/infra/local-source-paired-update.js +538 -0
- package/dist/infra/restart.js +41 -10
- package/dist/infra/update-runner.js +96 -0
- package/dist/light-update-status.js +11 -11
- package/dist/memory/backend-config.js +2 -1
- package/dist/memory/session-files.js +16 -2
- package/dist/plugin-sdk/{accounts-DxblobYM.js → accounts-Brn3-uWN.js} +1 -1
- package/dist/plugin-sdk/{actions-D4UNimWa.js → actions-DRu135h0.js} +2 -2
- package/dist/plugin-sdk/{active-listener-CFuy106s.js → active-listener-B_NMtgPL.js} +1 -1
- package/dist/plugin-sdk/{audio-preflight-Bwi2Oq03.js → audio-preflight-BYxAt775.js} +27 -27
- package/dist/plugin-sdk/{audit-Cs0K7LBp.js → audit-C5d2z5U1.js} +1 -1
- package/dist/plugin-sdk/{channel-access-D5aHoTY8.js → channel-access-CcFRdRPO.js} +1 -1
- package/dist/plugin-sdk/{channel-access-configure-Dvch9mMQ.js → channel-access-configure-DaI5wevh.js} +1 -1
- package/dist/plugin-sdk/channel-plugin-common.d.ts +3 -3
- package/dist/plugin-sdk/channel-plugin-common.js +4 -4
- package/dist/plugin-sdk/{chunk-B0FUGc5A.js → chunk-CyoyqpbG.js} +1 -1
- package/dist/plugin-sdk/{chunk-items-DzT4L4Ty.js → chunk-items-DIRHG2gJ.js} +1 -1
- package/dist/plugin-sdk/command-status.d.ts +2 -2
- package/dist/plugin-sdk/command-status.js +27 -27
- package/dist/plugin-sdk/{commands-registry-DmDRhRAU.js → commands-registry-BfqdEWdF.js} +2 -2
- package/dist/plugin-sdk/{common-CfUOowOp.js → common-HCcXU-NR.js} +1 -1
- package/dist/plugin-sdk/{config-mxULv0fQ.js → config-DlWVeLFB.js} +7113 -6877
- package/dist/plugin-sdk/{config-schema-BrkmtFxB.js → config-schema-BOT7kHb6.js} +6779 -3696
- package/dist/plugin-sdk/device-pair.d.ts +1 -1
- package/dist/plugin-sdk/discord.d.ts +6 -6
- package/dist/plugin-sdk/discord.js +19 -19
- package/dist/plugin-sdk/file-lock.js +35 -4
- package/dist/plugin-sdk/{filesystem-manager-ZoHU8oA7.js → filesystem-manager-DoI8IBNF.js} +1 -1
- package/dist/plugin-sdk/{group-mentions-BHRff6j8.d.ts → group-mentions-q3hUTCt7.d.ts} +1 -1
- package/dist/plugin-sdk/{handle-action-DbVCF9HZ.js → handle-action-hvwapMKg.js} +13 -13
- package/dist/plugin-sdk/{helpers-BXbdgs5H.js → helpers-DMBU4oTo.js} +1 -1
- package/dist/plugin-sdk/{image-ops-D7jPxaph.js → image-ops-DQi0n_Tt.js} +1 -1
- package/dist/plugin-sdk/index.d.ts +21 -39
- package/dist/plugin-sdk/index.js +29 -29
- package/dist/plugin-sdk/{login-BRxww6nv.js → login-Ca583Okc.js} +3 -3
- package/dist/plugin-sdk/{login-qr-DJXsk9XH.js → login-qr-CGciBSNP.js} +3 -3
- package/dist/plugin-sdk/{manager-Y2S_5IlI.js → manager-BegWxNYX.js} +4 -4
- package/dist/plugin-sdk/{markdown-tables-DHLFQqaF.js → markdown-tables-DCOXEUfZ.js} +1 -1
- package/dist/plugin-sdk/{media-Czo4carg.js → media-CAdQF5TG.js} +47 -47
- package/dist/plugin-sdk/{memory-search-DTjIMw8J.js → memory-search-CrFPOeTH.js} +4 -1
- package/dist/plugin-sdk/{monitor-M_ta2lMc.js → monitor-BCFDaqJ-.js} +31 -31
- package/dist/plugin-sdk/{monitor-wYVncM9w.js → monitor-BgbjRiPM.js} +32 -32
- package/dist/plugin-sdk/{openresponses-http-B2GmeSt4.js → openresponses-http-DFf87SMM.js} +27 -27
- package/dist/plugin-sdk/{optional-runtime-dependency-DdMbxS24.js → optional-runtime-dependency-0dznm3ZO.js} +1 -1
- package/dist/plugin-sdk/{outbound-Db8mRN6P.js → outbound-BgVDdHxx.js} +6 -6
- package/dist/plugin-sdk/{outbound-attachment-BqFjcypb.js → outbound-attachment-QqTnIhZL.js} +2 -2
- package/dist/plugin-sdk/{pairing-message-B3bHYGaw.d.ts → pairing-message-CS-9BHug.d.ts} +2 -2
- package/dist/plugin-sdk/{pairing-message-B3GC_7CD.js → pairing-message-D0CoLHRJ.js} +1 -1
- package/dist/plugin-sdk/{pairing-store-CQikgIbw.js → pairing-store-BChahgI2.js} +2 -2
- package/dist/plugin-sdk/{pi-model-discovery-CY3m5zJP.js → pi-model-discovery-BX_JLcEJ.js} +1 -1
- package/dist/plugin-sdk/{pi-model-discovery-runtime-DSh1T2pn.js → pi-model-discovery-runtime-DxTMz_XF.js} +2 -2
- package/dist/plugin-sdk/provider-web-search-config-contract.d.ts +2 -2
- package/dist/plugin-sdk/{pw-ai-AFUqn-tQ.js → pw-ai-natjh3rm.js} +3 -3
- package/dist/plugin-sdk/{qmd-manager-DSyx77KY.js → qmd-manager-CgRaTxUq.js} +1 -1
- package/dist/plugin-sdk/{query-expansion-CAI2kKLQ.js → query-expansion-BG3FgzF7.js} +14 -3
- package/dist/plugin-sdk/{replies-Fzh9PhXl.js → replies-ChYo5ZQM.js} +2 -2
- package/dist/plugin-sdk/{resolve-outbound-target-KpF3co6T.js → resolve-outbound-target-D-KGgHJZ.js} +1 -1
- package/dist/plugin-sdk/{resolve-route-CDl74O19.js → resolve-route-C8tvQm8Z.js} +1 -1
- package/dist/plugin-sdk/{retry-policy-D4uiqUOG.js → retry-policy-C6SjKjQA.js} +1 -1
- package/dist/plugin-sdk/{runtime-Bb6KRnCC.js → runtime-2B9_WKoI.js} +8 -3
- package/dist/plugin-sdk/{runtime-dependency-DLDmB94f.js → runtime-dependency-DczkvPxn.js} +2 -2
- package/dist/plugin-sdk/sat-runtime.d.ts +549 -309
- package/dist/plugin-sdk/sat-runtime.js +29 -29
- package/dist/plugin-sdk/{send-C4k4JuJN.js → send-B6KkGKZD.js} +6 -6
- package/dist/plugin-sdk/{send-D-uDKGIq.js → send-CFzGKJXo.js} +6 -6
- package/dist/plugin-sdk/{send-Du8ykP1k.js → send-DCO441U7.js} +8 -8
- package/dist/plugin-sdk/{send-5nFktR1n.js → send-gDoGA-7e.js} +5 -5
- package/dist/plugin-sdk/{send-Bfzf11VY.js → send-q9HXrSwK.js} +7 -7
- package/dist/plugin-sdk/{session-CS-Td9EM.js → session-DHD4ZKyG.js} +2 -2
- package/dist/plugin-sdk/{skill-commands-CFAlXWHk.js → skill-commands-FKEPLS7T.js} +201 -26
- package/dist/plugin-sdk/{slack-CxE1mfzj.js → slack-BUTt8cxK.js} +39 -39
- package/dist/plugin-sdk/{slack-actions-BcMkNv1Z.js → slack-actions-DhbMcp8M.js} +11 -11
- package/dist/plugin-sdk/slack.d.ts +6 -6
- package/dist/plugin-sdk/slack.js +9 -9
- package/dist/plugin-sdk/{sticker-cache-CGzGWu00.js → sticker-cache-D0wHVqjt.js} +3 -3
- package/dist/plugin-sdk/{tables-bFXyRpkx.js → tables-B3jr0Pvq.js} +1 -1
- package/dist/plugin-sdk/{targets-DH7a9lKB.js → targets-CtSbTxYX.js} +1 -1
- package/dist/plugin-sdk/{telegram-actions-DaEcYQ8r.js → telegram-actions-CC_qN4v0.js} +30 -30
- package/dist/plugin-sdk/telegram.d.ts +6 -6
- package/dist/plugin-sdk/telegram.js +8 -8
- package/dist/plugin-sdk/{thread-bindings.lifecycle-CgOvvdw8.js → thread-bindings.lifecycle-QbkNl-m6.js} +3 -3
- package/dist/plugin-sdk/{thread-bindings.state-CQpk44Hq.js → thread-bindings.state-D9hg_M1c.js} +2 -2
- package/dist/plugin-sdk/tmp-fased-dir-xOTGlzBE.d.ts +486 -0
- package/dist/plugin-sdk/{types-vcJrC_uv.d.ts → types-C3avNytw.d.ts} +3 -4
- package/dist/plugin-sdk/{types-DyhXnYUr.d.ts → types-D0kDG8l0.d.ts} +3 -2
- package/dist/plugin-sdk/{types-DFAW-n1J.d.ts → types-DWfbSAaM.d.ts} +2 -2
- package/dist/plugin-sdk/{types.plugin-_fbu40Re.d.ts → types.plugin-BcSBq42x.d.ts} +8 -3
- package/dist/plugin-sdk/{web-CInY-lCv.js → web-BXsJ1vqe.js} +32 -32
- package/dist/plugin-sdk/{whatsapp-actions-BtSc2CBv.js → whatsapp-actions-Bj_rOncr.js} +13 -13
- package/dist/plugin-sdk/{whatsapp-heartbeat-Bg9TuYzl.js → whatsapp-heartbeat-B1WHkJIg.js} +2 -2
- package/dist/plugin-sdk/whatsapp.d.ts +13 -13
- package/dist/plugin-sdk/whatsapp.js +12 -12
- package/dist/plugin-sdk/{wsl-C4DGFkOd.js → wsl-J-HscWxS.js} +3 -3
- package/dist/plugin-sdk/{zod-schema.providers-core-BEbNizPe.d.ts → zod-schema.providers-core-CIzCjXH9.d.ts} +61 -61
- package/dist/providers/refresh.js +1 -1
- package/dist/providers/registry.js +1 -1
- package/dist/secrets/apply.js +1 -1
- package/dist/secrets/runtime.js +1 -1
- package/dist/wallet/external-submission-ledger.js +376 -0
- package/dist/wallet/legacy-embedded-keystore.js +40 -0
- package/dist/wallet/local-socket-signer-archive.js +30 -0
- package/dist/wallet/local-socket-signer-lifecycle.js +97 -0
- package/dist/wallet/local-socket-signer-policy.js +166 -0
- package/dist/wallet/local-socket-signer-protocol.js +561 -118
- package/dist/wallet/native-signer-wallet-id.js +25 -0
- package/dist/wallet/providers/alchemy-adapter.js +3 -0
- package/dist/wallet/providers/local-socket-signer-adapter.js +380 -105
- package/dist/wallet/providers/turnkey-adapter.js +292 -34
- package/dist/wallet/providers/wallet-standard-adapter.js +100 -0
- package/dist/wallet/signer-network-admin.js +184 -0
- package/dist/wallet/signer-protocol-v2.generated.js +90 -0
- package/dist/wallet/solana-bond-signing.js +157 -69
- package/dist/wallet/solana-reviewed-transaction.js +375 -0
- package/dist/wallet/solana-swap.js +520 -64
- package/dist/wallet/solana-transaction-inspection.js +106 -4
- package/dist/wallet/solana-trigger.js +658 -320
- package/dist/wallet/turnkey-reviewed-state.js +370 -0
- package/dist/wallet/wallet-approval-auth.js +17 -28
- package/dist/wallet/wallet-atomic-state.js +53 -0
- package/dist/wallet/wallet-canary.js +0 -1
- package/dist/wallet/wallet-inbound-events.js +16 -11
- package/dist/wallet/wallet-policy-simulation.js +1 -1
- package/dist/wallet/wallet-policy.js +90 -36
- package/dist/wallet/wallet-provider-capabilities.js +28 -4
- package/dist/wallet/wallet-provider-registry.js +57 -44
- package/dist/wallet/wallet-provider-resolver.js +62 -63
- package/dist/wallet/wallet-runtime-config.js +28 -41
- package/dist/wallet/wallet-secrets-store.js +22 -0
- package/dist/wallet/wallet-send-approvals.js +1891 -589
- package/dist/wallet/wallet-send-execution-ledger.js +292 -0
- package/dist/wallet/wallet-settlement-links.js +14 -13
- package/dist/wallet/wallet-standard-review.js +353 -0
- package/dist/wallet/wallet-status.js +0 -9
- package/dist/wallet/wallet-task-ledger.js +16 -2
- package/dist/web/auto-reply/heartbeat-runner.js +1 -1
- package/dist/web/auto-reply/monitor.js +2 -2
- package/dist/wizard/host-security-capability.js +14 -23
- package/dist/wizard/onboarding.finalize.js +63 -184
- package/dist/wizard/onboarding.host-security.js +117 -1367
- package/dist/wizard/onboarding.js +172 -217
- package/dist/wizard/onboarding.wallet.js +232 -785
- package/extensions/acpx/package.json +1 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/fased-federation/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +12 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +2 -2
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +12 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +12 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/sat-mining/index.ts +498 -153
- package/extensions/sat-mining/package.json +1 -1
- package/extensions/sat-mining/signer-codec-schema.v1.json +374 -0
- package/extensions/sat-mining/src/audit-store.ts +50 -5
- package/extensions/sat-mining/src/claim-service.ts +85 -14
- package/extensions/sat-mining/src/config.ts +9 -0
- package/extensions/sat-mining/src/cycle-progress.ts +16 -8
- package/extensions/sat-mining/src/epoch-service.ts +9 -5
- package/extensions/sat-mining/src/gateway-runner.ts +21 -1
- package/extensions/sat-mining/src/protocol-contract.ts +45 -1
- package/extensions/sat-mining/src/recovery-service.ts +312 -0
- package/extensions/sat-mining/src/round-watcher.ts +391 -81
- package/extensions/sat-mining/src/rpc-read.ts +330 -89
- package/extensions/sat-mining/src/runtime.ts +14 -0
- package/extensions/sat-mining/src/sat-bond-layout.ts +6 -0
- package/extensions/sat-mining/src/signer-codec-manifest.ts +429 -0
- package/extensions/sat-mining/src/solana-submit.ts +1199 -428
- package/extensions/sat-mining/src/submission-ledger.ts +433 -0
- package/extensions/signal/package.json +1 -1
- package/extensions/synology-chat/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +12 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +12 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +12 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +12 -0
- package/extensions/zalouser/package.json +1 -1
- package/install.sh +4955 -0
- package/package.json +23 -5
- package/scripts/fased-host-updater.mjs +1421 -0
- package/scripts/fased-host-updaterctl.mjs +186 -0
- package/scripts/fased-managed-service.sh +36 -0
- package/scripts/fased-managed-updater.mjs +3065 -156
- package/scripts/fased-signer-enroll-hosting.sh +275 -0
- package/scripts/fased-signer-network-hosting.sh +143 -0
- package/scripts/fased-signer-owner-policy.mjs +1430 -0
- package/scripts/fased-signer-policy-hosting.sh +50 -0
- package/scripts/fased-signer-policy-local.sh +26 -0
- package/scripts/hosted-release-manifest.mjs +185 -0
- package/scripts/install-fased-signerd.sh +183 -108
- package/scripts/install-managed-runtime.mjs +212 -10
- package/scripts/managed-runtime-layout.mjs +75 -3
- package/scripts/migrate-hosted-signer-v2.mjs +58 -0
- package/scripts/start-managed.sh +169 -360
- package/dist/control-ui/assets/mining-commit-R-6nu87p.js +0 -2
- package/dist/plugin-sdk/tmp-fased-dir-CS6zsYEE.d.ts +0 -77
- package/dist/wallet/local-socket-signer-broker.js +0 -224
- package/dist/wallet/local-socket-signer-custody.js +0 -42
- package/dist/wallet/providers/embedded-keystore-adapter.js +0 -402
- package/dist/wallet/providers/privy-adapter.js +0 -80
- package/dist/wallet/wallet-custody.js +0 -1836
package/dist/commands/wallet.js
CHANGED
|
@@ -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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 {
|
|
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
|
|
52
|
-
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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[
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
225
|
+
scope: "gateway-jupiter-swap-only",
|
|
226
|
+
triggerCredentials: "signer-owned"
|
|
299
227
|
}, null, 2));
|
|
300
228
|
return;
|
|
301
229
|
}
|
|
302
|
-
runtime.log("Jupiter
|
|
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.
|
|
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
|
|
1001
|
-
if (
|
|
1002
|
-
|
|
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
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
-
|
|
1091
|
-
|
|
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
|
|
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("
|
|
1174
|
-
runtime.log("
|
|
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
|
-
|
|
1178
|
-
|
|
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
|
|
600
|
+
if (mode === "embedded" || mode === "embedded-create" || mode === "embedded-import") {
|
|
601
|
+
throwLegacyEmbeddedKeystoreMigrationRequired(`wallet setup mode ${mode} is unavailable`);
|
|
1183
602
|
}
|
|
1184
|
-
if (mode !== "
|
|
1185
|
-
throw new Error(`Unsupported wallet setup mode: ${String(mode)}. ` + "Use one of:
|
|
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
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1287
|
-
|
|
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
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
if (!
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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 === "
|
|
1339
|
-
|
|
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
|
|
1354
|
-
const
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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
|
|
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
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
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
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
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(
|
|
798
|
+
runtime.log(JSON.stringify({
|
|
799
|
+
ok: true,
|
|
800
|
+
status
|
|
801
|
+
}, null, 2));
|
|
1856
802
|
return;
|
|
1857
803
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
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 (
|
|
1862
|
-
|
|
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
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
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
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
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
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
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 ===
|
|
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
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
-
}
|
|
2162
|
-
push("pid.alive",
|
|
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
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
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
|
-
|
|
2178
|
-
|
|
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
|
|
2190
|
-
const
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
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
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
|
|
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
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
-
|
|
2352
|
-
|
|
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
|
|
2467
|
-
const
|
|
2468
|
-
|
|
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
|
|
2474
|
-
const
|
|
2475
|
-
if (
|
|
2476
|
-
throw new Error("
|
|
2477
|
-
}
|
|
2478
|
-
const
|
|
2479
|
-
|
|
2480
|
-
|
|
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
|
-
|
|
2504
|
-
|
|
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
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
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
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
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
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
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
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
1245
|
+
setWalletProviderEnabled({
|
|
1246
|
+
providerId: "local-socket-signer",
|
|
1247
|
+
enabled: true,
|
|
1248
|
+
env: effectiveEnv
|
|
2566
1249
|
});
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
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
|
-
|
|
1285
|
+
const result = {
|
|
2577
1286
|
ok: true,
|
|
2578
|
-
walletId
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
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
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
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,
|
|
1512
|
+
export { collectWalletSignerDoctorReport, walletCanaryCommand, walletInboundListCommand, walletInboundPollCommand, walletInboundReconcileCommand, walletLegacyMigrationFinalizeCommand, walletLimitOrdersConfigureCommand, walletMigrateCommand, walletPolicyProfileApplyCommand, walletProviderConfigureCommand, walletRoleSetCommand, walletRotateKeysCommand, walletSetupCommand, walletSignerDoctorCommand, walletSignerServeCommand, walletStatusCommand };
|