@dev.sail.money/sailor 0.0.2-22 → 0.0.2-24

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 (115) hide show
  1. package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +6 -8
  2. package/examples/permissions/SailCalldata.sol +118 -0
  3. package/package.json +1 -1
  4. package/packages/cli/dist/index.cjs +248 -28
  5. package/packages/cli/dist/server.cjs +56 -16
  6. package/packages/sdk/dist/index.d.ts +1 -1
  7. package/packages/sdk/dist/index.d.ts.map +1 -1
  8. package/packages/sdk/dist/index.js +1 -1
  9. package/packages/sdk/dist/index.js.map +1 -1
  10. package/packages/sdk/dist/intelligence.d.ts +1 -1
  11. package/packages/sdk/dist/intelligence.js +1 -1
  12. package/packages/sdk/dist/lifi.d.ts +17 -0
  13. package/packages/sdk/dist/lifi.d.ts.map +1 -1
  14. package/packages/sdk/dist/lifi.js +24 -0
  15. package/packages/sdk/dist/lifi.js.map +1 -1
  16. package/packages/sdk/dist/types.d.ts +17 -1
  17. package/packages/sdk/dist/types.d.ts.map +1 -1
  18. package/packages/ui/dist/assets/{add-C--RBwJe.js → add--OaWHMEX.js} +1 -1
  19. package/packages/ui/dist/assets/{all-wallets-_xwd_eso.js → all-wallets-BH_4qsJ0.js} +1 -1
  20. package/packages/ui/dist/assets/{app-store-CIQsK1zU.js → app-store-j8XNWdo_.js} +1 -1
  21. package/packages/ui/dist/assets/{apple-BdlAnnmO.js → apple-DoNsugim.js} +1 -1
  22. package/packages/ui/dist/assets/{arrow-bottom-B5p_6Dat.js → arrow-bottom-D_enDpNq.js} +1 -1
  23. package/packages/ui/dist/assets/{arrow-bottom-circle-D7c6JPTF.js → arrow-bottom-circle-WWFGXKiz.js} +1 -1
  24. package/packages/ui/dist/assets/{arrow-left-SA4NpEnP.js → arrow-left-BUJGpX55.js} +1 -1
  25. package/packages/ui/dist/assets/{arrow-right-mOJNWujS.js → arrow-right-D5mkI_SK.js} +1 -1
  26. package/packages/ui/dist/assets/{arrow-top-CvPVVpHl.js → arrow-top-BXhKZNjN.js} +1 -1
  27. package/packages/ui/dist/assets/{bank-B2j2rPm9.js → bank-Dkkf0tum.js} +1 -1
  28. package/packages/ui/dist/assets/{basic-Bw6cXOlk.js → basic-DOdQ4iGr.js} +1 -1
  29. package/packages/ui/dist/assets/{browser-CUSNF__N.js → browser-NOeeokaH.js} +1 -1
  30. package/packages/ui/dist/assets/{card-CpKLox49.js → card-cdDhvSTA.js} +1 -1
  31. package/packages/ui/dist/assets/{ccip-XB9iQjXB.js → ccip-Dsn_0RCo.js} +1 -1
  32. package/packages/ui/dist/assets/{checkmark-BRpXeSCK.js → checkmark-9fzIA8S7.js} +1 -1
  33. package/packages/ui/dist/assets/{checkmark-bold-BkPvoqxo.js → checkmark-bold-DDvnKkth.js} +1 -1
  34. package/packages/ui/dist/assets/{chevron-bottom-CtK0W2av.js → chevron-bottom-CZ0cAj9w.js} +1 -1
  35. package/packages/ui/dist/assets/{chevron-left-NayfPMDy.js → chevron-left-cbciRp76.js} +1 -1
  36. package/packages/ui/dist/assets/{chevron-right-BPU2hCfA.js → chevron-right-aaIM8CMM.js} +1 -1
  37. package/packages/ui/dist/assets/{chevron-top-CTXwC4nM.js → chevron-top-Cv9x0gjk.js} +1 -1
  38. package/packages/ui/dist/assets/{chrome-store-eWIk0-YZ.js → chrome-store-DlOKVEJe.js} +1 -1
  39. package/packages/ui/dist/assets/{clock-VmYiq5jB.js → clock-5QviMwVt.js} +1 -1
  40. package/packages/ui/dist/assets/{close-NfBukMzW.js → close-myWpcxkH.js} +1 -1
  41. package/packages/ui/dist/assets/{coinPlaceholder-BWOeJc6j.js → coinPlaceholder-D3UzHxQZ.js} +1 -1
  42. package/packages/ui/dist/assets/{compass-oRk8W3iM.js → compass-DN7a9rAJ.js} +1 -1
  43. package/packages/ui/dist/assets/{copy-GcYQZOsF.js → copy-5dhhqdUd.js} +1 -1
  44. package/packages/ui/dist/assets/{core-B_rvnvkC.js → core-BlknpGLl.js} +3 -3
  45. package/packages/ui/dist/assets/cursor-Ckhq1uuc.js +3 -0
  46. package/packages/ui/dist/assets/{cursor-transparent-CGox3wZ-.js → cursor-transparent-hWLSc0KZ.js} +1 -1
  47. package/packages/ui/dist/assets/{desktop-DU4yyiV4.js → desktop-CZ-Yyu9E.js} +1 -1
  48. package/packages/ui/dist/assets/{disconnect-CJm9NnxK.js → disconnect-BkFpHyPA.js} +1 -1
  49. package/packages/ui/dist/assets/{discord-MxDL8Eq6.js → discord-BQr8vCO7.js} +1 -1
  50. package/packages/ui/dist/assets/{etherscan-CkCvlZiA.js → etherscan-BRQnPWan.js} +1 -1
  51. package/packages/ui/dist/assets/{events-CkyJn32_.js → events-BPX61gpp.js} +1 -1
  52. package/packages/ui/dist/assets/{exclamation-triangle-hH1JdYAZ.js → exclamation-triangle-DgiBPDRx.js} +1 -1
  53. package/packages/ui/dist/assets/{extension-DTMrXG5m.js → extension-DqOhrhYM.js} +1 -1
  54. package/packages/ui/dist/assets/{external-link-GSwn5MzD.js → external-link-CN3oX5O9.js} +1 -1
  55. package/packages/ui/dist/assets/{facebook-Vw_uyzaE.js → facebook-Y4EwFoke.js} +1 -1
  56. package/packages/ui/dist/assets/{fallback-BL3U4ZRT.js → fallback-DTghxJb4.js} +1 -1
  57. package/packages/ui/dist/assets/{farcaster-F-_di36M.js → farcaster-Bk5F07SC.js} +1 -1
  58. package/packages/ui/dist/assets/{filters-DQzcstDl.js → filters-Cz-QDvgT.js} +1 -1
  59. package/packages/ui/dist/assets/{github-BSq3_rEd.js → github-Cxkp89Tq.js} +1 -1
  60. package/packages/ui/dist/assets/{google-BU4QXiDS.js → google-DYqw-KYU.js} +1 -1
  61. package/packages/ui/dist/assets/{help-circle-CuF4iPyF.js → help-circle-BXMNYoIA.js} +1 -1
  62. package/packages/ui/dist/assets/{id-BQWlv0a_.js → id-BaD71KYD.js} +1 -1
  63. package/packages/ui/dist/assets/{image-BPNySDPo.js → image-B23hGUdW.js} +1 -1
  64. package/packages/ui/dist/assets/{index-D2wgBslE.js → index-BN6XqPDp.js} +1 -1
  65. package/packages/ui/dist/assets/{index-CMyY4FOR.js → index-BQ4JZ1HB.js} +3 -3
  66. package/packages/ui/dist/assets/{index-BMPQOOgv.js → index-C3IX4alt.js} +1 -1
  67. package/packages/ui/dist/assets/{index-Dc9_WV0G.js → index-CL1Pp-W8.js} +77 -77
  68. package/packages/ui/dist/assets/index-DCnJ64lX.css +1 -0
  69. package/packages/ui/dist/assets/{index-CsbiKM3b.js → index-DXwK5tlD.js} +1 -1
  70. package/packages/ui/dist/assets/{index-D0SPxlSM.js → index-Diq2KQvQ.js} +1 -1
  71. package/packages/ui/dist/assets/{index.es-CvyDIsY4.js → index.es-5g6Py2iz.js} +4 -4
  72. package/packages/ui/dist/assets/{info-D20yslek.js → info-DXuKXV9d.js} +1 -1
  73. package/packages/ui/dist/assets/{info-circle-BEjvYTHa.js → info-circle-CYkuEbJC.js} +1 -1
  74. package/packages/ui/dist/assets/{lightbulb-DfvLi5mQ.js → lightbulb-CuI54_aI.js} +1 -1
  75. package/packages/ui/dist/assets/{mail-CkgaIJAd.js → mail--8E_kt-f.js} +1 -1
  76. package/packages/ui/dist/assets/{metamask-sdk-O-IBvvGq.js → metamask-sdk-yzZWWcs3.js} +1 -1
  77. package/packages/ui/dist/assets/{mobile-CGc88WfG.js → mobile-CZymO9zO.js} +1 -1
  78. package/packages/ui/dist/assets/{more-DnX8wlTn.js → more-BYAwsmOZ.js} +1 -1
  79. package/packages/ui/dist/assets/{network-placeholder-DDrgA4a3.js → network-placeholder-08UzyBww.js} +1 -1
  80. package/packages/ui/dist/assets/{nftPlaceholder-DhHWPuD3.js → nftPlaceholder-BYQcom9C.js} +1 -1
  81. package/packages/ui/dist/assets/{off-D1CsYvPQ.js → off-CYMrTsdm.js} +1 -1
  82. package/packages/ui/dist/assets/{parseSignature-BlZUbtEc.js → parseSignature-WNjhiGa-.js} +1 -1
  83. package/packages/ui/dist/assets/{play-store-Dbkk8PTZ.js → play-store-C8qwnge4.js} +1 -1
  84. package/packages/ui/dist/assets/{plus-B8jXpls3.js → plus-D44RqQir.js} +1 -1
  85. package/packages/ui/dist/assets/{qr-code-CDuJ3ftj.js → qr-code-B2Zo3ucm.js} +1 -1
  86. package/packages/ui/dist/assets/{recycle-horizontal-ZFGjaHsZ.js → recycle-horizontal-GWYpxQB9.js} +1 -1
  87. package/packages/ui/dist/assets/{refresh-D0rMEDtF.js → refresh-MgpEHHa3.js} +1 -1
  88. package/packages/ui/dist/assets/{reown-logo-NlCNVmgd.js → reown-logo-CteGf0y0.js} +1 -1
  89. package/packages/ui/dist/assets/{search-CrJAA2qW.js → search-tUzIsCFS.js} +1 -1
  90. package/packages/ui/dist/assets/{secp256k1-mJj6W2AI.js → secp256k1-DmFqeUJ_.js} +1 -1
  91. package/packages/ui/dist/assets/{send-C7CoRziM.js → send-C9UNMMEg.js} +1 -1
  92. package/packages/ui/dist/assets/{swapHorizontal-fD3wbCGJ.js → swapHorizontal-Bhb2KCgj.js} +1 -1
  93. package/packages/ui/dist/assets/{swapHorizontalBold-Cc-jQ6as.js → swapHorizontalBold-Dx8XHsp8.js} +1 -1
  94. package/packages/ui/dist/assets/{swapHorizontalMedium-DlJW6uX1.js → swapHorizontalMedium-BU5Bg66C.js} +1 -1
  95. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-1VHOerLO.js → swapHorizontalRoundedBold-DZtiRbnr.js} +1 -1
  96. package/packages/ui/dist/assets/{swapVertical-CKaRlkZK.js → swapVertical-Da4jr_Iy.js} +1 -1
  97. package/packages/ui/dist/assets/{telegram-DnCYed4D.js → telegram-2JwKMtW5.js} +1 -1
  98. package/packages/ui/dist/assets/{three-dots-BFluoxma.js → three-dots-BvVnpuAg.js} +1 -1
  99. package/packages/ui/dist/assets/{twitch-BXGv98S9.js → twitch-C6DOrjYX.js} +1 -1
  100. package/packages/ui/dist/assets/{twitterIcon-C6IdXEe5.js → twitterIcon-BQu41-nP.js} +1 -1
  101. package/packages/ui/dist/assets/{verify-D_QGyiLQ.js → verify-CFZQJntQ.js} +1 -1
  102. package/packages/ui/dist/assets/{verify-filled-DIW8QKL9.js → verify-filled-Cy6vpeJk.js} +1 -1
  103. package/packages/ui/dist/assets/{w3m-modal-Do9U160p.js → w3m-modal-De9EZPA2.js} +1 -1
  104. package/packages/ui/dist/assets/{wallet-CcARZnOx.js → wallet-BfWc3N5d.js} +1 -1
  105. package/packages/ui/dist/assets/{wallet-placeholder-X1coFzQa.js → wallet-placeholder-BuxnWFqL.js} +1 -1
  106. package/packages/ui/dist/assets/{walletconnect-Glte9ia7.js → walletconnect-CjirfANF.js} +1 -1
  107. package/packages/ui/dist/assets/{warning-circle-j-3V4KTo.js → warning-circle-CqS0eXEs.js} +1 -1
  108. package/packages/ui/dist/assets/{x-Bcc52c_T.js → x-DQeWAjll.js} +1 -1
  109. package/packages/ui/dist/index.html +2 -2
  110. package/templates/custom-mandate/README.md +31 -0
  111. package/templates/custom-mandate/mandates/BoundedCallPermission.sol +8 -2
  112. package/templates/custom-mandate/mandates/SailCalldata.sol +118 -0
  113. package/templates/default/AGENTS.md +51 -2
  114. package/packages/ui/dist/assets/cursor-BAViuJWh.js +0 -3
  115. package/packages/ui/dist/assets/index-DDKDa0s2.css +0 -1
@@ -38416,6 +38416,7 @@ function getRpcUrl(chainId) {
38416
38416
  }
38417
38417
 
38418
38418
  // src/lib/keys.ts
38419
+ init_esm2();
38419
38420
  var ROLES = ["manager", "permissionSigner"];
38420
38421
  function normalizeRole(input) {
38421
38422
  const n = input.trim().toLowerCase().replace(/[-_\s]/g, "");
@@ -38482,6 +38483,13 @@ async function loadManagerSigner(safe) {
38482
38483
  }
38483
38484
  return loadKeyring("manager", safe);
38484
38485
  }
38486
+ function managerKeystorePath(managerAddr) {
38487
+ if (!isAddress(managerAddr, { strict: false })) {
38488
+ throw new Error(`managerKeystorePath: invalid address "${managerAddr}"`);
38489
+ }
38490
+ const hex = managerAddr.toLowerCase().replace(/^0x/, "");
38491
+ return sailPath("keys", "managers", `${hex}.json`);
38492
+ }
38485
38493
  async function loadAnySigner() {
38486
38494
  if (keyExists("permissionSigner")) return loadKeyring("permissionSigner");
38487
38495
  if (keyExists("manager")) return loadKeyring("manager");
@@ -39899,11 +39907,29 @@ init_esm2();
39899
39907
  async function checkContractExists(pc, address) {
39900
39908
  try {
39901
39909
  const code = await pc.getCode({ address });
39902
- return { address, hasCode: !!code && code !== "0x" };
39910
+ const hasCode = !!code && code !== "0x";
39911
+ return { address, hasCode, bytecode: hasCode ? code : void 0 };
39903
39912
  } catch (err) {
39904
39913
  return { address, hasCode: false, error: err.message.split("\n")[0] };
39905
39914
  }
39906
39915
  }
39916
+ function checkSelectorRoutes(calldata, bytecode) {
39917
+ if (calldata.length < 10) {
39918
+ return { selector: "", routes: null, reason: "calldata shorter than 4 bytes \u2014 no selector" };
39919
+ }
39920
+ const selector = calldata.slice(2, 10).toLowerCase();
39921
+ const body = bytecode.slice(2).toLowerCase();
39922
+ if (body.length < 100) {
39923
+ return { selector, routes: null, reason: "proxy or minimal contract \u2014 routing not determinable from bytecode" };
39924
+ }
39925
+ if (body.includes("360894a13ba1a321")) {
39926
+ return { selector, routes: null, reason: "EIP-1967 proxy detected \u2014 routing is in the implementation contract" };
39927
+ }
39928
+ if (body.includes("a3f0ad74e5423aeb")) {
39929
+ return { selector, routes: null, reason: "EIP-1967 beacon proxy detected \u2014 routing is in the beacon implementation" };
39930
+ }
39931
+ return { selector, routes: body.includes(selector) };
39932
+ }
39907
39933
 
39908
39934
  // src/lib/permission-resolver.ts
39909
39935
  var IPERMISSION_ABI = [
@@ -40775,14 +40801,48 @@ var MandateStore = class {
40775
40801
  (m) => m.address.toLowerCase() === needle || m.name === addressOrName
40776
40802
  );
40777
40803
  }
40778
- /** Append a newly deployed mandate (replacing any prior record at the same address). */
40804
+ /**
40805
+ * Append a newly deployed mandate (replacing any prior record at the same address).
40806
+ * When another mandate with the same name already exists on the same chain, the
40807
+ * incoming mandate's name is suffixed with `[2]`, `[3]`, … to keep names unique.
40808
+ */
40779
40809
  add(mandate2) {
40780
40810
  const data = this.read();
40781
40811
  data.mandates = data.mandates.filter(
40782
40812
  (m) => m.address.toLowerCase() !== mandate2.address.toLowerCase()
40783
40813
  );
40814
+ const baseName = mandate2.name;
40815
+ const sameName = (m) => m.name === mandate2.name && m.chainId === mandate2.chainId;
40816
+ if (data.mandates.some(sameName)) {
40817
+ let n = 2;
40818
+ while (data.mandates.some((m) => m.name === `${baseName}[${n}]` && m.chainId === mandate2.chainId)) {
40819
+ n++;
40820
+ }
40821
+ mandate2 = { ...mandate2, name: `${baseName}[${n}]` };
40822
+ }
40784
40823
  data.mandates.push(mandate2);
40785
40824
  this.write(data);
40825
+ return mandate2;
40826
+ }
40827
+ /** Update mutable metadata fields on a tracked mandate (name, sourcePath, artifactPath). */
40828
+ update(addressOrName, patch) {
40829
+ const data = this.read();
40830
+ const needle = addressOrName.toLowerCase();
40831
+ const mandate2 = data.mandates.find(
40832
+ (m) => m.address.toLowerCase() === needle || m.name === addressOrName
40833
+ );
40834
+ if (!mandate2) throw new Error(`No tracked mandate found for: ${addressOrName}`);
40835
+ if (patch.name !== void 0 && patch.name !== mandate2.name) {
40836
+ const conflict = data.mandates.find(
40837
+ (m) => m.name === patch.name && m.chainId === mandate2.chainId && m.address.toLowerCase() !== mandate2.address.toLowerCase()
40838
+ );
40839
+ if (conflict) throw new Error(`Name "${patch.name}" is already used by ${conflict.address} on chain ${mandate2.chainId}`);
40840
+ mandate2.name = patch.name;
40841
+ }
40842
+ if (patch.sourcePath !== void 0) mandate2.sourcePath = patch.sourcePath;
40843
+ if (patch.artifactPath !== void 0) mandate2.artifactPath = patch.artifactPath;
40844
+ this.write(data);
40845
+ return mandate2;
40786
40846
  }
40787
40847
  /** Record that a tracked mandate was attached to an SMA. */
40788
40848
  recordAttachment(address, attachment) {
@@ -41290,6 +41350,7 @@ async function persistAccount(publicClient, account2) {
41290
41350
  owner: checksum4(account2.owner),
41291
41351
  permissionSigner: checksum4(account2.permissionSigner),
41292
41352
  manager: checksum4(account2.manager),
41353
+ managers: [checksum4(account2.manager)],
41293
41354
  chainId: account2.chainId,
41294
41355
  createdAtBlock,
41295
41356
  ...account2.saltNonce != null ? { saltNonce: account2.saltNonce.toString() } : {}
@@ -41396,7 +41457,18 @@ async function runDeploy(project, channel, options) {
41396
41457
  );
41397
41458
  }
41398
41459
  const { abi: abi2, bytecode, contractName, artifactPath } = resolveArtifact(options);
41399
- const args = coerceConstructorArgs(abi2, options.args);
41460
+ let argsJson;
41461
+ if (options.argsFile) {
41462
+ const argsFilePath = (0, import_node_path9.resolve)(options.argsFile);
41463
+ try {
41464
+ argsJson = (0, import_node_fs10.readFileSync)(argsFilePath, "utf8").trim();
41465
+ } catch {
41466
+ throw new Error(`Cannot read --args-file: ${argsFilePath}`);
41467
+ }
41468
+ } else {
41469
+ argsJson = options.args;
41470
+ }
41471
+ const args = coerceConstructorArgs(abi2, argsJson);
41400
41472
  const deployData = encodeDeployData({ abi: abi2, bytecode, args });
41401
41473
  const chainId = project.chainId;
41402
41474
  const publicClient = publicClientFor(project);
@@ -41419,7 +41491,10 @@ async function runDeploy(project, channel, options) {
41419
41491
  data: deployData,
41420
41492
  details: [
41421
41493
  { label: "Contract", value: contractName },
41422
- { label: "Constructor args", value: options.args ? options.args : "(none)" }
41494
+ {
41495
+ label: options.argsFile ? "Constructor args (from file)" : "Constructor args",
41496
+ value: argsJson ? argsJson : "(none)"
41497
+ }
41423
41498
  ]
41424
41499
  });
41425
41500
  if (response.status === "rejected") {
@@ -41445,13 +41520,13 @@ async function runDeploy(project, channel, options) {
41445
41520
  deployedAt: (/* @__PURE__ */ new Date()).toISOString()
41446
41521
  };
41447
41522
  const store = new MandateStore();
41448
- store.add(record);
41449
- say(() => console.log("Tracked in .sail/state/mandates.json"));
41523
+ const stored = store.add(record);
41524
+ say(() => console.log("Tracked in .sail/state/mandates.json" + (stored.name !== record.name ? ` as "${stored.name}"` : "")));
41450
41525
  appendActivity({
41451
41526
  ts: nowIso(),
41452
41527
  actor: "owner",
41453
41528
  type: "mandate_deployed",
41454
- name: record.name,
41529
+ name: stored.name,
41455
41530
  address: deployed,
41456
41531
  txHash: response.txHash,
41457
41532
  chainId
@@ -41465,7 +41540,7 @@ async function runDeploy(project, channel, options) {
41465
41540
  publicClient,
41466
41541
  sma,
41467
41542
  deployed,
41468
- record.name,
41543
+ stored.name,
41469
41544
  json
41470
41545
  );
41471
41546
  store.recordAttachment(deployed, { sma, txHash: attachTxHash });
@@ -41480,7 +41555,7 @@ Register it later with: sailor mandate attach --address ${deployed} --sma <SMA>`
41480
41555
  emit(json, () => {
41481
41556
  }, {
41482
41557
  status: "ok",
41483
- mandate: { name: record.name, address: deployed, txHash: response.txHash, chainId },
41558
+ mandate: { name: stored.name, address: deployed, txHash: response.txHash, chainId },
41484
41559
  attached: options.attach ? { sma: getAddress(options.sma), txHash: attachTxHash } : null
41485
41560
  });
41486
41561
  }
@@ -41742,7 +41817,7 @@ Connect the owner wallet (mandate signer) in the browser \u2014 the agent wallet
41742
41817
  }
41743
41818
  say(() => console.log("\u2713", `Deployed + registered ${spec.label} at ${clone}`));
41744
41819
  const store = new MandateStore();
41745
- store.add({
41820
+ const storedClone = store.add({
41746
41821
  name: label,
41747
41822
  address: clone,
41748
41823
  txHash,
@@ -41755,7 +41830,7 @@ Connect the owner wallet (mandate signer) in the browser \u2014 the agent wallet
41755
41830
  actor: "agent",
41756
41831
  type: "permission_registered",
41757
41832
  permission: clone,
41758
- name: label,
41833
+ name: storedClone.name,
41759
41834
  sma,
41760
41835
  txHash,
41761
41836
  chainId: project.chainId
@@ -42076,6 +42151,21 @@ function mandateContractsList() {
42076
42151
  }
42077
42152
  }
42078
42153
  }
42154
+ function mandateUpdate(options) {
42155
+ const { address, name, sourcePath, artifactPath, json } = options;
42156
+ if (!name && !sourcePath && !artifactPath) {
42157
+ throw new Error("Provide at least one of --name, --source-path, or --artifact-path");
42158
+ }
42159
+ const store = new MandateStore();
42160
+ const updated = store.update(address, { name, sourcePath, artifactPath });
42161
+ emit(!!json, () => {
42162
+ const changes = [];
42163
+ if (name) changes.push(`name \u2192 ${updated.name}`);
42164
+ if (sourcePath) changes.push(`sourcePath \u2192 ${updated.sourcePath}`);
42165
+ if (artifactPath) changes.push(`artifactPath \u2192 ${updated.artifactPath}`);
42166
+ console.log(`Updated ${updated.address}: ${changes.join(", ")}`);
42167
+ }, { status: "ok", mandate: updated });
42168
+ }
42079
42169
  function resolveArtifact(options) {
42080
42170
  let artifactPath = options.artifact;
42081
42171
  let contractName = options.contract ?? options.name ?? "";
@@ -42085,7 +42175,7 @@ function resolveArtifact(options) {
42085
42175
  }
42086
42176
  const resolved = (0, import_node_path9.resolve)(artifactPath);
42087
42177
  const projectRoot = (0, import_node_path9.resolve)(process.cwd());
42088
- if (!resolved.startsWith(projectRoot + "/") && resolved !== projectRoot) {
42178
+ if (!resolved.startsWith(projectRoot + import_node_path9.sep) && resolved !== projectRoot) {
42089
42179
  throw new Error(
42090
42180
  `Artifact path must be inside the project directory.
42091
42181
  Resolved: ${resolved}`
@@ -42530,6 +42620,7 @@ async function mandateSimulate(options) {
42530
42620
  checkContractExists(pc, c.target)
42531
42621
  ]);
42532
42622
  const result = probe.accepted ? "pass" : "fail";
42623
+ const selectorCheck = codeCheck.hasCode && codeCheck.bytecode ? checkSelectorRoutes(c.data, codeCheck.bytecode) : { selector: "", routes: null, reason: codeCheck.hasCode ? "bytecode unavailable" : void 0 };
42533
42624
  return {
42534
42625
  index: i,
42535
42626
  label: c.label,
@@ -42541,7 +42632,10 @@ async function mandateSimulate(options) {
42541
42632
  expect: c.expect ?? null,
42542
42633
  match: c.expect ? c.expect === result : null,
42543
42634
  targetHasCode: codeCheck.hasCode,
42544
- targetCheckError: codeCheck.error
42635
+ targetCheckError: codeCheck.error,
42636
+ selectorRoutes: selectorCheck.routes,
42637
+ selector: selectorCheck.selector,
42638
+ selectorRoutesReason: selectorCheck.reason
42545
42639
  };
42546
42640
  })
42547
42641
  );
@@ -42572,7 +42666,10 @@ async function mandateSimulate(options) {
42572
42666
  expect: r.expect,
42573
42667
  match: r.match,
42574
42668
  targetHasCode: r.targetHasCode,
42575
- targetCheckError: r.targetCheckError
42669
+ targetCheckError: r.targetCheckError,
42670
+ selector: r.selector,
42671
+ selectorRoutes: r.selectorRoutes,
42672
+ selectorRoutesReason: r.selectorRoutesReason
42576
42673
  })),
42577
42674
  mismatches: mismatches.length,
42578
42675
  noCodeTargets: noCodeTargets.map((r) => r.target),
@@ -42598,7 +42695,9 @@ async function mandateSimulate(options) {
42598
42695
  const expectStr = r.expect === null ? "" : r.match ? ` expected ${r.expect} \u2713 MATCH` : ` expected ${r.expect} \u2717 MISMATCH`;
42599
42696
  console.log(`[${r.index + 1}] ${verdict} ${r.label}${expectStr}`);
42600
42697
  const codeNote = r.targetCheckError ? `\u26A0 could not verify contract code (${r.targetCheckError})` : r.targetHasCode ? "\u2713 contract present" : `\u26A0 NO contract code on chain ${chainId} \u2014 this call would fail on-chain regardless of the permission`;
42698
+ const selectorNote = r.selectorRoutes === true ? `\u2713 selector 0x${r.selector} routes` : r.selectorRoutes === false ? `\u26A0 selector 0x${r.selector} NOT found in bytecode \u2014 call would likely revert with unknown selector` : r.selectorRoutesReason ? `~ selector check skipped (${r.selectorRoutesReason})` : null;
42601
42699
  console.log(` target ${r.target} ${codeNote}`);
42700
+ if (selectorNote) console.log(` ${selectorNote}`);
42602
42701
  if (r.reverted && r.revertReason) {
42603
42702
  console.log(` evaluate() reverted: ${r.revertReason}`);
42604
42703
  }
@@ -42661,6 +42760,24 @@ async function runRotateSigner(project, channel, options) {
42661
42760
  const account2 = resolveAccount(options);
42662
42761
  const smaAddress = account2.safe;
42663
42762
  const owner2 = account2.owner;
42763
+ if (options.list) {
42764
+ const stored = readJsonFile(sailPath("account.json"));
42765
+ const known = stored?.managers ?? (stored?.manager ? [stored.manager] : []);
42766
+ const active = stored?.manager ?? "";
42767
+ return {
42768
+ sma: smaAddress,
42769
+ oldManager: null,
42770
+ newManager: active,
42771
+ rotated: false,
42772
+ reattached: [],
42773
+ reattachDeferred: false,
42774
+ managers: known.map((m) => ({
42775
+ address: m,
42776
+ active: m.toLowerCase() === active.toLowerCase(),
42777
+ keystoreStored: readJsonFile(managerKeystorePath(m)) !== null
42778
+ }))
42779
+ };
42780
+ }
42664
42781
  const publicClient = createPublicClient({
42665
42782
  chain: getChainById(project.chainId),
42666
42783
  transport: http(getRpcUrl(project.chainId))
@@ -42793,6 +42910,7 @@ SMA: ${smaAddress}`);
42793
42910
  chainId: project.chainId
42794
42911
  });
42795
42912
  persistManager(smaAddress, newManager);
42913
+ if (options.to) promoteManagerKeystore(newManager, say);
42796
42914
  if (options.skipReattach || currentPermissions.length === 0) {
42797
42915
  return {
42798
42916
  sma: smaAddress,
@@ -42956,13 +43074,23 @@ async function resolveNewManager(options, oldManager, json, say) {
42956
43074
  throw new Error(`Invalid --to address: ${options.to}`);
42957
43075
  }
42958
43076
  const to = getAddress(options.to);
42959
- say(
42960
- () => console.log(
42961
- `
42962
- Rotating to existing address ${to}. The local agent keystore is left unchanged \u2014
43077
+ if (readJsonFile(managerKeystorePath(to)) !== null) {
43078
+ say(
43079
+ () => console.log(
43080
+ `
43081
+ Rotating to ${to}. Its stored keystore will become the active one
43082
+ (.sail/keys/manager.json) once the rotation confirms.`
43083
+ )
43084
+ );
43085
+ } else {
43086
+ say(
43087
+ () => console.log(
43088
+ `
43089
+ Rotating to existing address ${to}. No local keystore found for this address \u2014
42963
43090
  ensure the agent that signs dispatches holds this key.`
42964
- )
42965
- );
43091
+ )
43092
+ );
43093
+ }
42966
43094
  return to;
42967
43095
  }
42968
43096
  if (json) {
@@ -42982,6 +43110,9 @@ ensure the agent that signs dispatches holds this key.`
42982
43110
  const keyring = LocalKeyring.generate();
42983
43111
  const keystore = await keyring.exportKeystore(password);
42984
43112
  writeJsonFile(target, keystore);
43113
+ const perManagerPath = managerKeystorePath(keyring.address);
43114
+ (0, import_node_fs13.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43115
+ writeJsonFile(perManagerPath, keystore);
42985
43116
  say(
42986
43117
  () => console.log(
42987
43118
  ` New agent wallet: ${checksum4(keyring.address)} (keystore at .sail/keys/manager.json)`
@@ -42989,10 +43120,28 @@ ensure the agent that signs dispatches holds this key.`
42989
43120
  );
42990
43121
  return keyring.address;
42991
43122
  }
43123
+ function promoteManagerKeystore(newManager, say) {
43124
+ const stored = readJsonFile(managerKeystorePath(newManager));
43125
+ if (!stored) return;
43126
+ (0, import_node_fs13.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43127
+ const activeTarget = keyPath("manager");
43128
+ const displaced = readJsonFile(activeTarget);
43129
+ if (displaced?.address) {
43130
+ const snapshotPath = managerKeystorePath(displaced.address);
43131
+ if (readJsonFile(snapshotPath) === null) writeJsonFile(snapshotPath, displaced);
43132
+ } else if (displaced) {
43133
+ writeJsonFile(`${activeTarget}.${Date.now()}.bak`, displaced);
43134
+ }
43135
+ writeJsonFile(activeTarget, stored);
43136
+ say(
43137
+ () => console.log(` Agent wallet keystore for ${newManager} is now active (.sail/keys/manager.json).`)
43138
+ );
43139
+ }
42992
43140
  function persistManager(safe, manager) {
42993
43141
  const account2 = readJsonFile(sailPath("account.json"));
42994
43142
  if (account2 && account2.safe.toLowerCase() === safe.toLowerCase()) {
42995
- writeJsonFile(sailPath("account.json"), { ...account2, manager: checksum4(manager) });
43143
+ const managers = addToManagerList(account2.managers, account2.manager, manager);
43144
+ writeJsonFile(sailPath("account.json"), { ...account2, manager: checksum4(manager), managers });
42996
43145
  }
42997
43146
  const listPath = sailPath("state", "accounts.json");
42998
43147
  const list = readJsonFile(
@@ -43001,11 +43150,26 @@ function persistManager(safe, manager) {
43001
43150
  if (Array.isArray(list)) {
43002
43151
  const idx = list.findIndex((a) => a.safe.toLowerCase() === safe.toLowerCase());
43003
43152
  if (idx !== -1) {
43004
- list[idx] = { ...list[idx], manager: checksum4(manager) };
43153
+ const entry = list[idx];
43154
+ const managers = addToManagerList(entry.managers, entry.manager, manager);
43155
+ list[idx] = { ...entry, manager: checksum4(manager), managers };
43005
43156
  writeJsonFile(listPath, list);
43006
43157
  }
43007
43158
  }
43008
43159
  }
43160
+ function addToManagerList(existing, current, next) {
43161
+ const all = [...existing ?? [current], checksum4(next)];
43162
+ const seen = /* @__PURE__ */ new Set();
43163
+ const deduped = [];
43164
+ for (const a of all) {
43165
+ const lower = a.toLowerCase();
43166
+ if (!seen.has(lower)) {
43167
+ seen.add(lower);
43168
+ deduped.push(checksum4(a));
43169
+ }
43170
+ }
43171
+ return deduped;
43172
+ }
43009
43173
  function writePending(pending) {
43010
43174
  writeJsonFile(sailPath(...PENDING_REATTACH_FILE), pending);
43011
43175
  }
@@ -43016,6 +43180,19 @@ function clearPending() {
43016
43180
  }
43017
43181
  }
43018
43182
  function printSummary2(r) {
43183
+ if (r.managers) {
43184
+ console.log("\nKnown agent wallets for this SMA:");
43185
+ if (r.managers.length === 0) {
43186
+ console.log(" (none recorded)");
43187
+ } else {
43188
+ for (const m of r.managers) {
43189
+ const marker = m.active ? "* " : " ";
43190
+ console.log(`${marker}${m.address}${m.keystoreStored ? " (keystore stored)" : ""}`);
43191
+ }
43192
+ console.log("\n* = active");
43193
+ }
43194
+ return;
43195
+ }
43019
43196
  console.log(`
43020
43197
  ${"\u2500".repeat(56)}`);
43021
43198
  if (r.rotated) {
@@ -43138,13 +43315,30 @@ function isProcessAlive(pid) {
43138
43315
  // src/commands/run.ts
43139
43316
  var DEFAULT_INTERVAL_SEC = 60;
43140
43317
  var sleep2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
43141
- var ERC20_BALANCE_ABI = [
43318
+ var ERC20_READ_ABI = [
43142
43319
  {
43143
43320
  type: "function",
43144
43321
  name: "balanceOf",
43145
43322
  stateMutability: "view",
43146
43323
  inputs: [{ name: "account", type: "address" }],
43147
43324
  outputs: [{ name: "", type: "uint256" }]
43325
+ },
43326
+ {
43327
+ type: "function",
43328
+ name: "allowance",
43329
+ stateMutability: "view",
43330
+ inputs: [
43331
+ { name: "owner", type: "address" },
43332
+ { name: "spender", type: "address" }
43333
+ ],
43334
+ outputs: [{ name: "", type: "uint256" }]
43335
+ },
43336
+ {
43337
+ type: "function",
43338
+ name: "decimals",
43339
+ stateMutability: "view",
43340
+ inputs: [],
43341
+ outputs: [{ name: "", type: "uint8" }]
43148
43342
  }
43149
43343
  ];
43150
43344
  function loadAgentData(filePath) {
@@ -43164,7 +43358,8 @@ async function loadAgent() {
43164
43358
  let mod2;
43165
43359
  if (abs.endsWith(".ts")) {
43166
43360
  const { tsImport } = await import("tsx/esm/api");
43167
- mod2 = await tsImport(abs, (0, import_node_url.pathToFileURL)(abs).href);
43361
+ const absUrl = (0, import_node_url.pathToFileURL)(abs).href;
43362
+ mod2 = await tsImport(absUrl, absUrl);
43168
43363
  } else {
43169
43364
  mod2 = await import((0, import_node_url.pathToFileURL)(abs).href);
43170
43365
  }
@@ -43291,11 +43486,30 @@ Configure the chain in @sail/chains or set KERNEL_ADDRESS in .sail/.env.local.`
43291
43486
  }
43292
43487
  return publicClient.readContract({
43293
43488
  address: token,
43294
- abi: ERC20_BALANCE_ABI,
43489
+ abi: ERC20_READ_ABI,
43295
43490
  functionName: "balanceOf",
43296
43491
  args: [accountAddr]
43297
43492
  });
43298
43493
  };
43494
+ const readAllowance = (token, owner2, spender) => publicClient.readContract({
43495
+ address: token,
43496
+ abi: ERC20_READ_ABI,
43497
+ functionName: "allowance",
43498
+ args: [owner2, spender]
43499
+ });
43500
+ const decimalsCache = /* @__PURE__ */ new Map();
43501
+ const readDecimals = async (token) => {
43502
+ const key = getAddress(token);
43503
+ const cached = decimalsCache.get(key);
43504
+ if (cached !== void 0) return cached;
43505
+ const d = await publicClient.readContract({
43506
+ address: token,
43507
+ abi: ERC20_READ_ABI,
43508
+ functionName: "decimals"
43509
+ });
43510
+ decimalsCache.set(key, d);
43511
+ return d;
43512
+ };
43299
43513
  async function runTick() {
43300
43514
  appendActivity({ ts: nowIso(), actor: "agent", type: "tick_start" });
43301
43515
  let blockInfo = { number: 0n, timestamp: 0n };
@@ -43325,10 +43539,15 @@ Configure the chain in @sail/chains or set KERNEL_ADDRESS in .sail/.env.local.`
43325
43539
  dispatch: execClient.dispatch,
43326
43540
  strategy: execClient.strategy
43327
43541
  }),
43542
+ publicClient,
43328
43543
  manager: agentManager,
43329
43544
  log,
43330
43545
  data: agentData,
43331
- read: { balance: readBalance }
43546
+ read: {
43547
+ balance: readBalance,
43548
+ allowance: readAllowance,
43549
+ decimals: readDecimals
43550
+ }
43332
43551
  };
43333
43552
  let dispatches;
43334
43553
  try {
@@ -43949,11 +44168,11 @@ account.command("predict").description(
43949
44168
  "Agent (manager) wallet \u2014 mixed into the kernel salt (defaults to .sail/account.json)"
43950
44169
  ).option("--salt <n>", "CREATE2 salt nonce (default: 0)").option("--chain <id>", "Show prediction for one chain only").option("--json", "Emit machine-readable JSON").action(actionWith(accountPredict));
43951
44170
  account.command("deploy-chain").description("Deploy the same SMA address on an additional chain using the same owner, manager, and salt").requiredOption("--chain <id>", "Target EVM chain ID (e.g. 8453, 42161, 130, 1)").option("--salt <n>", "CREATE2 salt (defaults to saltNonce stored in .sail/account.json)").option("--json", "Emit machine-readable JSON").action(actionWith(accountDeployChain));
43952
- account.command("rotate-signer").description("Rotate the SMA's delegated signer (agent wallet) and re-approve its mandates").option("--sma <address>", "SMA to rotate (defaults to the active account)").option("--to <address>", "Rotate to an existing agent-wallet address instead of generating one").option("--generate", "Generate a fresh local agent wallet (default when --to is omitted)").option("--skip-reattach", "Do not re-approve the previously-attached mandates").option("--reattach-only", "Skip rotation; only re-approve mandates (resume after funding)").option("--json", "Machine-readable output").action(actionWith(rotateSigner));
44171
+ account.command("rotate-signer").description("Rotate the SMA's delegated signer (agent wallet) and re-approve its mandates").option("--sma <address>", "SMA to rotate (defaults to the active account)").option("--to <address>", "Rotate to an existing agent-wallet address instead of generating one").option("--generate", "Generate a fresh local agent wallet (default when --to is omitted)").option("--skip-reattach", "Do not re-approve the previously-attached mandates").option("--reattach-only", "Skip rotation; only re-approve mandates (resume after funding)").option("--list", "List known agent wallets for this SMA without rotating").option("--json", "Machine-readable output").action(actionWith(rotateSigner));
43953
44172
  var mandate = program2.command("mandate").description("Manage mandates");
43954
44173
  mandate.command("prepare").description("Prepare a mandate draft for review and signing in the UI (MetaMask)").action(action(mandatePrepare));
43955
44174
  mandate.command("sign").description("Review and confirm the permissions authorized for your SMA").option("--yes", "Skip the confirmation prompt (for non-interactive / CI use)").action(actionWith(mandateSign));
43956
- mandate.command("deploy").description("Deploy a Foundry-compiled permission contract via the browser signing UI").option("--artifact <path>", "Path to the Foundry artifact JSON (out/<Name>.sol/<Name>.json)").option("--contract <name>", "Contract name; resolves to <out>/<name>.sol/<name>.json").option("--out <dir>", "Foundry output directory", "out").option("--name <label>", "Label to track this permission under (defaults to contract name)").option("--args <json>", `Constructor args as a JSON array, e.g. '[["0x.."],"1000"]'`).option("--build", "Run `forge build` before deploying").option("--attach", "After deploy, register the permission on --sma").option("--sma <address>", "SMA to register on (required with --attach)").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeploy));
44175
+ mandate.command("deploy").description("Deploy a Foundry-compiled permission contract via the browser signing UI").option("--artifact <path>", "Path to the Foundry artifact JSON (out/<Name>.sol/<Name>.json)").option("--contract <name>", "Contract name; resolves to <out>/<name>.sol/<name>.json").option("--out <dir>", "Foundry output directory", "out").option("--name <label>", "Label to track this permission under (defaults to contract name)").option("--args <json>", `Constructor args as JSON array. Bash: '["0x..","1"]'. PowerShell: '[\\"0x..\\",\\"1\\"]'. Use --args-file to avoid quoting.`).option("--args-file <path>", "Path to a JSON file containing constructor args array (recommended on PowerShell)").option("--build", "Run `forge build` before deploying").option("--attach", "After deploy, register the permission on --sma").option("--sma <address>", "SMA to register on (required with --attach)").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeploy));
43957
44176
  mandate.command("attach").description("Register an already-deployed permission on an SMA (EIP-712 RegisterPermission)").requiredOption("--address <mandateOrName>", "Permission address, or a name tracked locally").requiredOption("--sma <address>", "SMA to register the permission on").option("--label <label>", "Human-readable label shown in the signing UI").option("--json", "Emit machine-readable JSON").action(actionWith(mandateAttach));
43958
44177
  mandate.command("deploy-clone").description("Deploy + register a standalone clone permission (e.g. boundedApprove) via the signing UI").requiredOption("--template <key>", "Standalone clone template key (e.g. boundedApprove)").requiredOption("--sma <address>", "SMA to deploy the clone for and register it on").option("--tokens <csv>", "Comma-separated allowed token addresses").option("--spenders <csv>", "Comma-separated allowed spender addresses").option("--max <amount>", "Max amount per tx in base units (default: uint256 max)").option("--label <label>", "Human-readable label to track this permission under").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeployClone));
43959
44178
  mandate.command("revoke").description("Revoke permission(s) from an SMA (EIP-712 RevokePermissions, owner-authorized)").option("--address <permissionOrName>", "Permission address, or a name tracked locally").requiredOption("--sma <address>", "Safe (SMA) to revoke the permission(s) from").option("--all", "Revoke every permission currently registered on the SMA").option("--json", "Output JSON").action(actionWith(mandateRevoke));
@@ -43961,6 +44180,7 @@ mandate.command("templates").description("Show how to author your own permission
43961
44180
  mandate.command("simulate").description(
43962
44181
  "Probe a permission against sample calls off-chain (eth_call, NO gas) \u2014 prove it accepts the calls you want and rejects the ones you don't, before authorizing on-chain"
43963
44182
  ).requiredOption("--address <permissionOrName>", "Permission to probe (address or tracked name)").option("--sma <address>", "SMA to probe as (ctx.account; defaults to .sail/account.json)").option("--target <address>", "Inline single call: target contract address").option("--calldata <hex>", "Inline single call: 0x-prefixed calldata").option("--value <wei>", "Inline single call: ETH value in wei (default 0)").option("--expect <pass|fail>", "Inline single call: expected outcome (sets non-zero exit on mismatch)").option("--label <text>", "Inline single call: human-readable label").option("--calls <file>", "Batch: JSON array of { target, calldata, value?, expect?, label? }").option("--json", "Emit machine-readable JSON").action(actionWith(mandateSimulate));
44183
+ mandate.command("update").description("Update metadata for a tracked permission contract (rename, source path, artifact path)").requiredOption("--address <mandateOrName>", "Permission address or tracked name to update").option("--name <label>", "New tracking label (must be unique within the same chain)").option("--source-path <path>", "Update the relative path to the Solidity source file").option("--artifact-path <path>", "Update the relative path to the Foundry artifact JSON").option("--json", "Emit machine-readable JSON").action(actionWith(mandateUpdate));
43964
44184
  mandate.command("list").description("List permission contracts deployed from this project").action(action(async () => mandateContractsList()));
43965
44185
  program2.command("onboard").description("Set up an SMA, register a permission, confirm the agent is operational").option("--sma <address>", "Use a specific SMA address instead of prompting").option("--new-sma", "Create a new SMA via SailKernel").option("--salt <n>", "CREATE2 salt for deterministic Safe address (default: 0; use 0 for first SMA, increment for subsequent)").option("--template <kindOrAddress>", "Register this permission contract (kind, label, or address)").option("--skip-mandate", "Skip the permission registration step").option("--json", "Emit machine-readable JSON (implies non-interactive)").action(actionWith(onboard));
43966
44186
  var station = program2.command("station").description("Manage the persistent signing station (browser signing daemon)");
@@ -49593,6 +49593,17 @@ function signerEntry(role, address, balanceByAddr) {
49593
49593
  status: balanceStatus(wei)
49594
49594
  };
49595
49595
  }
49596
+ function addManagerToList(existing, current, next) {
49597
+ const base = existing ?? (current ? [getAddress(current)] : []);
49598
+ const all = [...base, getAddress(next)];
49599
+ const seen = /* @__PURE__ */ new Set();
49600
+ return all.filter((a) => {
49601
+ const l = a.toLowerCase();
49602
+ if (seen.has(l)) return false;
49603
+ seen.add(l);
49604
+ return true;
49605
+ });
49606
+ }
49596
49607
  var OVERVIEW_TTL_MS = 1e4;
49597
49608
  function startServer(sailDir, { port = PORT } = {}) {
49598
49609
  const app = (0, import_express.default)();
@@ -49883,7 +49894,8 @@ function startServer(sailDir, { port = PORT } = {}) {
49883
49894
  try {
49884
49895
  const account = JSON.parse(import_node_fs2.default.readFileSync(at("account.json"), "utf-8"));
49885
49896
  if (account?.safe?.toLowerCase() === safe.toLowerCase()) {
49886
- import_node_fs2.default.writeFileSync(at("account.json"), `${JSON.stringify({ ...account, manager }, null, 2)}
49897
+ const managers = addManagerToList(account.managers, account.manager, manager);
49898
+ import_node_fs2.default.writeFileSync(at("account.json"), `${JSON.stringify({ ...account, manager, managers }, null, 2)}
49887
49899
  `);
49888
49900
  }
49889
49901
  } catch {
@@ -49893,7 +49905,9 @@ function startServer(sailDir, { port = PORT } = {}) {
49893
49905
  const list = JSON.parse(import_node_fs2.default.readFileSync(listPath, "utf-8"));
49894
49906
  const idx = list.findIndex((a) => a.safe?.toLowerCase() === safe.toLowerCase());
49895
49907
  if (idx !== -1) {
49896
- list[idx] = { ...list[idx], manager };
49908
+ const entry = list[idx];
49909
+ const managers = addManagerToList(entry.managers, entry.manager, manager);
49910
+ list[idx] = { ...entry, manager, managers };
49897
49911
  import_node_fs2.default.writeFileSync(listPath, `${JSON.stringify(list, null, 2)}
49898
49912
  `);
49899
49913
  }
@@ -49915,7 +49929,20 @@ function startServer(sailDir, { port = PORT } = {}) {
49915
49929
  res.status(500).json({ error: String(err) });
49916
49930
  }
49917
49931
  });
49918
- const isManagerKeyFile = (file) => file === "manager.json" || file.startsWith("manager-") && file.endsWith(".json");
49932
+ const isManagerKeyFile = (file) => file === "manager.json" || file.startsWith("manager-") && file.endsWith(".json") || file.startsWith("managers/") && file.endsWith(".json");
49933
+ const listManagerKeyFiles = () => {
49934
+ let files = [];
49935
+ try {
49936
+ files = import_node_fs2.default.readdirSync(at("keys"));
49937
+ } catch {
49938
+ files = [];
49939
+ }
49940
+ try {
49941
+ files.push(...import_node_fs2.default.readdirSync(at("keys/managers")).map((f) => `managers/${f}`));
49942
+ } catch {
49943
+ }
49944
+ return files;
49945
+ };
49919
49946
  const keystoreAddress = (file) => {
49920
49947
  try {
49921
49948
  const ks = JSON.parse(import_node_fs2.default.readFileSync(at(`keys/${file}`), "utf-8"));
@@ -49927,12 +49954,7 @@ function startServer(sailDir, { port = PORT } = {}) {
49927
49954
  };
49928
49955
  app.get("/api/signers", (_req, res) => {
49929
49956
  try {
49930
- let files = [];
49931
- try {
49932
- files = import_node_fs2.default.readdirSync(at("keys"));
49933
- } catch {
49934
- files = [];
49935
- }
49957
+ const files = listManagerKeyFiles();
49936
49958
  const activeSafe = readActiveSafe();
49937
49959
  let activeManager = null;
49938
49960
  try {
@@ -49972,12 +49994,7 @@ function startServer(sailDir, { port = PORT } = {}) {
49972
49994
  return;
49973
49995
  }
49974
49996
  try {
49975
- let files = [];
49976
- try {
49977
- files = import_node_fs2.default.readdirSync(at("keys"));
49978
- } catch {
49979
- files = [];
49980
- }
49997
+ const files = listManagerKeyFiles();
49981
49998
  const want = getAddress(address).toLowerCase();
49982
49999
  const match = files.find((file) => isManagerKeyFile(file) && keystoreAddress(file)?.toLowerCase() === want);
49983
50000
  if (!match) {
@@ -50610,8 +50627,9 @@ function startServer(sailDir, { port = PORT } = {}) {
50610
50627
  const [permissionSigner, manager, , sessionActive] = configs;
50611
50628
  const managerSet = Boolean(manager) && getAddress(manager) !== zeroAddress2;
50612
50629
  const managerAddr = managerSet ? getAddress(manager) : localSigner;
50630
+ const knownManagerAddrs = (account.managers ?? []).map((a) => getAddress(a));
50613
50631
  const signerAddrs = [
50614
- ...new Set([managerAddr, account.owner ? getAddress(account.owner) : null, ...localManagerAddrs].filter(Boolean))
50632
+ ...new Set([managerAddr, account.owner ? getAddress(account.owner) : null, ...localManagerAddrs, ...knownManagerAddrs].filter(Boolean))
50615
50633
  ];
50616
50634
  const balances = await Promise.all(signerAddrs.map((a) => client.getBalance({ address: a })));
50617
50635
  const balanceByAddr = new Map(signerAddrs.map((a, i) => [a.toLowerCase(), balances[i]]));
@@ -50637,6 +50655,17 @@ function startServer(sailDir, { port = PORT } = {}) {
50637
50655
  } else {
50638
50656
  managerEntry = { role: "manager", address: null, balanceWei: null, balanceEth: null, status: "unconfigured" };
50639
50657
  }
50658
+ if (knownManagerAddrs.length > 0) {
50659
+ const activeLower = (managerSet ? getAddress(manager) : account.manager ?? null)?.toLowerCase() ?? null;
50660
+ managerEntry.managers = knownManagerAddrs.map((a) => {
50661
+ const bal = balanceByAddr.get(a.toLowerCase());
50662
+ return {
50663
+ address: a,
50664
+ balanceEth: bal != null ? formatEther(bal) : null,
50665
+ isActive: a.toLowerCase() === activeLower
50666
+ };
50667
+ });
50668
+ }
50640
50669
  const signers = [managerEntry];
50641
50670
  for (const la of localManagerAddrs) {
50642
50671
  if (getAddress(la) !== (managerAddr ? getAddress(managerAddr) : null)) {
@@ -50665,6 +50694,17 @@ function startServer(sailDir, { port = PORT } = {}) {
50665
50694
  status: "local"
50666
50695
  }));
50667
50696
  }
50697
+ if (account.managers?.length > 0) {
50698
+ const onchainMgr = result.sma.manager;
50699
+ const activeLower = (onchainMgr && onchainMgr !== zeroAddress2 ? onchainMgr : account.manager ?? null)?.toLowerCase() ?? null;
50700
+ const managersPayload = account.managers.map((a) => ({
50701
+ address: getAddress(a),
50702
+ balanceEth: null,
50703
+ isActive: a.toLowerCase() === activeLower
50704
+ }));
50705
+ const mgrSigner = result.signers.find((s) => s.role === "manager");
50706
+ if (mgrSigner && !mgrSigner.managers) mgrSigner.managers = managersPayload;
50707
+ }
50668
50708
  return result;
50669
50709
  }
50670
50710
  const distDir = process.env.SAILOR_UI_DIST ?? import_node_path.default.join(import_node_path.default.dirname(_thisFile), "dist");