@fased/fased 0.1.66 → 0.1.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -0
  3. package/config/signer-protocol-v2.json +4 -0
  4. package/dist/brand.js +1 -1
  5. package/dist/build-info.json +3 -3
  6. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  7. package/dist/cli/program/register.wallet.js +33 -2
  8. package/dist/commands/onboard.js +0 -8
  9. package/dist/commands/wallet.js +485 -58
  10. package/dist/control-ui/assets/{app-DYxADzeH.js → app-DyPTS0oM.js} +1526 -1526
  11. package/dist/control-ui/assets/{index-vw_10Zeo.js → index-Cu13yYHr.js} +3 -3
  12. package/dist/control-ui/assets/{wallet-DvStR_AT.js → wallet-ByKBcYhm.js} +272 -188
  13. package/dist/control-ui/index.html +1 -1
  14. package/dist/control-ui/version.json +1 -1
  15. package/dist/gateway/server-http.js +349 -47
  16. package/dist/infra/runtime-guard.js +16 -0
  17. package/dist/plugin-sdk/{actions-DRu135h0.js → actions-B-Vwj64K.js} +3 -3
  18. package/dist/plugin-sdk/{allow-list-DyAxs4yw.js → allow-list-Bf45HfoP.js} +1 -1
  19. package/dist/plugin-sdk/{audio-preflight-BFLoas5R.js → audio-preflight-_FVQjR1r.js} +26 -25
  20. package/dist/plugin-sdk/{audit-C5d2z5U1.js → audit-CUULWG22.js} +1 -1
  21. package/dist/plugin-sdk/{channel-access-configure-DaI5wevh.js → channel-access-configure-BoUsC-Ys.js} +1 -1
  22. package/dist/plugin-sdk/{channel-adapters.discord-BS4S4Svh.js → channel-adapters.discord-DSiR4X2Y.js} +1 -1
  23. package/dist/plugin-sdk/channel-plugin-common.d.ts +2 -2
  24. package/dist/plugin-sdk/channel-plugin-common.js +1 -1
  25. package/dist/plugin-sdk/{chunk-CyoyqpbG.js → chunk-CBpl-AiQ.js} +1 -1
  26. package/dist/plugin-sdk/{chunk-items-TErs62MC.js → chunk-items-LH4uKSnh.js} +1 -1
  27. package/dist/plugin-sdk/command-status.js +26 -25
  28. package/dist/plugin-sdk/{commands-registry-BfqdEWdF.js → commands-registry-G5KXSi_y.js} +1 -1
  29. package/dist/plugin-sdk/{config-schema-BPnaDBwZ.js → config-schema-DjU_uD-6.js} +192 -94
  30. package/dist/plugin-sdk/device-pair.d.ts +1 -1
  31. package/dist/plugin-sdk/{diagnostic-DLL_zekv.js → diagnostic-sYs6Agr-.js} +1 -1
  32. package/dist/plugin-sdk/{directory-live-CSuYxdwk.js → directory-live-BXMpuYAF.js} +1 -1
  33. package/dist/plugin-sdk/{directory-live-Dw7ZgC9_.js → directory-live-Rn1BzPof.js} +1 -1
  34. package/dist/plugin-sdk/discord.d.ts +4 -4
  35. package/dist/plugin-sdk/discord.js +20 -20
  36. package/dist/plugin-sdk/{draft-stream-controls-BqchW1vi.js → draft-stream-controls-CMNdKSx-.js} +1 -1
  37. package/dist/plugin-sdk/{guilds-CDhRIX57.js → guilds-Dkwl0yHv.js} +1 -1
  38. package/dist/plugin-sdk/{handle-action-hvwapMKg.js → handle-action-CRdM6B-8.js} +15 -15
  39. package/dist/plugin-sdk/index.d.ts +16 -16
  40. package/dist/plugin-sdk/index.js +28 -27
  41. package/dist/plugin-sdk/{login-Ca583Okc.js → login-D7WnK8-P.js} +1 -1
  42. package/dist/plugin-sdk/{login-qr-CGciBSNP.js → login-qr-CRueOKZK.js} +2 -114
  43. package/dist/plugin-sdk/{markdown-tables-DCOXEUfZ.js → markdown-tables-d7GlUs5R.js} +1 -1
  44. package/dist/plugin-sdk/{media-CAdQF5TG.js → media-BAoaxCrt.js} +1 -1
  45. package/dist/plugin-sdk/{monitor-FuBPk-AL.js → monitor-DUMMbG4h.js} +34 -33
  46. package/dist/plugin-sdk/{monitor-sW6dQdX1.js → monitor-DXIM16Hj.js} +44 -43
  47. package/dist/plugin-sdk/{openresponses-http-CVZq0H-P.js → openresponses-http-C8lKwta1.js} +26 -25
  48. package/dist/plugin-sdk/{outbound-attachment-QqTnIhZL.js → outbound-attachment-CyXM3cAv.js} +1 -1
  49. package/dist/plugin-sdk/{outbound-BgVDdHxx.js → outbound-zB6llMGW.js} +6 -6
  50. package/dist/plugin-sdk/{pairing-message-L6L6L2YF.d.ts → pairing-message-mKO92L-M.d.ts} +1 -1
  51. package/dist/plugin-sdk/{pairing-store-BChahgI2.js → pairing-store-B6J2C0vP.js} +1 -1
  52. package/dist/plugin-sdk/{pi-model-discovery-runtime-DxTMz_XF.js → pi-model-discovery-runtime-6JzLw9Gk.js} +1 -1
  53. package/dist/plugin-sdk/{probe-CJb5uR8B.js → probe-BM7Oe40M.js} +2 -2
  54. package/dist/plugin-sdk/{probe-l_whvGLY.js → probe-CMPksimh.js} +1 -1
  55. package/dist/plugin-sdk/{probe-BFroeK3g.js → probe-oVp12D6L.js} +1 -1
  56. package/dist/plugin-sdk/provider-web-search-config-contract.d.ts +1 -1
  57. package/dist/plugin-sdk/{pw-ai-natjh3rm.js → pw-ai-DJ_gTc4M.js} +1 -1
  58. package/dist/plugin-sdk/qr-image-B4MlMG4N.js +115 -0
  59. package/dist/plugin-sdk/{replies-ChYo5ZQM.js → replies-CY-V0Zob.js} +3 -3
  60. package/dist/plugin-sdk/{resolve-channels-87LZrFk_.js → resolve-channels-1HtDcMqh.js} +2 -2
  61. package/dist/plugin-sdk/{resolve-channels-_-nV-EK2.js → resolve-channels-CU72Qh2j.js} +1 -1
  62. package/dist/plugin-sdk/{resolve-outbound-target-D-KGgHJZ.js → resolve-outbound-target-nQSBVYz9.js} +2 -2
  63. package/dist/plugin-sdk/{resolve-route-C8tvQm8Z.js → resolve-route-Dufnu2cT.js} +1 -1
  64. package/dist/plugin-sdk/{resolve-users-DN9VQBwv.js → resolve-users-Bk_ss2vC.js} +1 -1
  65. package/dist/plugin-sdk/{resolve-users-V6dHh_2B.js → resolve-users-IevCOk4W.js} +2 -2
  66. package/dist/plugin-sdk/{runtime-2B9_WKoI.js → runtime-CXSQwMaR.js} +1 -1
  67. package/dist/plugin-sdk/{runtime-dependency-DczkvPxn.js → runtime-dependency-nup8niQL.js} +9 -9
  68. package/dist/plugin-sdk/sat-runtime.d.ts +18 -2
  69. package/dist/plugin-sdk/sat-runtime.js +26 -25
  70. package/dist/plugin-sdk/{send-DCO441U7.js → send-BXBS_mRK.js} +8 -8
  71. package/dist/plugin-sdk/{send-CFzGKJXo.js → send-D-HfKQZm.js} +4 -4
  72. package/dist/plugin-sdk/{send-B6KkGKZD.js → send-D1j12ZgX.js} +5 -5
  73. package/dist/plugin-sdk/{send-q9HXrSwK.js → send-DWKm6yay.js} +10 -10
  74. package/dist/plugin-sdk/{send-gDoGA-7e.js → send-Di1Jit3L.js} +7 -7
  75. package/dist/plugin-sdk/{skill-commands-FKEPLS7T.js → skill-commands-iJtvpqgb.js} +2 -2
  76. package/dist/plugin-sdk/{slack-BZISDxDd.js → slack-B-nOOLM0.js} +45 -44
  77. package/dist/plugin-sdk/{slack-actions-DhbMcp8M.js → slack-actions-BzUEszJa.js} +10 -10
  78. package/dist/plugin-sdk/slack.d.ts +4 -4
  79. package/dist/plugin-sdk/slack.js +6 -6
  80. package/dist/plugin-sdk/{sticker-cache-B0aPv6Lg.js → sticker-cache-BLiId8Em.js} +2 -2
  81. package/dist/plugin-sdk/{tables-B3jr0Pvq.js → tables-_1W36GII.js} +2 -2
  82. package/dist/plugin-sdk/{targets-CtSbTxYX.js → targets-DDbNnLnB.js} +2 -2
  83. package/dist/plugin-sdk/{telegram-actions-H3gePEn6.js → telegram-actions-1X9A32Dn.js} +30 -29
  84. package/dist/plugin-sdk/telegram.d.ts +4 -4
  85. package/dist/plugin-sdk/telegram.js +3 -3
  86. package/dist/plugin-sdk/{thread-bindings.lifecycle-QbkNl-m6.js → thread-bindings.lifecycle-Y9T3CQGf.js} +3 -3
  87. package/dist/plugin-sdk/{thread-bindings.state-D9hg_M1c.js → thread-bindings.state-D2oOWDpv.js} +1 -1
  88. package/dist/plugin-sdk/{tmp-fased-dir-d1CmFAnJ.d.ts → tmp-fased-dir-BkpWlGUe.d.ts} +7 -1
  89. package/dist/plugin-sdk/{types-DUjYaWB2.d.ts → types-B-zL9gSv.d.ts} +1 -1
  90. package/dist/plugin-sdk/{types-xWjHXl1z.d.ts → types-DtCOO_hW.d.ts} +1 -1
  91. package/dist/plugin-sdk/{web-JwP9PAZR.js → web-BvjNyYBz.js} +33 -32
  92. package/dist/plugin-sdk/{whatsapp-actions-Bj_rOncr.js → whatsapp-actions-Blu9-eX-.js} +9 -9
  93. package/dist/plugin-sdk/{whatsapp-heartbeat-B1WHkJIg.js → whatsapp-heartbeat-DjmQM7hq.js} +2 -2
  94. package/dist/plugin-sdk/whatsapp.d.ts +9 -9
  95. package/dist/plugin-sdk/whatsapp.js +7 -7
  96. package/dist/plugin-sdk/{wsl-J-HscWxS.js → wsl-CTvIWvZz.js} +1 -1
  97. package/dist/plugin-sdk/{zod-schema.providers-core-CIzCjXH9.d.ts → zod-schema.providers-core-BCDlgtls.d.ts} +63 -63
  98. package/dist/wallet/local-socket-signer-lifecycle.js +29 -1
  99. package/dist/wallet/local-socket-signer-protocol.js +27 -0
  100. package/dist/wallet/providers/local-socket-signer-adapter.js +8 -0
  101. package/dist/wallet/signer-network-admin.js +11 -118
  102. package/dist/wallet/signer-protocol-v2.generated.js +4 -0
  103. package/dist/wallet/solana-assets.js +9 -3
  104. package/dist/wallet/solana-rpc-read-fetch.js +65 -0
  105. package/dist/wallet/wallet-status.js +7 -0
  106. package/dist/web/login-qr.js +1 -1
  107. package/dist/wizard/onboarding.js +61 -61
  108. package/extensions/acpx/package.json +1 -1
  109. package/extensions/bluebubbles/package.json +1 -1
  110. package/extensions/copilot-proxy/package.json +1 -1
  111. package/extensions/diagnostics-otel/package.json +1 -1
  112. package/extensions/fased-federation/package.json +1 -1
  113. package/extensions/google-gemini-cli-auth/package.json +1 -1
  114. package/extensions/imessage/package.json +1 -1
  115. package/extensions/irc/package.json +1 -1
  116. package/extensions/line/package.json +1 -1
  117. package/extensions/llm-task/package.json +1 -1
  118. package/extensions/lobster/package.json +1 -1
  119. package/extensions/matrix/CHANGELOG.md +12 -0
  120. package/extensions/matrix/package.json +1 -1
  121. package/extensions/mattermost/package.json +1 -1
  122. package/extensions/memory-core/package.json +2 -2
  123. package/extensions/memory-lancedb/package.json +1 -1
  124. package/extensions/minimax-portal-auth/package.json +1 -1
  125. package/extensions/msteams/CHANGELOG.md +12 -0
  126. package/extensions/msteams/package.json +1 -1
  127. package/extensions/nextcloud-talk/package.json +1 -1
  128. package/extensions/nostr/CHANGELOG.md +12 -0
  129. package/extensions/nostr/package.json +1 -1
  130. package/extensions/open-prose/package.json +1 -1
  131. package/extensions/sat-mining/package.json +1 -1
  132. package/extensions/signal/package.json +1 -1
  133. package/extensions/synology-chat/package.json +1 -1
  134. package/extensions/tlon/package.json +1 -1
  135. package/extensions/twitch/CHANGELOG.md +12 -0
  136. package/extensions/twitch/package.json +1 -1
  137. package/extensions/voice-call/CHANGELOG.md +12 -0
  138. package/extensions/voice-call/package.json +1 -1
  139. package/extensions/zalo/CHANGELOG.md +12 -0
  140. package/extensions/zalo/package.json +1 -1
  141. package/extensions/zalouser/CHANGELOG.md +12 -0
  142. package/extensions/zalouser/package.json +1 -1
  143. package/fased.mjs +14 -1
  144. package/install.sh +16 -10
  145. package/package.json +6 -1
  146. package/scripts/fased-managed-updater.mjs +10 -2
  147. package/scripts/fased-signer-wallet-import-hosting.sh +78 -0
  148. package/scripts/install-managed-runtime.mjs +10 -7
  149. /package/dist/plugin-sdk/{active-listener-B_NMtgPL.js → active-listener-p9Ooc1qj.js} +0 -0
  150. /package/dist/plugin-sdk/{blocks-input-Bg-8S2pl.js → blocks-input-DiZaqaN7.js} +0 -0
  151. /package/dist/plugin-sdk/{channel-access-CcFRdRPO.js → channel-access-B0aZBSd2.js} +0 -0
  152. /package/dist/plugin-sdk/{channel-activity-DBDMQ3yI.js → channel-activity-F07DjroV.js} +0 -0
  153. /package/dist/plugin-sdk/{channel-core-CFRs8LPy.js → channel-core-DoF4X04L.js} +0 -0
  154. /package/dist/plugin-sdk/{client-pI228AVT.js → client-DLY-kWF_.js} +0 -0
  155. /package/dist/plugin-sdk/{command-poll-backoff-yJU83wEq.js → command-poll-backoff-NAePk3eL.js} +0 -0
  156. /package/dist/plugin-sdk/{date-time-uUevHscU.js → date-time-ChY_tHoG.js} +0 -0
  157. /package/dist/plugin-sdk/{draft-stream-loop-CrOPfV3F.js → draft-stream-loop-DMBwjjd1.js} +0 -0
  158. /package/dist/plugin-sdk/{fetch-BtmoPIlf.js → fetch-B0v6OTUZ.js} +0 -0
  159. /package/dist/plugin-sdk/{filesystem-manager-DoI8IBNF.js → filesystem-manager-BzuIJ9K9.js} +0 -0
  160. /package/dist/plugin-sdk/{gateway-registry-DZDsqicG.js → gateway-registry-Bgl2GFgy.js} +0 -0
  161. /package/dist/plugin-sdk/{inbound-context-DrAcXE0y.js → inbound-context-CQnfFFQ9.js} +0 -0
  162. /package/dist/plugin-sdk/{pi-model-discovery-BX_JLcEJ.js → pi-model-discovery-BHNVyRny.js} +0 -0
  163. /package/dist/plugin-sdk/{polls-DKQXVfJp.js → polls-DXd67TD8.js} +0 -0
  164. /package/dist/plugin-sdk/{proxy-DWpYcsvR.js → proxy-CLRUdShp.js} +0 -0
  165. /package/dist/plugin-sdk/{qmd-manager-CgRaTxUq.js → qmd-manager-B8Vnu0ND.js} +0 -0
  166. /package/dist/plugin-sdk/{render-e7fENCYH.js → render-DrBfVs0u.js} +0 -0
  167. /package/dist/plugin-sdk/{reply-reference-Bqk-7HXE.js → reply-reference-BnAk5Wjb.js} +0 -0
  168. /package/dist/plugin-sdk/{retry-policy-C6SjKjQA.js → retry-policy-CXh1owkc.js} +0 -0
  169. /package/dist/plugin-sdk/{sandbox-paths-Dx2lUqCu.js → sandbox-paths-Bzh1CuZc.js} +0 -0
  170. /package/dist/plugin-sdk/{session-DHD4ZKyG.js → session-iuGR_rdi.js} +0 -0
  171. /package/dist/plugin-sdk/{shared-CBgp_GBw.js → shared-D1AD2Ejn.js} +0 -0
  172. /package/dist/plugin-sdk/{target-errors-Ckufe179.js → target-errors-DfS1sLR9.js} +0 -0
  173. /package/dist/plugin-sdk/{tool-loop-detection-Bp8bPJLs.js → tool-loop-detection-sDZl27w7.js} +0 -0
  174. /package/dist/plugin-sdk/{ui-Be8cLHIS.js → ui-GQDYm_jw.js} +0 -0
  175. /package/dist/plugin-sdk/{with-timeout-ckWB8rTo.js → with-timeout-CVV8tytk.js} +0 -0
@@ -18,6 +18,7 @@ import { buildWalletCanaryReport, runWalletProviderCanaryReport } from "../walle
18
18
  import { installSignerdBinary, resolveSignerdBinaryPath, restartLocalSocketSigner, writeLocalSignerEnvFile } from "../wizard/onboarding.wallet.js";
19
19
  import fs from "node:fs";
20
20
  import path from "node:path";
21
+ import { spawnSync } from "node:child_process";
21
22
  import { createInterface } from "node:readline/promises";
22
23
 
23
24
  //#region src/commands/wallet.ts
@@ -261,10 +262,6 @@ function rpcEnvKeyFor(chain, walletId) {
261
262
  }
262
263
  return "FASED_WALLET_SOLANA_RPC_URL";
263
264
  }
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
265
  function resolveRpcUrlForChain(env, chain, walletId, explicit) {
269
266
  const explicitValue = explicit?.trim();
270
267
  if (explicitValue) {
@@ -381,17 +378,23 @@ async function createSignerOwnedWalletForSetup(params) {
381
378
  const walletId = params.walletId?.trim() || params.role;
382
379
  const hosted = String(params.env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
383
380
  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
381
  const mergedEnv = {
389
382
  ...params.env,
390
383
  ...cfg.env?.vars
391
384
  };
392
385
  const socketPath = resolveLocalSignerSocketPath(mergedEnv);
393
386
  const expectedSignerWalletId = normalizeNativeSignerWalletId(walletId);
394
- const existingSignerIdCollision = findNativeSignerWalletIdCollision(readWalletProviderRegistry(params.env).wallets, walletId, expectedSignerWalletId);
387
+ const registeredWallets = readWalletProviderRegistry(params.env).wallets;
388
+ if (params.role === "mining") {
389
+ const activeMiningWallet = registeredWallets.find((entry) => {
390
+ const role = normalizeWalletUserRole(entry.metadata?.role ?? entry.metadata?.purpose);
391
+ return role === "mining" || entry.id === "mining";
392
+ });
393
+ if (activeMiningWallet && activeMiningWallet.id !== walletId) {
394
+ 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}.`);
395
+ }
396
+ }
397
+ const existingSignerIdCollision = findNativeSignerWalletIdCollision(registeredWallets, walletId, expectedSignerWalletId);
395
398
  if (existingSignerIdCollision) {
396
399
  throw new Error(`native signer wallet ID ${expectedSignerWalletId} is already registered as ${existingSignerIdCollision.id}; choose a distinct wallet ID`);
397
400
  }
@@ -428,15 +431,13 @@ async function createSignerOwnedWalletForSetup(params) {
428
431
  if (signerIdCollision) {
429
432
  throw new Error(`native signer wallet ID ${signerWalletId} is already registered as ${signerIdCollision.id}; choose a distinct wallet ID`);
430
433
  }
431
- const executionFallbackRpcUrl = hosted ? "" : [mergedEnv[executionFallbackRpcEnvKeyFor(walletId)], mergedEnv[walletIdEnvSuffix(walletId) ? `FASED_WALLET_SOLANA_WRITE_RPC_FALLBACK_URL__${walletIdEnvSuffix(walletId)}` : "FASED_WALLET_SOLANA_WRITE_RPC_FALLBACK_URL"]].map((value) => String(value ?? "").trim()).find(Boolean) ?? "";
432
- const network = configureSignerOwnedWalletNetwork({
434
+ const network = await configureSignerOwnedWalletNetwork({
433
435
  walletId: signerWalletId,
434
436
  primaryRpcUrl: params.rpcUrl,
435
- executionFallbackRpcUrl: executionFallbackRpcUrl || undefined,
436
437
  env: mergedEnv,
437
- signerBinPath: hosted ? undefined : resolveSignerdBinaryPath(mergedEnv),
438
- controlSocketPath: hosted ? undefined : resolveLocalSignerControlSocketPath(mergedEnv)
438
+ socketPath
439
439
  });
440
+ cfg = setConfigEnvVar(cfg, rpcEnvKeyFor(params.chain, walletId), params.rpcUrl);
440
441
  await writeConfigFile(cfg);
441
442
  const wallet = upsertNamedWallet({
442
443
  walletId,
@@ -463,6 +464,28 @@ async function createSignerOwnedWalletForSetup(params) {
463
464
  env: params.env
464
465
  });
465
466
  }
467
+ if (params.role === "mining") {
468
+ const currentEntry = cfg.plugins?.entries?.["sat-mining"];
469
+ const currentSatConfig = currentEntry?.config && typeof currentEntry.config === "object" && !Array.isArray(currentEntry.config) ? currentEntry.config : {};
470
+ cfg = {
471
+ ...cfg,
472
+ plugins: {
473
+ ...cfg.plugins,
474
+ entries: {
475
+ ...cfg.plugins?.entries,
476
+ "sat-mining": {
477
+ enabled: true,
478
+ ...currentEntry,
479
+ config: {
480
+ ...currentSatConfig,
481
+ walletId: wallet.id
482
+ }
483
+ }
484
+ }
485
+ }
486
+ };
487
+ await writeConfigFile(cfg);
488
+ }
466
489
  if (params.options.json) {
467
490
  params.runtime.log(JSON.stringify({
468
491
  ok: true,
@@ -475,25 +498,289 @@ async function createSignerOwnedWalletForSetup(params) {
475
498
  policyVersion: result.policy.version,
476
499
  policyState: "locked",
477
500
  networkVersion: network.version,
478
- networkReady: network.ready,
479
- ...network.rootAdminRequired ? {
480
- networkActivation: "root-admin-required",
481
- rootCommand: network.rootCommand
482
- } : {}
501
+ networkReady: network.ready
483
502
  }, null, 2));
484
503
  } else {
485
504
  params.runtime.log(`Signer wallet ID: ${signerWalletId}`);
486
505
  params.runtime.log(`${params.chain.toUpperCase()} address: ${result.wallet.publicKey}`);
487
506
  if (!params.options.noSignerHints) {
488
507
  params.runtime.log("Signer-owned wallet created with a deny-all policy. Configure explicit operations, destinations, assets, and positive caps before sending.");
489
- if (network.rootAdminRequired && network.rootCommand) {
490
- params.runtime.log("Hosted wallet remains fail-closed until a root administrator activates its RPC network from a root-owned file:");
491
- params.runtime.log(network.rootCommand);
492
- }
493
508
  }
494
509
  }
495
510
  return result;
496
511
  }
512
+ function requireOwnerOnlySignerImportFile(rawPath) {
513
+ const importPath = path.resolve(rawPath.trim());
514
+ if (!rawPath.trim() || !path.isAbsolute(rawPath.trim()) || importPath !== rawPath.trim()) {
515
+ throw new Error("--import-file must be an absolute clean path");
516
+ }
517
+ const info = fs.lstatSync(importPath);
518
+ if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
519
+ throw new Error("wallet import file must be one regular, non-symlink, single-link file");
520
+ }
521
+ if ((info.mode & 63) !== 0) {
522
+ throw new Error("wallet import file must be owner-only (chmod 600)");
523
+ }
524
+ const effectiveUid = process.geteuid?.();
525
+ if (effectiveUid !== undefined && info.uid !== effectiveUid) {
526
+ throw new Error("wallet import file must be owned by the current terminal user");
527
+ }
528
+ const fd = fs.openSync(importPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
529
+ try {
530
+ const opened = fs.fstatSync(fd);
531
+ if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== info.dev || opened.ino !== info.ino || (opened.mode & 63) !== 0 || effectiveUid !== undefined && opened.uid !== effectiveUid) {
532
+ throw new Error("wallet import file changed or became unsafe before it could be read");
533
+ }
534
+ return {
535
+ path: importPath,
536
+ fd
537
+ };
538
+ } catch (error) {
539
+ fs.closeSync(fd);
540
+ throw error;
541
+ }
542
+ }
543
+ function parseNativeSignerImportResult(params) {
544
+ let value;
545
+ try {
546
+ value = JSON.parse(params.stdout.trim());
547
+ } catch {
548
+ throw new Error("native signer import returned invalid JSON");
549
+ }
550
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
551
+ throw new Error("native signer import returned an invalid result");
552
+ }
553
+ const result = value;
554
+ 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.operations?.length !== 0 || result.policy.programs?.length !== 0 || result.policy.assets?.length !== 0 || typeof result.policy.hash !== "string" || !/^sha256:[0-9a-f]{64}$/.test(result.policy.hash)) {
555
+ throw new Error("native signer import did not return the exact deny-all wallet result");
556
+ }
557
+ return result;
558
+ }
559
+ function invokeNativeSignerWalletImport(params) {
560
+ if (params.hosted) {
561
+ throw new Error("Hosting private-key import is a signer-owner operation. Run /usr/local/sbin/fased-signer-wallet-import from the provider root console, then rerun wallet setup with Create and the same wallet ID to register the existing signer wallet.");
562
+ }
563
+ const input = requireOwnerOnlySignerImportFile(params.importFile);
564
+ const command = params.signerBinPath;
565
+ const args = [
566
+ "admin",
567
+ "wallet",
568
+ "import",
569
+ "--control-socket",
570
+ params.controlSocketPath,
571
+ "--wallet-id",
572
+ params.walletId,
573
+ "--locked-role",
574
+ params.role
575
+ ];
576
+ try {
577
+ const child = spawnSync(command, args, {
578
+ env: {
579
+ HOME: params.env.HOME,
580
+ LANG: params.env.LANG || "C.UTF-8",
581
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
582
+ },
583
+ stdio: [
584
+ input.fd,
585
+ "pipe",
586
+ "pipe"
587
+ ],
588
+ encoding: "utf8",
589
+ maxBuffer: 256 * 1024,
590
+ timeout: 3e4
591
+ });
592
+ if (child.error) {
593
+ throw child.error;
594
+ }
595
+ if (child.status !== 0) {
596
+ const detail = String(child.stderr || "native signer import failed").trim();
597
+ throw new Error(redactWalletDiagnosticText(detail));
598
+ }
599
+ return parseNativeSignerImportResult({
600
+ stdout: String(child.stdout ?? ""),
601
+ walletId: params.walletId,
602
+ role: params.role
603
+ });
604
+ } finally {
605
+ fs.closeSync(input.fd);
606
+ }
607
+ }
608
+ function invokeNativeSignerRecoveryImport(params) {
609
+ if (params.hosted) {
610
+ throw new Error("Hosting recovery import is a signer-owner operation. Run it from the provider root console; the Gateway must not receive the recovery password.");
611
+ }
612
+ const input = requireOwnerOnlySignerImportFile(params.recoveryFile);
613
+ try {
614
+ const child = spawnSync(params.signerBinPath, [
615
+ "admin",
616
+ "wallet",
617
+ "recovery-import",
618
+ "--control-socket",
619
+ params.controlSocketPath,
620
+ "--wallet-id",
621
+ params.walletId,
622
+ "--locked-role",
623
+ params.role,
624
+ "--recovery-file",
625
+ input.path
626
+ ], {
627
+ env: {
628
+ HOME: params.env.HOME,
629
+ LANG: params.env.LANG || "C.UTF-8",
630
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
631
+ },
632
+ stdio: [
633
+ "inherit",
634
+ "pipe",
635
+ "pipe"
636
+ ],
637
+ encoding: "utf8",
638
+ maxBuffer: 256 * 1024,
639
+ timeout: 12e4
640
+ });
641
+ if (child.error) {
642
+ throw child.error;
643
+ }
644
+ if (child.status !== 0) {
645
+ const detail = String(child.stderr || "native signer recovery import failed").trim();
646
+ throw new Error(redactWalletDiagnosticText(detail));
647
+ }
648
+ return parseNativeSignerImportResult({
649
+ stdout: String(child.stdout ?? ""),
650
+ walletId: params.walletId,
651
+ role: params.role
652
+ });
653
+ } finally {
654
+ fs.closeSync(input.fd);
655
+ }
656
+ }
657
+ async function importSignerOwnedWalletForSetup(params) {
658
+ if (params.chain !== "solana") {
659
+ throw new Error("fased-signerd protocol v2 currently supports Solana wallet import only");
660
+ }
661
+ const hosted = String(params.env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
662
+ let cfg = ensureLocalSignerProviderConfig(loadConfig(), params.env);
663
+ const mergedEnv = {
664
+ ...params.env,
665
+ ...cfg.env?.vars
666
+ };
667
+ const socketPath = resolveLocalSignerSocketPath(mergedEnv);
668
+ const signerWalletId = normalizeNativeSignerWalletId(params.walletId);
669
+ const registeredWallets = readWalletProviderRegistry(params.env).wallets;
670
+ if (params.role === "mining" && registeredWallets.some((entry) => {
671
+ const role = normalizeWalletUserRole(entry.metadata?.role ?? entry.metadata?.purpose);
672
+ return (role === "mining" || entry.id === "mining") && entry.id !== params.walletId;
673
+ })) {
674
+ throw new Error("Mining already has one active wallet. Complete the guarded Archive/Replace flow first.");
675
+ }
676
+ const collision = findNativeSignerWalletIdCollision(registeredWallets, params.walletId, signerWalletId);
677
+ if (collision) {
678
+ throw new Error(`native signer wallet ID ${signerWalletId} is already registered as ${collision.id}; choose a distinct wallet ID`);
679
+ }
680
+ const signerBinPath = hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(mergedEnv);
681
+ const controlSocketPath = hosted ? "/run/fased-signerd/control.sock" : resolveLocalSignerControlSocketPath(mergedEnv);
682
+ if (!hosted) {
683
+ writeLocalSignerEnvFile({
684
+ config: cfg,
685
+ env: mergedEnv
686
+ });
687
+ if (!fs.existsSync(signerBinPath)) {
688
+ installSignerdBinary(signerBinPath);
689
+ }
690
+ await restartLocalSocketSigner(undefined, mergedEnv);
691
+ }
692
+ const result = params.options.mode === "local-signer-recovery-import" ? invokeNativeSignerRecoveryImport({
693
+ hosted,
694
+ signerBinPath,
695
+ controlSocketPath,
696
+ walletId: signerWalletId,
697
+ role: params.role,
698
+ recoveryFile: params.importFile,
699
+ env: mergedEnv
700
+ }) : invokeNativeSignerWalletImport({
701
+ hosted,
702
+ signerBinPath,
703
+ controlSocketPath,
704
+ walletId: signerWalletId,
705
+ role: params.role,
706
+ importFile: params.importFile,
707
+ env: mergedEnv
708
+ });
709
+ const network = await configureSignerOwnedWalletNetwork({
710
+ walletId: signerWalletId,
711
+ primaryRpcUrl: params.rpcUrl,
712
+ env: mergedEnv,
713
+ socketPath
714
+ });
715
+ cfg = setConfigEnvVar(cfg, rpcEnvKeyFor(params.chain, params.walletId), params.rpcUrl);
716
+ await writeConfigFile(cfg);
717
+ const wallet = upsertNamedWallet({
718
+ walletId: params.walletId,
719
+ name: params.options.walletName || "Wallet",
720
+ providerId: "local-socket-signer",
721
+ addresses: { solana: result.wallet.publicKey },
722
+ metadata: {
723
+ role: params.role,
724
+ purpose: params.role,
725
+ keyAuthority: "signer-owned-v2",
726
+ signerWalletId,
727
+ policyHash: result.policy.hash,
728
+ policyVersion: result.policy.version,
729
+ policyState: "locked",
730
+ networkHash: network.hash,
731
+ networkVersion: network.version,
732
+ networkReady: network.ready
733
+ },
734
+ env: params.env
735
+ });
736
+ if (params.role === "agent") {
737
+ setDefaultWallet({
738
+ walletId: wallet.id,
739
+ env: params.env
740
+ });
741
+ }
742
+ if (params.role === "mining") {
743
+ const currentEntry = cfg.plugins?.entries?.["sat-mining"];
744
+ const currentConfig = currentEntry?.config && typeof currentEntry.config === "object" && !Array.isArray(currentEntry.config) ? currentEntry.config : {};
745
+ cfg = {
746
+ ...cfg,
747
+ plugins: {
748
+ ...cfg.plugins,
749
+ entries: {
750
+ ...cfg.plugins?.entries,
751
+ "sat-mining": {
752
+ enabled: true,
753
+ ...currentEntry,
754
+ config: {
755
+ ...currentConfig,
756
+ walletId: wallet.id
757
+ }
758
+ }
759
+ }
760
+ }
761
+ };
762
+ await writeConfigFile(cfg);
763
+ }
764
+ if (params.options.json) {
765
+ params.runtime.log(JSON.stringify({
766
+ ok: true,
767
+ provider: "local-socket-signer",
768
+ imported: true,
769
+ walletId: wallet.id,
770
+ signerWalletId,
771
+ role: params.role,
772
+ address: result.wallet.publicKey,
773
+ policyState: "locked",
774
+ networkVersion: network.version,
775
+ networkReady: network.ready
776
+ }, null, 2));
777
+ } else {
778
+ params.runtime.log(`Imported ${params.role} wallet ${wallet.id} into fased-signerd.`);
779
+ params.runtime.log(`SOLANA address: ${result.wallet.publicKey}`);
780
+ params.runtime.log(`Readiness: key=yes RPC=${network.ready ? "yes" : "no"} policy=receive-only`);
781
+ }
782
+ return result;
783
+ }
497
784
  async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
498
785
  const env = process.env;
499
786
  const interactive = !options.nonInteractive;
@@ -591,27 +878,32 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
591
878
  if (!mode) {
592
879
  runtime.log("Wallet setup modes:");
593
880
  runtime.log(" local-signer-create Create a signer-owned Solana wallet (default)");
594
- runtime.log(" local-signer-import Show the native signer-only import command");
881
+ runtime.log(" local-signer-import Import an owner-only Solana keypair into the signer");
882
+ runtime.log(" local-signer-recovery-import Restore an encrypted signer recovery package");
595
883
  runtime.log(" turnkey Configure hosted provider (Turnkey)");
596
884
  runtime.log(" alchemy Configure hosted provider (Alchemy)");
597
885
  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";
886
+ 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
887
  }
600
888
  if (mode === "embedded" || mode === "embedded-create" || mode === "embedded-import") {
601
889
  throwLegacyEmbeddedKeystoreMigrationRequired(`wallet setup mode ${mode} is unavailable`);
602
890
  }
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.");
891
+ if (mode !== "local-signer-create" && mode !== "local-signer-import" && mode !== "local-signer-recovery-import" && mode !== "local-signer" && mode !== "turnkey" && mode !== "alchemy" && mode !== "privy") {
892
+ 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
893
  }
606
894
  if (mode === "local-signer-create") {
607
895
  const chain = options.chain ?? "solana";
608
- const walletId = options.walletId ?? ((await prompt("Wallet id (optional, e.g. agent/mining/vault)", "")).trim() || undefined);
896
+ const roleInput = options.role ?? (interactive ? await prompt("Wallet role (agent|mining|vault)", "") : "");
897
+ const role = normalizeWalletUserRole(roleInput);
898
+ if (!role) {
899
+ throw new Error("--role is required for non-interactive wallet creation and must be agent, mining, or vault");
900
+ }
901
+ const walletId = options.walletId ?? ((await prompt("Wallet id", role)).trim() || role);
609
902
  const rpcUrlFallback = resolveRpcUrlForChain(env, chain, walletId, options.rpcUrl);
610
903
  const rpcUrl = (await prompt(`${chain.toUpperCase()} RPC URL (required for balances/readiness/send)`, rpcUrlFallback)).trim();
611
904
  if (!rpcUrl) {
612
905
  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
906
  }
614
- const role = normalizeWalletUserRole(options.role) ?? (walletId?.trim().toLowerCase() === "mining" ? "mining" : walletId?.trim().toLowerCase() === "vault" ? "vault" : "agent");
615
907
  await createSignerOwnedWalletForSetup({
616
908
  runtime,
617
909
  options,
@@ -627,35 +919,35 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
627
919
  await configureLimitOrdersIfRequested();
628
920
  return;
629
921
  }
630
- if (mode === "local-signer-import") {
631
- const walletId = normalizeNativeSignerWalletId(options.walletId ?? "agent");
632
- const role = normalizeWalletUserRole(options.role) ?? (walletId === "mining" ? "mining" : walletId === "vault" ? "vault" : "agent");
633
- const hosted = String(env.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
634
- const config = loadConfig();
635
- const effectiveEnv = {
636
- ...env,
637
- ...config.env?.vars
638
- };
639
- if (!hosted) {
640
- writeLocalSignerEnvFile({
641
- config,
642
- env: effectiveEnv
643
- });
644
- const signerBinPath = resolveSignerdBinaryPath(effectiveEnv);
645
- if (!fs.existsSync(signerBinPath)) {
646
- installSignerdBinary(signerBinPath);
647
- }
648
- await restartLocalSocketSigner(undefined, effectiveEnv);
922
+ if (mode === "local-signer-import" || mode === "local-signer-recovery-import") {
923
+ const roleInput = options.role ?? (interactive ? await prompt("Wallet role (agent|mining|vault)", "") : "");
924
+ const role = normalizeWalletUserRole(roleInput);
925
+ if (!role) {
926
+ throw new Error("--role is required for non-interactive wallet import and must be agent, mining, or vault");
927
+ }
928
+ const friendlyWalletId = options.walletId ?? (interactive ? await prompt("Wallet id", role) : role);
929
+ const walletId = friendlyWalletId.trim() || role;
930
+ const recoveryImport = mode === "local-signer-recovery-import";
931
+ 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();
932
+ if (!importFile) {
933
+ throw new Error(recoveryImport ? "--recovery-file is required for non-interactive native recovery import" : "--import-file is required for non-interactive native wallet import");
649
934
  }
650
- const signerBinary = hosted ? "/opt/fased/signer/fased-signerd" : resolveSignerdBinaryPath(effectiveEnv);
651
- const controlSocket = hosted ? "/run/fased-signerd/control.sock" : resolveLocalSignerControlSocketPath(effectiveEnv);
652
- runtime.log("Wallet import is native-signer-only; Fased Node will not read the key or passphrase.");
653
- runtime.log("Solana CLI keypair import:");
654
- runtime.log(`chmod 600 /absolute/path/to/solana-keypair.json && ${signerBinary} admin wallet import --control-socket ${controlSocket} --wallet-id ${walletId} --locked-role ${role} < /absolute/path/to/solana-keypair.json`);
655
- runtime.log("Legacy encrypted Fased keystore migration:");
656
- runtime.log(`chmod 600 /absolute/path/to/keystore-solana.v1.enc /absolute/path/to/passphrase && ${signerBinary} admin wallet import-legacy --control-socket ${controlSocket} --wallet-id ${walletId} --locked-role ${role} --keystore-path /absolute/path/to/keystore-solana.v1.enc --passphrase-path /absolute/path/to/passphrase`);
657
- runtime.log(`After verifying the returned public address, run: fased wallet finalize-legacy-migration --wallet-id ${options.walletId ?? walletId}`);
658
- runtime.log("Never put a private key or passphrase in the dashboard, Gateway, chat, argv, or an environment variable.");
935
+ const chain = options.chain ?? "solana";
936
+ const rpcUrlFallback = resolveRpcUrlForChain(env, chain, walletId, options.rpcUrl);
937
+ const rpcUrl = (await prompt(`${chain.toUpperCase()} RPC URL (one primary execution RPC)`, rpcUrlFallback)).trim();
938
+ if (!rpcUrl) {
939
+ throw new Error("--rpc-url is required for non-interactive native wallet import");
940
+ }
941
+ await importSignerOwnedWalletForSetup({
942
+ runtime,
943
+ options,
944
+ env,
945
+ chain,
946
+ walletId,
947
+ rpcUrl,
948
+ role,
949
+ importFile
950
+ });
659
951
  return;
660
952
  }
661
953
  if (mode === "local-signer") {
@@ -743,6 +1035,141 @@ async function walletSetupCommand(runtime = defaultRuntime, options = {}) {
743
1035
  return;
744
1036
  }
745
1037
  }
1038
+ async function walletRecoveryExportCommand(runtime = defaultRuntime, options) {
1039
+ const walletId = options.walletId.trim();
1040
+ const output = options.output.trim();
1041
+ if (!walletId) {
1042
+ throw new Error("--wallet-id is required");
1043
+ }
1044
+ if (!output || !path.isAbsolute(output) || path.resolve(output) !== output) {
1045
+ throw new Error("--output must be an absolute clean path for a new recovery file");
1046
+ }
1047
+ const cfg = loadConfig();
1048
+ const effectiveEnv = {
1049
+ ...process.env,
1050
+ ...cfg.env?.vars
1051
+ };
1052
+ const wallet = readWalletProviderRegistry(effectiveEnv).wallets.find((entry) => entry.id === walletId);
1053
+ if (!wallet || wallet.providerId !== "local-socket-signer") {
1054
+ throw new Error(`native signer wallet not found: ${walletId}`);
1055
+ }
1056
+ const publicKey = wallet.addresses?.solana?.trim() || "";
1057
+ if (!publicKey) {
1058
+ throw new Error(`native signer wallet ${walletId} has no verified Solana public address`);
1059
+ }
1060
+ const hosted = String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting";
1061
+ if (hosted) {
1062
+ throw new Error("Hosting recovery export remains root/signer-owner only so a compromised Gateway cannot choose the encryption password and export a wallet. Use the provider root console and fased-signerd admin wallet recovery-export until a signer-owned owner-authorization ceremony is configured.");
1063
+ }
1064
+ const signerWalletId = typeof wallet.metadata?.signerWalletId === "string" && wallet.metadata.signerWalletId.trim() ? wallet.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(wallet.id);
1065
+ const signerBinPath = resolveSignerdBinaryPath(effectiveEnv);
1066
+ const controlSocketPath = resolveLocalSignerControlSocketPath(effectiveEnv);
1067
+ runtime.log(`Creating encrypted recovery package for ${wallet.id}. The signer will ask for and confirm the recovery password without echoing it.`);
1068
+ const child = spawnSync(signerBinPath, [
1069
+ "admin",
1070
+ "wallet",
1071
+ "recovery-export",
1072
+ "--control-socket",
1073
+ controlSocketPath,
1074
+ "--wallet-id",
1075
+ signerWalletId,
1076
+ "--expected-public-key",
1077
+ publicKey,
1078
+ "--output",
1079
+ output
1080
+ ], {
1081
+ env: {
1082
+ HOME: effectiveEnv.HOME,
1083
+ LANG: effectiveEnv.LANG || "C.UTF-8",
1084
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
1085
+ },
1086
+ stdio: "inherit",
1087
+ timeout: 12e4
1088
+ });
1089
+ if (child.error) {
1090
+ throw child.error;
1091
+ }
1092
+ if (child.status !== 0) {
1093
+ throw new Error(`encrypted recovery export failed with exit code ${child.status ?? "unknown"}`);
1094
+ }
1095
+ runtime.log(`Encrypted recovery package written: ${output} (owner-only)`);
1096
+ runtime.log(`Wallet: ${wallet.id} · Role: ${resolveWalletUserRole(wallet)} · Address: ${publicKey}`);
1097
+ }
1098
+ async function walletRecoveryImportCommand(runtime = defaultRuntime, options) {
1099
+ await walletSetupCommand(runtime, {
1100
+ mode: "local-signer-recovery-import",
1101
+ chain: "solana",
1102
+ walletId: options.walletId,
1103
+ walletName: options.walletName,
1104
+ role: options.role,
1105
+ recoveryFile: options.recoveryFile,
1106
+ rpcUrl: options.rpcUrl,
1107
+ nonInteractive: true,
1108
+ noDoctor: true,
1109
+ noSignerHints: true
1110
+ });
1111
+ }
1112
+ async function walletRawExportCommand(runtime = defaultRuntime, options) {
1113
+ const walletId = options.walletId.trim();
1114
+ const output = options.output.trim();
1115
+ if (!walletId) {
1116
+ throw new Error("--wallet-id is required");
1117
+ }
1118
+ if (!options.acknowledgeCustodyReduction) {
1119
+ throw new Error("--acknowledge-custody-reduction is required for raw private-key export");
1120
+ }
1121
+ if (!output || !path.isAbsolute(output) || path.resolve(output) !== output) {
1122
+ throw new Error("--output must be an absolute clean path for a new owner-only keypair file");
1123
+ }
1124
+ const cfg = loadConfig();
1125
+ const effectiveEnv = {
1126
+ ...process.env,
1127
+ ...cfg.env?.vars
1128
+ };
1129
+ const wallet = readWalletProviderRegistry(effectiveEnv).wallets.find((entry) => entry.id === walletId);
1130
+ if (!wallet || wallet.providerId !== "local-socket-signer") {
1131
+ throw new Error(`native signer wallet not found: ${walletId}`);
1132
+ }
1133
+ const publicKey = wallet.addresses?.solana?.trim() || "";
1134
+ if (!publicKey) {
1135
+ throw new Error(`native signer wallet ${walletId} has no verified Solana public address`);
1136
+ }
1137
+ if (String(effectiveEnv.FASED_HOST_PROFILE ?? "").trim().toLowerCase() === "hosting") {
1138
+ throw new Error("Hosting raw-key export remains signer-owner only. Run the native signer admin command from the provider root console so the Gateway cannot export custody material.");
1139
+ }
1140
+ const signerWalletId = typeof wallet.metadata?.signerWalletId === "string" && wallet.metadata.signerWalletId.trim() ? wallet.metadata.signerWalletId.trim() : normalizeNativeSignerWalletId(wallet.id);
1141
+ runtime.log("WARNING: raw private-key export reduces signer custody protection.");
1142
+ const child = spawnSync(resolveSignerdBinaryPath(effectiveEnv), [
1143
+ "admin",
1144
+ "wallet",
1145
+ "export-raw",
1146
+ "--control-socket",
1147
+ resolveLocalSignerControlSocketPath(effectiveEnv),
1148
+ "--wallet-id",
1149
+ signerWalletId,
1150
+ "--expected-public-key",
1151
+ publicKey,
1152
+ "--output",
1153
+ output,
1154
+ "--acknowledge-custody-reduction"
1155
+ ], {
1156
+ env: {
1157
+ HOME: effectiveEnv.HOME,
1158
+ LANG: effectiveEnv.LANG || "C.UTF-8",
1159
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
1160
+ },
1161
+ stdio: "inherit",
1162
+ timeout: 12e4
1163
+ });
1164
+ if (child.error) {
1165
+ throw child.error;
1166
+ }
1167
+ if (child.status !== 0) {
1168
+ throw new Error(`raw private-key export failed with exit code ${child.status ?? "unknown"}`);
1169
+ }
1170
+ runtime.log(`Raw private key written: ${output} (owner-only)`);
1171
+ runtime.log(`Wallet: ${wallet.id} · Address: ${publicKey}`);
1172
+ }
746
1173
  async function walletRoleSetCommand(runtime = defaultRuntime, options) {
747
1174
  const walletId = options.walletId.trim();
748
1175
  const role = normalizeWalletRoleForCli(options.role);
@@ -1509,4 +1936,4 @@ async function walletInboundReconcileCommand(runtime = defaultRuntime, options =
1509
1936
  }
1510
1937
 
1511
1938
  //#endregion
1512
- export { collectWalletSignerDoctorReport, walletCanaryCommand, walletInboundListCommand, walletInboundPollCommand, walletInboundReconcileCommand, walletLegacyMigrationFinalizeCommand, walletLimitOrdersConfigureCommand, walletMigrateCommand, walletPolicyProfileApplyCommand, walletProviderConfigureCommand, walletRoleSetCommand, walletRotateKeysCommand, walletSetupCommand, walletSignerDoctorCommand, walletSignerServeCommand, walletStatusCommand };
1939
+ export { collectWalletSignerDoctorReport, walletCanaryCommand, walletInboundListCommand, walletInboundPollCommand, walletInboundReconcileCommand, walletLegacyMigrationFinalizeCommand, walletLimitOrdersConfigureCommand, walletMigrateCommand, walletPolicyProfileApplyCommand, walletProviderConfigureCommand, walletRawExportCommand, walletRecoveryExportCommand, walletRecoveryImportCommand, walletRoleSetCommand, walletRotateKeysCommand, walletSetupCommand, walletSignerDoctorCommand, walletSignerServeCommand, walletStatusCommand };