@fased/fased 0.1.67 → 0.1.69
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 +27 -0
- package/README.md +20 -22
- package/config/sat-runtime.env +6 -2
- package/config/signer-protocol-v2.json +10 -0
- package/dist/agents/tools/wallet-action-tool.js +2 -0
- package/dist/agents/tools/wallet-tool.js +5 -3
- package/dist/brand.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/cli/program/register.wallet.js +119 -5
- package/dist/commands/wallet.js +1286 -74
- package/dist/config/sat-runtime-ids.js +6 -1
- package/dist/control-ui/assets/{app-De8gTtZU.js → app-BLFLoFPk.js} +1526 -1526
- package/dist/control-ui/assets/{federation-DgA9e-Pn.js → federation-SGMAnM_8.js} +2 -2
- package/dist/control-ui/assets/{index-DYYBRBOS.js → index-S88kp5ZQ.js} +3 -3
- package/dist/control-ui/assets/{mining-Dg-3ll0F.js → mining-ClI8RW4u.js} +2 -2
- package/dist/control-ui/assets/{wallet-DvStR_AT.js → wallet-CRIhDQbW.js} +325 -156
- package/dist/control-ui/index.html +1 -1
- package/dist/control-ui/version.json +1 -1
- package/dist/gateway/server-http.js +394 -55
- package/dist/mining/mainnet-sync.js +67 -5
- package/dist/operator/operator-readiness.js +1 -1
- package/dist/plugin-sdk/{actions-DRu135h0.js → actions-B-Vwj64K.js} +3 -3
- package/dist/plugin-sdk/{allow-list-DyAxs4yw.js → allow-list-Bf45HfoP.js} +1 -1
- package/dist/plugin-sdk/{audio-preflight-Dq_rJtqH.js → audio-preflight-CS7Q_7-R.js} +26 -25
- package/dist/plugin-sdk/{audit-C5d2z5U1.js → audit-CUULWG22.js} +1 -1
- package/dist/plugin-sdk/{channel-access-configure-DaI5wevh.js → channel-access-configure-BoUsC-Ys.js} +1 -1
- package/dist/plugin-sdk/{channel-adapters.discord-BS4S4Svh.js → channel-adapters.discord-DSiR4X2Y.js} +1 -1
- package/dist/plugin-sdk/channel-plugin-common.d.ts +2 -2
- package/dist/plugin-sdk/channel-plugin-common.js +1 -1
- package/dist/plugin-sdk/{chunk-CyoyqpbG.js → chunk-CBpl-AiQ.js} +1 -1
- package/dist/plugin-sdk/{chunk-items-Dunoq4Er.js → chunk-items-BLBZNORg.js} +1 -1
- package/dist/plugin-sdk/command-status.js +26 -25
- package/dist/plugin-sdk/{commands-registry-BfqdEWdF.js → commands-registry-G5KXSi_y.js} +1 -1
- package/dist/plugin-sdk/{config-schema--vl89M7d.js → config-schema-BiBOUW89.js} +431 -125
- package/dist/plugin-sdk/device-pair.d.ts +1 -1
- package/dist/plugin-sdk/{diagnostic-DLL_zekv.js → diagnostic-sYs6Agr-.js} +1 -1
- package/dist/plugin-sdk/{directory-live-CSuYxdwk.js → directory-live-BXMpuYAF.js} +1 -1
- package/dist/plugin-sdk/{directory-live-Dw7ZgC9_.js → directory-live-Rn1BzPof.js} +1 -1
- package/dist/plugin-sdk/discord.d.ts +4 -4
- package/dist/plugin-sdk/discord.js +20 -20
- package/dist/plugin-sdk/{draft-stream-controls-BqchW1vi.js → draft-stream-controls-CMNdKSx-.js} +1 -1
- package/dist/plugin-sdk/{guilds-CDhRIX57.js → guilds-Dkwl0yHv.js} +1 -1
- package/dist/plugin-sdk/{handle-action-hvwapMKg.js → handle-action-CRdM6B-8.js} +15 -15
- package/dist/plugin-sdk/index.d.ts +15 -15
- package/dist/plugin-sdk/index.js +28 -27
- package/dist/plugin-sdk/{login-Ca583Okc.js → login-D7WnK8-P.js} +1 -1
- package/dist/plugin-sdk/{login-qr-CGciBSNP.js → login-qr-CRueOKZK.js} +2 -114
- package/dist/plugin-sdk/{markdown-tables-DCOXEUfZ.js → markdown-tables-d7GlUs5R.js} +1 -1
- package/dist/plugin-sdk/{media-CAdQF5TG.js → media-BAoaxCrt.js} +1 -1
- package/dist/plugin-sdk/{monitor-DrTy_4CL.js → monitor-WXdn4mR-.js} +34 -33
- package/dist/plugin-sdk/{monitor-CV1sr0p2.js → monitor-xzVAPbg2.js} +44 -43
- package/dist/plugin-sdk/{openresponses-http-BAjgURZa.js → openresponses-http-DnI1LFgq.js} +26 -25
- package/dist/plugin-sdk/{outbound-attachment-QqTnIhZL.js → outbound-attachment-CyXM3cAv.js} +1 -1
- package/dist/plugin-sdk/{outbound-BgVDdHxx.js → outbound-zB6llMGW.js} +6 -6
- package/dist/plugin-sdk/{pairing-message-BjIJYVvp.d.ts → pairing-message-CiTcdD6m.d.ts} +1 -1
- package/dist/plugin-sdk/{pairing-store-BChahgI2.js → pairing-store-B6J2C0vP.js} +1 -1
- package/dist/plugin-sdk/{pi-model-discovery-runtime-DxTMz_XF.js → pi-model-discovery-runtime-6JzLw9Gk.js} +1 -1
- package/dist/plugin-sdk/{probe-CJb5uR8B.js → probe-BM7Oe40M.js} +2 -2
- package/dist/plugin-sdk/{probe-l_whvGLY.js → probe-CMPksimh.js} +1 -1
- package/dist/plugin-sdk/{probe-BFroeK3g.js → probe-oVp12D6L.js} +1 -1
- package/dist/plugin-sdk/provider-web-search-config-contract.d.ts +1 -1
- package/dist/plugin-sdk/{pw-ai-DlxqX6Es.js → pw-ai-DZYLDC-S.js} +1 -1
- package/dist/plugin-sdk/qr-image-B4MlMG4N.js +115 -0
- package/dist/plugin-sdk/{replies-ChYo5ZQM.js → replies-CY-V0Zob.js} +3 -3
- package/dist/plugin-sdk/{resolve-channels-87LZrFk_.js → resolve-channels-1HtDcMqh.js} +2 -2
- package/dist/plugin-sdk/{resolve-channels-_-nV-EK2.js → resolve-channels-CU72Qh2j.js} +1 -1
- package/dist/plugin-sdk/{resolve-outbound-target-D-KGgHJZ.js → resolve-outbound-target-nQSBVYz9.js} +2 -2
- package/dist/plugin-sdk/{resolve-route-C8tvQm8Z.js → resolve-route-Dufnu2cT.js} +1 -1
- package/dist/plugin-sdk/{resolve-users-DN9VQBwv.js → resolve-users-Bk_ss2vC.js} +1 -1
- package/dist/plugin-sdk/{resolve-users-V6dHh_2B.js → resolve-users-IevCOk4W.js} +2 -2
- package/dist/plugin-sdk/{runtime-2B9_WKoI.js → runtime-CXSQwMaR.js} +1 -1
- package/dist/plugin-sdk/{runtime-dependency-nup8niQL.js → runtime-dependency-DczkvPxn.js} +9 -9
- package/dist/plugin-sdk/sat-runtime.d.ts +105 -10
- package/dist/plugin-sdk/sat-runtime.js +26 -25
- package/dist/plugin-sdk/{send-DCO441U7.js → send-BXBS_mRK.js} +8 -8
- package/dist/plugin-sdk/{send-CFzGKJXo.js → send-D-HfKQZm.js} +4 -4
- package/dist/plugin-sdk/{send-B6KkGKZD.js → send-D1j12ZgX.js} +5 -5
- package/dist/plugin-sdk/{send-q9HXrSwK.js → send-DWKm6yay.js} +10 -10
- package/dist/plugin-sdk/{send-gDoGA-7e.js → send-Di1Jit3L.js} +7 -7
- package/dist/plugin-sdk/{skill-commands-FKEPLS7T.js → skill-commands-iJtvpqgb.js} +2 -2
- package/dist/plugin-sdk/{slack-DSC6T2KW.js → slack-CkIRVDUK.js} +45 -44
- package/dist/plugin-sdk/{slack-actions-DhbMcp8M.js → slack-actions-BzUEszJa.js} +10 -10
- package/dist/plugin-sdk/slack.d.ts +4 -4
- package/dist/plugin-sdk/slack.js +6 -6
- package/dist/plugin-sdk/{sticker-cache-C0l_5IKg.js → sticker-cache-Dpap5ce_.js} +2 -2
- package/dist/plugin-sdk/{tables-B3jr0Pvq.js → tables-_1W36GII.js} +2 -2
- package/dist/plugin-sdk/{targets-CtSbTxYX.js → targets-DDbNnLnB.js} +2 -2
- package/dist/plugin-sdk/{telegram-actions-BwGBIVDE.js → telegram-actions-Cewdga8H.js} +30 -29
- package/dist/plugin-sdk/telegram.d.ts +4 -4
- package/dist/plugin-sdk/telegram.js +3 -3
- package/dist/plugin-sdk/{thread-bindings.lifecycle-QbkNl-m6.js → thread-bindings.lifecycle-Y9T3CQGf.js} +3 -3
- package/dist/plugin-sdk/{thread-bindings.state-D9hg_M1c.js → thread-bindings.state-D2oOWDpv.js} +1 -1
- package/dist/plugin-sdk/{tmp-fased-dir-DXnGKKgi.d.ts → tmp-fased-dir-BSEHc8MQ.d.ts} +8 -2
- package/dist/plugin-sdk/{types-eG9aYqXF.d.ts → types-BakzIzGN.d.ts} +1 -1
- package/dist/plugin-sdk/{types-Cegc86EP.d.ts → types-CzF0YQqK.d.ts} +3 -3
- package/dist/plugin-sdk/{web-C_AH5PyB.js → web-DGWi7i3B.js} +33 -32
- package/dist/plugin-sdk/{whatsapp-actions-Bj_rOncr.js → whatsapp-actions-Blu9-eX-.js} +9 -9
- package/dist/plugin-sdk/{whatsapp-heartbeat-B1WHkJIg.js → whatsapp-heartbeat-DjmQM7hq.js} +2 -2
- package/dist/plugin-sdk/whatsapp.d.ts +9 -9
- package/dist/plugin-sdk/whatsapp.js +7 -7
- package/dist/plugin-sdk/{wsl-J-HscWxS.js → wsl-CTvIWvZz.js} +1 -1
- package/dist/plugin-sdk/{zod-schema.providers-core-FaYR6KdD.d.ts → zod-schema.providers-core-CIzCjXH9.d.ts} +61 -61
- package/dist/wallet/local-socket-signer-lifecycle.js +107 -3
- package/dist/wallet/local-socket-signer-protocol.js +97 -10
- package/dist/wallet/mining-wallet-retirement.js +256 -0
- package/dist/wallet/providers/local-socket-signer-adapter.js +10 -0
- package/dist/wallet/signer-network-admin.js +11 -118
- package/dist/wallet/signer-protocol-v2.generated.js +10 -0
- package/dist/wallet/solana-assets.js +9 -3
- package/dist/wallet/solana-rpc-read-fetch.js +65 -0
- package/dist/wallet/wallet-agent-selection.js +6 -10
- package/dist/wallet/wallet-provider-registry.js +70 -29
- package/dist/wallet/wallet-send-approvals.js +6 -2
- package/dist/wallet/wallet-status.js +63 -1
- package/dist/web/login-qr.js +1 -1
- package/dist/wizard/onboarding.js +86 -84
- package/dist/wizard/onboarding.wallet.js +10 -1
- 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/package.json +1 -1
- 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 +286 -107
- package/package.json +4 -2
- package/scripts/fased-managed-updater.mjs +10 -2
- package/scripts/install-fased-signerd.sh +4 -8
- package/scripts/install-managed-runtime.mjs +10 -7
- package/scripts/install-runtime-profile.sh +27 -0
- package/scripts/start-managed.sh +13 -1
- /package/dist/plugin-sdk/{active-listener-B_NMtgPL.js → active-listener-p9Ooc1qj.js} +0 -0
- /package/dist/plugin-sdk/{blocks-input-Bg-8S2pl.js → blocks-input-DiZaqaN7.js} +0 -0
- /package/dist/plugin-sdk/{channel-access-CcFRdRPO.js → channel-access-B0aZBSd2.js} +0 -0
- /package/dist/plugin-sdk/{channel-activity-DBDMQ3yI.js → channel-activity-F07DjroV.js} +0 -0
- /package/dist/plugin-sdk/{channel-core-CFRs8LPy.js → channel-core-DoF4X04L.js} +0 -0
- /package/dist/plugin-sdk/{client-pI228AVT.js → client-DLY-kWF_.js} +0 -0
- /package/dist/plugin-sdk/{command-poll-backoff-yJU83wEq.js → command-poll-backoff-NAePk3eL.js} +0 -0
- /package/dist/plugin-sdk/{date-time-uUevHscU.js → date-time-ChY_tHoG.js} +0 -0
- /package/dist/plugin-sdk/{draft-stream-loop-CrOPfV3F.js → draft-stream-loop-DMBwjjd1.js} +0 -0
- /package/dist/plugin-sdk/{fetch-BtmoPIlf.js → fetch-B0v6OTUZ.js} +0 -0
- /package/dist/plugin-sdk/{filesystem-manager-DoI8IBNF.js → filesystem-manager-BzuIJ9K9.js} +0 -0
- /package/dist/plugin-sdk/{gateway-registry-DZDsqicG.js → gateway-registry-Bgl2GFgy.js} +0 -0
- /package/dist/plugin-sdk/{inbound-context-DrAcXE0y.js → inbound-context-CQnfFFQ9.js} +0 -0
- /package/dist/plugin-sdk/{pi-model-discovery-BX_JLcEJ.js → pi-model-discovery-BHNVyRny.js} +0 -0
- /package/dist/plugin-sdk/{polls-DKQXVfJp.js → polls-DXd67TD8.js} +0 -0
- /package/dist/plugin-sdk/{proxy-DWpYcsvR.js → proxy-CLRUdShp.js} +0 -0
- /package/dist/plugin-sdk/{qmd-manager-CgRaTxUq.js → qmd-manager-B8Vnu0ND.js} +0 -0
- /package/dist/plugin-sdk/{render-e7fENCYH.js → render-DrBfVs0u.js} +0 -0
- /package/dist/plugin-sdk/{reply-reference-Bqk-7HXE.js → reply-reference-BnAk5Wjb.js} +0 -0
- /package/dist/plugin-sdk/{retry-policy-C6SjKjQA.js → retry-policy-CXh1owkc.js} +0 -0
- /package/dist/plugin-sdk/{sandbox-paths-Dx2lUqCu.js → sandbox-paths-Bzh1CuZc.js} +0 -0
- /package/dist/plugin-sdk/{session-DHD4ZKyG.js → session-iuGR_rdi.js} +0 -0
- /package/dist/plugin-sdk/{shared-CBgp_GBw.js → shared-D1AD2Ejn.js} +0 -0
- /package/dist/plugin-sdk/{target-errors-Ckufe179.js → target-errors-DfS1sLR9.js} +0 -0
- /package/dist/plugin-sdk/{tool-loop-detection-Bp8bPJLs.js → tool-loop-detection-sDZl27w7.js} +0 -0
- /package/dist/plugin-sdk/{ui-Be8cLHIS.js → ui-GQDYm_jw.js} +0 -0
- /package/dist/plugin-sdk/{with-timeout-ckWB8rTo.js → with-timeout-CVV8tytk.js} +0 -0
package/dist/commands/wallet.js
CHANGED
|
@@ -3,21 +3,23 @@ import { loadConfig, writeConfigFile } from "../config/io.js";
|
|
|
3
3
|
import "../config/config.js";
|
|
4
4
|
import { hasLegacyEmbeddedKeystoreConfig, hasLegacyEmbeddedKeystoreMaterialHint, throwLegacyEmbeddedKeystoreMigrationRequired } from "../wallet/legacy-embedded-keystore.js";
|
|
5
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";
|
|
6
|
+
import { normalizeWalletUserRole, readWalletProviderRegistry, replaceRetiredMiningWallet, resolveWalletUserRole, setDefaultWallet, setNamedWalletRole, setWalletProviderEnabled, upsertNamedWallet, writeWalletProviderRegistry } 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
9
|
import { callLocalSocketSigner, probeLocalSocketSignerHealth, requireLocalSocketSignerPath } from "../wallet/providers/local-socket-signer-adapter.js";
|
|
10
10
|
import { readWalletProviderSecretStatus, saveWalletProviderSecret } from "../wallet/wallet-secrets-store.js";
|
|
11
11
|
import { createWalletProviderAdapter, resolveWalletProviderId } from "../wallet/wallet-provider-resolver.js";
|
|
12
|
-
import {
|
|
13
|
-
import { createLockedSignerOwnedWallet } from "../wallet/local-socket-signer-lifecycle.js";
|
|
12
|
+
import { activateSignerOwnedRoleBaseline, createRoleReadySignerOwnedWallet, readSignerOwnedWallet, readSignerOwnedWalletReadiness } from "../wallet/local-socket-signer-lifecycle.js";
|
|
14
13
|
import { normalizeNativeSignerWalletId } from "../wallet/native-signer-wallet-id.js";
|
|
14
|
+
import { readWalletStatusSnapshot, resolveWalletConfigForRuntime } from "../wallet/wallet-status.js";
|
|
15
|
+
import { buildMiningRetirementEvidence, verifyMiningRecoveryPackage, writeMiningRetirementReceipt } from "../wallet/mining-wallet-retirement.js";
|
|
15
16
|
import { configureSignerOwnedWalletNetwork } from "../wallet/signer-network-admin.js";
|
|
16
17
|
import { listWalletInboundEvents, pollWalletInboundEvents, reconcileWalletInboundEvents } from "../wallet/wallet-inbound-events.js";
|
|
17
18
|
import { buildWalletCanaryReport, runWalletProviderCanaryReport } from "../wallet/wallet-canary.js";
|
|
18
19
|
import { installSignerdBinary, resolveSignerdBinaryPath, restartLocalSocketSigner, writeLocalSignerEnvFile } from "../wizard/onboarding.wallet.js";
|
|
19
20
|
import fs from "node:fs";
|
|
20
21
|
import path from "node:path";
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
21
23
|
import { createInterface } from "node:readline/promises";
|
|
22
24
|
|
|
23
25
|
//#region src/commands/wallet.ts
|
|
@@ -261,10 +263,6 @@ function rpcEnvKeyFor(chain, walletId) {
|
|
|
261
263
|
}
|
|
262
264
|
return "FASED_WALLET_SOLANA_RPC_URL";
|
|
263
265
|
}
|
|
264
|
-
function executionFallbackRpcEnvKeyFor(walletId) {
|
|
265
|
-
const suffix = walletIdEnvSuffix(walletId);
|
|
266
|
-
return suffix ? `FASED_WALLET_SOLANA_EXECUTION_FALLBACK_RPC_URL__${suffix}` : "FASED_WALLET_SOLANA_EXECUTION_FALLBACK_RPC_URL";
|
|
267
|
-
}
|
|
268
266
|
function resolveRpcUrlForChain(env, chain, walletId, explicit) {
|
|
269
267
|
const explicitValue = explicit?.trim();
|
|
270
268
|
if (explicitValue) {
|
|
@@ -381,17 +379,23 @@ async function createSignerOwnedWalletForSetup(params) {
|
|
|
381
379
|
const walletId = params.walletId?.trim() || params.role;
|
|
382
380
|
const hosted = String(params.env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
|
|
383
381
|
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
382
|
const mergedEnv = {
|
|
389
383
|
...params.env,
|
|
390
384
|
...cfg.env?.vars
|
|
391
385
|
};
|
|
392
386
|
const socketPath = resolveLocalSignerSocketPath(mergedEnv);
|
|
393
387
|
const expectedSignerWalletId = normalizeNativeSignerWalletId(walletId);
|
|
394
|
-
const
|
|
388
|
+
const registeredWallets = readWalletProviderRegistry(params.env).wallets;
|
|
389
|
+
if (params.role === "mining") {
|
|
390
|
+
const activeMiningWallet = registeredWallets.find((entry) => {
|
|
391
|
+
const role = normalizeWalletUserRole(entry.metadata?.role ?? entry.metadata?.purpose);
|
|
392
|
+
return role === "mining" || entry.id === "mining";
|
|
393
|
+
});
|
|
394
|
+
if (activeMiningWallet && activeMiningWallet.id !== walletId) {
|
|
395
|
+
throw new Error(`Mining already has one active wallet (${activeMiningWallet.id}). Archive it after the safety checks or complete a reviewed replacement before creating ${walletId}.`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const existingSignerIdCollision = findNativeSignerWalletIdCollision(registeredWallets, walletId, expectedSignerWalletId);
|
|
395
399
|
if (existingSignerIdCollision) {
|
|
396
400
|
throw new Error(`native signer wallet ID ${expectedSignerWalletId} is already registered as ${existingSignerIdCollision.id}; choose a distinct wallet ID`);
|
|
397
401
|
}
|
|
@@ -408,7 +412,14 @@ async function createSignerOwnedWalletForSetup(params) {
|
|
|
408
412
|
}
|
|
409
413
|
let result;
|
|
410
414
|
try {
|
|
411
|
-
result =
|
|
415
|
+
result = hosted && params.env.FASED_GATEWAY_SERVICE !== "1" ? invokeNativeSignerWalletCreate({
|
|
416
|
+
signerBinPath: "/opt/fased/signer/fased-signerd",
|
|
417
|
+
operatorSocketPath: "/run/fased-signerd/operator.sock",
|
|
418
|
+
walletId: expectedSignerWalletId,
|
|
419
|
+
role: params.role,
|
|
420
|
+
allowExisting: Boolean(params.options.force),
|
|
421
|
+
env: mergedEnv
|
|
422
|
+
}) : await createRoleReadySignerOwnedWallet({
|
|
412
423
|
socketPath,
|
|
413
424
|
walletId,
|
|
414
425
|
role: params.role,
|
|
@@ -416,7 +427,7 @@ async function createSignerOwnedWalletForSetup(params) {
|
|
|
416
427
|
});
|
|
417
428
|
} catch (error) {
|
|
418
429
|
if (hosted) {
|
|
419
|
-
throw new Error(`
|
|
430
|
+
throw new Error(`hosted signer wallet creation failed: ${error instanceof Error ? error.message : String(error)}. Confirm the operator socket and exact installed signer release are healthy, then retry the same wallet ID.`, { cause: error });
|
|
420
431
|
}
|
|
421
432
|
throw error;
|
|
422
433
|
}
|
|
@@ -428,16 +439,36 @@ async function createSignerOwnedWalletForSetup(params) {
|
|
|
428
439
|
if (signerIdCollision) {
|
|
429
440
|
throw new Error(`native signer wallet ID ${signerWalletId} is already registered as ${signerIdCollision.id}; choose a distinct wallet ID`);
|
|
430
441
|
}
|
|
431
|
-
const
|
|
432
|
-
const network =
|
|
442
|
+
const operatorLifecycle = hosted && params.env.FASED_GATEWAY_SERVICE !== "1";
|
|
443
|
+
const network = operatorLifecycle ? invokeNativeSignerNetworkSetPrimary({
|
|
444
|
+
signerBinPath: "/opt/fased/signer/fased-signerd",
|
|
445
|
+
socketFlag: "--operator-socket",
|
|
446
|
+
socketPath: "/run/fased-signerd/operator.sock",
|
|
447
|
+
walletId: signerWalletId,
|
|
448
|
+
primaryRpcUrl: params.rpcUrl,
|
|
449
|
+
expectedVersion: 0,
|
|
450
|
+
env: mergedEnv
|
|
451
|
+
}) : await configureSignerOwnedWalletNetwork({
|
|
433
452
|
walletId: signerWalletId,
|
|
434
453
|
primaryRpcUrl: params.rpcUrl,
|
|
435
|
-
executionFallbackRpcUrl: executionFallbackRpcUrl || undefined,
|
|
436
454
|
env: mergedEnv,
|
|
437
|
-
|
|
438
|
-
controlSocketPath: hosted ? undefined : resolveLocalSignerControlSocketPath(mergedEnv)
|
|
455
|
+
socketPath
|
|
439
456
|
});
|
|
457
|
+
cfg = setConfigEnvVar(cfg, rpcEnvKeyFor(params.chain, walletId), params.rpcUrl);
|
|
440
458
|
await writeConfigFile(cfg);
|
|
459
|
+
const readiness = operatorLifecycle ? invokeNativeSignerWalletReadiness({
|
|
460
|
+
signerBinPath: "/opt/fased/signer/fased-signerd",
|
|
461
|
+
socketFlag: "--operator-socket",
|
|
462
|
+
socketPath: "/run/fased-signerd/operator.sock",
|
|
463
|
+
walletId: signerWalletId,
|
|
464
|
+
env: mergedEnv
|
|
465
|
+
}) : await readSignerOwnedWalletReadiness({
|
|
466
|
+
socketPath,
|
|
467
|
+
walletId: signerWalletId
|
|
468
|
+
});
|
|
469
|
+
if (!readiness.ready || readiness.walletId !== signerWalletId || readiness.publicKey !== result.wallet.publicKey || readiness.role !== params.role || readiness.baselineVersion !== 1 || readiness.policyHash !== result.policy.hash || readiness.policyVersion !== result.policy.version || readiness.networkHash !== network.hash || readiness.networkVersion !== network.version) {
|
|
470
|
+
throw new Error(`Setup incomplete: signer readiness does not match the created ${params.role} wallet lifecycle`);
|
|
471
|
+
}
|
|
441
472
|
const wallet = upsertNamedWallet({
|
|
442
473
|
walletId,
|
|
443
474
|
name: params.options.walletName || "Wallet",
|
|
@@ -450,18 +481,37 @@ async function createSignerOwnedWalletForSetup(params) {
|
|
|
450
481
|
signerWalletId,
|
|
451
482
|
policyHash: result.policy.hash,
|
|
452
483
|
policyVersion: result.policy.version,
|
|
453
|
-
|
|
484
|
+
baselineVersion: readiness.baselineVersion,
|
|
485
|
+
policyState: "ready",
|
|
454
486
|
networkHash: network.hash,
|
|
455
487
|
networkVersion: network.version,
|
|
456
|
-
networkReady: network.ready
|
|
488
|
+
networkReady: network.ready,
|
|
489
|
+
operationLane: readiness.operationLane,
|
|
490
|
+
roleReady: readiness.ready
|
|
457
491
|
},
|
|
458
492
|
env: params.env
|
|
459
493
|
});
|
|
460
|
-
if (params.role === "
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
494
|
+
if (params.role === "mining") {
|
|
495
|
+
const currentEntry = cfg.plugins?.entries?.["sat-mining"];
|
|
496
|
+
const currentSatConfig = currentEntry?.config && typeof currentEntry.config === "object" && !Array.isArray(currentEntry.config) ? currentEntry.config : {};
|
|
497
|
+
cfg = {
|
|
498
|
+
...cfg,
|
|
499
|
+
plugins: {
|
|
500
|
+
...cfg.plugins,
|
|
501
|
+
entries: {
|
|
502
|
+
...cfg.plugins?.entries,
|
|
503
|
+
"sat-mining": {
|
|
504
|
+
enabled: true,
|
|
505
|
+
...currentEntry,
|
|
506
|
+
config: {
|
|
507
|
+
...currentSatConfig,
|
|
508
|
+
walletId: wallet.id
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
await writeConfigFile(cfg);
|
|
465
515
|
}
|
|
466
516
|
if (params.options.json) {
|
|
467
517
|
params.runtime.log(JSON.stringify({
|
|
@@ -473,24 +523,582 @@ async function createSignerOwnedWalletForSetup(params) {
|
|
|
473
523
|
address: result.wallet.publicKey,
|
|
474
524
|
policyHash: result.policy.hash,
|
|
475
525
|
policyVersion: result.policy.version,
|
|
476
|
-
|
|
526
|
+
baselineVersion: readiness.baselineVersion,
|
|
527
|
+
policyState: "ready",
|
|
477
528
|
networkVersion: network.version,
|
|
478
529
|
networkReady: network.ready,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
rootCommand: network.rootCommand
|
|
482
|
-
} : {}
|
|
530
|
+
operationLane: readiness.operationLane,
|
|
531
|
+
roleReady: readiness.ready
|
|
483
532
|
}, null, 2));
|
|
484
533
|
} else {
|
|
485
534
|
params.runtime.log(`Signer wallet ID: ${signerWalletId}`);
|
|
486
535
|
params.runtime.log(`${params.chain.toUpperCase()} address: ${result.wallet.publicKey}`);
|
|
487
536
|
if (!params.options.noSignerHints) {
|
|
488
|
-
params.runtime.log(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
537
|
+
params.runtime.log(`Role baseline active: ${params.role} v${readiness.baselineVersion} (${readiness.operationLane}).`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
function requireOwnerOnlySignerImportFile(rawPath) {
|
|
543
|
+
const importPath = path.resolve(rawPath.trim());
|
|
544
|
+
if (!rawPath.trim() || !path.isAbsolute(rawPath.trim()) || importPath !== rawPath.trim()) {
|
|
545
|
+
throw new Error("--import-file must be an absolute clean path");
|
|
546
|
+
}
|
|
547
|
+
const info = fs.lstatSync(importPath);
|
|
548
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
|
|
549
|
+
throw new Error("wallet import file must be one regular, non-symlink, single-link file");
|
|
550
|
+
}
|
|
551
|
+
if ((info.mode & 63) !== 0) {
|
|
552
|
+
throw new Error("wallet import file must be owner-only (chmod 600)");
|
|
553
|
+
}
|
|
554
|
+
const effectiveUid = process.geteuid?.();
|
|
555
|
+
if (effectiveUid !== undefined && info.uid !== effectiveUid) {
|
|
556
|
+
throw new Error("wallet import file must be owned by the current terminal user");
|
|
557
|
+
}
|
|
558
|
+
const fd = fs.openSync(importPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
559
|
+
try {
|
|
560
|
+
const opened = fs.fstatSync(fd);
|
|
561
|
+
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== info.dev || opened.ino !== info.ino || (opened.mode & 63) !== 0 || effectiveUid !== undefined && opened.uid !== effectiveUid) {
|
|
562
|
+
throw new Error("wallet import file changed or became unsafe before it could be read");
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
path: importPath,
|
|
566
|
+
fd
|
|
567
|
+
};
|
|
568
|
+
} catch (error) {
|
|
569
|
+
fs.closeSync(fd);
|
|
570
|
+
throw error;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function parseNativeSignerImportResult(params) {
|
|
574
|
+
let value;
|
|
575
|
+
try {
|
|
576
|
+
value = JSON.parse(params.stdout.trim());
|
|
577
|
+
} catch {
|
|
578
|
+
throw new Error("native signer import returned invalid JSON");
|
|
579
|
+
}
|
|
580
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
581
|
+
throw new Error("native signer import returned an invalid result");
|
|
582
|
+
}
|
|
583
|
+
const result = value;
|
|
584
|
+
if (result.wallet?.walletId !== params.walletId || typeof result.wallet.publicKey !== "string" || !result.wallet.publicKey.trim() || !Number.isSafeInteger(result.wallet.version) || result.policy?.walletId !== params.walletId || result.policy.role !== params.role || result.policy.version !== 1 || result.policy.baselineVersion !== 1 || !result.policy.operations?.length || !result.policy.programs?.length || !result.policy.assets?.length || typeof result.policy.hash !== "string" || !/^sha256:[0-9a-f]{64}$/.test(result.policy.hash)) {
|
|
585
|
+
throw new Error("native signer import did not return an active signer-owned role baseline");
|
|
586
|
+
}
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
function invokeNativeSignerWalletImport(params) {
|
|
590
|
+
const input = requireOwnerOnlySignerImportFile(params.importFile);
|
|
591
|
+
const command = params.signerBinPath;
|
|
592
|
+
const args = [
|
|
593
|
+
"admin",
|
|
594
|
+
"wallet",
|
|
595
|
+
"import",
|
|
596
|
+
params.hosted ? "--operator-socket" : "--control-socket",
|
|
597
|
+
params.controlSocketPath,
|
|
598
|
+
"--wallet-id",
|
|
599
|
+
params.walletId,
|
|
600
|
+
"--baseline-role",
|
|
601
|
+
params.role
|
|
602
|
+
];
|
|
603
|
+
try {
|
|
604
|
+
const child = spawnSync(command, args, {
|
|
605
|
+
env: {
|
|
606
|
+
HOME: params.env.HOME,
|
|
607
|
+
LANG: params.env.LANG || "C.UTF-8",
|
|
608
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
609
|
+
},
|
|
610
|
+
stdio: [
|
|
611
|
+
input.fd,
|
|
612
|
+
"pipe",
|
|
613
|
+
"pipe"
|
|
614
|
+
],
|
|
615
|
+
encoding: "utf8",
|
|
616
|
+
maxBuffer: 256 * 1024,
|
|
617
|
+
timeout: 3e4
|
|
618
|
+
});
|
|
619
|
+
if (child.error) {
|
|
620
|
+
throw child.error;
|
|
621
|
+
}
|
|
622
|
+
if (child.status !== 0) {
|
|
623
|
+
const detail = String(child.stderr || "native signer import failed").trim();
|
|
624
|
+
throw new Error(redactWalletDiagnosticText(detail));
|
|
625
|
+
}
|
|
626
|
+
return parseNativeSignerImportResult({
|
|
627
|
+
stdout: String(child.stdout ?? ""),
|
|
628
|
+
walletId: params.walletId,
|
|
629
|
+
role: params.role
|
|
630
|
+
});
|
|
631
|
+
} finally {
|
|
632
|
+
fs.closeSync(input.fd);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function nativeSignerLifecycleEnv(env) {
|
|
636
|
+
return {
|
|
637
|
+
HOME: env.HOME,
|
|
638
|
+
LANG: env.LANG || "C.UTF-8",
|
|
639
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function invokeNativeSignerWalletCreate(params) {
|
|
643
|
+
const child = spawnSync(params.signerBinPath, [
|
|
644
|
+
"admin",
|
|
645
|
+
"wallet",
|
|
646
|
+
"create",
|
|
647
|
+
"--operator-socket",
|
|
648
|
+
params.operatorSocketPath,
|
|
649
|
+
"--wallet-id",
|
|
650
|
+
params.walletId,
|
|
651
|
+
"--baseline-role",
|
|
652
|
+
params.role,
|
|
653
|
+
...params.allowExisting ? ["--allow-existing"] : []
|
|
654
|
+
], {
|
|
655
|
+
env: nativeSignerLifecycleEnv(params.env),
|
|
656
|
+
stdio: [
|
|
657
|
+
"ignore",
|
|
658
|
+
"pipe",
|
|
659
|
+
"pipe"
|
|
660
|
+
],
|
|
661
|
+
encoding: "utf8",
|
|
662
|
+
maxBuffer: 256 * 1024,
|
|
663
|
+
timeout: 3e4
|
|
664
|
+
});
|
|
665
|
+
if (child.error) {
|
|
666
|
+
throw child.error;
|
|
667
|
+
}
|
|
668
|
+
if (child.status !== 0) {
|
|
669
|
+
throw new Error(redactWalletDiagnosticText(String(child.stderr || "native signer create failed").trim()));
|
|
670
|
+
}
|
|
671
|
+
return parseNativeSignerImportResult({
|
|
672
|
+
stdout: String(child.stdout ?? ""),
|
|
673
|
+
walletId: params.walletId,
|
|
674
|
+
role: params.role
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
function invokeNativeSignerNetworkSetPrimary(params) {
|
|
678
|
+
const input = JSON.stringify({ primaryRpcUrl: params.primaryRpcUrl });
|
|
679
|
+
const child = spawnSync(params.signerBinPath, [
|
|
680
|
+
"admin",
|
|
681
|
+
"network",
|
|
682
|
+
"set-primary",
|
|
683
|
+
params.socketFlag,
|
|
684
|
+
params.socketPath,
|
|
685
|
+
"--wallet-id",
|
|
686
|
+
params.walletId,
|
|
687
|
+
"--expected-version",
|
|
688
|
+
String(params.expectedVersion)
|
|
689
|
+
], {
|
|
690
|
+
env: nativeSignerLifecycleEnv(params.env),
|
|
691
|
+
input,
|
|
692
|
+
stdio: [
|
|
693
|
+
"pipe",
|
|
694
|
+
"pipe",
|
|
695
|
+
"pipe"
|
|
696
|
+
],
|
|
697
|
+
encoding: "utf8",
|
|
698
|
+
maxBuffer: 256 * 1024,
|
|
699
|
+
timeout: 6e4
|
|
700
|
+
});
|
|
701
|
+
if (child.error) {
|
|
702
|
+
throw child.error;
|
|
703
|
+
}
|
|
704
|
+
if (child.status !== 0) {
|
|
705
|
+
throw new Error(redactWalletDiagnosticText(String(child.stderr || "native signer RPC update failed").trim()));
|
|
706
|
+
}
|
|
707
|
+
let result;
|
|
708
|
+
try {
|
|
709
|
+
result = JSON.parse(String(child.stdout ?? ""));
|
|
710
|
+
} catch {
|
|
711
|
+
throw new Error("native signer RPC update returned invalid JSON");
|
|
712
|
+
}
|
|
713
|
+
const record = result;
|
|
714
|
+
if (!record || record.walletId !== params.walletId || record.configured !== true || record.ready !== true || !Number.isSafeInteger(record.version) || typeof record.hash !== "string" || !/^hmac-sha256:[0-9a-f]{64}$/.test(record.hash)) {
|
|
715
|
+
throw new Error("native signer RPC update returned an invalid readiness summary");
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
walletId: String(record.walletId),
|
|
719
|
+
configured: true,
|
|
720
|
+
ready: true,
|
|
721
|
+
version: Number(record.version),
|
|
722
|
+
hash: String(record.hash)
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function invokeNativeSignerWalletReadiness(params) {
|
|
726
|
+
const child = spawnSync(params.signerBinPath, [
|
|
727
|
+
"admin",
|
|
728
|
+
"wallet",
|
|
729
|
+
"readiness",
|
|
730
|
+
params.socketFlag,
|
|
731
|
+
params.socketPath,
|
|
732
|
+
"--wallet-id",
|
|
733
|
+
params.walletId
|
|
734
|
+
], {
|
|
735
|
+
env: nativeSignerLifecycleEnv(params.env),
|
|
736
|
+
stdio: [
|
|
737
|
+
"ignore",
|
|
738
|
+
"pipe",
|
|
739
|
+
"pipe"
|
|
740
|
+
],
|
|
741
|
+
encoding: "utf8",
|
|
742
|
+
maxBuffer: 256 * 1024,
|
|
743
|
+
timeout: 3e4
|
|
744
|
+
});
|
|
745
|
+
if (child.error) {
|
|
746
|
+
throw child.error;
|
|
747
|
+
}
|
|
748
|
+
if (child.status !== 0) {
|
|
749
|
+
throw new Error(redactWalletDiagnosticText(String(child.stderr || "native signer readiness failed").trim()));
|
|
750
|
+
}
|
|
751
|
+
let result;
|
|
752
|
+
try {
|
|
753
|
+
result = JSON.parse(String(child.stdout ?? ""));
|
|
754
|
+
} catch {
|
|
755
|
+
throw new Error("native signer readiness returned invalid JSON");
|
|
756
|
+
}
|
|
757
|
+
const readiness = result;
|
|
758
|
+
if (!readiness || readiness.walletId !== params.walletId || typeof readiness.publicKey !== "string" || !Number.isSafeInteger(readiness.walletVersion) || typeof readiness.role !== "string" || !Number.isSafeInteger(readiness.policyVersion) || !Number.isSafeInteger(readiness.networkVersion) || typeof readiness.ready !== "boolean") {
|
|
759
|
+
throw new Error("native signer readiness returned an invalid result");
|
|
760
|
+
}
|
|
761
|
+
return readiness;
|
|
762
|
+
}
|
|
763
|
+
function invokeNativeSignerJSON(params) {
|
|
764
|
+
const child = spawnSync(params.signerBinPath, params.args, {
|
|
765
|
+
env: nativeSignerLifecycleEnv(params.env),
|
|
766
|
+
input: params.input,
|
|
767
|
+
stdio: [
|
|
768
|
+
params.input === undefined ? "ignore" : "pipe",
|
|
769
|
+
"pipe",
|
|
770
|
+
"pipe"
|
|
771
|
+
],
|
|
772
|
+
encoding: "utf8",
|
|
773
|
+
maxBuffer: 512 * 1024,
|
|
774
|
+
timeout: 12e4
|
|
775
|
+
});
|
|
776
|
+
if (child.error) {
|
|
777
|
+
throw child.error;
|
|
778
|
+
}
|
|
779
|
+
if (child.status !== 0) {
|
|
780
|
+
throw new Error(redactWalletDiagnosticText(String(child.stderr || `${params.label} failed`).trim()));
|
|
781
|
+
}
|
|
782
|
+
let parsed;
|
|
783
|
+
try {
|
|
784
|
+
parsed = JSON.parse(String(child.stdout ?? ""));
|
|
785
|
+
} catch {
|
|
786
|
+
throw new Error(`${params.label} returned invalid JSON`);
|
|
787
|
+
}
|
|
788
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
789
|
+
throw new Error(`${params.label} returned an invalid result`);
|
|
790
|
+
}
|
|
791
|
+
return parsed;
|
|
792
|
+
}
|
|
793
|
+
function invokeNativeSignerBalance(params) {
|
|
794
|
+
const result = invokeNativeSignerJSON({
|
|
795
|
+
signerBinPath: params.signerBinPath,
|
|
796
|
+
args: [
|
|
797
|
+
"admin",
|
|
798
|
+
"wallet",
|
|
799
|
+
"balance",
|
|
800
|
+
params.socketFlag,
|
|
801
|
+
params.socketPath,
|
|
802
|
+
"--wallet-id",
|
|
803
|
+
params.walletId
|
|
804
|
+
],
|
|
805
|
+
env: params.env,
|
|
806
|
+
label: "native signer balance lookup"
|
|
807
|
+
});
|
|
808
|
+
const balance = typeof result.balance === "string" ? result.balance : "";
|
|
809
|
+
if (result.address !== params.publicKey || result.chain !== "solana" || result.unit !== "lamports" || !/^\d+$/u.test(balance)) {
|
|
810
|
+
throw new Error("native signer balance lookup returned an invalid result");
|
|
811
|
+
}
|
|
812
|
+
return balance;
|
|
813
|
+
}
|
|
814
|
+
function parseNativeSignerRotation(value) {
|
|
815
|
+
if (typeof value.rotationId !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(value.rotationId) || typeof value.sourceWalletId !== "string" || typeof value.sourcePublicKey !== "string" || typeof value.successorWalletId !== "string" || typeof value.successorPublicKey !== "string" || value.state !== "prepared" && value.state !== "committed" || !Number.isSafeInteger(value.version)) {
|
|
816
|
+
throw new Error("native signer rotation returned an invalid result");
|
|
817
|
+
}
|
|
818
|
+
return value;
|
|
819
|
+
}
|
|
820
|
+
function invokeNativeSignerRotationCreate(params) {
|
|
821
|
+
return parseNativeSignerRotation(invokeNativeSignerJSON({
|
|
822
|
+
signerBinPath: params.signerBinPath,
|
|
823
|
+
args: [
|
|
824
|
+
"admin",
|
|
825
|
+
"wallet",
|
|
826
|
+
"rotate-successor",
|
|
827
|
+
params.socketFlag,
|
|
828
|
+
params.socketPath,
|
|
829
|
+
"--wallet-id",
|
|
830
|
+
params.sourceWalletId,
|
|
831
|
+
"--successor-wallet-id",
|
|
832
|
+
params.successorWalletId,
|
|
833
|
+
"--expected-source-public-key",
|
|
834
|
+
params.sourcePublicKey,
|
|
835
|
+
"--expected-source-wallet-version",
|
|
836
|
+
String(params.sourceWalletVersion),
|
|
837
|
+
"--expected-source-policy-version",
|
|
838
|
+
String(params.sourcePolicyVersion)
|
|
839
|
+
],
|
|
840
|
+
env: params.env,
|
|
841
|
+
label: "native signer Mining successor preparation"
|
|
842
|
+
}));
|
|
843
|
+
}
|
|
844
|
+
function invokeNativeSignerRotationStatus(params) {
|
|
845
|
+
try {
|
|
846
|
+
return parseNativeSignerRotation(invokeNativeSignerJSON({
|
|
847
|
+
signerBinPath: params.signerBinPath,
|
|
848
|
+
args: [
|
|
849
|
+
"admin",
|
|
850
|
+
"wallet",
|
|
851
|
+
"rotation-status",
|
|
852
|
+
params.socketFlag,
|
|
853
|
+
params.socketPath,
|
|
854
|
+
"--wallet-id",
|
|
855
|
+
params.sourceWalletId
|
|
856
|
+
],
|
|
857
|
+
env: params.env,
|
|
858
|
+
label: "native signer Mining rotation status"
|
|
859
|
+
}));
|
|
860
|
+
} catch (error) {
|
|
861
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
862
|
+
if (/rotation.*not found/iu.test(message)) {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
throw error;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function invokeNativeSignerRotationCommit(params) {
|
|
869
|
+
const rotation = params.rotation;
|
|
870
|
+
return parseNativeSignerRotation(invokeNativeSignerJSON({
|
|
871
|
+
signerBinPath: params.signerBinPath,
|
|
872
|
+
args: [
|
|
873
|
+
"admin",
|
|
874
|
+
"wallet",
|
|
875
|
+
"rotation-commit",
|
|
876
|
+
params.socketFlag,
|
|
877
|
+
params.socketPath,
|
|
878
|
+
"--wallet-id",
|
|
879
|
+
rotation.sourceWalletId,
|
|
880
|
+
"--successor-wallet-id",
|
|
881
|
+
rotation.successorWalletId,
|
|
882
|
+
"--rotation-id",
|
|
883
|
+
rotation.rotationId,
|
|
884
|
+
"--expected-source-public-key",
|
|
885
|
+
rotation.sourcePublicKey,
|
|
886
|
+
"--expected-successor-public-key",
|
|
887
|
+
rotation.successorPublicKey,
|
|
888
|
+
"--expected-source-wallet-version",
|
|
889
|
+
String(rotation.prepareExpectedSourceWalletVersion),
|
|
890
|
+
"--expected-source-policy-version",
|
|
891
|
+
String(rotation.prepareExpectedSourcePolicyVersion),
|
|
892
|
+
"--expected-successor-wallet-version",
|
|
893
|
+
"1",
|
|
894
|
+
"--expected-successor-policy-version",
|
|
895
|
+
"1",
|
|
896
|
+
"--expected-rotation-version",
|
|
897
|
+
"1",
|
|
898
|
+
"--expected-successor-network-version",
|
|
899
|
+
String(params.successorNetworkVersion),
|
|
900
|
+
"--expected-successor-network-hash",
|
|
901
|
+
params.successorNetworkHash
|
|
902
|
+
],
|
|
903
|
+
input: JSON.stringify({
|
|
904
|
+
recoveryPackageHash: params.recoveryPackageHash,
|
|
905
|
+
safetyEvidence: params.safetyEvidence
|
|
906
|
+
}),
|
|
907
|
+
env: params.env,
|
|
908
|
+
label: "native signer Mining retirement commit"
|
|
909
|
+
}));
|
|
910
|
+
}
|
|
911
|
+
function invokeNativeSignerRecoveryImport(params) {
|
|
912
|
+
const input = requireOwnerOnlySignerImportFile(params.recoveryFile);
|
|
913
|
+
try {
|
|
914
|
+
const child = spawnSync(params.signerBinPath, [
|
|
915
|
+
"admin",
|
|
916
|
+
"wallet",
|
|
917
|
+
"recovery-import",
|
|
918
|
+
params.hosted ? "--operator-socket" : "--control-socket",
|
|
919
|
+
params.controlSocketPath,
|
|
920
|
+
"--wallet-id",
|
|
921
|
+
params.walletId,
|
|
922
|
+
"--baseline-role",
|
|
923
|
+
params.role,
|
|
924
|
+
"--recovery-file",
|
|
925
|
+
input.path
|
|
926
|
+
], {
|
|
927
|
+
env: {
|
|
928
|
+
HOME: params.env.HOME,
|
|
929
|
+
LANG: params.env.LANG || "C.UTF-8",
|
|
930
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
931
|
+
},
|
|
932
|
+
stdio: [
|
|
933
|
+
"inherit",
|
|
934
|
+
"pipe",
|
|
935
|
+
"pipe"
|
|
936
|
+
],
|
|
937
|
+
encoding: "utf8",
|
|
938
|
+
maxBuffer: 256 * 1024,
|
|
939
|
+
timeout: 12e4
|
|
940
|
+
});
|
|
941
|
+
if (child.error) {
|
|
942
|
+
throw child.error;
|
|
943
|
+
}
|
|
944
|
+
if (child.status !== 0) {
|
|
945
|
+
const detail = String(child.stderr || "native signer recovery import failed").trim();
|
|
946
|
+
throw new Error(redactWalletDiagnosticText(detail));
|
|
947
|
+
}
|
|
948
|
+
return parseNativeSignerImportResult({
|
|
949
|
+
stdout: String(child.stdout ?? ""),
|
|
950
|
+
walletId: params.walletId,
|
|
951
|
+
role: params.role
|
|
952
|
+
});
|
|
953
|
+
} finally {
|
|
954
|
+
fs.closeSync(input.fd);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async function importSignerOwnedWalletForSetup(params) {
|
|
958
|
+
if (params.chain !== "solana") {
|
|
959
|
+
throw new Error("fased-signerd protocol v2 currently supports Solana wallet import only");
|
|
960
|
+
}
|
|
961
|
+
const hosted = String(params.env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
|
|
962
|
+
let cfg = ensureLocalSignerProviderConfig(loadConfig(), params.env);
|
|
963
|
+
const mergedEnv = {
|
|
964
|
+
...params.env,
|
|
965
|
+
...cfg.env?.vars
|
|
966
|
+
};
|
|
967
|
+
const socketPath = resolveLocalSignerSocketPath(mergedEnv);
|
|
968
|
+
const signerWalletId = normalizeNativeSignerWalletId(params.walletId);
|
|
969
|
+
const registeredWallets = readWalletProviderRegistry(params.env).wallets;
|
|
970
|
+
if (params.role === "mining" && registeredWallets.some((entry) => {
|
|
971
|
+
const role = normalizeWalletUserRole(entry.metadata?.role ?? entry.metadata?.purpose);
|
|
972
|
+
return (role === "mining" || entry.id === "mining") && entry.id !== params.walletId;
|
|
973
|
+
})) {
|
|
974
|
+
throw new Error("Mining already has one active wallet. Complete the guarded Archive/Replace flow first.");
|
|
975
|
+
}
|
|
976
|
+
const collision = findNativeSignerWalletIdCollision(registeredWallets, params.walletId, signerWalletId);
|
|
977
|
+
if (collision) {
|
|
978
|
+
throw new Error(`native signer wallet ID ${signerWalletId} is already registered as ${collision.id}; choose a distinct wallet ID`);
|
|
979
|
+
}
|
|
980
|
+
const signerBinPath = hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(mergedEnv);
|
|
981
|
+
const controlSocketPath = hosted ? "/run/fased-signerd/operator.sock" : resolveLocalSignerControlSocketPath(mergedEnv);
|
|
982
|
+
if (!hosted) {
|
|
983
|
+
writeLocalSignerEnvFile({
|
|
984
|
+
config: cfg,
|
|
985
|
+
env: mergedEnv
|
|
986
|
+
});
|
|
987
|
+
if (!fs.existsSync(signerBinPath)) {
|
|
988
|
+
installSignerdBinary(signerBinPath);
|
|
493
989
|
}
|
|
990
|
+
await restartLocalSocketSigner(undefined, mergedEnv);
|
|
991
|
+
}
|
|
992
|
+
const result = params.options.mode === "local-signer-recovery-import" ? invokeNativeSignerRecoveryImport({
|
|
993
|
+
hosted,
|
|
994
|
+
signerBinPath,
|
|
995
|
+
controlSocketPath,
|
|
996
|
+
walletId: signerWalletId,
|
|
997
|
+
role: params.role,
|
|
998
|
+
recoveryFile: params.importFile,
|
|
999
|
+
env: mergedEnv
|
|
1000
|
+
}) : invokeNativeSignerWalletImport({
|
|
1001
|
+
hosted,
|
|
1002
|
+
signerBinPath,
|
|
1003
|
+
controlSocketPath,
|
|
1004
|
+
walletId: signerWalletId,
|
|
1005
|
+
role: params.role,
|
|
1006
|
+
importFile: params.importFile,
|
|
1007
|
+
env: mergedEnv
|
|
1008
|
+
});
|
|
1009
|
+
const network = hosted ? invokeNativeSignerNetworkSetPrimary({
|
|
1010
|
+
signerBinPath,
|
|
1011
|
+
socketFlag: "--operator-socket",
|
|
1012
|
+
socketPath: controlSocketPath,
|
|
1013
|
+
walletId: signerWalletId,
|
|
1014
|
+
primaryRpcUrl: params.rpcUrl,
|
|
1015
|
+
expectedVersion: 0,
|
|
1016
|
+
env: mergedEnv
|
|
1017
|
+
}) : await configureSignerOwnedWalletNetwork({
|
|
1018
|
+
walletId: signerWalletId,
|
|
1019
|
+
primaryRpcUrl: params.rpcUrl,
|
|
1020
|
+
env: mergedEnv,
|
|
1021
|
+
socketPath
|
|
1022
|
+
});
|
|
1023
|
+
cfg = setConfigEnvVar(cfg, rpcEnvKeyFor(params.chain, params.walletId), params.rpcUrl);
|
|
1024
|
+
await writeConfigFile(cfg);
|
|
1025
|
+
const readiness = hosted ? invokeNativeSignerWalletReadiness({
|
|
1026
|
+
signerBinPath,
|
|
1027
|
+
socketFlag: "--operator-socket",
|
|
1028
|
+
socketPath: controlSocketPath,
|
|
1029
|
+
walletId: signerWalletId,
|
|
1030
|
+
env: mergedEnv
|
|
1031
|
+
}) : await readSignerOwnedWalletReadiness({
|
|
1032
|
+
socketPath,
|
|
1033
|
+
walletId: signerWalletId
|
|
1034
|
+
});
|
|
1035
|
+
if (!readiness.ready || readiness.publicKey !== result.wallet.publicKey || readiness.role !== params.role || readiness.baselineVersion !== 1 || readiness.policyHash !== result.policy.hash || readiness.policyVersion !== result.policy.version || readiness.networkHash !== network.hash || readiness.networkVersion !== network.version) {
|
|
1036
|
+
throw new Error(`Setup incomplete: signer readiness does not match the imported ${params.role} wallet lifecycle`);
|
|
1037
|
+
}
|
|
1038
|
+
const wallet = upsertNamedWallet({
|
|
1039
|
+
walletId: params.walletId,
|
|
1040
|
+
name: params.options.walletName || "Wallet",
|
|
1041
|
+
providerId: "local-socket-signer",
|
|
1042
|
+
addresses: { solana: result.wallet.publicKey },
|
|
1043
|
+
metadata: {
|
|
1044
|
+
role: params.role,
|
|
1045
|
+
purpose: params.role,
|
|
1046
|
+
keyAuthority: "signer-owned-v2",
|
|
1047
|
+
signerWalletId,
|
|
1048
|
+
policyHash: result.policy.hash,
|
|
1049
|
+
policyVersion: result.policy.version,
|
|
1050
|
+
baselineVersion: readiness.baselineVersion,
|
|
1051
|
+
policyState: "ready",
|
|
1052
|
+
networkHash: network.hash,
|
|
1053
|
+
networkVersion: network.version,
|
|
1054
|
+
networkReady: network.ready,
|
|
1055
|
+
operationLane: readiness.operationLane,
|
|
1056
|
+
roleReady: readiness.ready
|
|
1057
|
+
},
|
|
1058
|
+
env: params.env
|
|
1059
|
+
});
|
|
1060
|
+
if (params.role === "mining") {
|
|
1061
|
+
const currentEntry = cfg.plugins?.entries?.["sat-mining"];
|
|
1062
|
+
const currentConfig = currentEntry?.config && typeof currentEntry.config === "object" && !Array.isArray(currentEntry.config) ? currentEntry.config : {};
|
|
1063
|
+
cfg = {
|
|
1064
|
+
...cfg,
|
|
1065
|
+
plugins: {
|
|
1066
|
+
...cfg.plugins,
|
|
1067
|
+
entries: {
|
|
1068
|
+
...cfg.plugins?.entries,
|
|
1069
|
+
"sat-mining": {
|
|
1070
|
+
enabled: true,
|
|
1071
|
+
...currentEntry,
|
|
1072
|
+
config: {
|
|
1073
|
+
...currentConfig,
|
|
1074
|
+
walletId: wallet.id
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
await writeConfigFile(cfg);
|
|
1081
|
+
}
|
|
1082
|
+
if (params.options.json) {
|
|
1083
|
+
params.runtime.log(JSON.stringify({
|
|
1084
|
+
ok: true,
|
|
1085
|
+
provider: "local-socket-signer",
|
|
1086
|
+
imported: true,
|
|
1087
|
+
walletId: wallet.id,
|
|
1088
|
+
signerWalletId,
|
|
1089
|
+
role: params.role,
|
|
1090
|
+
address: result.wallet.publicKey,
|
|
1091
|
+
baselineVersion: readiness.baselineVersion,
|
|
1092
|
+
policyState: "ready",
|
|
1093
|
+
networkVersion: network.version,
|
|
1094
|
+
networkReady: network.ready,
|
|
1095
|
+
operationLane: readiness.operationLane,
|
|
1096
|
+
roleReady: readiness.ready
|
|
1097
|
+
}, null, 2));
|
|
1098
|
+
} else {
|
|
1099
|
+
params.runtime.log(`Imported ${params.role} wallet ${wallet.id} into fased-signerd.`);
|
|
1100
|
+
params.runtime.log(`SOLANA address: ${result.wallet.publicKey}`);
|
|
1101
|
+
params.runtime.log(`Readiness: ${readiness.ready ? "ready" : "setup incomplete"} (${readiness.operationLane})`);
|
|
494
1102
|
}
|
|
495
1103
|
return result;
|
|
496
1104
|
}
|
|
@@ -591,27 +1199,32 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
|
|
|
591
1199
|
if (!mode) {
|
|
592
1200
|
runtime.log("Wallet setup modes:");
|
|
593
1201
|
runtime.log(" local-signer-create Create a signer-owned Solana wallet (default)");
|
|
594
|
-
runtime.log(" local-signer-import
|
|
1202
|
+
runtime.log(" local-signer-import Import an owner-only Solana keypair into the signer");
|
|
1203
|
+
runtime.log(" local-signer-recovery-import Restore an encrypted signer recovery package");
|
|
595
1204
|
runtime.log(" turnkey Configure hosted provider (Turnkey)");
|
|
596
1205
|
runtime.log(" alchemy Configure hosted provider (Alchemy)");
|
|
597
1206
|
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";
|
|
1207
|
+
mode = picked === "local-signer-create" || picked === "local-signer-import" || picked === "local-signer-recovery-import" || picked === "local-signer" || picked === "turnkey" || picked === "alchemy" ? picked : "local-signer-create";
|
|
599
1208
|
}
|
|
600
1209
|
if (mode === "embedded" || mode === "embedded-create" || mode === "embedded-import") {
|
|
601
1210
|
throwLegacyEmbeddedKeystoreMigrationRequired(`wallet setup mode ${mode} is unavailable`);
|
|
602
1211
|
}
|
|
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.");
|
|
1212
|
+
if (mode !== "local-signer-create" && mode !== "local-signer-import" && mode !== "local-signer-recovery-import" && mode !== "local-signer" && mode !== "turnkey" && mode !== "alchemy" && mode !== "privy") {
|
|
1213
|
+
throw new Error(`Unsupported wallet setup mode: ${String(mode)}. ` + "Use one of: local-signer-create, local-signer-import, local-signer-recovery-import, local-signer, turnkey, alchemy. Privy is unavailable.");
|
|
605
1214
|
}
|
|
606
1215
|
if (mode === "local-signer-create") {
|
|
607
1216
|
const chain = options.chain ?? "solana";
|
|
608
|
-
const
|
|
1217
|
+
const roleInput = options.role ?? (interactive ? await prompt("Wallet role (agent|mining|vault)", "") : "");
|
|
1218
|
+
const role = normalizeWalletUserRole(roleInput);
|
|
1219
|
+
if (!role) {
|
|
1220
|
+
throw new Error("--role is required for non-interactive wallet creation and must be agent, mining, or vault");
|
|
1221
|
+
}
|
|
1222
|
+
const walletId = options.walletId ?? ((await prompt("Wallet id", role)).trim() || role);
|
|
609
1223
|
const rpcUrlFallback = resolveRpcUrlForChain(env, chain, walletId, options.rpcUrl);
|
|
610
1224
|
const rpcUrl = (await prompt(`${chain.toUpperCase()} RPC URL (required for balances/readiness/send)`, rpcUrlFallback)).trim();
|
|
611
1225
|
if (!rpcUrl) {
|
|
612
1226
|
throw new Error(`${chain.toUpperCase()} RPC URL is required for self-hosted wallet setup. ` + "Pass --rpc-url or set FASED_WALLET_<CHAIN>_RPC_URL.");
|
|
613
1227
|
}
|
|
614
|
-
const role = normalizeWalletUserRole(options.role) ?? (walletId?.trim().toLowerCase() === "mining" ? "mining" : walletId?.trim().toLowerCase() === "vault" ? "vault" : "agent");
|
|
615
1228
|
await createSignerOwnedWalletForSetup({
|
|
616
1229
|
runtime,
|
|
617
1230
|
options,
|
|
@@ -627,35 +1240,35 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
|
|
|
627
1240
|
await configureLimitOrdersIfRequested();
|
|
628
1241
|
return;
|
|
629
1242
|
}
|
|
630
|
-
if (mode === "local-signer-import") {
|
|
631
|
-
const
|
|
632
|
-
const role = normalizeWalletUserRole(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1243
|
+
if (mode === "local-signer-import" || mode === "local-signer-recovery-import") {
|
|
1244
|
+
const roleInput = options.role ?? (interactive ? await prompt("Wallet role (agent|mining|vault)", "") : "");
|
|
1245
|
+
const role = normalizeWalletUserRole(roleInput);
|
|
1246
|
+
if (!role) {
|
|
1247
|
+
throw new Error("--role is required for non-interactive wallet import and must be agent, mining, or vault");
|
|
1248
|
+
}
|
|
1249
|
+
const friendlyWalletId = options.walletId ?? (interactive ? await prompt("Wallet id", role) : role);
|
|
1250
|
+
const walletId = friendlyWalletId.trim() || role;
|
|
1251
|
+
const recoveryImport = mode === "local-signer-recovery-import";
|
|
1252
|
+
const importFile = ((recoveryImport ? options.recoveryFile : options.importFile) ?? (interactive ? await prompt(recoveryImport ? "Absolute path to owner-only encrypted recovery package" : "Absolute path to owner-only Solana keypair JSON", "") : "")).trim();
|
|
1253
|
+
if (!importFile) {
|
|
1254
|
+
throw new Error(recoveryImport ? "--recovery-file is required for non-interactive native recovery import" : "--import-file is required for non-interactive native wallet import");
|
|
1255
|
+
}
|
|
1256
|
+
const chain = options.chain ?? "solana";
|
|
1257
|
+
const rpcUrlFallback = resolveRpcUrlForChain(env, chain, walletId, options.rpcUrl);
|
|
1258
|
+
const rpcUrl = (await prompt(`${chain.toUpperCase()} RPC URL (one primary execution RPC)`, rpcUrlFallback)).trim();
|
|
1259
|
+
if (!rpcUrl) {
|
|
1260
|
+
throw new Error("--rpc-url is required for non-interactive native wallet import");
|
|
1261
|
+
}
|
|
1262
|
+
await importSignerOwnedWalletForSetup({
|
|
1263
|
+
runtime,
|
|
1264
|
+
options,
|
|
1265
|
+
env,
|
|
1266
|
+
chain,
|
|
1267
|
+
walletId,
|
|
1268
|
+
rpcUrl,
|
|
1269
|
+
role,
|
|
1270
|
+
importFile
|
|
1271
|
+
});
|
|
659
1272
|
return;
|
|
660
1273
|
}
|
|
661
1274
|
if (mode === "local-signer") {
|
|
@@ -743,6 +1356,465 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
|
|
|
743
1356
|
return;
|
|
744
1357
|
}
|
|
745
1358
|
}
|
|
1359
|
+
async function walletRpcSetCommand(runtime = defaultRuntime, options) {
|
|
1360
|
+
const walletId = options.walletId.trim();
|
|
1361
|
+
const rpcUrl = options.rpcUrl.trim();
|
|
1362
|
+
if (!walletId) {
|
|
1363
|
+
throw new Error("--wallet-id is required");
|
|
1364
|
+
}
|
|
1365
|
+
if (!rpcUrl) {
|
|
1366
|
+
throw new Error("--rpc-url is required");
|
|
1367
|
+
}
|
|
1368
|
+
const cfg = loadConfig();
|
|
1369
|
+
const effectiveEnv = {
|
|
1370
|
+
...process.env,
|
|
1371
|
+
...cfg.env?.vars
|
|
1372
|
+
};
|
|
1373
|
+
const registry = readWalletProviderRegistry(effectiveEnv);
|
|
1374
|
+
const wallet = registry.wallets.find((entry) => entry.id === walletId);
|
|
1375
|
+
if (!wallet || wallet.providerId !== "local-socket-signer") {
|
|
1376
|
+
throw new Error(`native signer wallet not found: ${walletId}`);
|
|
1377
|
+
}
|
|
1378
|
+
const signerWalletId = typeof wallet.metadata?.signerWalletId === "string" && wallet.metadata.signerWalletId.trim() ? wallet.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(wallet.id);
|
|
1379
|
+
const hosted = String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
|
|
1380
|
+
const signerBinPath = hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(effectiveEnv);
|
|
1381
|
+
const socketFlag = hosted ? "--operator-socket" : "--control-socket";
|
|
1382
|
+
const socketPath = hosted ? "/run/fased-signerd/operator.sock" : resolveLocalSignerControlSocketPath(effectiveEnv);
|
|
1383
|
+
const current = hosted ? invokeNativeSignerWalletReadiness({
|
|
1384
|
+
signerBinPath,
|
|
1385
|
+
socketFlag,
|
|
1386
|
+
socketPath,
|
|
1387
|
+
walletId: signerWalletId,
|
|
1388
|
+
env: effectiveEnv
|
|
1389
|
+
}) : await readSignerOwnedWalletReadiness({
|
|
1390
|
+
socketPath: resolveLocalSignerSocketPath(effectiveEnv),
|
|
1391
|
+
walletId: signerWalletId
|
|
1392
|
+
});
|
|
1393
|
+
const network = invokeNativeSignerNetworkSetPrimary({
|
|
1394
|
+
signerBinPath,
|
|
1395
|
+
socketFlag,
|
|
1396
|
+
socketPath,
|
|
1397
|
+
walletId: signerWalletId,
|
|
1398
|
+
primaryRpcUrl: rpcUrl,
|
|
1399
|
+
expectedVersion: current.networkVersion,
|
|
1400
|
+
env: effectiveEnv
|
|
1401
|
+
});
|
|
1402
|
+
const nextConfig = setConfigEnvVar(cfg, rpcEnvKeyFor("solana", walletId), rpcUrl);
|
|
1403
|
+
await writeConfigFile(nextConfig);
|
|
1404
|
+
registry.wallets = registry.wallets.map((entry) => entry.id === wallet.id ? {
|
|
1405
|
+
...entry,
|
|
1406
|
+
metadata: {
|
|
1407
|
+
...entry.metadata,
|
|
1408
|
+
networkHash: network.hash,
|
|
1409
|
+
networkVersion: network.version,
|
|
1410
|
+
networkReady: network.ready
|
|
1411
|
+
},
|
|
1412
|
+
updatedAt: new Date().toISOString()
|
|
1413
|
+
} : entry);
|
|
1414
|
+
writeWalletProviderRegistry(registry, effectiveEnv);
|
|
1415
|
+
if (options.json) {
|
|
1416
|
+
runtime.log(JSON.stringify({
|
|
1417
|
+
ok: true,
|
|
1418
|
+
network
|
|
1419
|
+
}, null, 2));
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
runtime.log(`Primary Solana RPC verified for ${wallet.name} (${wallet.id}); signer network v${network.version} is ready.`);
|
|
1423
|
+
}
|
|
1424
|
+
async function walletRecoveryExportCommand(runtime = defaultRuntime, options) {
|
|
1425
|
+
const walletId = options.walletId.trim();
|
|
1426
|
+
const output = options.output.trim();
|
|
1427
|
+
if (!walletId) {
|
|
1428
|
+
throw new Error("--wallet-id is required");
|
|
1429
|
+
}
|
|
1430
|
+
if (!output || !path.isAbsolute(output) || path.resolve(output) !== output) {
|
|
1431
|
+
throw new Error("--output must be an absolute clean path for a new recovery file");
|
|
1432
|
+
}
|
|
1433
|
+
const cfg = loadConfig();
|
|
1434
|
+
const effectiveEnv = {
|
|
1435
|
+
...process.env,
|
|
1436
|
+
...cfg.env?.vars
|
|
1437
|
+
};
|
|
1438
|
+
const wallet = readWalletProviderRegistry(effectiveEnv).wallets.find((entry) => entry.id === walletId);
|
|
1439
|
+
if (!wallet || wallet.providerId !== "local-socket-signer") {
|
|
1440
|
+
throw new Error(`native signer wallet not found: ${walletId}`);
|
|
1441
|
+
}
|
|
1442
|
+
const publicKey = wallet.addresses?.solana?.trim() || "";
|
|
1443
|
+
if (!publicKey) {
|
|
1444
|
+
throw new Error(`native signer wallet ${walletId} has no verified Solana public address`);
|
|
1445
|
+
}
|
|
1446
|
+
const hosted = String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
|
|
1447
|
+
const signerWalletId = typeof wallet.metadata?.signerWalletId === "string" && wallet.metadata.signerWalletId.trim() ? wallet.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(wallet.id);
|
|
1448
|
+
const signerBinPath = hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(effectiveEnv);
|
|
1449
|
+
const controlSocketPath = hosted ? "/run/fased-signerd/operator.sock" : resolveLocalSignerControlSocketPath(effectiveEnv);
|
|
1450
|
+
runtime.log(`Creating encrypted recovery package for ${wallet.id}. The signer will ask for and confirm the recovery password without echoing it.`);
|
|
1451
|
+
const child = spawnSync(signerBinPath, [
|
|
1452
|
+
"admin",
|
|
1453
|
+
"wallet",
|
|
1454
|
+
"recovery-export",
|
|
1455
|
+
hosted ? "--operator-socket" : "--control-socket",
|
|
1456
|
+
controlSocketPath,
|
|
1457
|
+
"--wallet-id",
|
|
1458
|
+
signerWalletId,
|
|
1459
|
+
"--expected-public-key",
|
|
1460
|
+
publicKey,
|
|
1461
|
+
"--output",
|
|
1462
|
+
output
|
|
1463
|
+
], {
|
|
1464
|
+
env: {
|
|
1465
|
+
HOME: effectiveEnv.HOME,
|
|
1466
|
+
LANG: effectiveEnv.LANG || "C.UTF-8",
|
|
1467
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
1468
|
+
},
|
|
1469
|
+
stdio: "inherit",
|
|
1470
|
+
timeout: 12e4
|
|
1471
|
+
});
|
|
1472
|
+
if (child.error) {
|
|
1473
|
+
throw child.error;
|
|
1474
|
+
}
|
|
1475
|
+
if (child.status !== 0) {
|
|
1476
|
+
throw new Error(`encrypted recovery export failed with exit code ${child.status ?? "unknown"}`);
|
|
1477
|
+
}
|
|
1478
|
+
runtime.log(`Encrypted recovery package written: ${output} (owner-only)`);
|
|
1479
|
+
runtime.log(`Wallet: ${wallet.id} · Role: ${resolveWalletUserRole(wallet)} · Address: ${publicKey}`);
|
|
1480
|
+
}
|
|
1481
|
+
async function walletRecoveryImportCommand(runtime = defaultRuntime, options) {
|
|
1482
|
+
await walletSetupCommand(runtime, {
|
|
1483
|
+
mode: "local-signer-recovery-import",
|
|
1484
|
+
chain: "solana",
|
|
1485
|
+
walletId: options.walletId,
|
|
1486
|
+
walletName: options.walletName,
|
|
1487
|
+
role: options.role,
|
|
1488
|
+
recoveryFile: options.recoveryFile,
|
|
1489
|
+
rpcUrl: options.rpcUrl,
|
|
1490
|
+
nonInteractive: true,
|
|
1491
|
+
noDoctor: true,
|
|
1492
|
+
noSignerHints: true
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
async function walletRetireCommand(runtime = defaultRuntime, options) {
|
|
1496
|
+
const walletId = options.walletId.trim();
|
|
1497
|
+
const successorWalletId = options.successorWalletId.trim();
|
|
1498
|
+
const successorWalletName = options.successorWalletName.trim();
|
|
1499
|
+
const rpcUrl = options.rpcUrl.trim();
|
|
1500
|
+
if (!walletId || !successorWalletId || !successorWalletName || !rpcUrl) {
|
|
1501
|
+
throw new Error("--wallet-id, --successor-wallet-id, --successor-wallet-name, and --rpc-url are required");
|
|
1502
|
+
}
|
|
1503
|
+
if (walletId === successorWalletId) {
|
|
1504
|
+
throw new Error("Mining successor wallet id must differ from the retired wallet id");
|
|
1505
|
+
}
|
|
1506
|
+
if (!/^[a-zA-Z0-9_-]+$/u.test(successorWalletId)) {
|
|
1507
|
+
throw new Error("--successor-wallet-id must contain only letters, numbers, hyphens, or underscores");
|
|
1508
|
+
}
|
|
1509
|
+
const cfg = loadConfig();
|
|
1510
|
+
const effectiveEnv = {
|
|
1511
|
+
...process.env,
|
|
1512
|
+
...cfg.env?.vars
|
|
1513
|
+
};
|
|
1514
|
+
const registry = readWalletProviderRegistry(effectiveEnv);
|
|
1515
|
+
const source = registry.wallets.find((entry) => entry.id === walletId);
|
|
1516
|
+
if (!source || source.providerId !== "local-socket-signer" || resolveWalletUserRole(source) !== "mining") {
|
|
1517
|
+
throw new Error(`active signer-owned Mining wallet not found: ${walletId}`);
|
|
1518
|
+
}
|
|
1519
|
+
const configuredMiningWalletId = resolveConfiguredMiningWalletId(cfg);
|
|
1520
|
+
if (configuredMiningWalletId !== walletId && configuredMiningWalletId !== successorWalletId) {
|
|
1521
|
+
throw new Error(`${walletId} is not the active singleton Mining wallet`);
|
|
1522
|
+
}
|
|
1523
|
+
if (registry.wallets.some((entry) => entry.id === successorWalletId)) {
|
|
1524
|
+
throw new Error(`successor wallet id is already registered: ${successorWalletId}`);
|
|
1525
|
+
}
|
|
1526
|
+
const sourcePublicKey = source.addresses?.solana?.trim() ?? "";
|
|
1527
|
+
if (!sourcePublicKey) {
|
|
1528
|
+
throw new Error("source Mining wallet has no verified Solana address");
|
|
1529
|
+
}
|
|
1530
|
+
const sourceSignerWalletId = typeof source.metadata?.signerWalletId === "string" && source.metadata.signerWalletId.trim() ? source.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(source.id);
|
|
1531
|
+
const successorSignerWalletId = normalizeNativeSignerWalletId(successorWalletId);
|
|
1532
|
+
if (sourceSignerWalletId === successorSignerWalletId) {
|
|
1533
|
+
throw new Error("Mining successor signer wallet id must be distinct");
|
|
1534
|
+
}
|
|
1535
|
+
const recovery = verifyMiningRecoveryPackage({
|
|
1536
|
+
recoveryFile: options.recoveryFile,
|
|
1537
|
+
walletId: sourceSignerWalletId,
|
|
1538
|
+
publicKey: sourcePublicKey
|
|
1539
|
+
});
|
|
1540
|
+
const hosted = String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
|
|
1541
|
+
const signerBinPath = hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(effectiveEnv);
|
|
1542
|
+
const socketFlag = hosted ? "--operator-socket" : "--control-socket";
|
|
1543
|
+
const socketPath = hosted ? "/run/fased-signerd/operator.sock" : resolveLocalSignerControlSocketPath(effectiveEnv);
|
|
1544
|
+
let rotation = invokeNativeSignerRotationStatus({
|
|
1545
|
+
signerBinPath,
|
|
1546
|
+
socketFlag,
|
|
1547
|
+
socketPath,
|
|
1548
|
+
sourceWalletId: sourceSignerWalletId,
|
|
1549
|
+
env: effectiveEnv
|
|
1550
|
+
});
|
|
1551
|
+
if (rotation && rotation.successorWalletId !== successorSignerWalletId) {
|
|
1552
|
+
throw new Error(`source wallet is already bound to immutable successor ${rotation.successorWalletId}`);
|
|
1553
|
+
}
|
|
1554
|
+
if (!rotation && configuredMiningWalletId !== walletId) {
|
|
1555
|
+
throw new Error("Mining configuration changed before the signer prepared a successor");
|
|
1556
|
+
}
|
|
1557
|
+
let evidence;
|
|
1558
|
+
if (!rotation || rotation.state !== "committed") {
|
|
1559
|
+
const sourceReadiness = invokeNativeSignerWalletReadiness({
|
|
1560
|
+
signerBinPath,
|
|
1561
|
+
socketFlag,
|
|
1562
|
+
socketPath,
|
|
1563
|
+
walletId: sourceSignerWalletId,
|
|
1564
|
+
env: effectiveEnv
|
|
1565
|
+
});
|
|
1566
|
+
if (sourceReadiness.publicKey !== sourcePublicKey || sourceReadiness.role !== "mining" || !sourceReadiness.keyReady || !sourceReadiness.policyReady || !sourceReadiness.networkReady) {
|
|
1567
|
+
throw new Error("source Mining wallet is not live and role-ready in the signer");
|
|
1568
|
+
}
|
|
1569
|
+
const signerSOL = invokeNativeSignerBalance({
|
|
1570
|
+
signerBinPath,
|
|
1571
|
+
socketFlag,
|
|
1572
|
+
socketPath,
|
|
1573
|
+
walletId: sourceSignerWalletId,
|
|
1574
|
+
publicKey: sourcePublicKey,
|
|
1575
|
+
env: effectiveEnv
|
|
1576
|
+
});
|
|
1577
|
+
evidence = buildMiningRetirementEvidence({
|
|
1578
|
+
walletId,
|
|
1579
|
+
signerWalletId: sourceSignerWalletId,
|
|
1580
|
+
publicKey: sourcePublicKey,
|
|
1581
|
+
signerSolBalanceLamports: signerSOL,
|
|
1582
|
+
liveStatus: options.liveMiningStatus,
|
|
1583
|
+
env: effectiveEnv
|
|
1584
|
+
});
|
|
1585
|
+
rotation ??= invokeNativeSignerRotationCreate({
|
|
1586
|
+
signerBinPath,
|
|
1587
|
+
socketFlag,
|
|
1588
|
+
socketPath,
|
|
1589
|
+
sourceWalletId: sourceSignerWalletId,
|
|
1590
|
+
successorWalletId: successorSignerWalletId,
|
|
1591
|
+
sourcePublicKey,
|
|
1592
|
+
sourceWalletVersion: Number(sourceReadiness.walletVersion),
|
|
1593
|
+
sourcePolicyVersion: sourceReadiness.policyVersion,
|
|
1594
|
+
env: effectiveEnv
|
|
1595
|
+
});
|
|
1596
|
+
if (rotation.role !== "mining" || rotation.sourcePublicKey !== sourcePublicKey || rotation.successorPublicKey === sourcePublicKey) {
|
|
1597
|
+
throw new Error("signer prepared an invalid Mining successor binding");
|
|
1598
|
+
}
|
|
1599
|
+
let successorReadiness = invokeNativeSignerWalletReadiness({
|
|
1600
|
+
signerBinPath,
|
|
1601
|
+
socketFlag,
|
|
1602
|
+
socketPath,
|
|
1603
|
+
walletId: successorSignerWalletId,
|
|
1604
|
+
env: effectiveEnv
|
|
1605
|
+
});
|
|
1606
|
+
let successorNetwork = {
|
|
1607
|
+
version: successorReadiness.networkVersion,
|
|
1608
|
+
hash: successorReadiness.networkHash ?? "",
|
|
1609
|
+
ready: successorReadiness.networkReady
|
|
1610
|
+
};
|
|
1611
|
+
if (successorNetwork.version === 0) {
|
|
1612
|
+
successorNetwork = invokeNativeSignerNetworkSetPrimary({
|
|
1613
|
+
signerBinPath,
|
|
1614
|
+
socketFlag,
|
|
1615
|
+
socketPath,
|
|
1616
|
+
walletId: successorSignerWalletId,
|
|
1617
|
+
primaryRpcUrl: rpcUrl,
|
|
1618
|
+
expectedVersion: 0,
|
|
1619
|
+
env: effectiveEnv
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
if (!successorNetwork.ready || !successorNetwork.hash) {
|
|
1623
|
+
throw new Error("Mining successor RPC is not verified and ready");
|
|
1624
|
+
}
|
|
1625
|
+
rotation = invokeNativeSignerRotationCommit({
|
|
1626
|
+
signerBinPath,
|
|
1627
|
+
socketFlag,
|
|
1628
|
+
socketPath,
|
|
1629
|
+
rotation,
|
|
1630
|
+
successorNetworkVersion: successorNetwork.version,
|
|
1631
|
+
successorNetworkHash: successorNetwork.hash,
|
|
1632
|
+
recoveryPackageHash: recovery.packageHash,
|
|
1633
|
+
safetyEvidence: evidence,
|
|
1634
|
+
env: effectiveEnv
|
|
1635
|
+
});
|
|
1636
|
+
successorReadiness = invokeNativeSignerWalletReadiness({
|
|
1637
|
+
signerBinPath,
|
|
1638
|
+
socketFlag,
|
|
1639
|
+
socketPath,
|
|
1640
|
+
walletId: successorSignerWalletId,
|
|
1641
|
+
env: effectiveEnv
|
|
1642
|
+
});
|
|
1643
|
+
if (rotation.state !== "committed" || successorReadiness.publicKey !== rotation.successorPublicKey || successorReadiness.role !== "mining" || successorReadiness.operationLane !== "mining-typed-sat" || !successorReadiness.ready || successorReadiness.policyHash !== rotation.successorActivatedPolicyHash) {
|
|
1644
|
+
throw new Error("Mining successor did not become authoritatively role-ready after retirement");
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
if (rotation.state !== "committed" || rotation.recoveryPackageHash !== recovery.packageHash || !rotation.sourceRetiredPolicyHash || !rotation.successorActivatedPolicyHash || !rotation.safetyEvidenceHash) {
|
|
1648
|
+
throw new Error("signer retirement acknowledgement is incomplete");
|
|
1649
|
+
}
|
|
1650
|
+
const successorReadiness = invokeNativeSignerWalletReadiness({
|
|
1651
|
+
signerBinPath,
|
|
1652
|
+
socketFlag,
|
|
1653
|
+
socketPath,
|
|
1654
|
+
walletId: successorSignerWalletId,
|
|
1655
|
+
env: effectiveEnv
|
|
1656
|
+
});
|
|
1657
|
+
if (successorReadiness.publicKey !== rotation.successorPublicKey || successorReadiness.role !== "mining" || !successorReadiness.ready) {
|
|
1658
|
+
throw new Error("committed Mining successor is not live and ready");
|
|
1659
|
+
}
|
|
1660
|
+
const receipt = {
|
|
1661
|
+
version: 1,
|
|
1662
|
+
kind: "fased-mining-wallet-retirement",
|
|
1663
|
+
rotationId: rotation.rotationId,
|
|
1664
|
+
committedAt: rotation.committedAt,
|
|
1665
|
+
sourceWalletId: walletId,
|
|
1666
|
+
sourceSignerWalletId,
|
|
1667
|
+
sourcePublicKey,
|
|
1668
|
+
sourceRetiredPolicyVersion: rotation.sourceRetiredPolicyVersion,
|
|
1669
|
+
sourceRetiredPolicyHash: rotation.sourceRetiredPolicyHash,
|
|
1670
|
+
successorWalletId,
|
|
1671
|
+
successorSignerWalletId,
|
|
1672
|
+
successorPublicKey: rotation.successorPublicKey,
|
|
1673
|
+
successorPolicyVersion: successorReadiness.policyVersion,
|
|
1674
|
+
successorPolicyHash: successorReadiness.policyHash,
|
|
1675
|
+
successorNetworkVersion: successorReadiness.networkVersion,
|
|
1676
|
+
successorNetworkHash: successorReadiness.networkHash,
|
|
1677
|
+
recoveryPackageHash: rotation.recoveryPackageHash,
|
|
1678
|
+
safetyEvidenceHash: rotation.safetyEvidenceHash,
|
|
1679
|
+
balances: evidence ?? rotation.safetyEvidence ? {
|
|
1680
|
+
solBalanceLamports: (evidence ?? rotation.safetyEvidence).solBalanceLamports,
|
|
1681
|
+
satBalanceRaw: (evidence ?? rotation.safetyEvidence).satBalanceRaw
|
|
1682
|
+
} : undefined
|
|
1683
|
+
};
|
|
1684
|
+
const receiptPath = writeMiningRetirementReceipt({
|
|
1685
|
+
sourceWalletId: walletId,
|
|
1686
|
+
receipt,
|
|
1687
|
+
env: effectiveEnv
|
|
1688
|
+
});
|
|
1689
|
+
const currentEntry = cfg.plugins?.entries?.["sat-mining"];
|
|
1690
|
+
const currentPluginConfig = currentEntry?.config && typeof currentEntry.config === "object" && !Array.isArray(currentEntry.config) ? currentEntry.config : {};
|
|
1691
|
+
await writeConfigFile({
|
|
1692
|
+
...cfg,
|
|
1693
|
+
plugins: {
|
|
1694
|
+
...cfg.plugins,
|
|
1695
|
+
entries: {
|
|
1696
|
+
...cfg.plugins?.entries,
|
|
1697
|
+
"sat-mining": {
|
|
1698
|
+
...currentEntry,
|
|
1699
|
+
enabled: true,
|
|
1700
|
+
config: {
|
|
1701
|
+
...currentPluginConfig,
|
|
1702
|
+
walletId: successorWalletId
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
});
|
|
1708
|
+
replaceRetiredMiningWallet({
|
|
1709
|
+
sourceWalletId: walletId,
|
|
1710
|
+
signerAcknowledgement: {
|
|
1711
|
+
rotationId: rotation.rotationId,
|
|
1712
|
+
sourceRetiredPolicyHash: String(rotation.sourceRetiredPolicyHash),
|
|
1713
|
+
successorPublicKey: rotation.successorPublicKey,
|
|
1714
|
+
successorPolicyHash: successorReadiness.policyHash
|
|
1715
|
+
},
|
|
1716
|
+
successor: {
|
|
1717
|
+
id: successorWalletId,
|
|
1718
|
+
name: successorWalletName,
|
|
1719
|
+
providerId: "local-socket-signer",
|
|
1720
|
+
addresses: { solana: rotation.successorPublicKey },
|
|
1721
|
+
metadata: {
|
|
1722
|
+
role: "mining",
|
|
1723
|
+
purpose: "mining",
|
|
1724
|
+
keyAuthority: "signer-owned-v2",
|
|
1725
|
+
signerWalletId: successorSignerWalletId,
|
|
1726
|
+
policyHash: successorReadiness.policyHash,
|
|
1727
|
+
policyVersion: successorReadiness.policyVersion,
|
|
1728
|
+
baselineVersion: successorReadiness.baselineVersion,
|
|
1729
|
+
policyState: "ready",
|
|
1730
|
+
networkHash: successorReadiness.networkHash,
|
|
1731
|
+
networkVersion: successorReadiness.networkVersion,
|
|
1732
|
+
networkReady: true,
|
|
1733
|
+
operationLane: successorReadiness.operationLane,
|
|
1734
|
+
roleReady: true,
|
|
1735
|
+
predecessorWalletId: walletId,
|
|
1736
|
+
rotationId: rotation.rotationId
|
|
1737
|
+
}
|
|
1738
|
+
},
|
|
1739
|
+
env: effectiveEnv
|
|
1740
|
+
});
|
|
1741
|
+
const result = {
|
|
1742
|
+
ok: true,
|
|
1743
|
+
retiredWalletId: walletId,
|
|
1744
|
+
retiredAddress: sourcePublicKey,
|
|
1745
|
+
successorWalletId,
|
|
1746
|
+
successorAddress: rotation.successorPublicKey,
|
|
1747
|
+
rotationId: rotation.rotationId,
|
|
1748
|
+
receiptPath
|
|
1749
|
+
};
|
|
1750
|
+
if (options.json) {
|
|
1751
|
+
runtime.log(JSON.stringify(result, null, 2));
|
|
1752
|
+
} else {
|
|
1753
|
+
runtime.log(`Retired Mining wallet ${walletId} (${sourcePublicKey}).`);
|
|
1754
|
+
runtime.log(`Active Mining successor: ${successorWalletId} (${rotation.successorPublicKey}).`);
|
|
1755
|
+
runtime.log(`Audit receipt: ${receiptPath}`);
|
|
1756
|
+
}
|
|
1757
|
+
return result;
|
|
1758
|
+
}
|
|
1759
|
+
async function walletRawExportCommand(runtime = defaultRuntime, options) {
|
|
1760
|
+
const walletId = options.walletId.trim();
|
|
1761
|
+
const output = options.output.trim();
|
|
1762
|
+
if (!walletId) {
|
|
1763
|
+
throw new Error("--wallet-id is required");
|
|
1764
|
+
}
|
|
1765
|
+
if (!options.acknowledgeCustodyReduction) {
|
|
1766
|
+
throw new Error("--acknowledge-custody-reduction is required for raw private-key export");
|
|
1767
|
+
}
|
|
1768
|
+
if (!output || !path.isAbsolute(output) || path.resolve(output) !== output) {
|
|
1769
|
+
throw new Error("--output must be an absolute clean path for a new owner-only keypair file");
|
|
1770
|
+
}
|
|
1771
|
+
const cfg = loadConfig();
|
|
1772
|
+
const effectiveEnv = {
|
|
1773
|
+
...process.env,
|
|
1774
|
+
...cfg.env?.vars
|
|
1775
|
+
};
|
|
1776
|
+
const wallet = readWalletProviderRegistry(effectiveEnv).wallets.find((entry) => entry.id === walletId);
|
|
1777
|
+
if (!wallet || wallet.providerId !== "local-socket-signer") {
|
|
1778
|
+
throw new Error(`native signer wallet not found: ${walletId}`);
|
|
1779
|
+
}
|
|
1780
|
+
const publicKey = wallet.addresses?.solana?.trim() || "";
|
|
1781
|
+
if (!publicKey) {
|
|
1782
|
+
throw new Error(`native signer wallet ${walletId} has no verified Solana public address`);
|
|
1783
|
+
}
|
|
1784
|
+
const hosted = String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
|
|
1785
|
+
const signerWalletId = typeof wallet.metadata?.signerWalletId === "string" && wallet.metadata.signerWalletId.trim() ? wallet.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(wallet.id);
|
|
1786
|
+
runtime.log("WARNING: raw private-key export reduces signer custody protection.");
|
|
1787
|
+
const child = spawnSync(hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(effectiveEnv), [
|
|
1788
|
+
"admin",
|
|
1789
|
+
"wallet",
|
|
1790
|
+
"export-raw",
|
|
1791
|
+
hosted ? "--operator-socket" : "--control-socket",
|
|
1792
|
+
hosted ? "/run/fased-signerd/operator.sock" : resolveLocalSignerControlSocketPath(effectiveEnv),
|
|
1793
|
+
"--wallet-id",
|
|
1794
|
+
signerWalletId,
|
|
1795
|
+
"--expected-public-key",
|
|
1796
|
+
publicKey,
|
|
1797
|
+
"--output",
|
|
1798
|
+
output,
|
|
1799
|
+
"--acknowledge-custody-reduction"
|
|
1800
|
+
], {
|
|
1801
|
+
env: {
|
|
1802
|
+
HOME: effectiveEnv.HOME,
|
|
1803
|
+
LANG: effectiveEnv.LANG || "C.UTF-8",
|
|
1804
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
1805
|
+
},
|
|
1806
|
+
stdio: "inherit",
|
|
1807
|
+
timeout: 12e4
|
|
1808
|
+
});
|
|
1809
|
+
if (child.error) {
|
|
1810
|
+
throw child.error;
|
|
1811
|
+
}
|
|
1812
|
+
if (child.status !== 0) {
|
|
1813
|
+
throw new Error(`raw private-key export failed with exit code ${child.status ?? "unknown"}`);
|
|
1814
|
+
}
|
|
1815
|
+
runtime.log(`Raw private key written: ${output} (owner-only)`);
|
|
1816
|
+
runtime.log(`Wallet: ${wallet.id} · Address: ${publicKey}`);
|
|
1817
|
+
}
|
|
746
1818
|
async function walletRoleSetCommand(runtime = defaultRuntime, options) {
|
|
747
1819
|
const walletId = options.walletId.trim();
|
|
748
1820
|
const role = normalizeWalletRoleForCli(options.role);
|
|
@@ -790,10 +1862,56 @@ async function walletRoleSetCommand(runtime = defaultRuntime, options) {
|
|
|
790
1862
|
}, null, 2));
|
|
791
1863
|
return;
|
|
792
1864
|
}
|
|
793
|
-
runtime.log(`${wallet?.name ?? walletId} (${walletId}) set to ${role === "agent" ? "Agent wallet" : "Vault wallet"}${primary ? " and
|
|
1865
|
+
runtime.log(`${wallet?.name ?? walletId} (${walletId}) set to ${role === "agent" ? "Agent wallet" : "Vault wallet"}${primary ? " and Default Agent wallet fallback" : ""}.`);
|
|
794
1866
|
}
|
|
795
1867
|
async function walletStatusCommand(runtime = defaultRuntime, options = {}) {
|
|
796
|
-
const
|
|
1868
|
+
const config = loadConfig();
|
|
1869
|
+
const effectiveEnv = {
|
|
1870
|
+
...process.env,
|
|
1871
|
+
...config.env?.vars
|
|
1872
|
+
};
|
|
1873
|
+
const hostedOperator = String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting" && effectiveEnv.FASED_GATEWAY_SERVICE !== "1";
|
|
1874
|
+
if (hostedOperator) {
|
|
1875
|
+
const registered = readWalletProviderRegistry(effectiveEnv).wallets.filter((wallet) => wallet.providerId === "local-socket-signer" && (!options.walletId || wallet.id === options.walletId));
|
|
1876
|
+
if (options.walletId && registered.length === 0) {
|
|
1877
|
+
throw new Error(`native signer wallet not found: ${options.walletId}`);
|
|
1878
|
+
}
|
|
1879
|
+
const wallets = registered.map((wallet) => {
|
|
1880
|
+
const signerWalletId = typeof wallet.metadata?.signerWalletId === "string" && wallet.metadata.signerWalletId.trim() ? wallet.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(wallet.id);
|
|
1881
|
+
return {
|
|
1882
|
+
id: wallet.id,
|
|
1883
|
+
name: wallet.name,
|
|
1884
|
+
signer: invokeNativeSignerWalletReadiness({
|
|
1885
|
+
signerBinPath: "/opt/fased/signer/fased-signerd",
|
|
1886
|
+
socketFlag: "--operator-socket",
|
|
1887
|
+
socketPath: "/run/fased-signerd/operator.sock",
|
|
1888
|
+
walletId: signerWalletId,
|
|
1889
|
+
env: effectiveEnv
|
|
1890
|
+
})
|
|
1891
|
+
};
|
|
1892
|
+
});
|
|
1893
|
+
if (options.json) {
|
|
1894
|
+
runtime.log(JSON.stringify({
|
|
1895
|
+
ok: true,
|
|
1896
|
+
status: {
|
|
1897
|
+
mode: "hosting-operator",
|
|
1898
|
+
wallets
|
|
1899
|
+
}
|
|
1900
|
+
}, null, 2));
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
if (wallets.length === 0) {
|
|
1904
|
+
runtime.log("No signer-owned wallets are registered.");
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
for (const wallet of wallets) {
|
|
1908
|
+
runtime.log(`Wallet ${wallet.name} (${wallet.id}): ${wallet.signer.ready ? "ready" : "setup incomplete"}`);
|
|
1909
|
+
runtime.log(` signer=${wallet.signer.walletId} role=${wallet.signer.role} lane=${wallet.signer.operationLane} baseline=v${wallet.signer.baselineVersion}`);
|
|
1910
|
+
runtime.log(` key=${String(wallet.signer.keyReady)} policy=v${wallet.signer.policyVersion} ${wallet.signer.policyHash} network=v${wallet.signer.networkVersion} ${wallet.signer.networkHash ?? "unconfigured"}`);
|
|
1911
|
+
}
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
const status = await readWalletStatusSnapshot({ walletId: options.walletId });
|
|
797
1915
|
if (options.json) {
|
|
798
1916
|
runtime.log(JSON.stringify({
|
|
799
1917
|
ok: true,
|
|
@@ -821,10 +1939,104 @@ async function walletStatusCommand(runtime = defaultRuntime, options = {}) {
|
|
|
821
1939
|
if (status.addresses?.solana) {
|
|
822
1940
|
runtime.log(`Address: ${status.addresses.solana}`);
|
|
823
1941
|
}
|
|
1942
|
+
for (const wallet of status.wallets ?? []) {
|
|
1943
|
+
const signer = wallet.readiness.signer;
|
|
1944
|
+
runtime.log(`Wallet ${wallet.name} (${wallet.id}): ${wallet.readiness.ready ? "ready" : "setup incomplete"}`);
|
|
1945
|
+
if (signer) {
|
|
1946
|
+
runtime.log(` signer=${signer.walletId} role=${signer.role} lane=${signer.operationLane} baseline=v${signer.baselineVersion}`);
|
|
1947
|
+
runtime.log(` key=${String(signer.keyReady)} policy=v${signer.policyVersion} ${signer.policyHash} network=v${signer.networkVersion} ${signer.networkHash ?? "unconfigured"}`);
|
|
1948
|
+
}
|
|
1949
|
+
if (wallet.readiness.error) {
|
|
1950
|
+
runtime.log(` warning=${wallet.readiness.error}`);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
824
1953
|
if (status.error) {
|
|
825
1954
|
runtime.log(`Status warning: ${status.error}`);
|
|
826
1955
|
}
|
|
827
1956
|
}
|
|
1957
|
+
async function walletPolicyActivateRoleBaselineCommand(runtime = defaultRuntime, options) {
|
|
1958
|
+
if (!options.confirm) {
|
|
1959
|
+
throw new Error("Activate role baseline requires --confirm after reviewing the selected role");
|
|
1960
|
+
}
|
|
1961
|
+
const role = normalizeWalletUserRole(options.role);
|
|
1962
|
+
if (!role) {
|
|
1963
|
+
throw new Error("role must be one of: agent, mining, vault");
|
|
1964
|
+
}
|
|
1965
|
+
const registry = readWalletProviderRegistry(process.env);
|
|
1966
|
+
const wallet = registry.wallets.find((entry) => entry.id === options.walletId.trim());
|
|
1967
|
+
if (!wallet || wallet.providerId !== "local-socket-signer") {
|
|
1968
|
+
throw new Error(`registered signer-owned wallet was not found: ${options.walletId}`);
|
|
1969
|
+
}
|
|
1970
|
+
const registeredRole = resolveWalletUserRole(wallet);
|
|
1971
|
+
if (registeredRole && registeredRole !== role) {
|
|
1972
|
+
throw new Error(`wallet ${wallet.id} has immutable role=${registeredRole}; refusing requested role=${role}`);
|
|
1973
|
+
}
|
|
1974
|
+
const signerWalletId = typeof wallet.metadata?.signerWalletId === "string" && wallet.metadata.signerWalletId.trim() ? wallet.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(wallet.id);
|
|
1975
|
+
const socketPath = requireLocalSocketSignerPath(process.env);
|
|
1976
|
+
const current = await readSignerOwnedWallet({
|
|
1977
|
+
socketPath,
|
|
1978
|
+
walletId: signerWalletId
|
|
1979
|
+
});
|
|
1980
|
+
if (current.wallet.publicKey !== wallet.addresses?.solana) {
|
|
1981
|
+
throw new Error("registered wallet address does not match the signer-owned wallet");
|
|
1982
|
+
}
|
|
1983
|
+
if (current.policy.role !== role) {
|
|
1984
|
+
throw new Error(`signer-owned wallet ${signerWalletId} has immutable role=${current.policy.role}, not ${role}`);
|
|
1985
|
+
}
|
|
1986
|
+
let policy = current.policy;
|
|
1987
|
+
if (policy.baselineVersion === undefined || policy.baselineVersion === 0) {
|
|
1988
|
+
policy = await activateSignerOwnedRoleBaseline({
|
|
1989
|
+
socketPath,
|
|
1990
|
+
walletId: signerWalletId,
|
|
1991
|
+
role,
|
|
1992
|
+
expectedPolicyVersion: policy.version
|
|
1993
|
+
});
|
|
1994
|
+
} else if (policy.baselineVersion !== 1) {
|
|
1995
|
+
throw new Error(`signer-owned wallet ${signerWalletId} uses unsupported baseline version ${policy.baselineVersion}`);
|
|
1996
|
+
}
|
|
1997
|
+
const readiness = await readSignerOwnedWalletReadiness({
|
|
1998
|
+
socketPath,
|
|
1999
|
+
walletId: signerWalletId
|
|
2000
|
+
});
|
|
2001
|
+
if (readiness.publicKey !== current.wallet.publicKey || readiness.role !== role || readiness.policyHash !== policy.hash || readiness.policyVersion !== policy.version || readiness.baselineVersion !== 1) {
|
|
2002
|
+
throw new Error("Setup incomplete: activated role baseline does not match live signer state");
|
|
2003
|
+
}
|
|
2004
|
+
upsertNamedWallet({
|
|
2005
|
+
walletId: wallet.id,
|
|
2006
|
+
name: wallet.name,
|
|
2007
|
+
providerId: wallet.providerId,
|
|
2008
|
+
addresses: wallet.addresses,
|
|
2009
|
+
metadata: {
|
|
2010
|
+
...wallet.metadata,
|
|
2011
|
+
role,
|
|
2012
|
+
purpose: role,
|
|
2013
|
+
signerWalletId,
|
|
2014
|
+
policyHash: readiness.policyHash,
|
|
2015
|
+
policyVersion: readiness.policyVersion,
|
|
2016
|
+
baselineVersion: readiness.baselineVersion,
|
|
2017
|
+
policyState: readiness.policyReady ? "ready" : "setup-incomplete",
|
|
2018
|
+
networkHash: readiness.networkHash,
|
|
2019
|
+
networkVersion: readiness.networkVersion,
|
|
2020
|
+
networkReady: readiness.networkReady,
|
|
2021
|
+
operationLane: readiness.operationLane,
|
|
2022
|
+
roleReady: readiness.ready
|
|
2023
|
+
},
|
|
2024
|
+
env: process.env
|
|
2025
|
+
});
|
|
2026
|
+
const payload = {
|
|
2027
|
+
ok: true,
|
|
2028
|
+
walletId: wallet.id,
|
|
2029
|
+
signerWalletId,
|
|
2030
|
+
role,
|
|
2031
|
+
readiness
|
|
2032
|
+
};
|
|
2033
|
+
if (options.json) {
|
|
2034
|
+
runtime.log(JSON.stringify(payload, null, 2));
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
runtime.log(`Activated ${role} role baseline v${readiness.baselineVersion} for ${wallet.name} (${wallet.id}).`);
|
|
2038
|
+
runtime.log(`Readiness: ${readiness.ready ? "ready" : "setup incomplete"} (${readiness.operationLane}).`);
|
|
2039
|
+
}
|
|
828
2040
|
async function walletKeystoreInitCommand(runtime = defaultRuntime, options = {}) {
|
|
829
2041
|
void runtime;
|
|
830
2042
|
void options;
|
|
@@ -1509,4 +2721,4 @@ async function walletInboundReconcileCommand(runtime = defaultRuntime, options =
|
|
|
1509
2721
|
}
|
|
1510
2722
|
|
|
1511
2723
|
//#endregion
|
|
1512
|
-
export { collectWalletSignerDoctorReport, walletCanaryCommand, walletInboundListCommand, walletInboundPollCommand, walletInboundReconcileCommand, walletLegacyMigrationFinalizeCommand, walletLimitOrdersConfigureCommand, walletMigrateCommand, walletPolicyProfileApplyCommand, walletProviderConfigureCommand, walletRoleSetCommand, walletRotateKeysCommand, walletSetupCommand, walletSignerDoctorCommand, walletSignerServeCommand, walletStatusCommand };
|
|
2724
|
+
export { collectWalletSignerDoctorReport, walletCanaryCommand, walletInboundListCommand, walletInboundPollCommand, walletInboundReconcileCommand, walletLegacyMigrationFinalizeCommand, walletLimitOrdersConfigureCommand, walletMigrateCommand, walletPolicyActivateRoleBaselineCommand, walletPolicyProfileApplyCommand, walletProviderConfigureCommand, walletRawExportCommand, walletRecoveryExportCommand, walletRecoveryImportCommand, walletRetireCommand, walletRoleSetCommand, walletRotateKeysCommand, walletRpcSetCommand, walletSetupCommand, walletSignerDoctorCommand, walletSignerServeCommand, walletStatusCommand };
|