@dev.sail.money/sailor 1.3.0-225 → 1.3.0-226

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 (122) hide show
  1. package/README.md +16 -0
  2. package/package.json +7 -1
  3. package/packages/cli/dist/index.cjs +5 -2
  4. package/packages/cli/dist/server.cjs +105 -23
  5. package/packages/sdk/dist/intelligence.d.ts +1 -1
  6. package/packages/sdk/dist/intelligence.js +1 -1
  7. package/packages/ui/dist/assets/{add-QIu9SGBU.js → add-DoHGZfJI.js} +2 -2
  8. package/packages/ui/dist/assets/{all-wallets-2auxp9E5.js → all-wallets-CUKVFyT3.js} +2 -2
  9. package/packages/ui/dist/assets/{app-store-BVtVvk-F.js → app-store-DyQ4PZOs.js} +1 -1
  10. package/packages/ui/dist/assets/{apple-Dq7Ppvqv.js → apple-Dc-GOiDW.js} +2 -2
  11. package/packages/ui/dist/assets/arrow-bottom-C-dI8Oh_.js +8 -0
  12. package/packages/ui/dist/assets/{arrow-bottom-circle-hkf9bWhW.js → arrow-bottom-circle-BH2LebvP.js} +2 -2
  13. package/packages/ui/dist/assets/arrow-left-CUIn5NsD.js +8 -0
  14. package/packages/ui/dist/assets/arrow-right-B-X57DDF.js +8 -0
  15. package/packages/ui/dist/assets/arrow-top-BNOmzVbg.js +8 -0
  16. package/packages/ui/dist/assets/{bank-CkRSSgJy.js → bank-Dqcy3Map.js} +2 -2
  17. package/packages/ui/dist/assets/{basic-D9puI79I.js → basic-CN95kOlB.js} +177 -177
  18. package/packages/ui/dist/assets/{browser-BC8qo0Bh.js → browser-v-WzD6Au.js} +2 -2
  19. package/packages/ui/dist/assets/{card-BYuGTxNc.js → card-DJmQXuOi.js} +2 -2
  20. package/packages/ui/dist/assets/ccip-8oS512uk.js +1 -0
  21. package/packages/ui/dist/assets/{checkmark-C-MZeapS.js → checkmark-6Rvy6Al7.js} +2 -2
  22. package/packages/ui/dist/assets/{checkmark-bold-Bs3hadsW.js → checkmark-bold-GYXSMFUr.js} +2 -2
  23. package/packages/ui/dist/assets/chevron-bottom-BF1Fa2NO.js +8 -0
  24. package/packages/ui/dist/assets/chevron-left-kj9KjPLp.js +8 -0
  25. package/packages/ui/dist/assets/chevron-right-CHH0lg7_.js +8 -0
  26. package/packages/ui/dist/assets/chevron-top-DjHmnG32.js +8 -0
  27. package/packages/ui/dist/assets/{chrome-store-sDKm_6es.js → chrome-store-KzNe4H9H.js} +2 -2
  28. package/packages/ui/dist/assets/{clock-BwfFymxk.js → clock-xHvMNQNx.js} +2 -2
  29. package/packages/ui/dist/assets/{close-BewD5_E0.js → close-nKtgX0WM.js} +2 -2
  30. package/packages/ui/dist/assets/{coinPlaceholder-DRPvf0NA.js → coinPlaceholder-gnwD3g4G.js} +2 -2
  31. package/packages/ui/dist/assets/{compass-BHPvd2dw.js → compass-wnLBxh3w.js} +2 -2
  32. package/packages/ui/dist/assets/{copy-BMv4Y3KF.js → copy-CmLisiOG.js} +2 -2
  33. package/packages/ui/dist/assets/core-BLOM5s8O.js +907 -0
  34. package/packages/ui/dist/assets/cursor-DgwjrlpZ.js +3 -0
  35. package/packages/ui/dist/assets/{cursor-transparent-DT8YBqHU.js → cursor-transparent-CjVDknUI.js} +2 -2
  36. package/packages/ui/dist/assets/{desktop-BeePUUqi.js → desktop-BSGjR5fT.js} +2 -2
  37. package/packages/ui/dist/assets/{disconnect-C2qT29qY.js → disconnect-DkOQ_j9U.js} +2 -2
  38. package/packages/ui/dist/assets/{discord-I4ToELfU.js → discord-DtvZ281_.js} +2 -2
  39. package/packages/ui/dist/assets/{etherscan-3psXC5Ef.js → etherscan-BB3baLbR.js} +2 -2
  40. package/packages/ui/dist/assets/events-DQ172AOg.js +1 -0
  41. package/packages/ui/dist/assets/{exclamation-triangle-BCaWksGJ.js → exclamation-triangle-BrR8a-pR.js} +2 -2
  42. package/packages/ui/dist/assets/{extension-DfYavvbY.js → extension-BiGAMg97.js} +2 -2
  43. package/packages/ui/dist/assets/external-link-qUc7QuKA.js +8 -0
  44. package/packages/ui/dist/assets/{facebook-BSvrXy3w.js → facebook-BuXatc22.js} +2 -2
  45. package/packages/ui/dist/assets/{farcaster-CcAuzEDi.js → farcaster-USwPlLU8.js} +2 -2
  46. package/packages/ui/dist/assets/{filters-umCx-UFU.js → filters-BWx3pZxN.js} +2 -2
  47. package/packages/ui/dist/assets/{github-CBNoBl67.js → github-CUSWD7ff.js} +2 -2
  48. package/packages/ui/dist/assets/{google-yyR1jNOC.js → google-CxJSLEaz.js} +2 -2
  49. package/packages/ui/dist/assets/{help-circle-C0WjKnIM.js → help-circle-_8isjSO0.js} +1 -1
  50. package/packages/ui/dist/assets/{id-BwIK6e_N.js → id-DJCzMfx_.js} +2 -2
  51. package/packages/ui/dist/assets/{image-D5oAo1Te.js → image-CmG69MZj.js} +2 -2
  52. package/packages/ui/dist/assets/{index-DMNSiu42.js → index-3YqzzyrK.js} +3 -3
  53. package/packages/ui/dist/assets/index-B07bCKJ8.css +1 -0
  54. package/packages/ui/dist/assets/index-BIqjq0q5.js +1 -0
  55. package/packages/ui/dist/assets/index-BV8s6ez1.js +1 -0
  56. package/packages/ui/dist/assets/index-D8zwDy7d.js +1780 -0
  57. package/packages/ui/dist/assets/index-DHVwxjrm.js +47 -0
  58. package/packages/ui/dist/assets/index-DsxB7ORb.js +16 -0
  59. package/packages/ui/dist/assets/index.es-Hqwz_Ukd.js +26 -0
  60. package/packages/ui/dist/assets/{info-CtcvKk8x.js → info-XYctdUoY.js} +2 -2
  61. package/packages/ui/dist/assets/{info-circle-BIm6XYyt.js → info-circle-D_fYZUhv.js} +2 -2
  62. package/packages/ui/dist/assets/{lightbulb-CMj21RIi.js → lightbulb-BqKkDThe.js} +2 -2
  63. package/packages/ui/dist/assets/{mail-C805tcaU.js → mail-CM4laVWT.js} +2 -2
  64. package/packages/ui/dist/assets/metamask-sdk-8TvB8vxr.js +542 -0
  65. package/packages/ui/dist/assets/{mobile-BEii3NyZ.js → mobile-C2cl_MIN.js} +2 -2
  66. package/packages/ui/dist/assets/{more-RSnRt81X.js → more-DOik7ytj.js} +2 -2
  67. package/packages/ui/dist/assets/{network-placeholder-s9JaqzCQ.js → network-placeholder-CC9Cg0q_.js} +2 -2
  68. package/packages/ui/dist/assets/{nftPlaceholder-Ccur2yVc.js → nftPlaceholder-Oe_bTYkM.js} +2 -2
  69. package/packages/ui/dist/assets/{off-DUtG4rlI.js → off-ao0Rdo1E.js} +2 -2
  70. package/packages/ui/dist/assets/{play-store-Bt2HvO3e.js → play-store-BcQlROdR.js} +2 -2
  71. package/packages/ui/dist/assets/{plus-BtEzQu0h.js → plus-DSOprccT.js} +2 -2
  72. package/packages/ui/dist/assets/{qr-code-xbQZ-Nio.js → qr-code-B64-C9CJ.js} +1 -1
  73. package/packages/ui/dist/assets/{recycle-horizontal-d9pzE6vq.js → recycle-horizontal-BJg7qNO9.js} +2 -2
  74. package/packages/ui/dist/assets/{refresh-B3-2Sch0.js → refresh-Dexg1q9p.js} +2 -2
  75. package/packages/ui/dist/assets/{reown-logo-DBkmQAX8.js → reown-logo-D8KP4d24.js} +2 -2
  76. package/packages/ui/dist/assets/{search-dgDA8Ou5.js → search-CBfhIhB0.js} +2 -2
  77. package/packages/ui/dist/assets/{secp256k1-C4g4FCze.js → secp256k1-BOKPNuRR.js} +1 -1
  78. package/packages/ui/dist/assets/{send-CEDXr4l7.js → send-DpJV-P0b.js} +2 -2
  79. package/packages/ui/dist/assets/{swapHorizontal-Co6_f8oH.js → swapHorizontal-DC-Elc2j.js} +2 -2
  80. package/packages/ui/dist/assets/{swapHorizontalBold-k4d22FNy.js → swapHorizontalBold-Unn8xI55.js} +2 -2
  81. package/packages/ui/dist/assets/{swapHorizontalMedium-Des6w_mz.js → swapHorizontalMedium-B58RR8wP.js} +2 -2
  82. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-CVd4538j.js → swapHorizontalRoundedBold-UTrbE6zM.js} +2 -2
  83. package/packages/ui/dist/assets/{swapVertical-Cn_eRfv8.js → swapVertical-CLBtBVG1.js} +2 -2
  84. package/packages/ui/dist/assets/{telegram-BE46RC0s.js → telegram-C_tvCVU8.js} +2 -2
  85. package/packages/ui/dist/assets/{three-dots-CvqWsUWa.js → three-dots-BIn_IFa0.js} +2 -2
  86. package/packages/ui/dist/assets/{twitch-9V7YXB0X.js → twitch-BqiEXRul.js} +2 -2
  87. package/packages/ui/dist/assets/{twitterIcon-DbUxk6yk.js → twitterIcon-CquA5TCG.js} +2 -2
  88. package/packages/ui/dist/assets/{verify-CQSnwVrh.js → verify-DOUX5_SE.js} +2 -2
  89. package/packages/ui/dist/assets/{verify-filled-DG_hDTr8.js → verify-filled-nMk53S_a.js} +2 -2
  90. package/packages/ui/dist/assets/{w3m-modal-BsHGIrg7.js → w3m-modal-B-JVYtbP.js} +1 -1
  91. package/packages/ui/dist/assets/{wallet-lbVIReb_.js → wallet-BjbzMOXA.js} +2 -2
  92. package/packages/ui/dist/assets/{wallet-placeholder-C_ndmKAs.js → wallet-placeholder-DPidGoJt.js} +2 -2
  93. package/packages/ui/dist/assets/{walletconnect-ByDpG-E0.js → walletconnect-Dw14KwDj.js} +3 -3
  94. package/packages/ui/dist/assets/{warning-circle-hrS3II0d.js → warning-circle-Ewxxj9bx.js} +2 -2
  95. package/packages/ui/dist/assets/{x-BPMn3x5J.js → x-B5Li_yME.js} +2 -2
  96. package/packages/ui/dist/index.html +2 -2
  97. package/templates/default/.agents/skills/sail-servers/SKILL.md +1 -0
  98. package/templates/default/.env.example +9 -0
  99. package/packages/ui/dist/assets/arrow-bottom-DpT3R4Rr.js +0 -8
  100. package/packages/ui/dist/assets/arrow-left-3PhiE4SD.js +0 -8
  101. package/packages/ui/dist/assets/arrow-right-DUIXJHib.js +0 -8
  102. package/packages/ui/dist/assets/arrow-top-Cf--02la.js +0 -8
  103. package/packages/ui/dist/assets/ccip-BS_rjbk9.js +0 -1
  104. package/packages/ui/dist/assets/chevron-bottom-C8mqtkD4.js +0 -8
  105. package/packages/ui/dist/assets/chevron-left-Dxj-vVDJ.js +0 -8
  106. package/packages/ui/dist/assets/chevron-right-BoIrOTtL.js +0 -8
  107. package/packages/ui/dist/assets/chevron-top-BlgziB_P.js +0 -8
  108. package/packages/ui/dist/assets/core-BMNH6XFj.js +0 -907
  109. package/packages/ui/dist/assets/cursor-ir0cjMUl.js +0 -3
  110. package/packages/ui/dist/assets/events-Cdt6abbf.js +0 -1
  111. package/packages/ui/dist/assets/external-link-B8uBskz7.js +0 -8
  112. package/packages/ui/dist/assets/fallback-BSMYROva.js +0 -1
  113. package/packages/ui/dist/assets/index-B_Gc8xPp.js +0 -1
  114. package/packages/ui/dist/assets/index-BaEe036r.js +0 -1
  115. package/packages/ui/dist/assets/index-BuqyrXYX.js +0 -16
  116. package/packages/ui/dist/assets/index-CKoOBsKn.css +0 -1
  117. package/packages/ui/dist/assets/index-CPrhuDfP.js +0 -1782
  118. package/packages/ui/dist/assets/index-DCWU1RuS.js +0 -1
  119. package/packages/ui/dist/assets/index.es-DImsg59a.js +0 -26
  120. package/packages/ui/dist/assets/metamask-sdk-DDk4vkrl.js +0 -542
  121. package/packages/ui/dist/assets/native-CJ5et6AR.js +0 -1
  122. package/packages/ui/dist/assets/parseSignature-BbS8pxpB.js +0 -1
package/README.md CHANGED
@@ -295,6 +295,22 @@ the station), set a comma-separated list — the local origin is always allowed:
295
295
  SAILOR_CORS_ORIGINS=https://hermes.example.ts.net,https://another.host
296
296
  ```
297
297
 
298
+ By default the UI server binds `127.0.0.1` (local only). To reach it from another
299
+ device **without** Tailscale — e.g. behind your own reverse proxy on a domain — bind
300
+ all interfaces and throttle the key-management endpoints:
301
+
302
+ ```bash
303
+ SAILOR_HOST=0.0.0.0 # bind all interfaces (default 127.0.0.1)
304
+ SAILOR_TRUST_PROXY=1 # hops to your proxy (or an IP allowlist), so rate limits key on the real client IP
305
+ SAILOR_RATE_LIMIT_PER_MIN=100 # cap /api/signer + /api/onboard/generate-key (default 100)
306
+ ```
307
+
308
+ Behind a reverse proxy, set `SAILOR_TRUST_PROXY` (usually `1`) — otherwise the rate
309
+ limiter sees only the proxy's IP and applies one shared bucket to all clients.
310
+
311
+ The key-management endpoints are **unauthenticated** — only expose them behind your
312
+ own auth (reverse-proxy basic-auth, or a private tailnet), never the open internet.
313
+
298
314
  ---
299
315
 
300
316
  ## Agent-driven onboarding & custom mandates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.sail.money/sailor",
3
- "version": "1.3.0-225",
3
+ "version": "1.3.0-226",
4
4
  "description": "Operator toolkit for Sail Protocol",
5
5
  "bin": {
6
6
  "sailor": "packages/cli/dist/index.cjs"
@@ -42,5 +42,11 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "tsx": "^4.22.4"
45
+ },
46
+ "pnpm": {
47
+ "overrides": {
48
+ "form-data@<4.0.6": ">=4.0.6",
49
+ "hono@<4.12.25": ">=4.12.25"
50
+ }
45
51
  }
46
52
  }
@@ -39629,8 +39629,9 @@ var SigningServer = class {
39629
39629
  }
39630
39630
  }
39631
39631
  if (!(0, import_node_fs6.existsSync)(this.runtimeDir)) (0, import_node_fs6.mkdirSync)(this.runtimeDir, { recursive: true });
39632
+ const serverStatePath = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39632
39633
  (0, import_node_fs6.writeFileSync)(
39633
- (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE),
39634
+ serverStatePath,
39634
39635
  JSON.stringify(
39635
39636
  {
39636
39637
  url: this._url,
@@ -39642,8 +39643,10 @@ var SigningServer = class {
39642
39643
  },
39643
39644
  null,
39644
39645
  2
39645
- )
39646
+ ),
39647
+ { mode: 384 }
39646
39648
  );
39649
+ (0, import_node_fs6.chmodSync)(serverStatePath, 384);
39647
39650
  }
39648
39651
  removeRuntimeState() {
39649
39652
  const path11 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
@@ -46377,6 +46377,8 @@ var init_esm = __esm({
46377
46377
  // ../ui/server.js
46378
46378
  var server_exports = {};
46379
46379
  __export(server_exports, {
46380
+ isHttpUrl: () => isHttpUrl,
46381
+ isSafeEnvValue: () => isSafeEnvValue,
46380
46382
  startServer: () => startServer
46381
46383
  });
46382
46384
  module.exports = __toCommonJS(server_exports);
@@ -47879,6 +47881,41 @@ function persistPassphrase(envPath, passphrase) {
47879
47881
  import_node_fs2.default.writeFileSync(envPath, content, { mode: 384 });
47880
47882
  import_node_fs2.default.chmodSync(envPath, 384);
47881
47883
  }
47884
+ function isSafeEnvValue(v) {
47885
+ return typeof v === "string" && !/[\n\r]/.test(v);
47886
+ }
47887
+ function isHttpUrl(v) {
47888
+ try {
47889
+ const u = new URL(v);
47890
+ return u.protocol === "http:" || u.protocol === "https:";
47891
+ } catch {
47892
+ return false;
47893
+ }
47894
+ }
47895
+ function serverError(res, err) {
47896
+ console.error(err);
47897
+ res.status(500).json({ error: "internal server error" });
47898
+ }
47899
+ function rateLimit({ windowMs, max }) {
47900
+ const hits = /* @__PURE__ */ new Map();
47901
+ let lastSweep = 0;
47902
+ return (req, res, next) => {
47903
+ const now = Date.now();
47904
+ if (now - lastSweep > windowMs) {
47905
+ for (const [k, v] of hits) if (now > v.resetAt) hits.delete(k);
47906
+ lastSweep = now;
47907
+ }
47908
+ const ip = req.ip || req.socket?.remoteAddress || "local";
47909
+ const rec = hits.get(ip);
47910
+ if (!rec || now > rec.resetAt) {
47911
+ hits.set(ip, { count: 1, resetAt: now + windowMs });
47912
+ return next();
47913
+ }
47914
+ if (rec.count >= max) return res.status(429).json({ error: "too many requests" });
47915
+ rec.count += 1;
47916
+ next();
47917
+ };
47918
+ }
47882
47919
  var CHAIN_NAMES = {
47883
47920
  1: "ethereum",
47884
47921
  8453: "base",
@@ -47983,8 +48020,27 @@ function addManagerToList(existing, current, next) {
47983
48020
  var OVERVIEW_TTL_MS = 1e4;
47984
48021
  function startServer(sailDir, { port = PORT } = {}) {
47985
48022
  const app = (0, import_express.default)();
48023
+ const trustProxy = process.env.SAILOR_TRUST_PROXY;
48024
+ if (trustProxy && trustProxy !== "false" && trustProxy !== "0") {
48025
+ const hops = Number(trustProxy);
48026
+ app.set("trust proxy", Number.isFinite(hops) ? hops : trustProxy === "true" ? true : trustProxy);
48027
+ }
47986
48028
  app.use((0, import_cors.default)({ origin: CORS_ORIGINS }));
47987
48029
  app.use(import_express.default.json());
48030
+ const rateLimitPerMin = (() => {
48031
+ const fromEnv = Number(process.env.SAILOR_RATE_LIMIT_PER_MIN);
48032
+ if (Number.isFinite(fromEnv) && fromEnv > 0) return fromEnv;
48033
+ try {
48034
+ const cfg = JSON.parse(import_node_fs2.default.readFileSync(import_node_path.default.join(sailDir, "config.json"), "utf-8"));
48035
+ const fromCfg = Number(cfg?.rateLimitPerMin);
48036
+ if (Number.isFinite(fromCfg) && fromCfg > 0) return fromCfg;
48037
+ } catch {
48038
+ }
48039
+ return 100;
48040
+ })();
48041
+ const keyEndpointLimiter = rateLimit({ windowMs: 6e4, max: rateLimitPerMin });
48042
+ app.use("/api/signer", keyEndpointLimiter);
48043
+ app.use("/api/onboard/generate-key", keyEndpointLimiter);
47988
48044
  const at = (name) => import_node_path.default.join(sailDir, name);
47989
48045
  const syncConfigChainId = (chainId) => {
47990
48046
  if (chainId == null) return;
@@ -48113,7 +48169,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48113
48169
  overviewCacheByAccount.clear();
48114
48170
  res.json({ ok: true });
48115
48171
  } catch (err) {
48116
- res.status(500).json({ error: String(err) });
48172
+ serverError(res, err);
48117
48173
  }
48118
48174
  });
48119
48175
  app.post("/api/account", async (req, res) => {
@@ -48156,7 +48212,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48156
48212
  syncConfigChainId(chainId);
48157
48213
  res.json({ ok: true });
48158
48214
  } catch (err) {
48159
- res.status(500).json({ error: String(err) });
48215
+ serverError(res, err);
48160
48216
  }
48161
48217
  });
48162
48218
  app.get("/api/accounts", (_req, res) => {
@@ -48195,7 +48251,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48195
48251
  syncConfigChainId(target.chainId);
48196
48252
  res.json({ ok: true, active: target });
48197
48253
  } catch (err) {
48198
- res.status(500).json({ error: String(err) });
48254
+ serverError(res, err);
48199
48255
  }
48200
48256
  });
48201
48257
  app.post("/api/account/rename", (req, res) => {
@@ -48204,6 +48260,11 @@ function startServer(sailDir, { port = PORT } = {}) {
48204
48260
  res.status(400).json({ error: "safe and name are required" });
48205
48261
  return;
48206
48262
  }
48263
+ const cleanName = typeof name === "string" ? name.replace(/\p{Cc}/gu, "").trim() : "";
48264
+ if (!cleanName || cleanName.length > 100) {
48265
+ res.status(400).json({ error: "name must be 1\u2013100 characters" });
48266
+ return;
48267
+ }
48207
48268
  try {
48208
48269
  const accountsPath = at("state/accounts.json");
48209
48270
  const accounts = JSON.parse(import_node_fs2.default.readFileSync(accountsPath, "utf-8"));
@@ -48212,12 +48273,12 @@ function startServer(sailDir, { port = PORT } = {}) {
48212
48273
  res.status(404).json({ error: "SMA not found" });
48213
48274
  return;
48214
48275
  }
48215
- accounts[idx] = { ...accounts[idx], name };
48276
+ accounts[idx] = { ...accounts[idx], name: cleanName };
48216
48277
  import_node_fs2.default.writeFileSync(accountsPath, `${JSON.stringify(accounts, null, 2)}
48217
48278
  `);
48218
48279
  res.json({ ok: true });
48219
48280
  } catch (err) {
48220
- res.status(500).json({ error: String(err) });
48281
+ serverError(res, err);
48221
48282
  }
48222
48283
  });
48223
48284
  const readActiveSafe = () => {
@@ -48286,7 +48347,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48286
48347
  `);
48287
48348
  res.json({ ok: true, event });
48288
48349
  } catch (err) {
48289
- res.status(500).json({ error: String(err) });
48350
+ serverError(res, err);
48290
48351
  }
48291
48352
  });
48292
48353
  const managerKeyPath = (safe) => at(`keys/manager-${safe.toLowerCase()}.json`);
@@ -48345,7 +48406,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48345
48406
  invalidateOverviewCache(safe);
48346
48407
  res.json({ ok: true, address: keyring.address, revealed });
48347
48408
  } catch (err) {
48348
- res.status(500).json({ error: String(err) });
48409
+ serverError(res, err);
48349
48410
  }
48350
48411
  });
48351
48412
  app.post("/api/manager/complete", (req, res) => {
@@ -48392,7 +48453,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48392
48453
  invalidateOverviewCache(safe);
48393
48454
  res.json({ ok: true, manager });
48394
48455
  } catch (err) {
48395
- res.status(500).json({ error: String(err) });
48456
+ serverError(res, err);
48396
48457
  }
48397
48458
  });
48398
48459
  const isManagerKeyFile = (file) => file === "manager.json" || file.startsWith("manager-") && file.endsWith(".json") || file.startsWith("managers/") && file.endsWith(".json");
@@ -48445,7 +48506,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48445
48506
  }
48446
48507
  res.json({ signers: [...byAddress.values()] });
48447
48508
  } catch (err) {
48448
- res.status(500).json({ error: String(err) });
48509
+ serverError(res, err);
48449
48510
  }
48450
48511
  });
48451
48512
  app.post("/api/signer/activate", (req, res) => {
@@ -48476,7 +48537,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48476
48537
  invalidateOverviewCache(safe);
48477
48538
  res.json({ ok: true, address: getAddress(address), file: match });
48478
48539
  } catch (err) {
48479
- res.status(500).json({ error: String(err) });
48540
+ serverError(res, err);
48480
48541
  }
48481
48542
  });
48482
48543
  app.get("/api/mandate", (_req, res) => {
@@ -48575,7 +48636,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48575
48636
  }
48576
48637
  }
48577
48638
  } catch (err) {
48578
- res.status(500).json({ error: `On-chain registration failed: ${err?.message ?? String(err)}` });
48639
+ res.status(500).json({ error: `On-chain registration failed: ${(err?.message ?? String(err)).split("\n")[0]}` });
48579
48640
  return;
48580
48641
  }
48581
48642
  const mandate = {
@@ -48606,7 +48667,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48606
48667
  }
48607
48668
  res.json(mandate);
48608
48669
  } catch (err) {
48609
- res.status(500).json({ error: String(err) });
48670
+ serverError(res, err);
48610
48671
  }
48611
48672
  });
48612
48673
  const readPidFile = (filePath) => {
@@ -48822,6 +48883,19 @@ function startServer(sailDir, { port = PORT } = {}) {
48822
48883
  });
48823
48884
  app.post("/api/onboard/save-config", (req, res) => {
48824
48885
  const { rpcUrl, sailApiKey, chainId } = req.body ?? {};
48886
+ const cid = Number(chainId);
48887
+ if (chainId !== void 0 && !CHAIN_RPC_ENV_KEYS[cid]) {
48888
+ res.status(400).json({ error: "unsupported chainId" });
48889
+ return;
48890
+ }
48891
+ if (rpcUrl !== void 0 && (!isSafeEnvValue(rpcUrl) || !isHttpUrl(rpcUrl))) {
48892
+ res.status(400).json({ error: "invalid rpcUrl" });
48893
+ return;
48894
+ }
48895
+ if (sailApiKey !== void 0 && !isSafeEnvValue(sailApiKey)) {
48896
+ res.status(400).json({ error: "invalid sailApiKey" });
48897
+ return;
48898
+ }
48825
48899
  try {
48826
48900
  const config = (() => {
48827
48901
  try {
@@ -48834,20 +48908,20 @@ function startServer(sailDir, { port = PORT } = {}) {
48834
48908
  const envPath = at(".env.local");
48835
48909
  const existing = parseEnvFile(envPath);
48836
48910
  if (rpcUrl && chainId) {
48837
- existing[`RPC_URL_${chainId}`] = rpcUrl;
48838
- if (Number(chainId) === Number(activeChainId)) existing.RPC_URL = rpcUrl;
48911
+ existing[`RPC_URL_${cid}`] = rpcUrl;
48912
+ if (cid === Number(activeChainId)) existing.RPC_URL = rpcUrl;
48839
48913
  } else if (rpcUrl) {
48840
48914
  existing.RPC_URL = rpcUrl;
48841
48915
  }
48842
48916
  if (sailApiKey) existing.SAIL_API_KEY = sailApiKey;
48843
- if (chainId) existing.CHAIN_ID = String(chainId);
48917
+ if (chainId) existing.CHAIN_ID = String(cid);
48844
48918
  const content = Object.entries(existing).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
48845
48919
  import_node_fs2.default.mkdirSync(sailDir, { recursive: true });
48846
48920
  import_node_fs2.default.writeFileSync(envPath, content, { mode: 384 });
48847
48921
  import_node_fs2.default.chmodSync(envPath, 384);
48848
48922
  res.json({ ok: true });
48849
48923
  } catch (err) {
48850
- res.status(500).json({ error: String(err) });
48924
+ serverError(res, err);
48851
48925
  }
48852
48926
  });
48853
48927
  app.post("/api/onboard/generate-key", async (req, res) => {
@@ -48876,7 +48950,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48876
48950
  persistPassphrase(at(".env.local"), passphrase);
48877
48951
  res.json({ address: keyring.address, existed: false });
48878
48952
  } catch (err) {
48879
- res.status(500).json({ error: String(err) });
48953
+ serverError(res, err);
48880
48954
  }
48881
48955
  });
48882
48956
  app.post("/api/onboard/build-create-tx", (req, res) => {
@@ -48928,7 +49002,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48928
49002
  });
48929
49003
  res.json({ to: kernel, data, chainId, saltNonce: saltNonce.toString() });
48930
49004
  } catch (err) {
48931
- res.status(500).json({ error: String(err) });
49005
+ serverError(res, err);
48932
49006
  }
48933
49007
  });
48934
49008
  app.post("/api/onboard/build-register-path", (req, res) => {
@@ -48982,7 +49056,7 @@ function startServer(sailDir, { port = PORT } = {}) {
48982
49056
  saltNonce: saltNonce.toString()
48983
49057
  });
48984
49058
  } catch (err) {
48985
- res.status(500).json({ error: String(err) });
49059
+ serverError(res, err);
48986
49060
  }
48987
49061
  });
48988
49062
  app.post("/api/onboard/complete", async (req, res) => {
@@ -49050,7 +49124,7 @@ function startServer(sailDir, { port = PORT } = {}) {
49050
49124
  syncConfigChainId(chainId);
49051
49125
  res.json({ ok: true, account: record });
49052
49126
  } catch (err) {
49053
- res.status(500).json({ error: String(err) });
49127
+ serverError(res, err);
49054
49128
  }
49055
49129
  });
49056
49130
  app.get("/api/wizard-state", (_req, res) => {
@@ -49062,13 +49136,18 @@ function startServer(sailDir, { port = PORT } = {}) {
49062
49136
  }
49063
49137
  });
49064
49138
  app.post("/api/wizard-state", (req, res) => {
49139
+ const body = req.body;
49140
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
49141
+ res.status(400).json({ error: "wizard state must be an object" });
49142
+ return;
49143
+ }
49065
49144
  try {
49066
49145
  import_node_fs2.default.mkdirSync(sailDir, { recursive: true });
49067
- import_node_fs2.default.writeFileSync(at(".wizard-state.json"), `${JSON.stringify(req.body, null, 2)}
49146
+ import_node_fs2.default.writeFileSync(at(".wizard-state.json"), `${JSON.stringify(body, null, 2)}
49068
49147
  `);
49069
49148
  res.json({ ok: true });
49070
49149
  } catch (err) {
49071
- res.status(500).json({ error: String(err) });
49150
+ serverError(res, err);
49072
49151
  }
49073
49152
  });
49074
49153
  app.get("/api/positions", (_req, res) => {
@@ -49348,7 +49427,8 @@ function startServer(sailDir, { port = PORT } = {}) {
49348
49427
  res.sendFile(import_node_path.default.join(distDir, "index.html"));
49349
49428
  });
49350
49429
  }
49351
- const httpServer = app.listen(port, () => {
49430
+ const bindHost = process.env.SAILOR_HOST ?? "127.0.0.1";
49431
+ const httpServer = app.listen(port, bindHost, () => {
49352
49432
  console.log(`Sailor UI running at http://localhost:${port} (reading ${sailDir})`);
49353
49433
  });
49354
49434
  const wss = new import_websocket_server.default({ noServer: true });
@@ -49427,6 +49507,8 @@ if (isMain) {
49427
49507
  }
49428
49508
  // Annotate the CommonJS export names for ESM import in node:
49429
49509
  0 && (module.exports = {
49510
+ isHttpUrl,
49511
+ isSafeEnvValue,
49430
49512
  startServer
49431
49513
  });
49432
49514
  /*! Bundled license information:
@@ -5,7 +5,7 @@
5
5
  * Do not edit manually — run `pnpm build` to regenerate.
6
6
  *
7
7
  * Spec version : 1.2.1
8
- * Generated at : 2026-07-02T13:00:44.579Z
8
+ * Generated at : 2026-07-02T15:07:10.282Z
9
9
  */
10
10
  export declare const SAIL_INTELLIGENCE_BASE_URL = "https://api.sail.money";
11
11
  export declare const SAIL_INTELLIGENCE_DOCS_URL = "https://api.sail.money/docs";
@@ -5,7 +5,7 @@
5
5
  * Do not edit manually — run `pnpm build` to regenerate.
6
6
  *
7
7
  * Spec version : 1.2.1
8
- * Generated at : 2026-07-02T13:00:44.579Z
8
+ * Generated at : 2026-07-02T15:07:10.282Z
9
9
  */
10
10
  export const SAIL_INTELLIGENCE_BASE_URL = "https://api.sail.money";
11
11
  export const SAIL_INTELLIGENCE_DOCS_URL = "https://api.sail.money/docs";
@@ -1,4 +1,4 @@
1
- import{F as o}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./events-Cdt6abbf.js";import"./index.es-DImsg59a.js";import"./fallback-BSMYROva.js";const d=o`<svg
1
+ import{F as o}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const e=o`<svg
2
2
  width="14"
3
3
  height="14"
4
4
  viewBox="0 0 14 14"
@@ -12,4 +12,4 @@ import{F as o}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./even
12
12
  d="M7.0023 0.875C7.48571 0.875 7.8776 1.26675 7.8776 1.75V6.125H12.2541C12.7375 6.125 13.1294 6.51675 13.1294 7C13.1294 7.48325 12.7375 7.875 12.2541 7.875H7.8776V12.25C7.8776 12.7332 7.48571 13.125 7.0023 13.125C6.51889 13.125 6.12701 12.7332 6.12701 12.25V7.875H1.75054C1.26713 7.875 0.875244 7.48325 0.875244 7C0.875244 6.51675 1.26713 6.125 1.75054 6.125H6.12701V1.75C6.12701 1.26675 6.51889 0.875 7.0023 0.875Z"
13
13
  fill="#667dff"
14
14
  /></svg
15
- >`;export{d as addSvg};
15
+ >`;export{e as addSvg};
@@ -1,6 +1,6 @@
1
- import{F as o}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./events-Cdt6abbf.js";import"./index.es-DImsg59a.js";import"./fallback-BSMYROva.js";const p=o`<svg fill="none" viewBox="0 0 24 24">
1
+ import{F as o}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const r=o`<svg fill="none" viewBox="0 0 24 24">
2
2
  <path
3
3
  style="fill: var(--wui-color-accent-100);"
4
4
  d="M10.2 6.6a3.6 3.6 0 1 1-7.2 0 3.6 3.6 0 0 1 7.2 0ZM21 6.6a3.6 3.6 0 1 1-7.2 0 3.6 3.6 0 0 1 7.2 0ZM10.2 17.4a3.6 3.6 0 1 1-7.2 0 3.6 3.6 0 0 1 7.2 0ZM21 17.4a3.6 3.6 0 1 1-7.2 0 3.6 3.6 0 0 1 7.2 0Z"
5
5
  />
6
- </svg>`;export{p as allWalletsSvg};
6
+ </svg>`;export{r as allWalletsSvg};
@@ -1,4 +1,4 @@
1
- import{F as t}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./events-Cdt6abbf.js";import"./index.es-DImsg59a.js";import"./fallback-BSMYROva.js";const e=t`
1
+ import{F as t}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const e=t`
2
2
  <svg width="36" height="36">
3
3
  <path
4
4
  d="M28.724 0H7.271A7.269 7.269 0 0 0 0 7.272v21.46A7.268 7.268 0 0 0 7.271 36H28.73A7.272 7.272 0 0 0 36 28.728V7.272A7.275 7.275 0 0 0 28.724 0Z"
@@ -1,4 +1,4 @@
1
- import{F as i}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./events-Cdt6abbf.js";import"./index.es-DImsg59a.js";import"./fallback-BSMYROva.js";const h=i`<svg fill="none" viewBox="0 0 40 40">
1
+ import{F as i}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const c=i`<svg fill="none" viewBox="0 0 40 40">
2
2
  <g clip-path="url(#a)">
3
3
  <g clip-path="url(#b)">
4
4
  <circle cx="20" cy="19.89" r="20" fill="#000" />
@@ -15,4 +15,4 @@ import{F as i}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./even
15
15
  <clipPath id="b"><path fill="#fff" d="M0 0h40v40H0z" /></clipPath>
16
16
  <clipPath id="c"><path fill="#fff" d="M8 7.89h24v24H8z" /></clipPath>
17
17
  </defs>
18
- </svg>`;export{h as appleSvg};
18
+ </svg>`;export{c as appleSvg};
@@ -0,0 +1,8 @@
1
+ import{F as o}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const i=o`<svg fill="none" viewBox="0 0 14 15">
2
+ <path
3
+ fill="currentColor"
4
+ fill-rule="evenodd"
5
+ d="M7 1.99a1 1 0 0 1 1 1v7.58l2.46-2.46a1 1 0 0 1 1.41 1.42L7.7 13.69a1 1 0 0 1-1.41 0L2.12 9.53A1 1 0 0 1 3.54 8.1L6 10.57V3a1 1 0 0 1 1-1Z"
6
+ clip-rule="evenodd"
7
+ />
8
+ </svg>`;export{i as arrowBottomSvg};
@@ -1,4 +1,4 @@
1
- import{F as o}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./events-Cdt6abbf.js";import"./index.es-DImsg59a.js";import"./fallback-BSMYROva.js";const t=o`<svg
1
+ import{F as C}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const i=C`<svg
2
2
  fill="none"
3
3
  viewBox="0 0 21 20"
4
4
  >
@@ -8,4 +8,4 @@ import{F as o}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./even
8
8
  d="M10.5 2.42908C6.31875 2.42908 2.92859 5.81989 2.92859 10.0034C2.92859 14.1869 6.31875 17.5777 10.5 17.5777C14.6813 17.5777 18.0714 14.1869 18.0714 10.0034C18.0714 5.81989 14.6813 2.42908 10.5 2.42908ZM0.928589 10.0034C0.928589 4.71596 5.21355 0.429077 10.5 0.429077C15.7865 0.429077 20.0714 4.71596 20.0714 10.0034C20.0714 15.2908 15.7865 19.5777 10.5 19.5777C5.21355 19.5777 0.928589 15.2908 0.928589 10.0034ZM10.5 5.75003C11.0523 5.75003 11.5 6.19774 11.5 6.75003L11.5 10.8343L12.7929 9.54137C13.1834 9.15085 13.8166 9.15085 14.2071 9.54137C14.5976 9.9319 14.5976 10.5651 14.2071 10.9556L11.2071 13.9556C10.8166 14.3461 10.1834 14.3461 9.79291 13.9556L6.79291 10.9556C6.40239 10.5651 6.40239 9.9319 6.79291 9.54137C7.18343 9.15085 7.8166 9.15085 8.20712 9.54137L9.50002 10.8343L9.50002 6.75003C9.50002 6.19774 9.94773 5.75003 10.5 5.75003Z"
9
9
  clip-rule="evenodd"
10
10
  /></svg
11
- >`;export{t as arrowBottomCircleSvg};
11
+ >`;export{i as arrowBottomCircleSvg};
@@ -0,0 +1,8 @@
1
+ import{F as o}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const t=o`<svg fill="none" viewBox="0 0 14 15">
2
+ <path
3
+ fill="currentColor"
4
+ fill-rule="evenodd"
5
+ d="M13 7.99a1 1 0 0 1-1 1H4.4l2.46 2.46a1 1 0 1 1-1.41 1.41L1.29 8.7a1 1 0 0 1 0-1.41L5.46 3.1a1 1 0 0 1 1.41 1.42L4.41 6.99H12a1 1 0 0 1 1 1Z"
6
+ clip-rule="evenodd"
7
+ />
8
+ </svg>`;export{t as arrowLeftSvg};
@@ -0,0 +1,8 @@
1
+ import{F as o}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const t=o`<svg fill="none" viewBox="0 0 14 15">
2
+ <path
3
+ fill="currentColor"
4
+ fill-rule="evenodd"
5
+ d="M1 7.99a1 1 0 0 1 1-1h7.58L7.12 4.53A1 1 0 1 1 8.54 3.1l4.16 4.17a1 1 0 0 1 0 1.41l-4.16 4.17a1 1 0 1 1-1.42-1.41l2.46-2.46H2a1 1 0 0 1-1-1Z"
6
+ clip-rule="evenodd"
7
+ />
8
+ </svg>`;export{t as arrowRightSvg};
@@ -0,0 +1,8 @@
1
+ import{F as o}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const a=o`<svg fill="none" viewBox="0 0 14 15">
2
+ <path
3
+ fill="currentColor"
4
+ fill-rule="evenodd"
5
+ d="M7 13.99a1 1 0 0 1-1-1V5.4L3.54 7.86a1 1 0 0 1-1.42-1.41L6.3 2.28a1 1 0 0 1 1.41 0l4.17 4.17a1 1 0 1 1-1.41 1.41L8 5.4v7.59a1 1 0 0 1-1 1Z"
6
+ clip-rule="evenodd"
7
+ />
8
+ </svg>`;export{a as arrowTopSvg};
@@ -1,4 +1,4 @@
1
- import{F as o}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./events-Cdt6abbf.js";import"./index.es-DImsg59a.js";import"./fallback-BSMYROva.js";const H=o`<svg
1
+ import{F as o}from"./core-BLOM5s8O.js";import"./index-D8zwDy7d.js";import"./index.es-Hqwz_Ukd.js";import"./events-DQ172AOg.js";const l=o`<svg
2
2
  xmlns="http://www.w3.org/2000/svg"
3
3
  width="12"
4
4
  height="13"
@@ -11,4 +11,4 @@ import{F as o}from"./core-BMNH6XFj.js";import"./index-CPrhuDfP.js";import"./even
11
11
  d="M5.61391 1.57124C5.85142 1.42873 6.14813 1.42873 6.38564 1.57124L11.0793 4.38749C11.9179 4.89067 11.5612 6.17864 10.5832 6.17864H9.96398V10.0358H10.2854C10.6996 10.0358 11.0354 10.3716 11.0354 10.7858C11.0354 11.2 10.6996 11.5358 10.2854 11.5358H1.71416C1.29995 11.5358 0.964172 11.2 0.964172 10.7858C0.964172 10.3716 1.29995 10.0358 1.71416 10.0358H2.03558L2.03558 6.17864H1.41637C0.438389 6.17864 0.0816547 4.89066 0.920263 4.38749L5.61391 1.57124ZM3.53554 6.17864V10.0358H5.24979V6.17864H3.53554ZM6.74976 6.17864V10.0358H8.46401V6.17864H6.74976ZM8.64913 4.67864H3.35043L5.99978 3.089L8.64913 4.67864Z"
12
12
  fill="currentColor"
13
13
  /></svg
14
- >`;export{H as bankSvg};
14
+ >`;export{l as bankSvg};