@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
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { resolveProcessScopedMap } from "../shared/process-scoped-map.js";
|
|
2
|
+
import { acquireFileLock, withFileLock } from "../plugin-sdk/file-lock.js";
|
|
3
|
+
import "../infra/file-lock.js";
|
|
4
|
+
import { ensureWalletStateDir } from "./wallet-runtime-config.js";
|
|
5
|
+
import { serializeWalletState, writeWalletStateFileAtomically } from "./wallet-atomic-state.js";
|
|
6
|
+
import syncFs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
|
|
10
|
+
//#region src/wallet/wallet-send-execution-ledger.ts
|
|
11
|
+
const STATES = new Set([
|
|
12
|
+
"reserved",
|
|
13
|
+
"approval_pending",
|
|
14
|
+
"executing",
|
|
15
|
+
"unknown",
|
|
16
|
+
"executed",
|
|
17
|
+
"failed"
|
|
18
|
+
]);
|
|
19
|
+
const TRANSITIONS = {
|
|
20
|
+
reserved: new Set([
|
|
21
|
+
"reserved",
|
|
22
|
+
"approval_pending",
|
|
23
|
+
"executing",
|
|
24
|
+
"unknown",
|
|
25
|
+
"failed"
|
|
26
|
+
]),
|
|
27
|
+
approval_pending: new Set([
|
|
28
|
+
"approval_pending",
|
|
29
|
+
"executed",
|
|
30
|
+
"unknown",
|
|
31
|
+
"failed"
|
|
32
|
+
]),
|
|
33
|
+
executing: new Set([
|
|
34
|
+
"executing",
|
|
35
|
+
"unknown",
|
|
36
|
+
"executed",
|
|
37
|
+
"failed"
|
|
38
|
+
]),
|
|
39
|
+
unknown: new Set([
|
|
40
|
+
"unknown",
|
|
41
|
+
"executed",
|
|
42
|
+
"failed"
|
|
43
|
+
]),
|
|
44
|
+
executed: new Set(["executed"]),
|
|
45
|
+
failed: new Set(["failed"])
|
|
46
|
+
};
|
|
47
|
+
const MUTATION_LOCK_OPTIONS = {
|
|
48
|
+
retries: {
|
|
49
|
+
retries: 100,
|
|
50
|
+
factor: 1.15,
|
|
51
|
+
minTimeout: 10,
|
|
52
|
+
maxTimeout: 200,
|
|
53
|
+
randomize: true
|
|
54
|
+
},
|
|
55
|
+
stale: 3e4
|
|
56
|
+
};
|
|
57
|
+
const EXECUTION_LOCK_OPTIONS = {
|
|
58
|
+
retries: {
|
|
59
|
+
retries: 1,
|
|
60
|
+
factor: 1,
|
|
61
|
+
minTimeout: 10,
|
|
62
|
+
maxTimeout: 10,
|
|
63
|
+
randomize: false
|
|
64
|
+
},
|
|
65
|
+
stale: 3e4
|
|
66
|
+
};
|
|
67
|
+
const MUTATION_QUEUES = resolveProcessScopedMap(Symbol.for("fased.wallet.sendExecution.mutationQueues"));
|
|
68
|
+
const ACTIVE_EXECUTIONS = resolveProcessScopedMap(Symbol.for("fased.wallet.sendExecution.activeExecutions"));
|
|
69
|
+
function containsControlCharacter(value) {
|
|
70
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
71
|
+
if (value.charCodeAt(index) <= 31) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
function canonicalJson(value) {
|
|
78
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
79
|
+
return JSON.stringify(value);
|
|
80
|
+
}
|
|
81
|
+
if (typeof value === "number") {
|
|
82
|
+
if (!Number.isFinite(value)) {
|
|
83
|
+
throw new Error("wallet send intent contains a non-finite number");
|
|
84
|
+
}
|
|
85
|
+
return JSON.stringify(value);
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
89
|
+
}
|
|
90
|
+
if (value && typeof value === "object") {
|
|
91
|
+
const record = value;
|
|
92
|
+
return `{${Object.keys(record).filter((key) => record[key] !== undefined).toSorted().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
93
|
+
}
|
|
94
|
+
throw new Error("wallet send intent contains an unsupported value");
|
|
95
|
+
}
|
|
96
|
+
function walletSendIntentDigest(intent) {
|
|
97
|
+
return `sha256:${createHash("sha256").update(canonicalJson(intent)).digest("hex")}`;
|
|
98
|
+
}
|
|
99
|
+
function walletSendRequestId(executionIntentId) {
|
|
100
|
+
const normalized = executionIntentId.trim();
|
|
101
|
+
if (!normalized || normalized.length > 240 || containsControlCharacter(normalized)) {
|
|
102
|
+
throw new Error("wallet send executionIntentId is invalid");
|
|
103
|
+
}
|
|
104
|
+
return `wallet-send-${createHash("sha256").update(normalized).digest("hex")}`;
|
|
105
|
+
}
|
|
106
|
+
function ledgerPath(env) {
|
|
107
|
+
return path.join(ensureWalletStateDir(env).rootDir, "wallet-send-executions.json");
|
|
108
|
+
}
|
|
109
|
+
function executionLockTarget(executionIntentId, env) {
|
|
110
|
+
return path.join(path.dirname(ledgerPath(env)), ".wallet-send-executions", createHash("sha256").update(executionIntentId).digest("hex"));
|
|
111
|
+
}
|
|
112
|
+
function isRecord(value) {
|
|
113
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
114
|
+
}
|
|
115
|
+
function validateResult(value) {
|
|
116
|
+
if (value === undefined) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
if (!isRecord(value) || value.chain !== "solana" || typeof value.txHash !== "string" || !value.txHash.trim() || value.signer !== undefined && typeof value.signer !== "string" || value.metadata !== undefined && !isRecord(value.metadata)) {
|
|
120
|
+
throw new Error("wallet send execution ledger contains an invalid result");
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
function validateEntry(value) {
|
|
125
|
+
if (!isRecord(value)) {
|
|
126
|
+
throw new Error("wallet send execution ledger contains an invalid entry");
|
|
127
|
+
}
|
|
128
|
+
if (typeof value.executionIntentId !== "string" || !value.executionIntentId.trim() || typeof value.intentDigest !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(value.intentDigest) || typeof value.requestId !== "string" || !value.requestId.trim() || typeof value.walletId !== "string" || !value.walletId.trim() || typeof value.providerId !== "string" || !value.providerId.trim() || typeof value.state !== "string" || !STATES.has(value.state) || typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") {
|
|
129
|
+
throw new Error("wallet send execution ledger contains an invalid entry");
|
|
130
|
+
}
|
|
131
|
+
for (const field of [
|
|
132
|
+
"approvalRequestId",
|
|
133
|
+
"signature",
|
|
134
|
+
"reason"
|
|
135
|
+
]) {
|
|
136
|
+
if (value[field] !== undefined && typeof value[field] !== "string") {
|
|
137
|
+
throw new Error(`wallet send execution ledger contains an invalid ${field}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const expectedRequestId = walletSendRequestId(value.executionIntentId);
|
|
141
|
+
if (value.requestId !== expectedRequestId) {
|
|
142
|
+
throw new Error("wallet send execution ledger contains a mismatched request ID");
|
|
143
|
+
}
|
|
144
|
+
const result = validateResult(value.result);
|
|
145
|
+
if (value.state === "executed" && (!result || typeof value.signature !== "string" || value.signature !== result.txHash)) {
|
|
146
|
+
throw new Error("wallet send execution ledger contains an incomplete executed result");
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
function loadLedger(env) {
|
|
151
|
+
const filePath = ledgerPath(env);
|
|
152
|
+
if (!syncFs.existsSync(filePath)) {
|
|
153
|
+
return {
|
|
154
|
+
version: 1,
|
|
155
|
+
entries: []
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(syncFs.readFileSync(filePath, "utf8"));
|
|
160
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
161
|
+
throw new Error("unsupported shape");
|
|
162
|
+
}
|
|
163
|
+
const entries = parsed.entries.map(validateEntry);
|
|
164
|
+
if (new Set(entries.map((entry) => entry.executionIntentId)).size !== entries.length || new Set(entries.map((entry) => entry.requestId)).size !== entries.length) {
|
|
165
|
+
throw new Error("duplicate execution identity");
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
version: 1,
|
|
169
|
+
entries
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw new Error("wallet send execution ledger is unreadable; refusing to reset it", { cause: error });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function saveLedger(file, env) {
|
|
176
|
+
writeWalletStateFileAtomically(ledgerPath(env), serializeWalletState(file));
|
|
177
|
+
}
|
|
178
|
+
async function withMutationLock(env, task) {
|
|
179
|
+
const filePath = ledgerPath(env);
|
|
180
|
+
const previous = MUTATION_QUEUES.get(filePath) ?? Promise.resolve();
|
|
181
|
+
let releaseQueue;
|
|
182
|
+
const turn = new Promise((resolve) => {
|
|
183
|
+
releaseQueue = resolve;
|
|
184
|
+
});
|
|
185
|
+
const queued = previous.then(() => turn, () => turn);
|
|
186
|
+
MUTATION_QUEUES.set(filePath, queued);
|
|
187
|
+
await previous.catch(() => undefined);
|
|
188
|
+
try {
|
|
189
|
+
return await withFileLock(filePath, MUTATION_LOCK_OPTIONS, async () => await task());
|
|
190
|
+
} finally {
|
|
191
|
+
releaseQueue();
|
|
192
|
+
if (MUTATION_QUEUES.get(filePath) === queued) {
|
|
193
|
+
MUTATION_QUEUES.delete(filePath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function getWalletSendExecution(params) {
|
|
198
|
+
const executionIntentId = params.executionIntentId.trim();
|
|
199
|
+
if (!executionIntentId) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
return loadLedger(params.env ?? process.env).entries.find((entry) => entry.executionIntentId === executionIntentId);
|
|
203
|
+
}
|
|
204
|
+
async function beginWalletSendExecution(params) {
|
|
205
|
+
const env = params.env ?? process.env;
|
|
206
|
+
const executionIntentId = params.executionIntentId.trim();
|
|
207
|
+
const walletId = params.walletId.trim();
|
|
208
|
+
const providerId = params.providerId.trim();
|
|
209
|
+
const requestId = walletSendRequestId(executionIntentId);
|
|
210
|
+
if (!walletId || !providerId || !/^sha256:[0-9a-f]{64}$/u.test(params.intentDigest)) {
|
|
211
|
+
throw new Error("wallet send execution identity is incomplete");
|
|
212
|
+
}
|
|
213
|
+
return await withMutationLock(env, () => {
|
|
214
|
+
const file = loadLedger(env);
|
|
215
|
+
const existing = file.entries.find((entry) => entry.executionIntentId === executionIntentId);
|
|
216
|
+
if (existing) {
|
|
217
|
+
if (existing.intentDigest !== params.intentDigest || existing.walletId !== walletId || existing.providerId !== providerId || existing.requestId !== requestId) {
|
|
218
|
+
throw new Error("wallet send executionIntentId is already bound to a different immutable intent");
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
entry: existing,
|
|
222
|
+
created: false
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const now = new Date().toISOString();
|
|
226
|
+
const entry = {
|
|
227
|
+
executionIntentId,
|
|
228
|
+
intentDigest: params.intentDigest,
|
|
229
|
+
requestId,
|
|
230
|
+
walletId,
|
|
231
|
+
providerId,
|
|
232
|
+
state: "reserved",
|
|
233
|
+
createdAt: now,
|
|
234
|
+
updatedAt: now
|
|
235
|
+
};
|
|
236
|
+
file.entries.push(entry);
|
|
237
|
+
saveLedger(file, env);
|
|
238
|
+
return {
|
|
239
|
+
entry,
|
|
240
|
+
created: true
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function updateWalletSendExecution(params) {
|
|
245
|
+
const env = params.env ?? process.env;
|
|
246
|
+
return await withMutationLock(env, () => {
|
|
247
|
+
const file = loadLedger(env);
|
|
248
|
+
const entry = file.entries.find((candidate) => candidate.executionIntentId === params.executionIntentId.trim());
|
|
249
|
+
if (!entry) {
|
|
250
|
+
throw new Error("wallet send execution entry not found");
|
|
251
|
+
}
|
|
252
|
+
if (!params.expectedStates.includes(entry.state)) {
|
|
253
|
+
throw new Error(`wallet send execution is ${entry.state}; expected ${params.expectedStates.join(" or ")}`);
|
|
254
|
+
}
|
|
255
|
+
if (!TRANSITIONS[entry.state].has(params.state)) {
|
|
256
|
+
throw new Error(`wallet send execution cannot transition from ${entry.state} to ${params.state}`);
|
|
257
|
+
}
|
|
258
|
+
Object.assign(entry, params.patch ?? {});
|
|
259
|
+
entry.state = params.state;
|
|
260
|
+
entry.updatedAt = new Date().toISOString();
|
|
261
|
+
saveLedger(file, env);
|
|
262
|
+
return entry;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
async function claimWalletSendExecution(executionIntentIdRaw, env = process.env) {
|
|
266
|
+
const executionIntentId = executionIntentIdRaw.trim();
|
|
267
|
+
walletSendRequestId(executionIntentId);
|
|
268
|
+
const executionKey = `${ledgerPath(env)}\0${executionIntentId}`;
|
|
269
|
+
if (ACTIVE_EXECUTIONS.has(executionKey)) {
|
|
270
|
+
throw new Error("wallet send execution is already in progress");
|
|
271
|
+
}
|
|
272
|
+
ACTIVE_EXECUTIONS.set(executionKey, true);
|
|
273
|
+
let lock;
|
|
274
|
+
try {
|
|
275
|
+
lock = await acquireFileLock(executionLockTarget(executionIntentId, env), EXECUTION_LOCK_OPTIONS);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
ACTIVE_EXECUTIONS.delete(executionKey);
|
|
278
|
+
throw new Error("wallet send execution is already in progress", { cause: error });
|
|
279
|
+
}
|
|
280
|
+
let released = false;
|
|
281
|
+
return async () => {
|
|
282
|
+
if (released) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
released = true;
|
|
286
|
+
ACTIVE_EXECUTIONS.delete(executionKey);
|
|
287
|
+
await lock.release();
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
//#endregion
|
|
292
|
+
export { beginWalletSendExecution, claimWalletSendExecution, getWalletSendExecution, updateWalletSendExecution, walletSendIntentDigest, walletSendRequestId };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ensureWalletStateDir } from "./wallet-runtime-config.js";
|
|
2
|
+
import { serializeWalletState, writeWalletStateFileAtomically } from "./wallet-atomic-state.js";
|
|
2
3
|
import syncFs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
@@ -6,6 +7,13 @@ import path from "node:path";
|
|
|
6
7
|
function linksFilePath(env = process.env) {
|
|
7
8
|
return path.join(ensureWalletStateDir(env).rootDir, "wallet-settlement-links.json");
|
|
8
9
|
}
|
|
10
|
+
function isWalletSettlementLink(value) {
|
|
11
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const entry = value;
|
|
15
|
+
return typeof entry.requestId === "string" && Boolean(entry.requestId.trim()) && typeof entry.taskId === "string" && Boolean(entry.taskId.trim()) && (entry.mode === "manual" || entry.mode === "autonomous") && (entry.status === "pending" || entry.status === "unknown" || entry.status === "executed" || entry.status === "failed" || entry.status === "rejected") && typeof entry.createdAt === "string" && Boolean(entry.createdAt) && typeof entry.updatedAt === "string" && Boolean(entry.updatedAt);
|
|
16
|
+
}
|
|
9
17
|
function loadFile(env = process.env) {
|
|
10
18
|
const filePath = linksFilePath(env);
|
|
11
19
|
if (!syncFs.existsSync(filePath)) {
|
|
@@ -16,27 +24,20 @@ function loadFile(env = process.env) {
|
|
|
16
24
|
}
|
|
17
25
|
try {
|
|
18
26
|
const parsed = JSON.parse(syncFs.readFileSync(filePath, "utf8"));
|
|
19
|
-
if (parsed?.version === 1 && Array.isArray(parsed.links)) {
|
|
27
|
+
if (parsed?.version === 1 && Array.isArray(parsed.links) && parsed.links.every(isWalletSettlementLink)) {
|
|
20
28
|
return {
|
|
21
29
|
version: 1,
|
|
22
30
|
links: parsed.links
|
|
23
31
|
};
|
|
24
32
|
}
|
|
25
|
-
} catch {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error("wallet settlement state is unreadable; refusing to reset request links", { cause: error });
|
|
35
|
+
}
|
|
36
|
+
throw new Error("wallet settlement state has an unsupported shape; refusing to reset links");
|
|
30
37
|
}
|
|
31
38
|
function saveFile(file, env = process.env) {
|
|
32
39
|
const filePath = linksFilePath(env);
|
|
33
|
-
|
|
34
|
-
encoding: "utf8",
|
|
35
|
-
mode: 384
|
|
36
|
-
});
|
|
37
|
-
try {
|
|
38
|
-
syncFs.chmodSync(filePath, 384);
|
|
39
|
-
} catch {}
|
|
40
|
+
writeWalletStateFileAtomically(filePath, serializeWalletState(file));
|
|
40
41
|
}
|
|
41
42
|
function upsertWalletSettlementLink(params) {
|
|
42
43
|
const env = params.env ?? process.env;
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { withFileLock } from "../plugin-sdk/file-lock.js";
|
|
2
|
+
import "../infra/file-lock.js";
|
|
3
|
+
import { ensureWalletStateDir } from "./wallet-runtime-config.js";
|
|
4
|
+
import { fetchSolanaRpc } from "./solana-assets.js";
|
|
5
|
+
import { WalletProviderError } from "./wallet-provider-adapter.js";
|
|
6
|
+
import { broadcastReviewedSolanaTransaction, buildReviewedSolanaTransaction, computeReviewedSolanaRequestDigest, reconcileReviewedSolanaTransaction, validateReviewedSolanaSignedTransaction } from "./solana-reviewed-transaction.js";
|
|
7
|
+
import syncFs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
|
+
|
|
11
|
+
//#region src/wallet/wallet-standard-review.ts
|
|
12
|
+
const REVIEW_FILE_NAME = "wallet-standard-reviews.v1.json";
|
|
13
|
+
const REVIEW_TTL_MS = 2 * 60 * 1e3;
|
|
14
|
+
const REVIEW_FILE_MODE = 384;
|
|
15
|
+
const MAX_REVIEW_RECORDS = 1e4;
|
|
16
|
+
const MAX_RETAINED_TERMINAL_REVIEWS = 5e3;
|
|
17
|
+
const TERMINAL_REVIEW_RETENTION_MS = 30 * 24 * 60 * 6e4;
|
|
18
|
+
const REVIEW_LOCK_OPTIONS = {
|
|
19
|
+
retries: {
|
|
20
|
+
retries: 80,
|
|
21
|
+
factor: 1.15,
|
|
22
|
+
minTimeout: 20,
|
|
23
|
+
maxTimeout: 200,
|
|
24
|
+
randomize: true
|
|
25
|
+
},
|
|
26
|
+
stale: 3e4
|
|
27
|
+
};
|
|
28
|
+
const SOLANA_MAINNET_GENESIS_HASH = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
29
|
+
const SOLANA_DEVNET_GENESIS_HASH = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
30
|
+
let reviewStateLock = Promise.resolve();
|
|
31
|
+
function reviewPath(env) {
|
|
32
|
+
return path.join(ensureWalletStateDir(env).rootDir, REVIEW_FILE_NAME);
|
|
33
|
+
}
|
|
34
|
+
function readFile(env) {
|
|
35
|
+
const filePath = reviewPath(env);
|
|
36
|
+
if (!syncFs.existsSync(filePath)) {
|
|
37
|
+
return {
|
|
38
|
+
version: 1,
|
|
39
|
+
reviews: []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(syncFs.readFileSync(filePath, "utf8"));
|
|
44
|
+
if (parsed.version === 1 && Array.isArray(parsed.reviews)) {
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
throw new WalletProviderError({
|
|
49
|
+
code: "wallet_provider_unavailable",
|
|
50
|
+
message: "Wallet Standard review state is invalid; repair it before approving Vault sends",
|
|
51
|
+
retryable: false
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function saveFile(file, env) {
|
|
55
|
+
const filePath = reviewPath(env);
|
|
56
|
+
const temporary = `${filePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
57
|
+
let descriptor;
|
|
58
|
+
try {
|
|
59
|
+
descriptor = syncFs.openSync(temporary, "wx", REVIEW_FILE_MODE);
|
|
60
|
+
syncFs.writeFileSync(descriptor, `${JSON.stringify(file, null, 2)}\n`, "utf8");
|
|
61
|
+
syncFs.fsyncSync(descriptor);
|
|
62
|
+
syncFs.closeSync(descriptor);
|
|
63
|
+
descriptor = undefined;
|
|
64
|
+
syncFs.renameSync(temporary, filePath);
|
|
65
|
+
try {
|
|
66
|
+
syncFs.chmodSync(filePath, REVIEW_FILE_MODE);
|
|
67
|
+
} catch {}
|
|
68
|
+
try {
|
|
69
|
+
const directory = syncFs.openSync(path.dirname(filePath), "r");
|
|
70
|
+
try {
|
|
71
|
+
syncFs.fsyncSync(directory);
|
|
72
|
+
} finally {
|
|
73
|
+
syncFs.closeSync(directory);
|
|
74
|
+
}
|
|
75
|
+
} catch {}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (descriptor !== undefined) {
|
|
78
|
+
syncFs.closeSync(descriptor);
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
syncFs.unlinkSync(temporary);
|
|
82
|
+
} catch {}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function compactReviewState(file, nowMs = Date.now()) {
|
|
87
|
+
const terminal = file.reviews.filter((review) => review.status === "broadcast" || review.status === "failed").toSorted((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
|
|
88
|
+
const removable = new Set(terminal.filter((review, index) => nowMs - Date.parse(review.createdAt) > TERMINAL_REVIEW_RETENTION_MS || terminal.length - index > MAX_RETAINED_TERMINAL_REVIEWS).map((review) => review.requestId));
|
|
89
|
+
return {
|
|
90
|
+
version: 1,
|
|
91
|
+
reviews: file.reviews.filter((review) => !removable.has(review.requestId))
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function requireReviewCapacity(file) {
|
|
95
|
+
if (file.reviews.length >= MAX_REVIEW_RECORDS) {
|
|
96
|
+
throw new WalletProviderError({
|
|
97
|
+
code: "wallet_provider_unavailable",
|
|
98
|
+
message: "Wallet Standard review state reached its durable safety limit; reconcile unresolved reviews",
|
|
99
|
+
retryable: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function secureEqual(left, right) {
|
|
104
|
+
const leftBytes = Buffer.from(left, "utf8");
|
|
105
|
+
const rightBytes = Buffer.from(right, "utf8");
|
|
106
|
+
return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes);
|
|
107
|
+
}
|
|
108
|
+
function rpcUrlDigest(rpcUrl) {
|
|
109
|
+
let normalized;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = new URL(rpcUrl.trim());
|
|
112
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
113
|
+
throw new Error("unsupported protocol");
|
|
114
|
+
}
|
|
115
|
+
normalized = parsed.toString();
|
|
116
|
+
} catch {
|
|
117
|
+
throw new WalletProviderError({
|
|
118
|
+
code: "wallet_provider_invalid_config",
|
|
119
|
+
message: "Wallet Standard review requires a valid HTTP(S) Solana RPC URL"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return createHash("sha256").update(normalized, "utf8").digest("base64url");
|
|
123
|
+
}
|
|
124
|
+
async function resolveWalletStandardChain(rpcUrl) {
|
|
125
|
+
const genesisHash = await fetchSolanaRpc(rpcUrl, "getGenesisHash", []);
|
|
126
|
+
if (genesisHash === SOLANA_MAINNET_GENESIS_HASH) {
|
|
127
|
+
return "solana:mainnet";
|
|
128
|
+
}
|
|
129
|
+
if (genesisHash === SOLANA_DEVNET_GENESIS_HASH) {
|
|
130
|
+
return "solana:devnet";
|
|
131
|
+
}
|
|
132
|
+
throw new WalletProviderError({
|
|
133
|
+
code: "wallet_provider_invalid_config",
|
|
134
|
+
message: "Hardware Vault supports only verified Solana mainnet or devnet RPC endpoints"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function withReviewLock(env, task) {
|
|
138
|
+
const run = async () => await withFileLock(reviewPath(env), REVIEW_LOCK_OPTIONS, async () => await task());
|
|
139
|
+
const current = reviewStateLock.then(run, run);
|
|
140
|
+
reviewStateLock = current.then(() => undefined, () => undefined);
|
|
141
|
+
return await current;
|
|
142
|
+
}
|
|
143
|
+
function toPrepareResult(record) {
|
|
144
|
+
return {
|
|
145
|
+
requestId: record.requestId,
|
|
146
|
+
preparedId: record.preparedId,
|
|
147
|
+
signer: record.signer,
|
|
148
|
+
unsignedTxBase64: record.unsignedTxBase64,
|
|
149
|
+
messageBase64: record.messageBase64,
|
|
150
|
+
intentDigest: record.intentDigest,
|
|
151
|
+
expiresAt: record.expiresAt,
|
|
152
|
+
chain: record.chain,
|
|
153
|
+
simulation: {
|
|
154
|
+
ok: true,
|
|
155
|
+
...typeof record.simulationUnitsConsumed === "number" && Number.isFinite(record.simulationUnitsConsumed) ? { unitsConsumed: record.simulationUnitsConsumed } : {}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function prepareWalletStandardReview(params) {
|
|
160
|
+
const env = params.env ?? process.env;
|
|
161
|
+
const requestId = params.requestId.trim();
|
|
162
|
+
if (!requestId) {
|
|
163
|
+
throw new WalletProviderError({
|
|
164
|
+
code: "wallet_provider_invalid_config",
|
|
165
|
+
message: "Wallet Standard review requires an approval request ID"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return await withReviewLock(env, async () => {
|
|
169
|
+
const requestDigest = computeReviewedSolanaRequestDigest({
|
|
170
|
+
request: params.payload,
|
|
171
|
+
signer: params.signerAddress.trim()
|
|
172
|
+
});
|
|
173
|
+
const expectedRpcUrlDigest = rpcUrlDigest(params.rpcUrl);
|
|
174
|
+
const file = compactReviewState(readFile(env));
|
|
175
|
+
const existing = file.reviews.find((review) => review.requestId === requestId);
|
|
176
|
+
if (existing) {
|
|
177
|
+
const sameRequest = Boolean(existing.requestDigest) && secureEqual(existing.requestDigest, requestDigest);
|
|
178
|
+
const sameRpc = Boolean(existing.rpcUrlDigest) && secureEqual(existing.rpcUrlDigest, expectedRpcUrlDigest);
|
|
179
|
+
if (sameRequest && sameRpc && existing.status === "prepared" && Date.parse(existing.expiresAt) > Date.now()) {
|
|
180
|
+
return toPrepareResult(existing);
|
|
181
|
+
}
|
|
182
|
+
throw new WalletProviderError({
|
|
183
|
+
code: existing.status === "broadcasting" || existing.status === "unknown" ? "wallet_provider_ambiguous" : "wallet_provider_invalid_config",
|
|
184
|
+
message: "This approval request ID already has an immutable Wallet Standard review; create a new approval request",
|
|
185
|
+
retryable: false
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
requireReviewCapacity(file);
|
|
189
|
+
const chain = await resolveWalletStandardChain(params.rpcUrl);
|
|
190
|
+
const prepared = await buildReviewedSolanaTransaction({
|
|
191
|
+
request: params.payload,
|
|
192
|
+
signerAddress: params.signerAddress,
|
|
193
|
+
rpcUrl: params.rpcUrl
|
|
194
|
+
});
|
|
195
|
+
const now = Date.now();
|
|
196
|
+
const record = {
|
|
197
|
+
requestId,
|
|
198
|
+
preparedId: randomBytes(32).toString("base64url"),
|
|
199
|
+
createdAt: new Date(now).toISOString(),
|
|
200
|
+
expiresAt: new Date(now + REVIEW_TTL_MS).toISOString(),
|
|
201
|
+
status: "prepared",
|
|
202
|
+
signer: prepared.signer,
|
|
203
|
+
requestDigest,
|
|
204
|
+
intentDigest: prepared.intentDigest,
|
|
205
|
+
unsignedTxBase64: prepared.unsignedTxBase64,
|
|
206
|
+
messageBase64: prepared.messageBase64,
|
|
207
|
+
recentBlockhash: prepared.recentBlockhash,
|
|
208
|
+
lastValidBlockHeight: prepared.lastValidBlockHeight,
|
|
209
|
+
chain,
|
|
210
|
+
rpcUrlDigest: expectedRpcUrlDigest,
|
|
211
|
+
simulationUnitsConsumed: prepared.simulation.unitsConsumed
|
|
212
|
+
};
|
|
213
|
+
file.reviews.push(record);
|
|
214
|
+
saveFile(file, env);
|
|
215
|
+
return toPrepareResult(record);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async function executeLocked(params) {
|
|
219
|
+
const file = readFile(params.env);
|
|
220
|
+
const review = file.reviews.find((entry) => entry.requestId === params.requestId);
|
|
221
|
+
if (!review || !secureEqual(review.preparedId, params.preparedId)) {
|
|
222
|
+
throw new WalletProviderError({
|
|
223
|
+
code: "wallet_provider_invalid_config",
|
|
224
|
+
message: "Wallet Standard review is missing or does not match this approval"
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (!secureEqual(review.intentDigest, params.intentDigest)) {
|
|
228
|
+
throw new WalletProviderError({
|
|
229
|
+
code: "wallet_provider_invalid_config",
|
|
230
|
+
message: "Wallet Standard intent digest does not match the approved transaction"
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (!review.rpcUrlDigest || !secureEqual(review.rpcUrlDigest, rpcUrlDigest(params.rpcUrl))) {
|
|
234
|
+
throw new WalletProviderError({
|
|
235
|
+
code: "wallet_provider_invalid_config",
|
|
236
|
+
message: "Solana RPC configuration changed after review; approve again"
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (review.status === "broadcast" && review.txHash) {
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
txHash: review.txHash,
|
|
243
|
+
signer: review.signer,
|
|
244
|
+
idempotent: true
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (review.status === "broadcasting" || review.status === "unknown") {
|
|
248
|
+
if (!review.txHash) {
|
|
249
|
+
throw new WalletProviderError({
|
|
250
|
+
code: "wallet_provider_ambiguous",
|
|
251
|
+
message: "Hardware-wallet broadcast is already in progress or unknown; do not submit it again",
|
|
252
|
+
retryable: false
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
let reconciled;
|
|
256
|
+
try {
|
|
257
|
+
reconciled = await reconcileReviewedSolanaTransaction({
|
|
258
|
+
rpcUrl: params.rpcUrl,
|
|
259
|
+
expectedTxHash: review.txHash,
|
|
260
|
+
lastValidBlockHeight: review.lastValidBlockHeight
|
|
261
|
+
});
|
|
262
|
+
} catch {
|
|
263
|
+
throw new WalletProviderError({
|
|
264
|
+
code: "wallet_provider_ambiguous",
|
|
265
|
+
message: `Hardware-wallet broadcast remains unknown; reconcile signature ${review.txHash} before any new send`,
|
|
266
|
+
retryable: false
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (reconciled.state === "landed") {
|
|
270
|
+
review.status = "broadcast";
|
|
271
|
+
saveFile(file, params.env);
|
|
272
|
+
return {
|
|
273
|
+
ok: true,
|
|
274
|
+
txHash: review.txHash,
|
|
275
|
+
signer: review.signer,
|
|
276
|
+
idempotent: true
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (reconciled.state === "failed" || reconciled.state === "expired") {
|
|
280
|
+
review.status = "failed";
|
|
281
|
+
review.failureReason = reconciled.state === "failed" ? reconciled.reason : `reviewed transaction expired at block height ${review.lastValidBlockHeight} without landing`;
|
|
282
|
+
saveFile(file, params.env);
|
|
283
|
+
throw new WalletProviderError({
|
|
284
|
+
code: "wallet_provider_unavailable",
|
|
285
|
+
message: `${review.failureReason}; create and approve a new Wallet Standard review`,
|
|
286
|
+
retryable: false
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
throw new WalletProviderError({
|
|
290
|
+
code: "wallet_provider_ambiguous",
|
|
291
|
+
message: `Hardware-wallet broadcast remains pending; reconcile signature ${review.txHash} before any new send`,
|
|
292
|
+
retryable: false
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (review.status === "failed") {
|
|
296
|
+
throw new WalletProviderError({
|
|
297
|
+
code: "wallet_provider_unavailable",
|
|
298
|
+
message: `${review.failureReason ?? "Wallet Standard transaction failed"}; create and approve a new Wallet Standard review`,
|
|
299
|
+
retryable: false
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (Date.parse(review.expiresAt) <= Date.now()) {
|
|
303
|
+
throw new WalletProviderError({
|
|
304
|
+
code: "wallet_provider_invalid_config",
|
|
305
|
+
message: "Hardware-wallet review expired; approve again to receive a fresh blockhash"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const verified = validateReviewedSolanaSignedTransaction({
|
|
309
|
+
unsignedTxBase64: review.unsignedTxBase64,
|
|
310
|
+
signedTxBase64: params.signedTxBase64,
|
|
311
|
+
signerAddress: review.signer
|
|
312
|
+
});
|
|
313
|
+
review.status = "broadcasting";
|
|
314
|
+
review.txHash = verified.txHash;
|
|
315
|
+
review.signedTxBase64 = verified.signedBytes.toString("base64");
|
|
316
|
+
saveFile(file, params.env);
|
|
317
|
+
try {
|
|
318
|
+
const txHash = await broadcastReviewedSolanaTransaction({
|
|
319
|
+
rpcUrl: params.rpcUrl,
|
|
320
|
+
signedTxBase64: review.signedTxBase64,
|
|
321
|
+
expectedTxHash: verified.txHash
|
|
322
|
+
});
|
|
323
|
+
review.status = "broadcast";
|
|
324
|
+
review.txHash = txHash;
|
|
325
|
+
saveFile(file, params.env);
|
|
326
|
+
return {
|
|
327
|
+
ok: true,
|
|
328
|
+
txHash,
|
|
329
|
+
signer: review.signer,
|
|
330
|
+
idempotent: false
|
|
331
|
+
};
|
|
332
|
+
} catch (error) {
|
|
333
|
+
review.status = "unknown";
|
|
334
|
+
saveFile(file, params.env);
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function executeWalletStandardReview(params) {
|
|
339
|
+
const env = params.env ?? process.env;
|
|
340
|
+
const requestId = params.requestId.trim();
|
|
341
|
+
return await withReviewLock(env, async () => await executeLocked({
|
|
342
|
+
...params,
|
|
343
|
+
requestId,
|
|
344
|
+
env
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
function readWalletStandardReviewTxHash(params) {
|
|
348
|
+
const file = readFile(params.env ?? process.env);
|
|
349
|
+
return file.reviews.find((review) => review.requestId === params.requestId.trim())?.txHash;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
//#endregion
|
|
353
|
+
export { executeWalletStandardReview, prepareWalletStandardReview, readWalletStandardReviewTxHash };
|