dexterm-cli 0.0.0-stage → 0.1.1

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 (349) hide show
  1. package/LICENSE +661 -0
  2. package/dist/packages/agents/src/audit.js +122 -0
  3. package/dist/packages/agents/src/bus.js +379 -0
  4. package/dist/packages/agents/src/executor.js +177 -0
  5. package/dist/packages/agents/src/gate.js +176 -0
  6. package/dist/packages/agents/src/identity.js +179 -0
  7. package/dist/packages/agents/src/index.js +15 -0
  8. package/dist/packages/agents/src/proposals.js +153 -0
  9. package/dist/packages/agents/src/schema.js +87 -0
  10. package/dist/packages/agents/src/sdk.js +140 -0
  11. package/dist/packages/agents/src/tiers.js +114 -0
  12. package/dist/packages/agents/src/tools.js +69 -0
  13. package/dist/packages/agents/src/ws.js +158 -0
  14. package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
  15. package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
  16. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
  17. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +67 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -0
  19. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
  20. package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
  21. package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
  22. package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
  23. package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
  24. package/dist/packages/cli/bin/dexterm.mjs +11 -0
  25. package/dist/packages/cli/src/agent-cmd.js +303 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/analyst/backend.js +526 -0
  28. package/dist/packages/cli/src/analyst/results.js +38 -0
  29. package/dist/packages/cli/src/analyst/session.js +319 -0
  30. package/dist/packages/cli/src/audit.js +51 -0
  31. package/dist/packages/cli/src/backtest-cmd.js +149 -0
  32. package/dist/packages/cli/src/boot-state.js +62 -0
  33. package/dist/packages/cli/src/custom.js +261 -0
  34. package/dist/packages/cli/src/display.js +191 -0
  35. package/dist/packages/cli/src/doctor.js +361 -0
  36. package/dist/packages/cli/src/feed.js +634 -0
  37. package/dist/packages/cli/src/fixtures.js +275 -0
  38. package/dist/packages/cli/src/follow-cmd.js +221 -0
  39. package/dist/packages/cli/src/images-config.js +59 -0
  40. package/dist/packages/cli/src/index.js +31 -0
  41. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  42. package/dist/packages/cli/src/larp-record.js +21 -0
  43. package/dist/packages/cli/src/live/copy.js +80 -0
  44. package/dist/packages/cli/src/live/flow.js +233 -0
  45. package/dist/packages/cli/src/live/index.js +509 -0
  46. package/dist/packages/cli/src/live/intel.js +227 -0
  47. package/dist/packages/cli/src/live/newpairs.js +334 -0
  48. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  49. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  50. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  51. package/dist/packages/cli/src/live/token.js +585 -0
  52. package/dist/packages/cli/src/live/trade.js +929 -0
  53. package/dist/packages/cli/src/live/watchlist.js +178 -0
  54. package/dist/packages/cli/src/main.js +5583 -0
  55. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  56. package/dist/packages/cli/src/pack-cmd.js +323 -0
  57. package/dist/packages/cli/src/plan-cmd.js +278 -0
  58. package/dist/packages/cli/src/popup.js +810 -0
  59. package/dist/packages/cli/src/rules-cmd.js +131 -0
  60. package/dist/packages/cli/src/rules.js +339 -0
  61. package/dist/packages/cli/src/scan/command.js +57 -0
  62. package/dist/packages/cli/src/scan/engine.js +419 -0
  63. package/dist/packages/cli/src/scan/observe.js +64 -0
  64. package/dist/packages/cli/src/scan/ui.js +98 -0
  65. package/dist/packages/cli/src/screener/filter.js +241 -0
  66. package/dist/packages/cli/src/screener/presets.js +80 -0
  67. package/dist/packages/cli/src/screener/rows.js +146 -0
  68. package/dist/packages/cli/src/screener/rug.js +50 -0
  69. package/dist/packages/cli/src/screener/sort.js +52 -0
  70. package/dist/packages/cli/src/screener/view.js +183 -0
  71. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  72. package/dist/packages/cli/src/screens/analyst.js +326 -0
  73. package/dist/packages/cli/src/screens/boot.js +74 -0
  74. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  75. package/dist/packages/cli/src/screens/coin.js +33 -0
  76. package/dist/packages/cli/src/screens/follows.js +201 -0
  77. package/dist/packages/cli/src/screens/kol.js +90 -0
  78. package/dist/packages/cli/src/screens/narrative.js +90 -0
  79. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  80. package/dist/packages/cli/src/screens/overlay.js +257 -0
  81. package/dist/packages/cli/src/screens/plans.js +126 -0
  82. package/dist/packages/cli/src/screens/positions.js +176 -0
  83. package/dist/packages/cli/src/screens/rules.js +170 -0
  84. package/dist/packages/cli/src/screens/screener.js +459 -0
  85. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  86. package/dist/packages/cli/src/screens/ticket.js +342 -0
  87. package/dist/packages/cli/src/screens/token.js +312 -0
  88. package/dist/packages/cli/src/screens/trending.js +181 -0
  89. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  90. package/dist/packages/cli/src/social/demo.js +96 -0
  91. package/dist/packages/cli/src/social/view.js +250 -0
  92. package/dist/packages/cli/src/state.js +463 -0
  93. package/dist/packages/cli/src/theme-cmd.js +126 -0
  94. package/dist/packages/cli/src/trade.js +319 -0
  95. package/dist/packages/cli/src/voice-hud.js +42 -0
  96. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  97. package/dist/packages/cli/src/wallet-command.js +31 -0
  98. package/dist/packages/cli/src/widgets.js +208 -0
  99. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  100. package/dist/packages/cli/src/x-cmd.js +154 -0
  101. package/dist/packages/custom/src/config.js +213 -0
  102. package/dist/packages/custom/src/index.js +10 -0
  103. package/dist/packages/custom/src/keymap.js +107 -0
  104. package/dist/packages/custom/src/layout.js +235 -0
  105. package/dist/packages/custom/src/packs.js +633 -0
  106. package/dist/packages/custom/src/rules.js +704 -0
  107. package/dist/packages/custom/src/schema.js +515 -0
  108. package/dist/packages/custom/src/themes.js +176 -0
  109. package/dist/packages/custom/src/widget-html.js +222 -0
  110. package/dist/packages/custom/src/workspace.js +141 -0
  111. package/dist/packages/data/src/analyst-actions.js +133 -0
  112. package/dist/packages/data/src/budget.js +88 -0
  113. package/dist/packages/data/src/cache.js +142 -0
  114. package/dist/packages/data/src/candles.js +168 -0
  115. package/dist/packages/data/src/clock.js +64 -0
  116. package/dist/packages/data/src/credits.js +137 -0
  117. package/dist/packages/data/src/detail.js +97 -0
  118. package/dist/packages/data/src/fast-lane.js +321 -0
  119. package/dist/packages/data/src/fixtures.js +135 -0
  120. package/dist/packages/data/src/health.js +245 -0
  121. package/dist/packages/data/src/http.js +277 -0
  122. package/dist/packages/data/src/images/cache.js +141 -0
  123. package/dist/packages/data/src/images/decode-worker.js +28 -0
  124. package/dist/packages/data/src/images/decode.js +81 -0
  125. package/dist/packages/data/src/images/fetch.js +162 -0
  126. package/dist/packages/data/src/images/gif.js +214 -0
  127. package/dist/packages/data/src/images/index.js +10 -0
  128. package/dist/packages/data/src/images/jpeg.js +544 -0
  129. package/dist/packages/data/src/images/png.js +310 -0
  130. package/dist/packages/data/src/images/service.js +334 -0
  131. package/dist/packages/data/src/images/types.js +27 -0
  132. package/dist/packages/data/src/index.js +208 -0
  133. package/dist/packages/data/src/intel/helius-history.js +59 -0
  134. package/dist/packages/data/src/intel/holder-class.js +125 -0
  135. package/dist/packages/data/src/intel/rules.js +421 -0
  136. package/dist/packages/data/src/intel/scan.js +170 -0
  137. package/dist/packages/data/src/larp/evidence.js +288 -0
  138. package/dist/packages/data/src/larp/links.js +137 -0
  139. package/dist/packages/data/src/larp/robots.js +90 -0
  140. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  141. package/dist/packages/data/src/limiter.js +341 -0
  142. package/dist/packages/data/src/model.js +12 -0
  143. package/dist/packages/data/src/paper.js +331 -0
  144. package/dist/packages/data/src/rpc-config.js +126 -0
  145. package/dist/packages/data/src/rpc-pool.js +166 -0
  146. package/dist/packages/data/src/social/chain.js +83 -0
  147. package/dist/packages/data/src/social/dedupe.js +205 -0
  148. package/dist/packages/data/src/social/feed.js +462 -0
  149. package/dist/packages/data/src/social/firewall.js +110 -0
  150. package/dist/packages/data/src/social/index.js +11 -0
  151. package/dist/packages/data/src/social/kol.js +269 -0
  152. package/dist/packages/data/src/social/model.js +11 -0
  153. package/dist/packages/data/src/social/rank.js +184 -0
  154. package/dist/packages/data/src/social/rss.js +113 -0
  155. package/dist/packages/data/src/social/summary.js +104 -0
  156. package/dist/packages/data/src/social/venues.js +186 -0
  157. package/dist/packages/data/src/sources/dexscreener.js +170 -0
  158. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  159. package/dist/packages/data/src/sources/goplus.js +161 -0
  160. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  161. package/dist/packages/data/src/sources/jupiter.js +198 -0
  162. package/dist/packages/data/src/sources/pda.js +113 -0
  163. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  164. package/dist/packages/data/src/sources/pumpportal.js +247 -0
  165. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  166. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  167. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  168. package/dist/packages/data/src/store.js +289 -0
  169. package/dist/packages/data/src/trade/bridge.js +464 -0
  170. package/dist/packages/data/src/trade/caps.js +323 -0
  171. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  172. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  173. package/dist/packages/data/src/trade/copy/index.js +9 -0
  174. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  175. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  176. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  177. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  178. package/dist/packages/data/src/trade/copy/types.js +6 -0
  179. package/dist/packages/data/src/trade/executor.js +933 -0
  180. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  181. package/dist/packages/data/src/trade/link-store.js +52 -0
  182. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  183. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  184. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  185. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  186. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  187. package/dist/packages/data/src/trade/plans/index.js +12 -0
  188. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  189. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  190. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  191. package/dist/packages/data/src/trade/plans/service.js +168 -0
  192. package/dist/packages/data/src/trade/plans/types.js +6 -0
  193. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  194. package/dist/packages/data/src/trade/signer/client.js +197 -0
  195. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  196. package/dist/packages/data/src/trade/signer/host.js +97 -0
  197. package/dist/packages/data/src/trade/signer/main.js +121 -0
  198. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  199. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  200. package/dist/packages/data/src/trade/signer/service.js +400 -0
  201. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  202. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  203. package/dist/packages/data/src/trade/swap-build.js +108 -0
  204. package/dist/packages/data/src/trade/token-delta.js +201 -0
  205. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  206. package/dist/packages/data/src/util.js +24 -0
  207. package/dist/packages/data/src/watchlist.js +176 -0
  208. package/dist/packages/data/src/x/api.js +256 -0
  209. package/dist/packages/data/src/x/budget.js +66 -0
  210. package/dist/packages/data/src/x/config.js +49 -0
  211. package/dist/packages/data/src/x/index.js +10 -0
  212. package/dist/packages/data/src/x/mock-server.js +217 -0
  213. package/dist/packages/data/src/x/oauth.js +242 -0
  214. package/dist/packages/data/src/x/pkce.js +21 -0
  215. package/dist/packages/data/src/x/source.js +285 -0
  216. package/dist/packages/data/src/x/token-store.js +134 -0
  217. package/dist/packages/data/src/x/transport.js +71 -0
  218. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  219. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  220. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  221. package/dist/packages/mcp/assets/cases.json +1194 -0
  222. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  223. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  224. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  225. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  226. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  227. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  228. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  229. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  230. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  231. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  232. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  233. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  234. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  235. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  236. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  237. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  238. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  239. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  240. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  241. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  242. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  243. package/dist/packages/mcp/src/dossier.js +78 -0
  244. package/dist/packages/mcp/src/firewall.js +21 -0
  245. package/dist/packages/mcp/src/index.js +22 -0
  246. package/dist/packages/mcp/src/server.js +849 -0
  247. package/dist/packages/mcp/src/user-content.js +434 -0
  248. package/dist/packages/packs/src/backtest.js +183 -0
  249. package/dist/packages/packs/src/digest.js +95 -0
  250. package/dist/packages/packs/src/firewall.js +57 -0
  251. package/dist/packages/packs/src/index.js +19 -0
  252. package/dist/packages/packs/src/larp-eval.js +100 -0
  253. package/dist/packages/packs/src/narrative.js +309 -0
  254. package/dist/packages/packs/src/reason.js +135 -0
  255. package/dist/packages/packs/src/rug-model.js +137 -0
  256. package/dist/packages/packs/src/schema.js +54 -0
  257. package/dist/packages/packs/src/token-dossier.js +174 -0
  258. package/dist/packages/packs/src/verdicts.js +17 -0
  259. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  260. package/dist/packages/popup/src/config.js +71 -0
  261. package/dist/packages/popup/src/dock.js +271 -0
  262. package/dist/packages/popup/src/host.js +210 -0
  263. package/dist/packages/popup/src/index.js +12 -0
  264. package/dist/packages/popup/src/loopback.js +53 -0
  265. package/dist/packages/popup/src/open.js +183 -0
  266. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  267. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  268. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  269. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  270. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  271. package/dist/packages/popup/src/pages/chart.css +50 -0
  272. package/dist/packages/popup/src/pages/chart.html +56 -0
  273. package/dist/packages/popup/src/pages/chart.js +554 -0
  274. package/dist/packages/popup/src/pages/common.css +34 -0
  275. package/dist/packages/popup/src/pages/common.js +187 -0
  276. package/dist/packages/popup/src/pages/feeds.html +20 -0
  277. package/dist/packages/popup/src/pages/feeds.js +77 -0
  278. package/dist/packages/popup/src/pages/narrative.html +20 -0
  279. package/dist/packages/popup/src/pages/narrative.js +61 -0
  280. package/dist/packages/popup/src/pages/plans.css +30 -0
  281. package/dist/packages/popup/src/pages/plans.html +26 -0
  282. package/dist/packages/popup/src/pages/plans.js +82 -0
  283. package/dist/packages/popup/src/pages/roster.css +41 -0
  284. package/dist/packages/popup/src/pages/roster.html +25 -0
  285. package/dist/packages/popup/src/pages/roster.js +75 -0
  286. package/dist/packages/popup/src/pages/token.html +20 -0
  287. package/dist/packages/popup/src/pages/token.js +112 -0
  288. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  289. package/dist/packages/popup/src/pages/widget.css +5 -0
  290. package/dist/packages/popup/src/pages/widget.html +21 -0
  291. package/dist/packages/popup/src/pages/widget.js +85 -0
  292. package/dist/packages/popup/src/protocol.js +31 -0
  293. package/dist/packages/popup/src/selftest.js +156 -0
  294. package/dist/packages/popup/src/server.js +582 -0
  295. package/dist/packages/popup/src/sinkproxy.js +167 -0
  296. package/dist/packages/popup/src/widget-html.js +231 -0
  297. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  298. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  299. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  300. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  301. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  302. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  303. package/dist/packages/recorder/src/cli.js +113 -0
  304. package/dist/packages/recorder/src/export.js +71 -0
  305. package/dist/packages/recorder/src/index.js +20 -0
  306. package/dist/packages/recorder/src/labels.js +151 -0
  307. package/dist/packages/recorder/src/open.js +29 -0
  308. package/dist/packages/recorder/src/recorder.js +553 -0
  309. package/dist/packages/recorder/src/schedule.js +43 -0
  310. package/dist/packages/recorder/src/schema.js +135 -0
  311. package/dist/packages/recorder/src/snapshot.js +191 -0
  312. package/dist/packages/recorder/src/sweep.js +52 -0
  313. package/dist/packages/recorder/src/verdicts.js +52 -0
  314. package/dist/packages/tui/src/bubbles.js +415 -0
  315. package/dist/packages/tui/src/canvas.js +370 -0
  316. package/dist/packages/tui/src/chrome.js +85 -0
  317. package/dist/packages/tui/src/color.js +133 -0
  318. package/dist/packages/tui/src/flush.js +62 -0
  319. package/dist/packages/tui/src/fmt.js +188 -0
  320. package/dist/packages/tui/src/fx-trade.js +278 -0
  321. package/dist/packages/tui/src/fx.js +527 -0
  322. package/dist/packages/tui/src/images/blocks.js +141 -0
  323. package/dist/packages/tui/src/images/coin.js +22 -0
  324. package/dist/packages/tui/src/images/detect.js +140 -0
  325. package/dist/packages/tui/src/images/index.js +7 -0
  326. package/dist/packages/tui/src/images/layer.js +146 -0
  327. package/dist/packages/tui/src/images/pixels.js +157 -0
  328. package/dist/packages/tui/src/images/protocols.js +87 -0
  329. package/dist/packages/tui/src/index.js +15 -0
  330. package/dist/packages/tui/src/keymap.js +355 -0
  331. package/dist/packages/tui/src/layout.js +11 -0
  332. package/dist/packages/tui/src/sound.js +177 -0
  333. package/dist/packages/tui/src/theme.js +182 -0
  334. package/dist/packages/tui/src/widgets.js +598 -0
  335. package/dist/packages/voice/src/controller.js +395 -0
  336. package/dist/packages/voice/src/doctor.js +63 -0
  337. package/dist/packages/voice/src/engines.js +44 -0
  338. package/dist/packages/voice/src/index.js +16 -0
  339. package/dist/packages/voice/src/router.js +502 -0
  340. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  341. package/dist/packages/voice/src/stt/apple.js +179 -0
  342. package/dist/packages/voice/src/stt/fake.js +34 -0
  343. package/dist/packages/voice/src/stt/whisper.js +102 -0
  344. package/dist/packages/voice/src/summary.js +44 -0
  345. package/dist/packages/voice/src/tts/fake.js +30 -0
  346. package/dist/packages/voice/src/tts/say.js +96 -0
  347. package/dist/packages/voice/src/types.js +3 -0
  348. package/package.json +42 -4
  349. package/README.md +0 -3
@@ -0,0 +1,86 @@
1
+ // dexterm T1 — the SOL/USD price the signer prices a trade with (task-1310 round 2, HIGH #2).
2
+ //
3
+ // The USD caps are only as good as the SOL/USD number behind them: a stale or absurd price (say
4
+ // $0.0001) makes a 100 SOL trade look like a cent. The executor therefore never takes a bare number.
5
+ // It takes timestamped SAMPLES from named sources and resolves them here, refusing when:
6
+ // * a sample is older than MAX_AGE_MS (60 s) or dated in the future;
7
+ // * a price is outside the plausibility band [MIN_PLAUSIBLE_USD, MAX_PLAUSIBLE_USD] ($1–$10,000);
8
+ // * on MAINNET, fewer than two INDEPENDENT non-Jupiter sources agree within AGREE_PCT (3%). Jupiter
9
+ // is the swap vendor, so its price API is not an independent check of a Jupiter route and can
10
+ // never be the only source.
11
+ // The resolved price is the HIGHEST of the agreeing sources: that over-states the notional slightly,
12
+ // which is the conservative direction for a cap. Pure; no I/O.
13
+ export const SOL_USD_MAX_AGE_MS = 60_000;
14
+ export const SOL_USD_MIN_PLAUSIBLE = 1;
15
+ export const SOL_USD_MAX_PLAUSIBLE = 10_000;
16
+ export const SOL_USD_AGREE_PCT = 3;
17
+ /** Sources that are the swap vendor itself (or a re-export of it) and so count as dependent. */
18
+ export const SOL_USD_DEPENDENT_SOURCES = new Set(['jupiter', 'jupiter-swap', 'jupiter-price', 'jup']);
19
+ function isSample(x) {
20
+ if (x === null || typeof x !== 'object')
21
+ return false;
22
+ const s = x;
23
+ return typeof s.price === 'number' && typeof s.fetchedAt === 'number' && typeof s.source === 'string';
24
+ }
25
+ function dependent(source) {
26
+ const s = source.toLowerCase();
27
+ return SOL_USD_DEPENDENT_SOURCES.has(s) || s.startsWith('jupiter') || s.startsWith('jup-');
28
+ }
29
+ /**
30
+ * Resolve a SOL/USD price from samples. `input` may be a single sample, an array, or anything else
31
+ * (a bare number, null, undefined) — anything that is not a well-formed sample is ignored, so a legacy
32
+ * `() => 150` feed yields NO price and a mainnet trade is refused ('no-usd-value').
33
+ */
34
+ export function resolveSolUsd(input, now, network) {
35
+ const raw = Array.isArray(input) ? input : input === null || input === undefined ? [] : [input];
36
+ const rejected = [];
37
+ const fresh = new Map(); // one sample per source (the freshest)
38
+ for (const x of raw) {
39
+ if (!isSample(x)) {
40
+ rejected.push('malformed sample');
41
+ continue;
42
+ }
43
+ const src = x.source.trim().toLowerCase();
44
+ if (src.length === 0) {
45
+ rejected.push('unnamed source');
46
+ continue;
47
+ }
48
+ if (!Number.isFinite(x.price) || x.price < SOL_USD_MIN_PLAUSIBLE || x.price > SOL_USD_MAX_PLAUSIBLE) {
49
+ rejected.push(`${src}: implausible $${x.price}`);
50
+ continue;
51
+ }
52
+ if (!Number.isFinite(x.fetchedAt) || x.fetchedAt > now + 5_000) {
53
+ rejected.push(`${src}: bad timestamp`);
54
+ continue;
55
+ }
56
+ const age = now - x.fetchedAt;
57
+ if (age > SOL_USD_MAX_AGE_MS) {
58
+ rejected.push(`${src}: stale (${Math.round(age / 1000)} s)`);
59
+ continue;
60
+ }
61
+ const prev = fresh.get(src);
62
+ if (!prev || x.fetchedAt > prev.fetchedAt)
63
+ fresh.set(src, { price: x.price, fetchedAt: x.fetchedAt, source: src });
64
+ }
65
+ const valid = [...fresh.values()];
66
+ const why = rejected.length > 0 ? ` (${rejected.join('; ')})` : '';
67
+ if (network !== 'mainnet') {
68
+ // Devnet funds have no USD value; one fresh plausible sample from anyone is enough for display.
69
+ if (valid.length === 0)
70
+ return { price: null, reason: `no fresh SOL/USD sample${why}` };
71
+ const best = valid.reduce((a, b) => (b.price > a.price ? b : a));
72
+ return { price: best.price, sources: [best.source] };
73
+ }
74
+ const independent = valid.filter((s) => !dependent(s.source));
75
+ if (independent.length < 2) {
76
+ const have = independent.length === 1 ? `only ${independent[0].source}` : valid.length > 0 ? 'only the swap vendor' : 'none';
77
+ return { price: null, reason: `mainnet needs two independent non-Jupiter SOL/USD sources; have ${have}${why}` };
78
+ }
79
+ const lo = Math.min(...independent.map((s) => s.price));
80
+ const hi = Math.max(...independent.map((s) => s.price));
81
+ const spreadPct = ((hi - lo) / lo) * 100;
82
+ if (spreadPct > SOL_USD_AGREE_PCT) {
83
+ return { price: null, reason: `SOL/USD sources disagree by ${spreadPct.toFixed(1)}% (${independent.map((s) => `${s.source} $${s.price}`).join(', ')})` };
84
+ }
85
+ return { price: hi, sources: independent.map((s) => s.source) };
86
+ }
@@ -0,0 +1,108 @@
1
+ // dexterm T1 — Jupiter keyless SWAP BUILD (SPEC §6.1 step 2). Separate file from the T0 quote-only
2
+ // `sources/jupiter-swap.ts` ON PURPOSE: that file is provably unable to reach a signable transaction
3
+ // (the verifier key-scan relies on it), so the tx-building step lives here, clearly T1-gated.
4
+ //
5
+ // Flow: GET /swap/v1/quote (keyless, raw) -> POST /swap/v1/swap { quoteResponse, userPublicKey } ->
6
+ // a base64 VersionedTransaction. dexterm does NOT sign it; it hands the bytes to the wallet bridge.
7
+ //
8
+ // IMPORTANT (design constraint): Jupiter's quote/swap liquidity is
9
+ // MAINNET-only — there is no Jupiter devnet. So a Jupiter swap plan is a MAINNET plan. With the
10
+ // default devnet caps the signer refuses to sign it (mainnet-not-enabled): the default experience is
11
+ // a read-only plan + simulation (SPEC §6.10 "mainnet read-only quotes/sims, confirm disabled").
12
+ // Enabling a real swap needs --mainnet + the USD caps. The devnet signer/bridge plumbing is proven
13
+ // separately with a wallet self-transfer (scripts/phantom-devnet.md). No key ever enters dexterm.
14
+ import { JUP_SWAP_HOSTS, JupiterHosts } from "../sources/jupiter-swap.js";
15
+ const BUILD = { source: 'jupiter-swap', bucket: 'jupiter' };
16
+ /**
17
+ * task-1368: a raw quote fetched this recently for the SAME request is reused instead of re-fetched. The LIVE
18
+ * ticket's preview (`quote` through the signer), the signer's own re-quote at ⏎ and the build then cost the
19
+ * per-IP keyless budget two calls, not three, and are paced by ONE limiter (the signer's). The executor still
20
+ * binds whatever quote it gets to the order (mints, mode, exact amount) and simulates the built transaction.
21
+ */
22
+ export const QUOTE_REUSE_MS = 15_000;
23
+ export class JupiterSwapBuild {
24
+ http;
25
+ hosts;
26
+ /** The last raw quote per request key, for QUOTE_REUSE_MS. One entry per distinct order is plenty. */
27
+ recent = new Map();
28
+ constructor(http, hosts = JUP_SWAP_HOSTS, retry = {}) {
29
+ this.http = http;
30
+ this.hosts = new JupiterHosts(hosts, { clock: http.clock, bucketPausedMs: () => http.pausedFor(BUILD.bucket), sourceUnavailableMs: () => http.unavailableFor(BUILD.source), ...retry });
31
+ }
32
+ get host() { return this.hosts.host; }
33
+ /**
34
+ * Fetch the RAW quote object (needed verbatim by /swap). Keyless, read-only. A quote for the same request
35
+ * fetched within QUOTE_REUSE_MS is returned again (`fresh: false` forces a network quote).
36
+ */
37
+ async quoteRaw(req, signal, opts = {}) {
38
+ if (!(req.amount > 0n))
39
+ throw new Error('quote amount must be a positive integer of base units');
40
+ if (req.inputMint === req.outputMint)
41
+ throw new Error('quote needs two different mints');
42
+ const params = new URLSearchParams({
43
+ inputMint: req.inputMint,
44
+ outputMint: req.outputMint,
45
+ amount: req.amount.toString(),
46
+ slippageBps: String(Math.max(0, Math.round(req.slippageBps))),
47
+ swapMode: req.mode ?? 'ExactIn',
48
+ });
49
+ const key = params.toString();
50
+ const reuseMs = opts.reuseMs ?? QUOTE_REUSE_MS;
51
+ const now = this.http.clock.now();
52
+ const hit = this.recent.get(key);
53
+ if (hit && reuseMs > 0 && now - hit.at <= reuseMs)
54
+ return hit.raw;
55
+ const r = await this.getJson(`/swap/v1/quote?${key}`, signal);
56
+ if (!r || typeof r !== 'object' || r.inAmount === undefined || r.outAmount === undefined) {
57
+ throw new Error('empty or malformed quote');
58
+ }
59
+ this.recent.clear();
60
+ this.recent.set(key, { raw: r, at: this.http.clock.now() });
61
+ return r;
62
+ }
63
+ /** Age in ms of the reusable quote for this request, or null when none is held. */
64
+ quoteAge(req) {
65
+ const key = new URLSearchParams({
66
+ inputMint: req.inputMint, outputMint: req.outputMint, amount: req.amount.toString(),
67
+ slippageBps: String(Math.max(0, Math.round(req.slippageBps))), swapMode: req.mode ?? 'ExactIn',
68
+ }).toString();
69
+ const hit = this.recent.get(key);
70
+ return hit ? this.http.clock.now() - hit.at : null;
71
+ }
72
+ /**
73
+ * Build an unsigned swap transaction for `userPublicKey` from a raw quote. KEYLESS. The returned
74
+ * bytes are an unsigned VersionedTransaction; dexterm never signs — the wallet bridge does.
75
+ */
76
+ async buildSwap(args) {
77
+ if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(args.userPublicKey))
78
+ throw new Error('buildSwap needs a base58 public key');
79
+ const body = {
80
+ quoteResponse: args.quote,
81
+ userPublicKey: args.userPublicKey,
82
+ wrapAndUnwrapSol: true,
83
+ dynamicComputeUnitLimit: true,
84
+ // Jupiter managed landing / priority-fee defaults; dexterm never sets a fee it cannot cap.
85
+ };
86
+ const r = await this.postJson(`/swap/v1/swap`, body, args.signal);
87
+ const tx = r?.swapTransaction;
88
+ if (typeof tx !== 'string' || tx.length === 0)
89
+ throw new Error('swap build returned no transaction');
90
+ if (!/^[A-Za-z0-9+/=]+$/.test(tx))
91
+ throw new Error('swap transaction is not base64');
92
+ return {
93
+ swapTransactionBase64: tx,
94
+ lastValidBlockHeight: typeof r.lastValidBlockHeight === 'number' ? r.lastValidBlockHeight : null,
95
+ prioritizationFeeLamports: typeof r.prioritizationFeeLamports === 'number' ? r.prioritizationFeeLamports : null,
96
+ quote: args.quote,
97
+ };
98
+ }
99
+ /** task-1368: 401/403 → sticky failover; 429/5xx/transport → alternate host + bounded retry (JupiterHosts). */
100
+ async getJson(path, signal) {
101
+ const r = await this.hosts.run((host) => this.http.getJson(`${host}${path}`, BUILD, undefined, signal), signal);
102
+ return r.data;
103
+ }
104
+ async postJson(path, body, signal) {
105
+ const r = await this.hosts.run((host) => this.http.postJson(`${host}${path}`, body, BUILD, undefined, signal), signal);
106
+ return r.data;
107
+ }
108
+ }
@@ -0,0 +1,201 @@
1
+ // dexterm T1/T2 — task-1337: the TOKEN-BALANCE DIFF (the round-3 verifier's "recommended next hardening").
2
+ //
3
+ // The static policy fixes WHICH instructions a transaction may carry and binds the one Jupiter route to the
4
+ // plan (amounts, the wallet's own token accounts, no fee). What it cannot see is what the route DOES once
5
+ // it runs: a legit-looking route instruction whose `route_plan` names crafted pool accounts could, if the
6
+ // on-chain program ever let it, move tokens out of one of the wallet's OTHER token accounts. The SOL-delta
7
+ // check would not notice (only the fee leaves the wallet in SOL). This module is the backstop: simulate
8
+ // with the wallet's token accounts in `accounts[]` and refuse when any of them lost tokens the plan does
9
+ // not spend.
10
+ //
11
+ // buy (SOL → token): NO token account of the wallet may decrease.
12
+ // sell (token → SOL): ONLY the wallet's token accounts for the ORDER mint may decrease, by at most the
13
+ // order amount in total. Everything else must not decrease.
14
+ // wrapped SOL: the wallet's WSOL accounts ARE watched (task-1337 F2). A swap funds, syncs and
15
+ // closes a wrapped-SOL account back to the wallet, so WSOL leaving the wallet is
16
+ // SOL-value out — not a free pass. `checkTokenDeltas` tallies how much wrapped SOL
17
+ // left (`wsolLost`); the executor bounds `solOut + wsolLost` against the plan's SOL
18
+ // leg (`checkWsolBound`), so a route that drains a PRE-EXISTING WSOL account is
19
+ // caught by the SOL-value budget instead of being ignored.
20
+ //
21
+ // One simulation can carry only so many token accounts (`MAX_TOKEN_WATCH`). A referenced account is NEVER
22
+ // dropped to fit that cap (task-1337 F3): `buildTokenWatch` keeps every referenced account and bounds only
23
+ // the unreferenced tail, and the executor refuses outright (`referencedOverCap`) when the transaction
24
+ // references more wallet token accounts than one simulation can hold.
25
+ //
26
+ // Pure: no I/O here. The executor fetches the pre-state (getTokenAccountsByOwner / getMultipleAccounts),
27
+ // resolves address-lookup tables so "referenced by the transaction" is exact for v0 messages, asks the
28
+ // simulation for the post-state and hands both to `checkTokenDeltas`. Unknowable = refuse (fail closed).
29
+ import { base58 } from "../sources/solana-rpc.js";
30
+ import { WSOL_MINT } from "./tx-decode.js";
31
+ /** SPL Token / Token-2022 account base layout: mint[0..32] owner[32..64] amount u64 LE [64..72] state[108]. */
32
+ export const TOKEN_ACCOUNT_LEN = 165;
33
+ /** Parse an SPL token account (both programs share the 165-byte base layout; Token-2022 may append extensions). */
34
+ export function parseTokenAccount(data) {
35
+ if (!data || data.length < TOKEN_ACCOUNT_LEN)
36
+ return null;
37
+ if (data[108] === 0)
38
+ return null; // AccountState::Uninitialized
39
+ return { mint: base58(data.subarray(0, 32)), owner: base58(data.subarray(32, 64)), amount: data.readBigUInt64LE(64) };
40
+ }
41
+ /** Address-lookup-table account layout: 56-byte LookupTableMeta, then 32-byte addresses. */
42
+ export const LOOKUP_TABLE_META_LEN = 56;
43
+ export function parseLookupTable(data) {
44
+ if (!data || data.length < LOOKUP_TABLE_META_LEN)
45
+ return null;
46
+ const body = data.subarray(LOOKUP_TABLE_META_LEN);
47
+ if (body.length % 32 !== 0)
48
+ return null;
49
+ const out = [];
50
+ for (let i = 0; i < body.length; i += 32)
51
+ out.push(base58(body.subarray(i, i + 32)));
52
+ return out;
53
+ }
54
+ /**
55
+ * Every account a transaction can touch: its static keys plus the lookup-table entries it loads.
56
+ * `tables` maps a table address → its parsed address list. A table the caller could not read, or an
57
+ * index past the table's end, makes the set UNKNOWABLE → null (the executor refuses, fail closed).
58
+ */
59
+ export function referencedAccounts(tx, tables) {
60
+ const out = new Set(tx.staticKeys);
61
+ for (const l of tx.lookups) {
62
+ const t = tables.get(l.table);
63
+ if (!t)
64
+ return null;
65
+ for (const i of [...l.writable, ...l.readonly]) {
66
+ if (i < 0 || i >= t.length)
67
+ return null;
68
+ out.add(t[i]);
69
+ }
70
+ }
71
+ return out;
72
+ }
73
+ /** Build the watch list from the wallet's token accounts (pre-state) and the transaction's account set. */
74
+ export function buildTokenWatch(user, accounts, referenced, opts = {}) {
75
+ const seen = new Set();
76
+ const all = [];
77
+ for (const a of accounts) {
78
+ if (seen.has(a.pubkey))
79
+ continue;
80
+ const st = parseTokenAccount(a.data);
81
+ if (!st || st.owner !== user)
82
+ continue;
83
+ seen.add(a.pubkey);
84
+ // task-1337 F2: wrapped SOL is watched too (its loss is SOL-value out, bounded by checkWsolBound).
85
+ all.push({ address: a.pubkey, mint: st.mint, pre: st.amount, referenced: referenced.has(a.pubkey), isWsol: st.mint === WSOL_MINT });
86
+ }
87
+ // task-1337 F3: a referenced account can be touched by the transaction, so it must NEVER be dropped to
88
+ // fit the cap. Keep every referenced account; bound only the unreferenced tail (largest balances first).
89
+ // When referenced accounts alone exceed the cap the list exceeds it on purpose and the executor refuses
90
+ // (see `referencedOverCap`) — truncating them here would hide a drain of a dropped account.
91
+ const max = opts.max ?? MAX_TOKEN_WATCH;
92
+ const ref = all.filter((w) => w.referenced).sort(byBalance);
93
+ const unref = all.filter((w) => !w.referenced).sort(byBalance);
94
+ return [...ref, ...unref.slice(0, Math.max(0, max - ref.length))];
95
+ }
96
+ function byBalance(x, y) { return y.pre > x.pre ? 1 : y.pre < x.pre ? -1 : 0; }
97
+ /** How many token accounts ride along in one simulateTransaction `accounts[]` (plus the wallet itself). */
98
+ export const MAX_TOKEN_WATCH = 48;
99
+ /**
100
+ * task-1337 F3: does the transaction reference MORE of the wallet's own token accounts than one simulation
101
+ * can carry? If so the watch list cannot cover them all and the executor must refuse (fail closed) rather
102
+ * than silently drop referenced accounts. Counts only referenced accounts (the unreferenced tail is
103
+ * bounded safely by `buildTokenWatch`).
104
+ */
105
+ export function referencedOverCap(watch, max = MAX_TOKEN_WATCH) {
106
+ let n = 0;
107
+ for (const w of watch)
108
+ if (w.referenced)
109
+ n++;
110
+ return n > max;
111
+ }
112
+ /**
113
+ * The decision. `post` maps a watched address → its post-simulation balance, or null when the node
114
+ * returned no account for it (closed, or — for an account the transaction never references — simply
115
+ * not loaded: such an account cannot have changed, so it keeps its pre-balance). An account the
116
+ * transaction DOES reference and that comes back null was closed: a positive pre-balance then counts
117
+ * as fully drained. A watched account missing from `post` entirely (the node sent no data) is
118
+ * unknowable → refused when it held anything.
119
+ */
120
+ export function checkTokenDeltas(side, orderMint, amountRaw, watch, post) {
121
+ let spent = 0n;
122
+ let wsolLost = 0n;
123
+ let checked = 0;
124
+ for (const w of watch) {
125
+ if (post === null || !post.has(w.address)) {
126
+ if (w.pre > 0n)
127
+ return { ok: false, reason: 'token-delta-unknown', message: `the simulation did not report the post-balance of the wallet's ${short(w.mint)} token account ${short(w.address)}` };
128
+ continue;
129
+ }
130
+ checked++;
131
+ const raw = post.get(w.address) ?? null;
132
+ const after = raw === null ? (w.referenced ? 0n : w.pre) : raw;
133
+ if (w.isWsol) {
134
+ // task-1337 F2: wrapped SOL is SOL-value, not a token holding. A legit wrap/sync/close returns it to
135
+ // the wallet as native SOL. Only tally what left; the combined SOL-value bound lives in the executor
136
+ // (checkWsolBound), where the plan's SOL leg and the native SOL delta are known.
137
+ if (after < w.pre)
138
+ wsolLost += w.pre - after;
139
+ continue;
140
+ }
141
+ if (after >= w.pre)
142
+ continue;
143
+ const lost = w.pre - after;
144
+ if (side === 'sell' && w.mint === orderMint) {
145
+ spent += lost;
146
+ if (spent > amountRaw)
147
+ return { ok: false, reason: 'token-delta', message: `the simulation spends ${spent} base units of ${short(orderMint)} but the order is ${amountRaw}` };
148
+ continue;
149
+ }
150
+ return { ok: false, reason: 'token-delta', message: `the simulation moves ${lost} base units of ${short(w.mint)} out of the wallet's token account ${short(w.address)}, which this ${side} does not spend` };
151
+ }
152
+ return { ok: true, spent, wsolLost, checked };
153
+ }
154
+ /**
155
+ * task-1337 F2: pre-existing wrapped SOL that leaves the wallet is SOL-value out and must fit the plan.
156
+ * buy: native SOL out + wrapped SOL consumed may be at most the planned SOL leg plus the fee allowance
157
+ * (wrapping is fungible, so the two are one budget). A null native delta with WSOL gone is
158
+ * unmeasurable → refuse.
159
+ * sell: a sell RECEIVES SOL, so its own WSOL accounts must not shrink by more than fee dust — the
160
+ * incoming proceeds must never mask a drain, so the native delta is not credited here.
161
+ * `wsolLost <= 0` is the overwhelmingly common case (no pre-existing WSOL touched) → pass cheaply.
162
+ */
163
+ export function checkWsolBound(side, solOut, wsolLost, solLegLamports, allowanceLamports) {
164
+ if (wsolLost <= 0n)
165
+ return { ok: true };
166
+ if (side === 'buy') {
167
+ if (solOut === null)
168
+ return { ok: false, reason: 'wsol-drain', message: `the simulation removes ${wsolLost} lamports of pre-existing wrapped SOL but the native SOL delta is unknown` };
169
+ const out = solOut + wsolLost;
170
+ const limit = solLegLamports + allowanceLamports;
171
+ if (out > limit)
172
+ return { ok: false, reason: 'wsol-drain', message: `the simulation removes ${wsolLost} lamports of wrapped SOL plus ${solOut} native lamports (${out} total) but the plan's SOL leg is ${solLegLamports} (+${allowanceLamports} allowance)` };
173
+ return { ok: true };
174
+ }
175
+ if (wsolLost > allowanceLamports)
176
+ return { ok: false, reason: 'wsol-drain', message: `a sell removes ${wsolLost} lamports of pre-existing wrapped SOL from the wallet, beyond the ${allowanceLamports}-lamport fee allowance` };
177
+ return { ok: true };
178
+ }
179
+ /** Post-simulation balances for the watched accounts from `simulate(..., { accounts: [user, ...watch] })`. */
180
+ export function postBalances(watch, accountsData, offset) {
181
+ if (accountsData === null)
182
+ return null;
183
+ const out = new Map();
184
+ for (let i = 0; i < watch.length; i++) {
185
+ const d = accountsData[offset + i];
186
+ if (d === undefined)
187
+ return null; // the node answered for fewer accounts than asked → unknowable
188
+ out.set(watch[i].address, d === null ? null : (parseTokenAccount(d)?.amount ?? 0n));
189
+ }
190
+ return out;
191
+ }
192
+ /** Refresh the pre-balances of an existing watch list from fresh account snapshots (the confirm step). */
193
+ export function refreshWatch(watch, infos) {
194
+ if (infos.length !== watch.length)
195
+ return null;
196
+ return watch.map((w, i) => {
197
+ const st = parseTokenAccount(infos[i]?.data);
198
+ return { ...w, pre: st ? st.amount : 0n };
199
+ });
200
+ }
201
+ function short(k) { return k.length > 12 ? `${k.slice(0, 4)}…${k.slice(-4)}` : k; }