tollkit-wallet 0.2.0 → 0.2.2

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 (78) hide show
  1. package/README.md +63 -4
  2. package/THIRD_PARTY_NOTICES.md +8 -0
  3. package/dist/actions.d.ts +196 -16
  4. package/dist/actions.js +46 -6
  5. package/dist/actions.js.map +1 -1
  6. package/dist/ai-icons.d.ts +5 -0
  7. package/dist/ai-icons.js +3 -0
  8. package/dist/ai-icons.js.map +1 -0
  9. package/dist/ai.d.ts +77 -0
  10. package/dist/ai.js +227 -0
  11. package/dist/ai.js.map +1 -0
  12. package/dist/bridge.d.ts +148 -0
  13. package/dist/bridge.js +460 -0
  14. package/dist/bridge.js.map +1 -0
  15. package/dist/buy.d.ts +17 -0
  16. package/dist/buy.js +23 -0
  17. package/dist/buy.js.map +1 -0
  18. package/dist/cli.js +82 -3
  19. package/dist/cli.js.map +1 -1
  20. package/dist/connect.d.ts +29 -0
  21. package/dist/connect.js +227 -0
  22. package/dist/connect.js.map +1 -0
  23. package/dist/dapp.d.ts +89 -0
  24. package/dist/dapp.js +505 -0
  25. package/dist/dapp.js.map +1 -0
  26. package/dist/dashboard.d.ts +5 -0
  27. package/dist/dashboard.js +203 -2
  28. package/dist/dashboard.js.map +1 -1
  29. package/dist/elizaos.js +2 -0
  30. package/dist/elizaos.js.map +1 -1
  31. package/dist/holdings.d.ts +92 -0
  32. package/dist/holdings.js +200 -0
  33. package/dist/holdings.js.map +1 -0
  34. package/dist/keystore.d.ts +58 -11
  35. package/dist/keystore.js +87 -11
  36. package/dist/keystore.js.map +1 -1
  37. package/dist/ledger.d.ts +1 -1
  38. package/dist/mcp.d.ts +7 -2
  39. package/dist/mcp.js +11 -5
  40. package/dist/mcp.js.map +1 -1
  41. package/dist/netguard.d.ts +8 -0
  42. package/dist/netguard.js +44 -0
  43. package/dist/netguard.js.map +1 -0
  44. package/dist/paths.d.ts +5 -0
  45. package/dist/paths.js +5 -0
  46. package/dist/paths.js.map +1 -1
  47. package/dist/policy.d.ts +4 -4
  48. package/dist/rules.d.ts +11 -2
  49. package/dist/rules.js +23 -1
  50. package/dist/rules.js.map +1 -1
  51. package/dist/sell.d.ts +60 -0
  52. package/dist/sell.js +71 -0
  53. package/dist/sell.js.map +1 -0
  54. package/dist/stake.d.ts +84 -0
  55. package/dist/stake.js +158 -0
  56. package/dist/stake.js.map +1 -0
  57. package/dist/telegram.d.ts +2 -0
  58. package/dist/telegram.js +4 -0
  59. package/dist/telegram.js.map +1 -1
  60. package/dist/ui.js +463 -36
  61. package/dist/ui.js.map +1 -1
  62. package/dist/wallet.d.ts +57 -2
  63. package/dist/wallet.js +185 -4
  64. package/dist/wallet.js.map +1 -1
  65. package/extension/PRIVACY.md +19 -0
  66. package/extension/README.md +16 -0
  67. package/extension/background.js +88 -0
  68. package/extension/content.js +34 -0
  69. package/extension/icons/icon.svg +1 -0
  70. package/extension/icons/icon128.png +0 -0
  71. package/extension/icons/icon16.png +0 -0
  72. package/extension/icons/icon32.png +0 -0
  73. package/extension/icons/icon48.png +0 -0
  74. package/extension/inpage.js +129 -0
  75. package/extension/manifest.json +15 -0
  76. package/extension/popup.html +35 -0
  77. package/extension/popup.js +39 -0
  78. package/package.json +3 -2
package/dist/ui.js CHANGED
@@ -1,9 +1,11 @@
1
1
  /** The dashboard page. One file, no outside scripts, fonts or images. */
2
2
  import { COIN_ICONS } from "./coin-icons.js";
3
+ import { AI_ICONS } from "./ai-icons.js";
3
4
  const COIN_JSON = JSON.stringify(COIN_ICONS).replace(/</g, "\\u003c");
5
+ const AI_ICON_JSON = JSON.stringify(AI_ICONS).replace(/</g, "\\u003c");
4
6
  export const PAGE = String.raw `<!doctype html>
5
7
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>Tollkit Wallet</title>
8
+ <title>Tollkit</title>
7
9
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='20' fill='%230a0a0a'/%3E%3Ctext x='50' y='68' font-family='ui-monospace,SFMono-Regular,monospace' font-size='56' font-weight='700' fill='%234a8eff' text-anchor='middle'%3Et%3C/text%3E%3C/svg%3E">
8
10
  <style>
9
11
  :root{--bg:#0d0e12;--card:#16171d;--surf:#1e2027;--surf2:#262934;--line:#2c2f3a;--tx:#f1f2f6;--mut:#979aab;--acc:#4a8eff;--acc2:#3a7bea;--ok:#3ccf91;--bad:#ff6b6b;--warn:#f5b84a}
@@ -39,13 +41,14 @@ input,select{width:100%;background:var(--surf);border:1px solid var(--line);colo
39
41
  input:focus,select:focus{outline:2px solid var(--acc);border-color:transparent}
40
42
  .grid2{display:grid;grid-template-columns:1fr 1fr;gap:8px}
41
43
  .msg{margin-top:10px;font-size:13px;padding:9px 11px;border-radius:10px;background:var(--surf)}.msg.err{color:var(--bad)}.msg.okk{color:var(--ok)}
42
- .ap{background:var(--surf);border:1px solid var(--acc);border-radius:14px;padding:12px;margin-top:10px;white-space:pre-wrap}
44
+ .ap{background:var(--surf);border:1px solid var(--acc);border-radius:14px;padding:12px;margin-top:10px;white-space:pre-wrap;overflow-wrap:anywhere}
43
45
  .ap .btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}
44
46
  .empty{text-align:center;color:var(--mut);padding:40px 10px}
45
47
  a{color:var(--acc)}
46
48
  .ok{color:var(--ok)}.bad{color:var(--bad)}.warn{color:var(--warn)}
47
49
  .toggles{display:flex;gap:6px;flex-wrap:wrap}.toggles label{display:flex;align-items:center;gap:6px;margin:0;padding:8px 10px;background:var(--surf);border-radius:10px;color:var(--tx);font-size:13px}
48
50
  .toggles input{width:auto}
51
+ .sgrp{font-size:13px;font-weight:600;color:var(--tx);margin:16px 0 0}.sth{margin-top:4px}.stg{flex-direction:column;align-items:flex-start}.ssave{display:flex;align-items:center;gap:12px;margin-top:12px}button.b.sbtn{padding:8px 12px;font-size:13px;white-space:nowrap}
49
52
 
50
53
  /* ── Motion, in the same language as tollkit.dev (motion.css) ─────────── */
51
54
  :root{--ease:cubic-bezier(.16,1,.3,1);--glow:74,142,255}
@@ -134,10 +137,137 @@ input,select{transition:border-color .2s ease,box-shadow .25s ease}input:focus,s
134
137
  @keyframes tk-down{0%{background:rgba(255,107,107,.25);color:var(--bad)}100%{background:transparent}}
135
138
  .daychg{margin:2px 0 10px}
136
139
  @media (prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}
140
+ .aib{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;margin-top:14px;background:linear-gradient(135deg,var(--acc),var(--acc2));box-shadow:0 6px 24px rgba(var(--glow),.25)}
141
+ .aib:hover{transform:translateY(-1px);box-shadow:0 10px 32px rgba(var(--glow),.38)}
142
+ .aiconn{display:flex;gap:8px;margin-top:14px;align-items:stretch}
143
+ .aich{flex:1;min-width:0;display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surf);border:1px solid var(--line);border-radius:14px;color:var(--tx);font:inherit;cursor:pointer;text-align:left;transition:border-color .15s,transform .2s var(--ease)}
144
+ .aich:hover{border-color:var(--acc);transform:translateY(-1px)}
145
+ .aistk{display:flex;flex:none}.aistk i{width:30px;height:30px;border-radius:50%;background:var(--surf2);border:2px solid var(--surf);display:flex;align-items:center;justify-content:center;margin-left:-8px}.aistk i:first-child{margin-left:0}
146
+ .aistk svg{width:16px;height:16px;margin:0}.ail-claude{color:#d97757}
147
+ .aichn{min-width:0;display:flex;flex-direction:column}.aichn b{font-size:13.5px;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.aichn small{font-size:11.5px;color:var(--mut)}
148
+ .aiap{flex:none;display:flex;align-items:center;gap:7px;padding:0 16px;border-radius:14px;background:linear-gradient(135deg,var(--acc),var(--acc2));box-shadow:0 6px 24px rgba(var(--glow),.25)}.aiap svg{margin:0}
149
+ .aiap:hover{transform:translateY(-1px);box-shadow:0 10px 32px rgba(var(--glow),.38)}
150
+ .aiwt{min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:#fff;color:var(--acc2);font-size:11px;display:inline-flex;align-items:center;justify-content:center}
151
+ .aiic{display:inline-flex}.aiic svg{width:16px;height:16px;margin-left:-3px;padding:2px;border-radius:50%;background:rgba(0,0,0,.25)}
152
+ .aio{position:fixed;inset:0;z-index:50;background:rgba(5,6,9,.72);backdrop-filter:blur(6px);display:flex;align-items:flex-start;justify-content:center;padding:5vh 16px 16px;animation:aifade .25s ease}
153
+ .aidlg{position:relative;width:100%;max-width:540px;max-height:90vh;overflow-y:auto;background:var(--card);border:1px solid var(--line);border-radius:20px;padding:22px 20px 18px;box-shadow:0 30px 90px rgba(0,0,0,.6);animation:tk-rise .5s var(--ease) both}
154
+ .aidlg h2{margin:0 0 4px;font-size:19px;letter-spacing:-.01em}
155
+ .aix{position:absolute;top:10px;right:10px;width:34px;height:34px;border-radius:10px;border:1px solid transparent;background:none;color:var(--mut);font-size:22px;line-height:1;cursor:pointer}.aix:hover{border-color:var(--line);color:var(--tx)}
156
+ .ailead{color:var(--mut);font-size:14px;margin:0 0 14px;line-height:1.5}
157
+ .ailbl{display:block;font-size:12px;font-weight:600;color:var(--mut);margin-bottom:5px}
158
+ .aiin{width:100%;background:var(--surf);border:1px solid var(--line);color:var(--tx);border-radius:11px;padding:11px 12px;font:inherit}
159
+ .aiin:focus{outline:none;border-color:var(--acc);box-shadow:0 0 0 3px rgba(var(--glow),.18)}
160
+ .aismall{font-size:12px;color:var(--mut);margin:6px 0 14px;line-height:1.45}
161
+ .ailist{display:flex;flex-direction:column;gap:8px}
162
+ .airow{background:var(--surf);border:1px solid var(--line);border-radius:14px;padding:11px 12px 8px;animation:tk-rise .5s var(--ease) both;animation-delay:calc(var(--i)*40ms)}
163
+ .airt{display:flex;align-items:center;gap:11px}.airt>svg{width:24px;height:24px;flex:none}
164
+ .ainm{flex:1;min-width:0;display:flex;flex-direction:column}.ainm b{font-size:14.5px}.ainm small{font-size:12px;color:var(--mut)}
165
+ .aiadd{padding:8px 16px;border-radius:10px;font-size:13.5px;text-decoration:none;flex:none}a.aiadd{display:inline-block;background:var(--acc2);color:#fff;font-weight:600}.aiadd[disabled]{opacity:.6;cursor:default}
166
+ .aim{font-size:12.5px;margin:7px 0 0 35px}
167
+ .aihand{background:none;border:0;padding:4px 0 0 35px;color:var(--mut);font:inherit;font-size:12px;cursor:pointer}.aihand:hover{color:var(--tx)}
168
+ .aihb{margin:6px 0 4px 35px}
169
+ .aip{font-size:13px;margin:6px 0}.aisml2{padding:5px 10px;font-size:12.5px;text-decoration:none;display:inline-block}
170
+ .aist{margin:0 0 4px;padding-left:18px;font-size:13px;line-height:1.55}.aist li{margin:5px 0}
171
+ .aidlg code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.88em;color:var(--acc);overflow-wrap:anywhere}
172
+ .kbd{font-family:ui-monospace,monospace;font-size:.85em;background:var(--surf2);border:1px solid var(--line);border-radius:6px;padding:1px 6px}
173
+ .aicmd{display:flex;gap:8px;align-items:flex-start;background:#08090c;border:1px solid var(--line);border-radius:12px;padding:9px 9px 9px 13px;margin:8px 0}
174
+ .aicmd pre{flex:1;min-width:0;margin:0;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#cdd6e4;white-space:pre-wrap;overflow-wrap:anywhere}
175
+ .aicp{padding:6px 11px;font-size:12.5px;border-radius:9px}
176
+ .aigoap{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;margin-top:14px;background:linear-gradient(135deg,var(--acc),var(--acc2))}.aigoap svg{margin:0}
177
+ .aidone{margin-top:16px;padding-top:13px;border-top:1px solid var(--line);font-size:13px;color:var(--mut);line-height:1.5}.aidone b{color:var(--tx)}
178
+ @keyframes aifade{from{opacity:0}to{opacity:1}}
179
+ @media (prefers-reduced-motion:reduce){.aio,.aidlg,.airow{animation:none}}
180
+ .tc-chat{background:var(--surf);border:1px solid var(--line);border-radius:18px;margin-top:12px;overflow:hidden}
181
+ .tc-chd{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 10px;padding:11px 12px;border-bottom:1px solid var(--line)}
182
+ .tc-chn{display:flex;align-items:center;gap:9px;min-width:0}.tc-chn>svg{width:22px;height:22px;flex:none;color:#d97757}.tc-chn b{display:block;font-size:14px}.tc-chn small{display:block;font-size:11.5px;color:var(--mut)}
183
+ .tc-chr{display:flex;align-items:center;gap:6px;margin-left:auto}.tc-chk{display:flex;align-items:center;gap:4px;color:var(--mut)}
184
+ .tc-chk select{background:var(--surf2);color:var(--tx);border:1px solid var(--line);border-radius:9px;padding:5px 6px;font:inherit;font-size:12px}
185
+ .tc-chx{height:32px;padding:0 10px;border-radius:9px;display:flex;align-items:center;gap:5px;flex:none;font-size:12px;white-space:nowrap}.tc-chx svg{display:inline-block;width:14px;height:14px;margin:0}.b.tc-chx.tc-warn{background:rgba(245,184,74,.18);color:var(--warn)}.tc-csend svg{margin:0}
186
+ .tc-cbody{position:relative;max-height:min(58vh,520px);min-height:180px;overflow-y:auto;padding:14px 12px 6px;display:flex;flex-direction:column;gap:12px}
187
+ .tc-cm{display:flex;gap:8px}.tc-cm.tc-new{animation:tk-rise .45s var(--ease) both}.tc-cm.tc-you{justify-content:flex-end}
188
+ .tc-cm.tc-you .tc-cb{background:linear-gradient(135deg,var(--acc),var(--acc2));color:#fff;border-radius:16px 16px 4px 16px;max-width:82%}
189
+ .tc-cav{width:26px;height:26px;border-radius:50%;background:var(--surf2);display:flex;align-items:center;justify-content:center;flex:none;color:#d97757}.tc-cav svg{width:15px;height:15px}
190
+ .tc-cbw{min-width:0;flex:1;max-width:92%}
191
+ .tc-cb{padding:9px 12px;font-size:14px;line-height:1.5;border-radius:16px 16px 16px 4px;background:var(--card);border:1px solid var(--line);overflow-wrap:anywhere}
192
+ .tc-cm.tc-you .tc-cb{border:0}.tc-cb p{margin:0 0 7px}.tc-cb p:last-child{margin:0}.tc-cb ul{margin:0 0 7px;padding-left:18px}.tc-cb li{margin:2px 0}.tc-cb code{font-family:ui-monospace,monospace;font-size:.9em}
193
+ .tc-cm.tc-err .tc-cb{border-color:rgba(255,107,107,.45);color:var(--bad)}
194
+ .tc-ctag{font-size:11px;color:var(--mut);margin:0 0 4px 2px}
195
+ .tc-ccards{margin-top:8px;display:flex;flex-direction:column;gap:8px}.tc-ccards .row{margin:0;background:var(--card)}
196
+ .tc-cq{display:flex;align-items:center;justify-content:center;gap:5px;font-size:11.5px;color:var(--mut);text-align:center}
197
+ .tc-cempty{padding:22px 12px;text-align:center}.tc-cempty b{font-size:15px}.tc-cempty p{color:var(--mut);font-size:13.5px;line-height:1.5;margin:6px auto 12px;max-width:380px}
198
+ .tc-cchips{display:flex;flex-wrap:wrap;gap:7px;justify-content:center}
199
+ .tc-cchip{border:1px solid var(--line);background:var(--card);color:var(--tx);border-radius:99px;padding:7px 12px;font:inherit;font-size:12.5px;cursor:pointer;transition:border-color .15s,transform .2s var(--ease)}.tc-cchip:hover{border-color:var(--acc);transform:translateY(-1px)}
200
+ .tc-ctyping{display:inline-flex;align-items:center;gap:4px;color:var(--mut);font-size:12.5px}.tc-ctyping span{margin-left:6px}
201
+ .tc-ctyping i{width:6px;height:6px;border-radius:50%;background:var(--mut);animation:cdot 1.2s infinite ease-in-out}.tc-ctyping i:nth-child(2){animation-delay:.15s}.tc-ctyping i:nth-child(3){animation-delay:.3s}
202
+ @keyframes cdot{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}
203
+ .tc-cpw{margin:0 12px 10px;padding:10px 11px;border-radius:12px;background:rgba(245,184,74,.1);border:1px solid rgba(245,184,74,.4)}
204
+ .tc-crow{display:flex;gap:8px;margin-top:8px}.tc-crow input{flex:1;min-width:0;background:var(--card);border:1px solid var(--line);color:var(--tx);border-radius:10px;padding:9px 10px;font:inherit}
205
+ .tc-cin{display:flex;gap:8px;align-items:flex-end;padding:10px 12px 12px;border-top:1px solid var(--line)}
206
+ .tc-cin textarea{flex:1;resize:none;background:var(--card);border:1px solid var(--line);color:var(--tx);border-radius:14px;padding:10px 12px;font:inherit;font-size:14px;line-height:1.4;max-height:140px}
207
+ .tc-cin textarea:focus{outline:none;border-color:var(--acc);box-shadow:0 0 0 3px rgba(var(--glow),.18)}
208
+ .tc-csend{width:42px;height:42px;padding:0;border-radius:50%;display:flex;align-items:center;justify-content:center;flex:none}.tc-csend[disabled]{opacity:.45;cursor:default}
209
+ .tc-why{font-size:12.5px;color:var(--tx);opacity:.85;margin-top:5px;line-height:1.45}
210
+ .mrule summary{cursor:pointer;color:var(--mut);font-size:13px;margin-top:14px}
211
+ @media (prefers-reduced-motion:reduce){.tc-cm.tc-new{animation:none}.tc-ctyping i{animation:none}}
212
+ .bkb{display:flex;align-items:center;gap:11px;margin-top:14px;padding:11px 12px;border-radius:14px;background:rgba(245,184,74,.1);border:1px solid rgba(245,184,74,.45)}
213
+ .bki{color:var(--warn);flex:none;display:flex}.bki svg{margin:0}
214
+ .bkt{flex:1;min-width:0;display:flex;flex-direction:column}.bkt b{font-size:14px}.bkt small{font-size:12px;color:var(--mut);line-height:1.4}
215
+ .bkgo{flex:none;padding:8px 14px;border-radius:10px;font-size:13px}
216
+ .bkl{text-align:center;margin-top:14px}
217
+ .bkgrid{position:relative;display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin:4px 0 6px}
218
+ .bkgrid div{background:var(--surf);border:1px solid var(--line);border-radius:10px;padding:9px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}
219
+ .bkgrid div span{display:inline-block;min-width:22px;color:var(--mut);font-size:11px}
220
+ .bkgrid.bkblur div{filter:blur(7px);user-select:none}pre.bkblur{filter:blur(6px);user-select:none}
221
+ .bkreveal{position:absolute;inset:0;margin:auto;width:150px;height:42px;border-radius:21px;border:1px solid var(--line);background:var(--card);color:var(--tx);font:inherit;font-weight:600;cursor:pointer;filter:none}
222
+ .bkkey{margin-top:10px}.bkkey small{color:var(--mut);font-size:12px}
223
+ .bkchk{display:flex;align-items:center;gap:8px;margin-top:14px;font-size:13.5px;justify-content:flex-start}.bkchk input{width:18px;height:18px;flex:none;margin:0}
224
+ .bkq{margin:12px 0}.bkq b{font-size:13px;color:var(--mut)}
225
+ .bkopts{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-top:6px}
226
+ .bkopt{padding:9px 4px;border-radius:10px;border:1px solid var(--line);background:var(--surf);color:var(--tx);font:inherit;font-size:13px;cursor:pointer}
227
+ .bkopt.ok{border-color:var(--ok);color:var(--ok)}.bkopt.bad{border-color:var(--bad);color:var(--bad)}
228
+ .bkdone{text-align:center}.bkdone>svg{width:44px;height:44px;color:var(--ok);margin:6px auto 4px;display:block}
229
+ @media (max-width:480px){.bkopts{grid-template-columns:repeat(2,1fr)}}
230
+ .tl{width:100%;height:100%;border-radius:50%;background:var(--surf2);display:flex;align-items:center;justify-content:center;font-size:10.5px;font-weight:700;color:var(--mut);letter-spacing:-.02em}
231
+ .rowc{cursor:pointer}.rowc:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
232
+ .hfoot{margin-top:8px;font-size:12px;color:var(--mut);text-align:center}
233
+ .crow2{display:flex;gap:6px;margin-top:8px}.crow2 select,.crow2 input{background:var(--card);border:1px solid var(--line);color:var(--tx);border-radius:10px;padding:8px;font:inherit;font-size:13px}.crow2 input{flex:1;min-width:0}.crow2 .b{padding:8px 14px}
234
+ .chdlg{max-width:560px}
235
+ .chh{display:flex;align-items:center;gap:10px}.chh h2{margin:0;font-size:18px}
236
+ .chhero{margin:14px 0 10px}.chp{font-size:30px;font-weight:700;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
237
+ .chr{display:flex;gap:6px;margin:0 0 10px}
238
+ .chrb{flex:1;padding:7px 0;border-radius:9px;border:1px solid var(--line);background:var(--surf);color:var(--mut);font:inherit;font-size:12.5px;font-weight:600;cursor:pointer}
239
+ .chrb.on{background:var(--surf2);color:var(--tx);border-color:var(--acc)}
240
+ .chbox{background:var(--surf);border:1px solid var(--line);border-radius:14px;padding:8px 8px 6px}
241
+ .chsvg{display:block;width:100%;height:auto;touch-action:none;transition:opacity .2s}.chsvg.ld{opacity:.45}.chsvg:focus-visible{outline:2px solid var(--acc);outline-offset:2px;border-radius:8px}
242
+ .chl{fill:none;stroke:var(--acc);stroke-width:2;vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}
243
+ .chg{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke;stroke-dasharray:2 4}
244
+ .chx{stroke:var(--mut);stroke-width:1;vector-effect:non-scaling-stroke}
245
+ .chd{fill:var(--acc);stroke:var(--surf);stroke-width:2;vector-effect:non-scaling-stroke}
246
+ .chax{display:flex;justify-content:space-between;gap:8px;font-size:11px;color:var(--mut);margin-top:4px;font-variant-numeric:tabular-nums}
247
+ .chft{display:flex;justify-content:space-between;align-items:center;margin-top:8px;font-size:12px}
248
+ .chtbl{max-height:220px;overflow:auto;margin-top:8px;border:1px solid var(--line);border-radius:10px}.chtbl table{width:100%;border-collapse:collapse;font-size:12.5px;font-variant-numeric:tabular-nums}.chtbl th,.chtbl td{padding:6px 10px;text-align:left;border-bottom:1px solid var(--line)}.chtbl td:last-child,.chtbl th:last-child{text-align:right}
249
+ .wact{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:8px;margin-top:14px}
250
+ .wtile{display:flex;flex-direction:column;align-items:center;gap:6px;padding:11px 0 9px;border-radius:14px;background:var(--surf);border:1px solid var(--line);color:var(--tx);font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;transition:border-color .2s,transform .2s var(--ease)}
251
+ .wtile:hover{border-color:var(--acc)}.wtile:active{transform:scale(.97)}
252
+ .wtile i{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(135deg,var(--acc),var(--acc2));color:#fff}
253
+ .wtile i svg{width:18px;height:18px;margin:0;display:block}
254
+ .stk{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--surf);border-radius:12px;margin-top:8px}
255
+ .stk .r{margin-left:auto;display:flex;align-items:center;gap:8px}
256
+ .stc{font-size:11px;padding:2px 8px;border-radius:99px;background:var(--surf2);color:var(--mut);white-space:nowrap}.stc.active{color:var(--ok)}.stc.ready{color:var(--acc)}
257
+ .mvamt{display:flex;align-items:center;gap:8px}.mvamt input{flex:1}
258
+ .mvq{margin-top:12px;padding:10px 12px;background:var(--surf);border-radius:12px}
259
+ .mvr{display:flex;justify-content:space-between;gap:10px;font-size:13px;padding:3px 0;color:var(--mut)}.mvr b{color:var(--tx)}.mvr.tot{border-top:1px solid var(--line);margin-top:4px;padding-top:7px;color:var(--tx)}
260
+ .stnote{font-size:12.5px;color:var(--mut);margin-top:10px;line-height:1.5}
261
+ .bseg{display:flex;flex-wrap:wrap;gap:6px;margin:4px 0 12px}
262
+ .bsb{display:flex;align-items:center;gap:7px;padding:7px 12px 7px 7px;border-radius:12px;border:1px solid var(--line);background:var(--surf);color:var(--tx);font:inherit;font-size:13.5px;font-weight:600;cursor:pointer}
263
+ .bsb .ic{width:24px;height:24px}.bsb .ic svg{width:24px;height:24px}.bsb .cb{width:11px;height:11px;right:-3px;bottom:-3px}
264
+ .bseg .bsb:not(:has(.ic)){padding:7px 14px}
265
+ .bsb.on{border-color:var(--acc);background:var(--surf2)}
266
+ .bcust{width:90px;background:var(--surf);border:1px solid var(--line);color:var(--tx);border-radius:12px;padding:7px 10px;font:inherit;font-size:13.5px}
137
267
  </style></head><body>
138
268
  <div class="aur" aria-hidden="true"><div class="drift"><i></i><i></i><i></i></div></div>
139
269
  <div class="wrap"><div class="card">
140
- <header><svg style="--i:0" class="logo" viewBox="0 0 100 100" aria-hidden="true"><rect x="1.5" y="1.5" width="97" height="97" rx="20" fill="#0a0a0a" stroke="#2f3442" stroke-width="3"/><text x="50" y="68" font-family="ui-monospace,SFMono-Regular,monospace" font-size="56" font-weight="700" fill="#4a8eff" text-anchor="middle">t</text></svg><span class="brand" style="--i:1">Toll<span class="dot">kit</span><span class="sub">Wallet</span></span><span class="pill" style="--i:2" id="appr">…</span></header>
270
+ <header><svg style="--i:0" class="logo" viewBox="0 0 100 100" aria-hidden="true"><rect x="1.5" y="1.5" width="97" height="97" rx="20" fill="#0a0a0a" stroke="#2f3442" stroke-width="3"/><text x="50" y="68" font-family="ui-monospace,SFMono-Regular,monospace" font-size="56" font-weight="700" fill="#4a8eff" text-anchor="middle">t</text></svg><span class="brand" style="--i:1">Toll<span class="dot">kit</span></span><span class="pill" style="--i:2" id="appr">…</span></header>
141
271
  <div id="toasts" aria-live="polite"></div><main id="main"><div class="empty">Loading…</div></main>
142
272
  <nav id="nav"></nav>
143
273
  </div><p class="mut sm" style="text-align:center">Running on this computer only. Keys never leave it.</p></div>
@@ -151,9 +281,266 @@ function esc(s){return String(s==null?"":s).replace(/[&<>"']/g,function(c){retur
151
281
  function api(p,body){return fetch("/api"+p,{method:body?"POST":"GET",headers:{"x-tw-token":T,"content-type":"application/json"},body:body?JSON.stringify(body):undefined}).then(function(r){return r.json().then(function(j){if(!r.ok)throw new Error(j.error||("HTTP "+r.status));return j})})}
152
282
  function short(a){a=String(a||"");return a.length>14?a.slice(0,a.indexOf("0x")===0?6:4)+"…"+a.slice(-4):a}
153
283
  var COIN=${COIN_JSON};
154
- function tokIcon(sym,chain){var k=String(sym||"").toUpperCase();if(k==="WETH")k="ETH";var svg=COIN.coin[k];return '<div class="ic tok">'+(svg||esc(k.slice(0,3)))+(chain&&COIN.chain[chain]?'<span class="cb">'+COIN.chain[chain]+'</span>':"")+'</div>'}
284
+ var AIIC=${AI_ICON_JSON},AIO=null,AIPW="",AIMSG={},AIOPEN={},AIBUSY=null;
285
+ function aiSvg(k){var i=AIIC[k];return '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor"'+(i.rule?' fill-rule="'+i.rule+'"':'')+' d="'+i.d+'"/></svg>'}
286
+ var AIMETA={claude:["claude","Claude"],"claude-code":["claude","Claude Code"],cursor:["cursor","Cursor"],vscode:["copilot","VS Code"],codex:["terminal","Codex"],gemini:["gemini","Gemini CLI"]};
287
+ function aiBtn(){var on=((S.connect||{}).apps||[]).filter(function(a){return a.added});
288
+ if(!on.length)return '<button class="b aib" onclick="openAI()"><span class="aiic">'+aiSvg("claude")+aiSvg("cursor")+aiSvg("gemini")+'</span>Connect your AI</button>';
289
+ var icons=[],names=[];on.forEach(function(a){var m=AIMETA[a.id];if(!m)return;names.push(m[1]);if(icons.indexOf(m[0])<0)icons.push(m[0])});
290
+ var wait=(S.rules||[]).filter(function(r){return r.status==="proposed"}).length;
291
+ return '<div class="aiconn"><button class="aich" onclick="openAI()" title="Manage connected AI apps"><span class="aistk">'+icons.slice(0,4).map(function(k){return '<i class="ail-'+k+'">'+aiSvg(k)+'</i>'}).join("")+'</span><span class="aichn"><b>'+esc(names.join(" + "))+'</b><small><span class="ok">●</span> Connected</small></span></button>'+
292
+ '<button class="b aiap" onclick="goAuto()"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M13 2L4 14h7l-1 8 9-12h-7z" fill="currentColor"/></svg>Autopilot'+(wait?'<span class="aiwt">'+wait+'</span>':'')+'</button></div>'}
293
+ function goAuto(){go("autopilot");setTimeout(function(){var t=document.getElementById("chin");if(t&&!t.disabled)t.focus()},120)}
294
+ function aiCfg(){var c=S.connect||{};return c.launch||{command:"npx",args:["-y","tollkit-wallet","mcp"],env:{TOLLKIT_WALLET_PASSWORD_FILE:c.password_file||""}}}
295
+ function shq(s){s=String(s);if(/^[\w@%+=:,.\/-]+$/.test(s))return s;return (S.connect&&S.connect.windows)?'"'+s.replace(/"/g,'\\"')+'"':"'"+s.replace(/'/g,"'\\''")+"'"}
296
+ function aiEnv(flag){var e=aiCfg().env;return Object.keys(e).map(function(k){return flag+" "+shq(k+"="+e[k])}).join(" ")}
297
+ function aiCmd(){var c=aiCfg();return [c.command].concat(c.args).map(shq).join(" ")}
298
+ function aiJson(key,extra){var c=aiCfg(),o={};o[key]={};o[key]["tollkit-wallet"]=extra?Object.assign({},extra,c):c;return JSON.stringify(o,null,2)}
299
+ function aiCode(id,t){return '<div class="aicmd"><pre id="'+id+'">'+esc(t)+'</pre><button class="b g aicp" data-copy="'+id+'">Copy</button></div>'}
300
+ function b64u(s){try{return btoa(unescape(encodeURIComponent(s)))}catch(e){return btoa(s)}}
301
+ // Steps checked against each app's docs or the app itself on 2026-10-14. Change only with a source.
302
+ var AIAPPS=[
303
+ {id:"claude",name:"Claude",sub:"Desktop app",icon:"claude",hand:function(){return '<ol class="aist"><li>In the menu bar, click <b>Claude → Settings…</b>, open <b>Developer</b> and click <b>Edit Config</b>.</li><li>Put this in the file. If it already has <code>"mcpServers"</code>, add just the <code>"tollkit-wallet"</code> entry:</li></ol>'+aiCode("ai-cd",aiJson("mcpServers"))+'<ol class="aist" start="3"><li>Quit Claude completely and open it again.</li></ol>'}},
304
+ {id:"claude-code",name:"Claude Code",sub:"Terminal",icon:"claude",hand:function(){return '<p class="aip">Paste into your terminal:</p>'+aiCode("ai-cc","claude mcp add "+aiEnv("--env")+" --transport stdio --scope user tollkit-wallet -- "+aiCmd())}},
305
+ {id:"cursor",name:"Cursor",sub:"Code editor",icon:"cursor",hand:function(){var link="cursor://anysphere.cursor-deeplink/mcp/install?name=tollkit-wallet&config="+encodeURIComponent(b64u(JSON.stringify(aiCfg())));return '<p class="aip"><a class="b g aisml2" href="'+esc(link)+'">Open in Cursor</a> or add this to <span class="kbd">~/.cursor/mcp.json</span> and restart Cursor:</p>'+aiCode("ai-cu",aiJson("mcpServers"))}},
306
+ {id:"vscode",name:"VS Code",sub:"With GitHub Copilot",icon:"copilot",hand:function(){return '<p class="aip">Run <b>MCP: Open User Configuration</b> from the Command Palette and add:</p>'+aiCode("ai-vs",aiJson("servers",{type:"stdio"}))}},
307
+ {id:"codex",name:"Codex",sub:"OpenAI’s terminal agent",icon:"terminal",hand:function(){return '<p class="aip">Paste into your terminal:</p>'+aiCode("ai-cx","codex mcp add tollkit-wallet "+aiEnv("--env")+" -- "+aiCmd())}},
308
+ {id:"gemini",name:"Gemini CLI",sub:"Terminal",icon:"gemini",hand:function(){var c=aiCfg();return '<p class="aip">Paste into your terminal:</p>'+aiCode("ai-gm","gemini mcp add -s user "+aiEnv("-e")+" tollkit-wallet "+shq(c.command)+" -- "+c.args.map(shq).join(" "))}}
309
+ ];
310
+ function aiSt(id){var a=((S.connect||{}).apps||[]).filter(function(x){return x.id===id})[0];return a||{id:id,found:false,added:false,current:false,how:"file"}}
311
+ function aiRow(x,i){var st=aiSt(x.id),busy=AIBUSY===x.id,m=AIMSG[x.id],line,btn;
312
+ if(st.how==="link"){line='Opens VS Code to finish';btn='<a class="b aiadd" href="'+esc((S.connect||{}).vscode||"#")+'">Add</a>'}
313
+ else if(!st.found){line='Not found on this computer';btn=''}
314
+ else if(st.added&&st.current){line='<span class="ok">'+svgOk()+'Connected</span>';btn='<button class="b g aiadd" data-act="remove" data-id="'+x.id+'"'+(busy?" disabled":"")+'>'+(busy?"…":"Remove")+'</button>'}
315
+ else if(st.added){line='<span class="warn">Needs an update</span>';btn='<button class="b aiadd" data-act="add" data-id="'+x.id+'"'+(busy?" disabled":"")+'>'+(busy?"…":"Update")+'</button>'}
316
+ else{line='Found on this computer';btn='<button class="b aiadd" data-act="add" data-id="'+x.id+'"'+(busy?" disabled":"")+'>'+(busy?"Adding…":"Add")+'</button>'}
317
+ return '<div class="airow" style="--i:'+i+'"><div class="airt">'+aiSvg(x.icon)+'<div class="ainm"><b>'+esc(x.name)+'</b><small>'+line+'</small></div>'+btn+'</div>'+(m?'<div class="aim '+(m[0]?"ok":"bad")+'">'+esc(m[1])+'</div>':'')+'<button class="aihand" data-hand="'+x.id+'">'+(AIOPEN[x.id]?"Hide steps":"Do it by hand")+'</button>'+(AIOPEN[x.id]?'<div class="aihb">'+x.hand()+'</div>':'')+'</div>'}
318
+ function svgOk(){return '<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" style="vertical-align:-2px;margin-right:4px"><path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>'}
319
+ function aiRender(){if(!AIO)return;var c=S.connect||{},foc=document.activeElement&&document.activeElement.id==="aipw";
320
+ var h='<h2>Connect your AI</h2><p class="ailead">Let Claude, Cursor or another AI app use this wallet. It follows your limits, can’t withdraw or see your keys, and any rules it suggests wait in Autopilot for you.</p>'+
321
+ '<label class="ailbl" for="aipw">Wallet password</label><input id="aipw" class="aiin" type="password" autocomplete="current-password" placeholder="Needed to add an app" value="'+esc(AIPW)+'">'+
322
+ '<p class="aismall">'+(c.ready?'Your AI apps unlock the wallet with a saved copy of this password, readable only by your account on this computer.':'Adding an app saves a copy of this password on this computer, readable only by your account, so the app can unlock the wallet. Anyone who can read it and your wallet folder could spend within your limits.')+'</p>'+
323
+ '<div class="ailist">'+AIAPPS.map(aiRow).join("")+'</div>'+
324
+ (((S.connect||{}).apps||[]).some(function(a){return a.added})?'<button class="b aigoap" onclick="closeAI();goAuto()"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M13 2L4 14h7l-1 8 9-12h-7z" fill="currentColor"/></svg>Open Autopilot</button>':'')+'<div class="aidone"><b>Check it worked.</b> Ask your AI: <b>“What’s in my Tollkit wallet?”</b> Other apps: run <code>'+esc(aiCmd())+'</code> with <code>TOLLKIT_WALLET_PASSWORD_FILE</code> set.</div>';
325
+ var sc=AIO.querySelector(".aidlg").scrollTop;AIO.querySelector(".aibody").innerHTML=h;AIO.querySelector(".aidlg").scrollTop=sc;var pw=document.getElementById("aipw");pw.oninput=function(){AIPW=pw.value};if(foc)pw.focus()}
326
+ function openAI(){if(!AIO){AIO=document.createElement("div");AIO.className="aio";AIO.innerHTML='<div class="aidlg" role="dialog" aria-modal="true" aria-label="Connect your AI"><button class="aix" aria-label="Close">×</button><div class="aibody"></div></div>';AIO.addEventListener("mousedown",function(e){if(e.target===AIO)closeAI()});AIO.addEventListener("click",aiClick);document.body.appendChild(AIO)}aiRender();setTimeout(function(){var p=document.getElementById("aipw");if(p&&!AIPW)p.focus()},60)}
327
+ function closeAI(){if(AIO){AIO.remove();AIO=null;AIPW="";AIMSG={}}}
328
+ function aiDo(act,id){if(act!=="remove"&&!AIPW){AIMSG[id]=[0,"Type your wallet password first."];aiRender();document.getElementById("aipw").focus();return}
329
+ AIBUSY=id;AIMSG[id]=null;aiRender();api("/connect",{password:AIPW,action:act,app:id}).then(function(r){AIMSG[id]=[1,r.message];return refresh()}).catch(function(e){AIMSG[id]=[0,e.message]}).then(function(){AIBUSY=null;aiRender()})}
330
+ function aiClick(e){var t=e.target.closest("button,a");if(!t)return;if(t.classList.contains("aix"))closeAI();else if(t.hasAttribute("data-act"))aiDo(t.getAttribute("data-act"),t.getAttribute("data-id"));else if(t.hasAttribute("data-hand")){var k=t.getAttribute("data-hand");AIOPEN[k]=!AIOPEN[k];aiRender()}else if(t.hasAttribute("data-copy")){var el=document.getElementById(t.getAttribute("data-copy")),v=el?el.textContent:"",done=function(){t.textContent="Copied";setTimeout(function(){t.textContent="Copy"},1500)};if(navigator.clipboard&&navigator.clipboard.writeText)navigator.clipboard.writeText(v).then(done,function(){fallback(v);done()});else{fallback(v);done()}}}
331
+ document.addEventListener("keydown",function(e){if(AIO&&e.key==="Escape")closeAI()});
332
+ var BUY={o:null,chain:null,asset:"USDC",amt:"50",custom:"",test:false,err:null,busy:false,done:null};
333
+ var BUYA={base:["USDC","ETH"],ethereum:["USDC","ETH"],solana:["USDC","SOL"]};
334
+ var WICO={buy:'<path d="M12 5v14M5 12h14"/>',receive:'<path d="M12 4v12m0 0l-5-5m5 5l5-5M5 20h14"/>',stake:'<path d="M4 17l6-6 4 4 6-6M14 9h6v6"/>',move:'<path d="M7 7h12l-3-3M17 17H5l3 3"/>',sell:'<path d="M12 3v18M16.5 7.5c0-1.7-2-2.8-4.5-2.8S7.5 5.8 7.5 7.5s2 2.5 4.5 3 4.5 1.3 4.5 3.2-2 3-4.5 3-4.5-1.1-4.5-2.9"/>'};
335
+ function wTile(k,label,fn){return '<button class="wtile" onclick="'+fn+'"><i><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'+WICO[k]+'</svg></i>'+label+'</button>'}
336
+ function walletActions(){var ch=(S.policy&&S.policy.chains)||[];return '<div class="wact">'+wTile("buy","Buy","openBuy()")+wTile("sell","Sell","openSell()")+wTile("receive","Receive","goReceive()")+(ch.length>1?wTile("move","Move","openMove()"):"")+(ch.indexOf("solana")>=0?wTile("stake","Stake","openStake()"):"")+'</div>'}
337
+ function goReceive(){var h=[].slice.call(document.querySelectorAll("#main h3")).filter(function(x){return /Deposit/.test(x.textContent)})[0];if(h)h.scrollIntoView({behavior:"smooth",block:"start"})}
338
+ function openBuy(){var ch=(S.policy&&S.policy.chains)||["base"];BUY.chain=BUY.chain&&ch.indexOf(BUY.chain)>=0?BUY.chain:ch[0];if(BUYA[BUY.chain].indexOf(BUY.asset)<0)BUY.asset="USDC";BUY.err=null;BUY.done=null;BUY.busy=false;if(!BUY.o){BUY.o=document.createElement("div");BUY.o.className="aio";BUY.o.innerHTML='<div class="aidlg" role="dialog" aria-modal="true" aria-label="Buy crypto"><button class="aix" aria-label="Close">×</button><div class="aibody"></div></div>';BUY.o.addEventListener("mousedown",function(e){if(e.target===BUY.o)closeBuy()});BUY.o.addEventListener("click",buyClick);document.body.appendChild(BUY.o)}buyRender()}
339
+ var STK={o:null,data:null,vals:null,err:null,ok:null,busy:null,amt:"",vote:"",pw:""};
340
+ function openStake(){STK.err=null;STK.ok=null;STK.busy=null;STK.pw="";if(!STK.o){STK.o=document.createElement("div");STK.o.className="aio";STK.o.innerHTML='<div class="aidlg" role="dialog" aria-modal="true" aria-label="Stake SOL"><button class="aix" aria-label="Close">×</button><div class="aibody"></div></div>';STK.o.addEventListener("mousedown",function(e){if(e.target===STK.o)closeStake()});STK.o.addEventListener("click",stkClick);document.body.appendChild(STK.o)}stkRender();stkLoad();if(!STK.vals)api("/validators").then(function(r){STK.vals=r.validators;stkRender()}).catch(function(e){STK.err=e.message;stkRender()})}
341
+ function closeStake(){if(STK.o){STK.o.remove();STK.o=null}}
342
+ document.addEventListener("keydown",function(e){if(STK.o&&e.key==="Escape")closeStake()});
343
+ function stkLoad(){return api("/stake").then(function(d){STK.data=d;stkRender()}).catch(function(e){STK.err=e.message;stkRender()})}
344
+ function sol(l){var n=Number(l)/1e9;return n.toLocaleString(undefined,{maximumFractionDigits:n<1?6:4})+" SOL"}
345
+ function hrs(s){s=Number(s)||0;return s>=3600?"about "+Math.round(s/3600)+" h":"about "+Math.max(1,Math.round(s/60))+" min"}
346
+ var STLBL={activating:["Starting","stc"],active:["Earning","stc active"],deactivating:["Unstaking","stc"],inactive:["Ready to withdraw","stc ready"]};
347
+ function stkRender(){if(!STK.o)return;var d=STK.data,h='<h2>Stake SOL</h2><p class="ailead">Stake SOL with a validator to earn rewards. It stays under this wallet\'s control: unstake any time, then withdraw once the current epoch ends.</p>';
348
+ if(!d)h+='<div class="sm mut">Loading your stakes…</div>';else{
349
+ h+='<div class="ailbl">Your stakes</div>'+(d.accounts.length?d.accounts.map(function(a){var lb=STLBL[a.status]||[a.status,"stc"];var act=a.status==="active"||a.status==="activating"?'<button class="b g sbtn" data-su="'+esc(a.address)+'"'+(STK.busy?" disabled":"")+'>Unstake</button>':a.status==="inactive"?'<button class="b sbtn" data-sw="'+esc(a.address)+'"'+(STK.busy?" disabled":"")+'>Withdraw</button>':"";
350
+ return '<div class="stk">'+tokIcon("SOL","solana")+'<div style="min-width:0"><div>'+sol(a.status==="inactive"?a.lamports:(a.delegated||a.lamports))+'</div><div class="sm mut" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+esc(a.validator||(a.voter?short(a.voter):"Not delegated"))+'</div></div><div class="r"><span class="'+lb[1]+'">'+lb[0]+'</span>'+act+'</div></div>'}).join(""):'<div class="sm mut">No stakes yet.</div>')+
351
+ '<div class="stnote">Epoch '+d.epoch.epoch+' ends in '+hrs(d.epoch.seconds_left)+'. New stakes start earning, and unstaked SOL can be withdrawn, after it ends.</div>'}
352
+ h+='<div class="ailbl" style="margin-top:16px">New stake</div><div class="grid2"><div><label for="stamt">Amount (SOL)</label><input id="stamt" inputmode="decimal" placeholder="'+(d?d.min_delegation/1e9:1)+' or more" value="'+esc(STK.amt)+'" oninput="STK.amt=this.value.replace(/[^0-9.]/g,\'\')"></div><div><label for="stval">Validator</label><select id="stval" onchange="STK.vote=this.value">'+(STK.vals?'<option value="">Pick one</option>'+STK.vals.map(function(v){return '<option value="'+esc(v.vote)+'"'+(STK.vote===v.vote?" selected":"")+'>'+esc((v.name||short(v.vote))+" · "+v.commission+"% fee")+'</option>'}).join(""):'<option value="">Loading…</option>')+'</select></div></div>'+
353
+ '<div class="stnote">Validators are listed by how much SOL is staked with them. The fee is the share of rewards the validator keeps. '+(d?'About '+sol(d.rent_reserve)+' stays in the stake account for rent and comes back when you withdraw.':'')+'</div>'+
354
+ '<label for="stpw">Password</label><input id="stpw" type="password" autocomplete="current-password" value="'+esc(STK.pw)+'" oninput="STK.pw=this.value">'+
355
+ (STK.err?'<div class="msg err">'+esc(STK.err)+'</div>':'')+(STK.ok?'<div class="msg okk">'+esc(STK.ok)+'</div>':'')+
356
+ '<button class="b aigoap" data-sgo'+(STK.busy?" disabled":"")+'>'+(STK.busy==="stake"?"Staking…":"Stake")+'</button>';
357
+ var fid=document.activeElement&&document.activeElement.id,body=STK.o.querySelector(".aibody");body.innerHTML=h;if(fid&&/^st(amt|pw)$/.test(fid)){var i=document.getElementById(fid);i.focus();i.setSelectionRange(i.value.length,i.value.length)}}
358
+ function stkClick(e){var t=e.target.closest("button");if(!t)return;if(t.classList.contains("aix"))return closeStake();
359
+ if(t.hasAttribute("data-sgo"))stkDo({action:"stake",sol:Number(STK.amt),vote:STK.vote});
360
+ else if(t.hasAttribute("data-su"))stkDo({action:"unstake",account:t.getAttribute("data-su")});
361
+ else if(t.hasAttribute("data-sw"))stkDo({action:"withdraw",account:t.getAttribute("data-sw")})}
362
+ function stkDo(b){STK.err=null;STK.ok=null;if(!STK.pw){STK.err="Enter your password.";stkRender();return}
363
+ if(b.action==="stake"){if(!(b.sol>0)){STK.err="Enter how much SOL to stake.";stkRender();return}if(!b.vote){STK.err="Pick a validator.";stkRender();return}}
364
+ b.password=STK.pw;STK.busy=b.action;stkRender();
365
+ api("/stake",b).then(function(){STK.busy=null;STK.pw="";if(b.action==="stake")STK.amt="";STK.ok=b.action==="stake"?"Staked. It starts earning after this epoch ends.":b.action==="unstake"?"Unstaking. You can withdraw it after this epoch ends.":"Withdrawn to your wallet.";stkLoad();refresh();loadBal()}).catch(function(e){STK.busy=null;STK.err=e.message;stkRender()})}
366
+ var MV={o:null,asset:"USDC",from:null,to:null,amt:"",pw:"",q:null,qerr:null,qbusy:false,err:null,ok:null,busy:false,moves:null,t:null,seq:0};
367
+ function mvChains(){var ch=(S.policy&&S.policy.chains)||[];return MV.asset==="ETH"?ch.filter(function(c){return c!=="solana"}):ch}
368
+ function openMove(){MV.err=null;MV.ok=null;MV.busy=false;MV.pw="";var ch=mvChains();if(ch.indexOf(MV.from)<0)MV.from=ch[0];if(ch.indexOf(MV.to)<0||MV.to===MV.from)MV.to=ch.filter(function(c){return c!==MV.from})[0];
369
+ if(!MV.o){MV.o=document.createElement("div");MV.o.className="aio";MV.o.innerHTML='<div class="aidlg" role="dialog" aria-modal="true" aria-label="Move between networks"><button class="aix" aria-label="Close">×</button><div class="aibody"></div></div>';MV.o.addEventListener("mousedown",function(e){if(e.target===MV.o)closeMove()});MV.o.addEventListener("click",mvClick);document.body.appendChild(MV.o)}mvRender();mvMoves();mvQuote()}
370
+ function closeMove(){if(MV.o){MV.o.remove();MV.o=null}}
371
+ document.addEventListener("keydown",function(e){if(MV.o&&e.key==="Escape")closeMove()});
372
+ function mvMoves(){api("/moves").then(function(r){MV.moves=r.moves;mvRender()}).catch(function(){})}
373
+ function mvHave(c,a){var BL=(S.live&&S.live.balances&&Object.keys(S.live.balances).length)?S.live.balances:B;var r=BL&&BL[c];if(!r||r.error)return null;for(var i=0;i<r.length;i++)if(r[i].token===a)return Number(r[i].amount);return null}
374
+ function mvQuote(){clearTimeout(MV.t);MV.q=null;MV.qerr=null;var a=Number(MV.amt);if(!(a>0)||!MV.from||!MV.to){MV.qbusy=false;mvRender();return}MV.qbusy=true;mvRender();var my=++MV.seq;
375
+ MV.t=setTimeout(function(){api("/move/quote?asset="+MV.asset+"&from="+MV.from+"&to="+MV.to+"&amount="+encodeURIComponent(MV.amt)).then(function(q){if(my!==MV.seq)return;MV.q=q;MV.qbusy=false;mvRender()}).catch(function(e){if(my!==MV.seq)return;MV.qerr=e.message;MV.qbusy=false;mvRender()})},450)}
376
+ function mvAmt(atomic){var d=MV.asset==="USDC"?6:18,n=Number(atomic)/Math.pow(10,d);return n.toLocaleString(undefined,{maximumFractionDigits:MV.asset==="USDC"?2:6})+" "+MV.asset}
377
+ function eta(s){s=Number(s)||0;return s<90?"about "+Math.max(5,Math.round(s/5)*5)+" seconds":"about "+Math.round(s/60)+" minutes"}
378
+ var MVST={sent:["On the way","stc"],arrived:["Arrived","stc active"],failed:["Failed","stc"]};
379
+ function mvRender(){if(!MV.o)return;var ch=mvChains(),have=MV.from?mvHave(MV.from,MV.asset):null,q=MV.q;
380
+ var seg=function(attr,list,cur){return '<div class="bseg">'+list.map(function(c){return '<button '+attr+'="'+c+'" class="bsb'+(cur===c?" on":"")+'">'+chainIcon(c)+esc(CHAIN_NAMES[c]||c)+'</button>'}).join("")+'</div>'};
381
+ var h='<h2>Move between networks</h2><p class="ailead">Send your own '+(MV.asset==="ETH"?"ETH":"USDC")+' from one network to another. It arrives at this wallet\'s address there.</p>'+
382
+ '<div class="ailbl">Coin</div><div class="bseg">'+["USDC","ETH"].map(function(a){return '<button data-ma="'+a+'" class="bsb'+(MV.asset===a?" on":"")+'">'+tokIcon(a)+a+'</button>'}).join("")+'</div>'+
383
+ '<div class="ailbl">From</div>'+seg("data-mf",ch,MV.from)+'<div class="ailbl">To</div>'+seg("data-mt",ch.filter(function(c){return c!==MV.from}),MV.to)+
384
+ '<div class="ailbl">Amount</div><div class="mvamt"><input id="mvamt" inputmode="decimal" placeholder="0.00" value="'+esc(MV.amt)+'" oninput="MV.amt=this.value.replace(/[^0-9.]/g,\'\');mvQuote()"><span class="mut sm">'+MV.asset+'</span>'+(have!==null&&MV.asset==="USDC"&&have>0?'<button class="b g sbtn" data-mmax>Max</button>':'')+'</div>'+
385
+ (have!==null?'<div class="sm mut" style="margin-top:6px">Available on '+esc(CHAIN_NAMES[MV.from]||MV.from)+': '+have.toLocaleString(undefined,{maximumFractionDigits:MV.asset==="USDC"?2:6})+' '+MV.asset+'</div>':'');
386
+ if(MV.qbusy)h+='<div class="mvq sm mut">Getting a price…</div>';
387
+ else if(MV.qerr)h+='<div class="msg err">'+esc(MV.qerr)+'</div>';
388
+ else if(q)h+='<div class="mvq"><div class="mvr"><span>You send</span><b>'+mvAmt(q.amount)+'</b></div><div class="mvr"><span>Tollkit fee (0.25%)</span><span>'+mvAmt(q.tollkit_fee)+'</span></div><div class="mvr"><span>'+(q.via==="cctp"?"Circle's fee, up to":"Across fees")+'</span><span>'+mvAmt(q.bridge_fee_max)+'</span></div><div class="mvr tot"><span>You get at least</span><b>'+mvAmt(q.receive_min)+'</b></div><div class="mvr"><span>Arrives in</span><span>'+eta(q.eta_seconds)+'</span></div></div>'+
389
+ '<div class="stnote">'+(q.via==="cctp"?'Uses Circle\'s own USDC transfer (CCTP): USDC is burned here and minted there by Circle, who also pays the network fee on arrival.'+(MV.from==="solana"?" About 0.003 SOL goes to a record account Circle requires.":" Needs a little ETH on "+esc(CHAIN_NAMES[MV.from])+" for network fees."):'Powered by Across. By moving you agree to <a href="https://across.to/terms-of-service" target="_blank" rel="noopener">Across\'s terms</a>. Needs a little extra ETH for the network fee.')+'</div>';
390
+ h+='<label for="mvpw">Password</label><input id="mvpw" type="password" autocomplete="current-password" value="'+esc(MV.pw)+'" oninput="MV.pw=this.value">'+
391
+ (MV.err?'<div class="msg err">'+esc(MV.err)+'</div>':'')+(MV.ok?'<div class="msg okk">'+esc(MV.ok)+'</div>':'')+
392
+ '<button class="b aigoap" data-mgo'+(MV.busy||!q?" disabled":"")+'>'+(MV.busy?"Moving…":"Move"+(q?" "+mvAmt(q.amount):""))+'</button>';
393
+ if(MV.moves&&MV.moves.length)h+='<div class="ailbl" style="margin-top:18px">Recent moves</div>'+MV.moves.slice(0,5).map(function(m){var st=MVST[m.state]||[m.state,"stc"],d=m.asset==="USDC"?6:18;var amt=(Number(m.amount)/Math.pow(10,d)).toLocaleString(undefined,{maximumFractionDigits:m.asset==="USDC"?2:6})+" "+m.asset;
394
+ var link=EXP[m.from]?' · <a target="_blank" rel="noopener" href="'+EXP[m.from]+esc(m.tx)+'">sent</a>':"";var link2=m.dest_tx&&EXP[m.to]?' · <a target="_blank" rel="noopener" href="'+EXP[m.to]+esc(m.dest_tx)+'">arrived</a>':"";
395
+ return '<div class="stk">'+tokIcon(m.asset)+'<div style="min-width:0"><div>'+amt+'</div><div class="sm mut">'+esc(CHAIN_NAMES[m.from]||m.from)+' → '+esc(CHAIN_NAMES[m.to]||m.to)+' · '+ago(m.ts)+link+link2+'</div></div><div class="r"><span class="'+st[1]+'">'+st[0]+'</span></div></div>'}).join("");
396
+ var fid=document.activeElement&&document.activeElement.id;MV.o.querySelector(".aibody").innerHTML=h;if(fid==="mvamt"||fid==="mvpw"){var i=document.getElementById(fid);i.focus();i.setSelectionRange(i.value.length,i.value.length)}}
397
+ function mvClick(e){var t=e.target.closest("button");if(!t)return;if(t.classList.contains("aix"))return closeMove();
398
+ if(t.hasAttribute("data-ma")){MV.asset=t.getAttribute("data-ma");var ch=mvChains();if(ch.indexOf(MV.from)<0)MV.from=ch[0];if(ch.indexOf(MV.to)<0||MV.to===MV.from)MV.to=ch.filter(function(c){return c!==MV.from})[0];mvQuote()}
399
+ else if(t.hasAttribute("data-mf")){MV.from=t.getAttribute("data-mf");if(MV.to===MV.from)MV.to=mvChains().filter(function(c){return c!==MV.from})[0];mvQuote()}
400
+ else if(t.hasAttribute("data-mt")){MV.to=t.getAttribute("data-mt");mvQuote()}
401
+ else if(t.hasAttribute("data-mmax")){var hv=mvHave(MV.from,MV.asset);if(hv)MV.amt=String(Math.floor(hv*100)/100);mvQuote()}
402
+ else if(t.hasAttribute("data-mgo"))mvGo()}
403
+ function mvGo(){MV.err=null;MV.ok=null;if(!MV.pw){MV.err="Enter your password.";mvRender();return}MV.busy=true;mvRender();
404
+ api("/move",{password:MV.pw,asset:MV.asset,from:MV.from,to:MV.to,amount:MV.amt,receive_min:MV.q?MV.q.receive_min:undefined}).then(function(r){MV.busy=false;MV.pw="";MV.amt="";MV.q=null;MV.ok="Sent. It should arrive in "+eta(r.move.eta_seconds)+".";mvMoves();refresh();loadBal()}).catch(function(e){MV.busy=false;MV.err=e.message;if(/price changed/.test(e.message))mvQuote();else mvRender()})}
405
+ setInterval(function(){if(MV.o&&MV.moves&&MV.moves.some(function(m){return m.state==="sent"}))mvMoves()},10000);
406
+ var SL={o:null,chain:null,asset:"USDC",amt:"",err:null,busy:false,ref:null,sale:null,tx:null,pw:"",ok:null,t:null};
407
+ function openSell(){var ch=(S.policy&&S.policy.chains)||["base"];if(ch.indexOf(SL.chain)<0)SL.chain=ch[0];if(BUYA[SL.chain].indexOf(SL.asset)<0)SL.asset="USDC";SL.err=null;SL.ok=null;SL.busy=false;
408
+ if(!SL.o){SL.o=document.createElement("div");SL.o.className="aio";SL.o.innerHTML='<div class="aidlg" role="dialog" aria-modal="true" aria-label="Cash out"><button class="aix" aria-label="Close">×</button><div class="aibody"></div></div>';SL.o.addEventListener("mousedown",function(e){if(e.target===SL.o)closeSell()});SL.o.addEventListener("click",slClick);document.body.appendChild(SL.o)}
409
+ if(!SL.ref)api("/sells").then(function(r){var open=(r.sales||[]).filter(function(x){return (x.state==="open"||x.state==="sent")&&Date.now()-Date.parse(x.ts)<3600000})[0];if(open&&!SL.ref){SL.ref=open.ref;SL.sale=open;slPoll()}else slRender()}).catch(function(){});slRender();if(SL.ref)slPoll()}
410
+ function closeSell(){if(SL.o){SL.o.remove();SL.o=null}clearTimeout(SL.t)}
411
+ document.addEventListener("keydown",function(e){if(SL.o&&e.key==="Escape")closeSell()});
412
+ function slPoll(){clearTimeout(SL.t);if(!SL.ref||!SL.o)return;api("/sell/status?ref="+SL.ref).then(function(r){SL.sale=r.sale;SL.tx=r.transaction;slRender();var st=SL.sale&&SL.sale.state;if(st==="open"||st==="sent")SL.t=setTimeout(slPoll,st==="sent"?10000:4000)}).catch(function(e){SL.err=e.message;slRender();SL.t=setTimeout(slPoll,10000)})}
413
+ function slLeft(t){var at=Date.parse(t.updated_at||t.created_at||"");if(!isFinite(at))return "";var m=Math.max(0,Math.round((at+1800000-Date.now())/60000));return m+" min left to send"}
414
+ function slRender(){if(!SL.o)return;var ch=(S.policy&&S.policy.chains)||["base"],h='<h2>Cash out</h2>';var sale=SL.sale,t=SL.tx;
415
+ if(!SL.ref){var have=mvHave(SL.chain,SL.asset);
416
+ h+='<p class="ailead">Sell crypto to your bank or PayPal with Coinbase. You\'ll need a Coinbase account. Coinbase shows its fee and the payout before you confirm.</p>'+
417
+ '<div class="ailbl">Network</div><div class="bseg">'+ch.map(function(c){return '<button data-sc="'+c+'" class="bsb'+(SL.chain===c?" on":"")+'">'+chainIcon(c)+esc(CHAIN_NAMES[c]||c)+'</button>'}).join("")+'</div>'+
418
+ '<div class="ailbl">Coin</div><div class="bseg">'+BUYA[SL.chain].map(function(a){return '<button data-sa="'+a+'" class="bsb'+(SL.asset===a?" on":"")+'">'+tokIcon(a)+esc(a)+'</button>'}).join("")+'</div>'+
419
+ '<div class="ailbl">Amount</div><div class="mvamt"><input id="slamt" inputmode="decimal" placeholder="How much to sell" value="'+esc(SL.amt)+'" oninput="SL.amt=this.value.replace(/[^0-9.]/g,\'\')"><span class="mut sm">'+SL.asset+'</span></div>'+
420
+ (have!==null?'<div class="sm mut" style="margin-top:6px">Available: '+have.toLocaleString(undefined,{maximumFractionDigits:6})+' '+SL.asset+'</div>':'')+
421
+ (SL.err?'<div class="msg err">'+esc(SL.err)+'</div>':'')+'<button class="b aigoap" data-sgo2'+(SL.busy?" disabled":"")+'>'+(SL.busy?"Opening Coinbase…":"Continue to Coinbase")+'</button>'}
422
+ else{var st=sale&&sale.state;
423
+ if(st==="sent"||st==="sending"||st==="done"){h+='<p class="ailead">'+(st==="done"?"Done. Coinbase has paid out"+(sale.payout?" "+esc(sale.payout):"")+".":"Sent to Coinbase. It pays out once the transfer confirms, usually within a few minutes.")+'</p>'+(sale.tx&&EXP[sale.chain]?'<div class="sm"><a target="_blank" rel="noopener" href="'+EXP[sale.chain]+esc(sale.tx)+'">View the transfer</a></div>':'')+'<button class="b g aigoap" data-snew>Start another cash out</button>'}
424
+ else if(st==="failed"||st==="expired"){h+='<p class="ailead">This cash out '+(st==="failed"?"failed on Coinbase.":"expired.")+' Nothing was sent from this wallet unless Activity shows it.</p><button class="b aigoap" data-snew>Start again</button>'}
425
+ else if(t&&/STARTED/.test(t.status)&&t.to_address){var amt=t.sell_amount?t.sell_amount.value+" "+t.sell_amount.currency:"";
426
+ h+='<p class="ailead">Coinbase is ready. Send the crypto to finish.</p><div class="mvq"><div class="mvr"><span>Send</span><b>'+esc(amt)+'</b></div><div class="mvr"><span>On</span><span>'+esc(CHAIN_NAMES[t.network]||t.network)+'</span></div><div class="mvr"><span>To Coinbase</span></div><div class="addr sm" style="margin:0 0 6px">'+esc(t.to_address)+'</div>'+(t.coinbase_fee&&t.coinbase_fee.value?'<div class="mvr"><span>Coinbase fee</span><span>'+esc(t.coinbase_fee.value+" "+t.coinbase_fee.currency)+'</span></div>':'')+(t.total&&t.total.value?'<div class="mvr tot"><span>You get</span><b>'+esc(t.total.value+" "+t.total.currency)+'</b></div>':'')+'</div>'+
427
+ '<div class="stnote">'+esc(slLeft(t))+'. Network fees are paid from this wallet.</div><label for="slpw">Password</label><input id="slpw" type="password" autocomplete="current-password" value="'+esc(SL.pw)+'" oninput="SL.pw=this.value">'+
428
+ (SL.err?'<div class="msg err">'+esc(SL.err)+'</div>':'')+'<button class="b aigoap" data-ssend'+(SL.busy?" disabled":"")+'>'+(SL.busy?"Sending…":"Send "+esc(amt)+" to Coinbase")+'</button><button class="b g aigoap" data-snew>Cancel</button>'}
429
+ else{h+='<p class="ailead">Finish on Coinbase: sign in, choose where the money goes, and click <b>Cash out now</b>. Then come back here to send the crypto. This page checks every few seconds.</p><div class="sm mut">Waiting for Coinbase…</div>'+(SL.err?'<div class="msg err">'+esc(SL.err)+'</div>':'')+'<button class="b g aigoap" data-snew>Cancel</button>'}}
430
+ var fid=document.activeElement&&document.activeElement.id;SL.o.querySelector(".aibody").innerHTML=h;if(fid==="slamt"||fid==="slpw"){var i=document.getElementById(fid);i.focus();i.setSelectionRange(i.value.length,i.value.length)}}
431
+ function slClick(e){var t=e.target.closest("button");if(!t)return;if(t.classList.contains("aix"))return closeSell();
432
+ if(t.hasAttribute("data-sc")){SL.chain=t.getAttribute("data-sc");if(BUYA[SL.chain].indexOf(SL.asset)<0)SL.asset="USDC";slRender()}
433
+ else if(t.hasAttribute("data-sa")){SL.asset=t.getAttribute("data-sa");slRender()}
434
+ else if(t.hasAttribute("data-snew")){clearTimeout(SL.t);SL.ref=null;SL.sale=null;SL.tx=null;SL.err=null;SL.pw="";slRender()}
435
+ else if(t.hasAttribute("data-sgo2")){var amt=SL.amt.trim();if(!(Number(amt)>0)){SL.err="Enter how much to cash out.";slRender();return}var win=window.open("about:blank","_blank");SL.busy=true;SL.err=null;slRender();
436
+ api("/sell",{chain:SL.chain,asset:SL.asset,amount:amt}).then(function(r){SL.busy=false;SL.ref=r.ref;SL.sale={ref:r.ref,state:"open",chain:SL.chain,asset:SL.asset};if(win){win.opener=null;win.location.href=r.url}else window.open(r.url,"_blank","noopener");slRender();slPoll()}).catch(function(e){if(win)win.close();SL.busy=false;SL.err=e.message;slRender()})}
437
+ else if(t.hasAttribute("data-ssend")){if(!SL.pw){SL.err="Enter your password.";slRender();return}SL.busy=true;SL.err=null;slRender();
438
+ api("/sell/send",{password:SL.pw,ref:SL.ref}).then(function(){SL.busy=false;SL.pw="";slPoll();refresh();loadBal()}).catch(function(e){SL.busy=false;SL.err=e.message;slRender()})}}
439
+ function closeBuy(){if(BUY.o){BUY.o.remove();BUY.o=null}}
440
+ document.addEventListener("keydown",function(e){if(BUY.o&&e.key==="Escape")closeBuy()});
441
+ function buyRender(){if(!BUY.o)return;var ch=(S.policy&&S.policy.chains)||["base"],addr=BUY.chain==="solana"?S.addresses.solana:S.addresses.evm;
442
+ var h='<h2>Buy crypto</h2><p class="ailead">Pay by card or bank with Coinbase. The crypto goes straight to this wallet.</p>'+
443
+ '<div class="ailbl">Network</div><div class="bseg">'+ch.map(function(c){return '<button data-bc="'+c+'" class="bsb'+(BUY.chain===c?" on":"")+'">'+chainIcon(c)+esc(CHAIN_NAMES[c]||c)+'</button>'}).join("")+'</div>'+
444
+ '<div class="ailbl">Coin</div><div class="bseg">'+BUYA[BUY.chain].map(function(a){return '<button data-ba="'+a+'" class="bsb'+(BUY.asset===a?" on":"")+'">'+tokIcon(a)+esc(a)+'</button>'}).join("")+'</div>'+
445
+ '<div class="ailbl">Amount</div><div class="bseg">'+["25","50","100","250"].map(function(v){return '<button data-bm="'+v+'" class="bsb'+(BUY.amt===v&&!BUY.custom?" on":"")+'">$'+v+'</button>'}).join("")+'<input id="bcust" class="bcust" inputmode="decimal" placeholder="Other" value="'+esc(BUY.custom)+'" oninput="BUY.custom=this.value.replace(/[^0-9.]/g,\'\')"></div>'+
446
+ '<div class="sm mut" style="margin-top:12px">Sends to <span class="addr">'+esc(short(addr))+'</span> on '+esc(CHAIN_NAMES[BUY.chain]||BUY.chain)+'. Coinbase may ask you to sign in or confirm your ID. Tollkit never sees your payment details.</div>'+
447
+
448
+ (BUY.err?'<div class="msg err">'+esc(BUY.err)+'</div>':'')+(BUY.done?'<div class="msg okk">'+esc(BUY.done)+'</div>':'')+
449
+ '<button class="b aigoap" data-bgo'+(BUY.busy?" disabled":"")+'>'+(BUY.busy?"Opening Coinbase…":"Continue to Coinbase")+'</button>';
450
+ var f=document.activeElement&&document.activeElement.id==="bcust";BUY.o.querySelector(".aibody").innerHTML=h;if(f){var i=document.getElementById("bcust");i.focus();i.setSelectionRange(i.value.length,i.value.length)}}
451
+ function buyClick(e){var t=e.target.closest("button");if(!t)return;if(t.classList.contains("aix"))return closeBuy();
452
+ if(t.hasAttribute("data-bc")){BUY.chain=t.getAttribute("data-bc");if(BUYA[BUY.chain].indexOf(BUY.asset)<0)BUY.asset="USDC";buyRender()}
453
+ else if(t.hasAttribute("data-ba")){BUY.asset=t.getAttribute("data-ba");buyRender()}
454
+ else if(t.hasAttribute("data-bm")){BUY.amt=t.getAttribute("data-bm");BUY.custom="";buyRender()}
455
+ else if(t.hasAttribute("data-bgo"))buyGo()}
456
+ function buyGo(){var amt=BUY.custom?Number(BUY.custom):Number(BUY.amt);if(!(amt>=5&&amt<=10000)){BUY.err="Pick an amount from $5 to $10,000.";buyRender();return}
457
+ var win=window.open("about:blank","_blank");BUY.busy=true;BUY.err=null;BUY.done=null;buyRender();
458
+ api("/buy",{chain:BUY.chain,asset:BUY.asset,amount_usd:amt,sandbox:BUY.test}).then(function(r){BUY.busy=false;if(win){win.opener=null;win.location.href=r.url}else window.open(r.url,"_blank","noopener");BUY.done="Coinbase opened in a new tab"+(r.sandbox?" (test mode)":"")+". Your balance updates here once the crypto arrives.";buyRender()}).catch(function(e){if(win)win.close();BUY.busy=false;BUY.err=e.message;buyRender()})}
459
+ var CHT={o:null,coin:"",sym:"",chain:"",name:"",range:"1D",data:null,loading:false,err:null,table:false,hover:null};
460
+ function coinOf(chain,sym){var c=(S.coins||{})[chain]||{};return c[sym]||c[String(sym).toUpperCase()]||""}
461
+ function rowAttrs(coin,sym,chain,name){return coin?' rowc" tabindex="0" role="button" aria-label="'+esc((name||sym)+" price chart")+'" data-chart="'+esc(coin)+'" data-sym="'+esc(sym)+'" data-chain="'+esc(chain)+'" data-name="'+esc(name||sym)+'"':'"'}
462
+ function holdRows(){var H=S.holdings;if(!H)return {rows:"",tot:0,chg:0};var rows="",tot=0,chg=0;(H.tokens||[]).forEach(function(t){var a=Number(t.amount),val=t.usd||0;tot+=val;if(typeof t.change_24h==="number")chg+=val-val/(1+t.change_24h);
463
+ rows+='<div class="row'+rowAttrs(t.coin,t.symbol,t.chain,t.name)+'>'+tokIcon(t.symbol,t.chain)+'<div style="min-width:0"><div class="tname">'+esc(t.name||t.symbol)+'</div><div class="sm">'+(typeof t.change_24h==="number"?'<span class="'+(t.change_24h>=0?"ok":"bad")+'">'+(t.change_24h>=0?"+":"")+(t.change_24h*100).toFixed(2)+'%</span><span class="mut"> · '+esc(CHAIN_NAMES[t.chain]||t.chain)+'</span>':'<span class="mut">'+esc(CHAIN_NAMES[t.chain]||t.chain)+'</span>')+'</div></div><div class="r"><div class="usdv">'+fmtUsd(val)+'</div><div class="sm mut">'+esc(fmtAmt(a))+' '+esc(t.symbol)+'</div></div></div>'});
464
+ return {rows:rows,tot:tot,chg:chg}}
465
+ function holdFoot(){var H=S.holdings;if(!H)return "";return '<div class="hfoot">'+(H.hidden?'<span>'+H.hidden+' token'+(H.hidden>1?"s":"")+' without a price hidden</span> · ':'')+'<button class="aihand" style="padding:0" onclick="addTok()">Add a token</button></div>'+(ADDT?'<div class="row addt" style="display:block"><div class="sm mut">Base or Ethereum token not showing? Paste its contract address.</div><div class="crow2"><select id="at_chain"><option value="base">Base</option><option value="ethereum">Ethereum</option></select><input id="at_addr" placeholder="0x…" autocomplete="off" spellcheck="false"><button class="b" onclick="addTokGo()">Add</button></div>'+(ADDM?'<div class="msg '+(ADDM[0]?"okk":"err")+'">'+esc(ADDM[1])+'</div>':'')+'</div>':(ADDM&&ADDM[0]?'<div class="msg okk">'+esc(ADDM[1])+'</div>':''))}
466
+ var ADDT=false,ADDM=null;function addTok(){ADDT=!ADDT;ADDM=null;render()}
467
+ function addTokGo(){var c=document.getElementById("at_chain").value,a=document.getElementById("at_addr").value.trim();ADDM=[1,"Checking…"];render();var ce=document.getElementById("at_chain"),ae=document.getElementById("at_addr");if(ce)ce.value=c;if(ae)ae.value=a;api("/tokens/add",{chain:c,address:a}).then(function(r){ADDT=false;ADDM=[1,"Added "+r.token.symbol+". It shows here once it has a balance and a price."];refresh()}).catch(function(e){ADDM=[0,e.message];render();var ce2=document.getElementById("at_chain"),ae2=document.getElementById("at_addr");if(ce2)ce2.value=c;if(ae2)ae2.value=a})}
468
+ document.addEventListener("click",function(e){var r=e.target.closest&&e.target.closest("[data-chart]");if(r)openChart(r.getAttribute("data-chart"),r.getAttribute("data-sym"),r.getAttribute("data-chain"),r.getAttribute("data-name"))});
469
+ document.addEventListener("keydown",function(e){if(e.key==="Enter"&&document.activeElement&&document.activeElement.hasAttribute&&document.activeElement.hasAttribute("data-chart")){var r=document.activeElement;openChart(r.getAttribute("data-chart"),r.getAttribute("data-sym"),r.getAttribute("data-chain"),r.getAttribute("data-name"))}if(CHT.o&&e.key==="Escape")closeChart()});
470
+ function openChart(coin,sym,chain,name){CHT.coin=coin;CHT.sym=sym;CHT.chain=chain;CHT.name=name;CHT.data=null;CHT.err=null;CHT.table=false;CHT.hover=null;if(!CHT.o){CHT.o=document.createElement("div");CHT.o.className="aio";CHT.o.innerHTML='<div class="aidlg chdlg" role="dialog" aria-modal="true" aria-label="Price chart"><button class="aix" aria-label="Close">×</button><div class="aibody"></div></div>';CHT.o.addEventListener("mousedown",function(e){if(e.target===CHT.o)closeChart()});CHT.o.addEventListener("click",function(e){var t=e.target.closest("button");if(!t)return;if(t.classList.contains("aix"))closeChart();else if(t.hasAttribute("data-rg")){CHT.range=t.getAttribute("data-rg");loadChart()}else if(t.hasAttribute("data-tbl")){CHT.table=!CHT.table;drawChart()}});document.body.appendChild(CHT.o)}loadChart()}
471
+ function closeChart(){if(CHT.o){CHT.o.remove();CHT.o=null}}
472
+ function loadChart(){CHT.loading=true;CHT.err=null;drawChart();var want=CHT.coin+CHT.range;fetch("/api/chart?coin="+encodeURIComponent(CHT.coin)+"&range="+CHT.range,{headers:{"x-tw-token":T}}).then(function(r){return r.json().then(function(j){if(!r.ok)throw new Error(j.error||"HTTP "+r.status);return j})}).then(function(j){if(want!==CHT.coin+CHT.range)return;CHT.data=j;CHT.loading=false;CHT.hover=null;drawChart()}).catch(function(e){CHT.err=e.message;CHT.loading=false;drawChart()})}
473
+ function fmtP(p){return "$"+Number(p).toLocaleString(undefined,{maximumFractionDigits:p>=100?2:p>=1?4:p>=0.01?5:8,minimumFractionDigits:p>=100?2:0})}
474
+ function fmtT(t,rg){var d=new Date(t*1000);return rg==="1D"?d.toLocaleTimeString([],{hour:"numeric",minute:"2-digit"}):rg==="1Y"?d.toLocaleDateString([],{month:"short",day:"numeric",year:"numeric"}):d.toLocaleDateString([],{month:"short",day:"numeric"})+(rg==="1W"?" "+d.toLocaleTimeString([],{hour:"numeric"}):"")}
475
+ function drawChart(){if(!CHT.o)return;var D=CHT.data,P=D&&D.points||[],W=Math.max(260,((CHT.o.querySelector(".aibody")||{}).clientWidth||496)-18),H=200,pad={l:6,r:6,t:12,b:18};
476
+ var head='<div class="chh">'+tokIcon(CHT.sym,CHT.chain)+'<div><h2>'+esc(CHT.name)+'</h2><div class="sm mut">'+esc(CHT.sym)+' · '+esc(CHAIN_NAMES[CHT.chain]||CHT.chain)+'</div></div></div>';
477
+ var rg='<div class="chr" role="group" aria-label="Time range">'+["1D","1W","1M","1Y"].map(function(r){return '<button data-rg="'+r+'" class="chrb'+(CHT.range===r?" on":"")+'" aria-pressed="'+(CHT.range===r)+'">'+r+'</button>'}).join("")+'</div>';
478
+ if(!P.length){CHT.o.querySelector(".aibody").innerHTML=head+'<div class="chhero"><span class="skel" style="width:120px"></span></div>'+rg+'<div class="chbox">'+(CHT.err?'<div class="msg err">'+esc(CHT.err)+'</div>':'<div class="mut sm" style="padding:80px 0;text-align:center">Loading prices…</div>')+'</div>';return}
479
+ var first=P[0].p,last=P[P.length-1].p,chg=(last-first)/first,mn=Math.min.apply(null,P.map(function(x){return x.p})),mx=Math.max.apply(null,P.map(function(x){return x.p})),t0=P[0].t,t1=P[P.length-1].t;
480
+ var X=function(t){return pad.l+(t1===t0?0:(t-t0)/(t1-t0))*(W-pad.l-pad.r)},Y=function(p){return pad.t+(mx===mn?.5:(mx-p)/(mx-mn))*(H-pad.t-pad.b)};
481
+ var d=P.map(function(x,i){return (i?"L":"M")+X(x.t).toFixed(1)+" "+Y(x.p).toFixed(1)}).join(""),hv=CHT.hover!==null?P[CHT.hover]:null,cur=hv||P[P.length-1],up=chg>=0;
482
+ var cc=(cur.p-first)/first,up2=cc>=0;var hero='<div class="chhero"><div class="chp">'+fmtP(cur.p)+'</div><div class="sm"><span class="'+(up2?"ok":"bad")+'">'+(up2?"▲ +":"▼ ")+(cc*100).toFixed(2)+'%</span><span class="mut"> '+(hv?"by "+esc(fmtT(hv.t,CHT.range)):({"1D":"past day","1W":"past week","1M":"past month","1Y":"past year"})[CHT.range])+'</span></div></div>';
483
+ var grid=[0,.5,1].map(function(f){var y=pad.t+f*(H-pad.t-pad.b);return '<line x1="'+pad.l+'" x2="'+(W-pad.r)+'" y1="'+y+'" y2="'+y+'" class="chg"/>'}).join("");
484
+ var svg='<svg class="chsvg'+(CHT.loading?" ld":"")+'" viewBox="0 0 '+W+' '+H+'" tabindex="0" role="img" aria-label="'+esc(CHT.sym+" price, "+CHT.range+": from "+fmtP(first)+" to "+fmtP(last))+'">'+grid+'<path d="'+d+'" class="chl"/>'+(hv?'<line x1="'+X(hv.t)+'" x2="'+X(hv.t)+'" y1="'+pad.t+'" y2="'+(H-pad.b)+'" class="chx"/><circle cx="'+X(hv.t)+'" cy="'+Y(hv.p)+'" r="4.5" class="chd"/>':'')+'</svg>';
485
+ var axis='<div class="chax"><span>'+esc(fmtT(t0,CHT.range))+'</span><span class="mut">High '+fmtP(mx)+' · Low '+fmtP(mn)+'</span><span>'+esc(fmtT(t1,CHT.range))+'</span></div>';
486
+ var tbl=CHT.table?'<div class="chtbl"><table><thead><tr><th>Time</th><th>Price</th></tr></thead><tbody>'+P.slice().reverse().map(function(x){return '<tr><td>'+esc(fmtT(x.t,CHT.range))+'</td><td>'+fmtP(x.p)+'</td></tr>'}).join("")+'</tbody></table></div>':'';
487
+ CHT.o.querySelector(".aibody").innerHTML=head+hero+rg+'<div class="chbox">'+svg+axis+'</div><div class="chft"><button class="aihand" style="padding:0" data-tbl>'+(CHT.table?"Hide table":"Show as table")+'</button><span class="mut">Prices: DefiLlama</span></div>'+tbl;
488
+ var s=CHT.o.querySelector(".chsvg");var pick=function(clientX){var r=s.getBoundingClientRect(),x=(clientX-r.left)/r.width*W,best=0,bd=1e9;P.forEach(function(p,i){var dd=Math.abs(X(p.t)-x);if(dd<bd){bd=dd;best=i}});if(best!==CHT.hover){CHT.hover=best;drawChart()}};
489
+ s.addEventListener("pointermove",function(e){pick(e.clientX)});s.addEventListener("pointerleave",function(){CHT.hover=null;drawChart()});
490
+ s.addEventListener("keydown",function(e){if(e.key==="ArrowLeft"||e.key==="ArrowRight"){e.preventDefault();var i=CHT.hover===null?P.length-1:CHT.hover;CHT.hover=Math.max(0,Math.min(P.length-1,i+(e.key==="ArrowLeft"?-1:1)));drawChart();CHT.o.querySelector(".chsvg").focus()}else if(e.key==="Escape"&&CHT.hover!==null){e.stopPropagation();CHT.hover=null;drawChart()}})}
491
+ document.addEventListener("keydown",function(e){if(BK.o&&e.key==="Escape")closeBk()});
492
+ var BK={o:null,step:"pw",pw:"",err:null,data:null,shown:false,quiz:null,ans:{},saved:false};
493
+ function bkBanner(){var b=S.backup;if(!b||b.done)return "";return '<div class="bkb"><div class="bki"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M12 8v5M12 16.5v.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg></div><div class="bkt"><b>Back up your wallet</b><small>'+(b.kind==="phrase"?"Write down your 12-word recovery phrase. It’s the only way back in if this computer is lost.":"Save your two backup keys. They’re the only way back in if this computer is lost.")+'</small></div><button class="b bkgo" onclick="openBk()">Back up</button></div>'}
494
+ function bkLink(){var b=S.backup;if(!b||!b.done)return "";return '<div class="bkl"><button class="aihand" style="padding:0" onclick="openBk()">'+(b.kind==="phrase"?"View recovery phrase":"View backup keys")+'</button></div>'}
495
+ function openBk(){BK={o:null,step:"pw",pw:"",err:null,data:null,shown:false,quiz:null,ans:{},saved:false};BK.o=document.createElement("div");BK.o.className="aio";BK.o.innerHTML='<div class="aidlg" role="dialog" aria-modal="true" aria-label="Back up your wallet"><button class="aix" aria-label="Close">×</button><div class="aibody"></div></div>';BK.o.addEventListener("mousedown",function(e){if(e.target===BK.o)closeBk()});BK.o.addEventListener("click",bkClick);document.body.appendChild(BK.o);bkRender();setTimeout(function(){var p=document.getElementById("bkpw");if(p)p.focus()},60)}
496
+ function closeBk(){if(BK.o){BK.o.remove()}BK={o:null,step:"pw",pw:"",err:null,data:null,shown:false,quiz:null,ans:{},saved:false}}
497
+ function bkWords(){return BK.data&&BK.data.phrase?BK.data.phrase.split(" "):[]}
498
+ function bkRender(){if(!BK.o)return;var kind=(S.backup||{}).kind,h="";
499
+ if(BK.step==="pw")h='<h2>'+(kind==="phrase"?"Your recovery phrase":"Your backup keys")+'</h2><p class="ailead">'+(kind==="phrase"?"12 words that restore this wallet here, or in MetaMask and Phantom. Anyone who sees them can take your funds, so check no one is looking.":"This wallet was made before recovery phrases, so its backup is two private keys: one for Base/Ethereum, one for Solana. Anyone who sees them can take your funds, so check no one is looking.")+'</p><label class="ailbl" for="bkpw">Wallet password</label><input id="bkpw" class="aiin" type="password" autocomplete="current-password" value="'+esc(BK.pw)+'" oninput="BK.pw=this.value" onkeydown="if(event.key===\'Enter\')bkReveal()">'+(BK.err?'<div class="msg err">'+esc(BK.err)+'</div>':'')+'<button class="b aigoap" onclick="bkReveal()">Show '+(kind==="phrase"?"my recovery phrase":"my backup keys")+'</button>';
500
+ else if(BK.step==="show"&&BK.data.kind==="phrase"){var w=bkWords();h='<h2>Write these down</h2><p class="ailead">On paper, in order. Don’t screenshot them or save them in email or notes.</p><div class="bkgrid'+(BK.shown?"":" bkblur")+'">'+w.map(function(x,i){return '<div><span>'+(i+1)+'</span>'+esc(x)+'</div>'}).join("")+(BK.shown?'':'<button class="bkreveal" data-bk="show">Tap to show</button>')+'</div><button class="b aigoap" data-bk="quiz"'+(BK.shown?"":" disabled")+'>I wrote them down</button>'}
501
+ else if(BK.step==="show"){var d=BK.data;h='<h2>Save both keys</h2><p class="ailead">Somewhere offline, like a password manager or on paper. Other wallets import them with “Import private key”.</p><div class="bkkey"><small>Base / Ethereum</small><div class="aicmd"><pre id="bk-evm" class="'+(BK.shown?"":"bkblur")+'">'+esc(d.evm_private_key)+'</pre><button class="b g aicp" data-copy="bk-evm">Copy</button></div></div><div class="bkkey"><small>Solana</small><div class="aicmd"><pre id="bk-sol" class="'+(BK.shown?"":"bkblur")+'">'+esc(d.solana_secret_key)+'</pre><button class="b g aicp" data-copy="bk-sol">Copy</button></div></div>'+(BK.shown?'':'<button class="b g aisml" data-bk="show">Show keys</button>')+'<label class="bkchk"><input type="checkbox" '+(BK.saved?"checked":"")+' onchange="BK.saved=this.checked;bkRender()"> I saved both keys somewhere safe</label><button class="b aigoap" data-bk="done"'+(BK.saved?"":" disabled")+'>Done</button>'}
502
+ else if(BK.step==="quiz"){var w2=bkWords();h='<h2>Check your copy</h2><p class="ailead">Pick the right word for each number.</p>'+BK.quiz.map(function(q){return '<div class="bkq"><b>Word #'+(q.i+1)+'</b><div class="bkopts">'+q.opts.map(function(o){var st=BK.ans[q.i]===o?(o===w2[q.i]?" ok":" bad"):"";return '<button class="bkopt'+st+'" data-q="'+q.i+'" data-o="'+esc(o)+'">'+esc(o)+'</button>'}).join("")+'</div></div>'}).join("")+(BK.err?'<div class="msg err">'+esc(BK.err)+'</div>':'')+'<div class="btns"><button class="b g" data-bk="back">Show the words again</button><button class="b" data-bk="check">Confirm</button></div>'}
503
+ else h='<div class="bkdone">'+svgOk()+'<h2>Backed up</h2><p class="ailead">Keep '+(BK.data&&BK.data.kind==="keys"?"those keys":"those 12 words")+' safe and offline. To restore, run <code>tollkit-wallet restore'+(BK.data&&BK.data.kind==="keys"?" --keys":"")+'</code>'+(BK.data&&BK.data.kind==="phrase"?", or import the phrase into MetaMask or Phantom":"")+'.</p><button class="b aigoap" data-bk="close">Close</button></div>';
504
+ BK.o.querySelector(".aibody").innerHTML=h}
505
+ function bkReveal(){if(!BK.pw){BK.err="Type your wallet password.";bkRender();return}api("/backup/reveal",{password:BK.pw}).then(function(d){BK.data=d;BK.pw="";BK.err=null;BK.step="show";bkRender()}).catch(function(e){BK.err=e.message;bkRender()})}
506
+ function bkQuiz(){var w=bkWords(),idx=[];while(idx.length<3){var r=Math.floor(Math.random()*w.length);if(idx.indexOf(r)<0)idx.push(r)}idx.sort(function(a,b){return a-b});BK.quiz=idx.map(function(i){var o=[w[i]];while(o.length<4){var c=w[Math.floor(Math.random()*w.length)];if(o.indexOf(c)<0)o.push(c)}o.sort(function(){return Math.random()-.5});return {i:i,opts:o}});BK.ans={}}
507
+ function bkDone(){api("/backup/done",{}).then(function(){BK.step="done";bkRender();refresh()}).catch(function(e){BK.err=e.message;bkRender()})}
508
+ function bkClick(e){var t=e.target.closest("button");if(!t)return;if(t.classList.contains("aix"))return closeBk();var a=t.getAttribute("data-bk");
509
+ if(t.hasAttribute("data-copy")){var el=document.getElementById(t.getAttribute("data-copy")),v=el?el.textContent:"",done=function(){t.textContent="Copied";setTimeout(function(){t.textContent="Copy"},1500)};if(navigator.clipboard&&navigator.clipboard.writeText)navigator.clipboard.writeText(v).then(done,function(){fallback(v);done()});else{fallback(v);done()}return}
510
+ if(t.hasAttribute("data-q")){BK.ans[Number(t.getAttribute("data-q"))]=t.getAttribute("data-o");BK.err=null;bkRender();return}
511
+ if(a==="show"){BK.shown=true;bkRender()}else if(a==="quiz"){bkQuiz();BK.step="quiz";BK.err=null;bkRender()}else if(a==="back"){BK.step="show";bkRender()}
512
+ else if(a==="check"){var w=bkWords();if(BK.quiz.every(function(q){return BK.ans[q.i]===w[q.i]}))bkDone();else{BK.err="Not quite. Check your copy against the words and try again.";bkRender()}}
513
+ else if(a==="done")bkDone();else if(a==="close")closeBk()}
514
+ var LASTAP=null,CH={seen:null,n:-1,top:null,draft:"",pw:"",err:null,sending:false,manual:false},CHIPS=["Protect my ETH if the price drops hard","Buy SOL on dips, about $20 at a time","Set up a weekly buy I can stick with","How are my rules doing?"];
515
+ function fmtAi(t){var B=/^\s*([-•*]|\d+\.)\s+/,h=esc(t).replace(/\*\*([^*]+)\*\*/g,"<b>$1</b>").replace(/\x60([^\x60]+)\x60/g,"<code>$1</code>");return h.split(/\n{2,}/).map(function(p){var out="",ul=[],tx=[];function fT(){if(tx.length){out+='<p>'+tx.join("<br>")+'</p>';tx=[]}}function fU(){if(ul.length){out+='<ul>'+ul.map(function(l){return '<li>'+l+'</li>'}).join("")+'</ul>';ul=[]}}p.split("\n").forEach(function(l){if(B.test(l)){fT();ul.push(l.replace(B,""))}else{fU();tx.push(l)}});fT();fU();return out}).join("")}
516
+ function tm(ts){var d=new Date(ts);return d.toLocaleTimeString([],{hour:"numeric",minute:"2-digit"})+(Date.now()-d>864e5?" · "+d.toLocaleDateString([],{month:"short",day:"numeric"}):"")}
517
+ function aiName(){var A=S.ai||{};return A.provider?A.provider.name:"AI"}
518
+ function ckey(m){return m.role==="you"?"y:"+m.text:(m.id||m.ts)}
519
+ function chatMsg(m){var nw=CH.seen&&!CH.seen[ckey(m)]?" tc-new":"";if(m.role==="you")return '<div class="tc-cm tc-you'+nw+'"><div class="tc-cb">'+esc(m.text).replace(/\n/g,"<br>")+'</div></div>';
520
+ if(m.checkin&&m.quiet&&!m.error)return '<div class="tc-cq">'+svgClock()+'Check-in '+tm(m.ts)+' · '+esc(m.text.replace(/^No changes[.:,]?\s*/i,"No changes. "))+'</div>';
521
+ var cards=(m.rules||[]).map(function(id){var r=(S.rules||[]).filter(function(x){return x.id===id})[0];return r?ruleCard(r):""}).join("");
522
+ return '<div class="tc-cm tc-ai'+nw+(m.error?" tc-err":"")+'"><div class="tc-cav">'+aiSvg("claude")+'</div><div class="tc-cbw">'+(m.checkin?'<div class="tc-ctag">Check-in · '+tm(m.ts)+'</div>':'')+'<div class="tc-cb">'+fmtAi(m.text)+'</div>'+(cards?'<div class="tc-ccards">'+cards+'</div>':'')+'</div></div>'}
523
+ function svgClock(){return '<svg viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/><path d="M12 7v5l3 2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>'}
524
+ function chatBox(){var A=S.ai;if(!A)return "";var M=A.messages||[],busy=A.busy||CH.sending;
525
+ var head='<div class="tc-chd"><div class="tc-chn">'+aiSvg("claude")+'<div><b>'+esc(aiName())+'</b><small>'+(busy?'<span class="ok">Working…</span>':A.provider?'Your Claude, on this computer':'Not set up')+'</small></div></div>'+
526
+ '<div class="tc-chr"><label class="tc-chk" title="Let the AI review your rules on a schedule">'+svgClock()+'<select onchange="setCheckin(this.value)">'+[[0,"Check-ins off"],[1,"Every hour"],[4,"Every 4 hours"],[12,"Every 12 hours"],[24,"Daily"]].map(function(o){return '<option value="'+o[0]+'"'+(A.checkin.every_hours===o[0]?" selected":"")+'>'+o[1]+'</option>'}).join("")+'</select></label>'+(M.length?'<button class="b g tc-chx'+(CH.confirmNew?" tc-warn":"")+'" onclick="newChat()" title="Start over. Your rules stay on.">'+(CH.confirmNew?'Clear chat?':'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z M14 6l4 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/></svg>New chat')+'</button>':'')+'</div></div>';
527
+ if(!A.provider)return '<div class="tc-chat">'+head+'<div class="tc-cempty"><b>Chat with your AI here</b><p>Install <b>Claude Code</b> on this computer and sign in once (run <code>claude</code> in Terminal). Then reopen this page. It uses your own Claude plan; the wallet never sees your Claude login.</p></div></div>';
528
+ var body=M.length?M.map(chatMsg).join(""):'<div class="tc-cempty"><b>What do you want your autopilot to do?</b><p>Say it in your own words. '+esc(aiName())+' checks prices and your balances, then suggests rules you can turn on with one tap.</p><div class="tc-cchips">'+CHIPS.map(function(c,i){return '<button class="tc-cchip" onclick="chatSend(CHIPS['+i+'])">'+esc(c)+'</button>'}).join("")+'</div></div>';
529
+ if(busy)body+='<div class="tc-cm tc-ai"><div class="tc-cav">'+aiSvg("claude")+'</div><div class="tc-cbw"><div class="tc-cb tc-ctyping"><i></i><i></i><i></i><span>'+(A.busy&&A.busy.kind==="checkin"?"Checking in on your rules…":"Looking at prices and your wallet…")+'</span></div></div></div>';
530
+ var pw=!A.ready?'<div class="tc-cpw"><div class="sm">One time: your wallet password lets '+esc(aiName())+' read your balances. It’s saved on this computer, readable only by your account.</div><div class="tc-crow"><input id="chpw" type="password" placeholder="Wallet password" value="'+esc(CH.pw)+'" oninput="CH.pw=this.value"><button class="b" onclick="chatPw()">Save</button></div></div>':"";
531
+ var inp='<div class="tc-cin"><textarea id="chin" rows="1" placeholder="Message '+esc(aiName())+'…" oninput="CH.draft=this.value;grow(this)" onkeydown="if(event.key===\'Enter\'&&!event.shiftKey){event.preventDefault();chatSend()}"'+(A.ready?"":" disabled")+'>'+esc(CH.draft)+'</textarea><button class="b tc-csend" onclick="chatSend()"'+(busy||!A.ready?" disabled":"")+' aria-label="Send"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M5 12h13M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button></div>';
532
+ return '<div class="tc-chat">'+head+'<div class="tc-cbody" id="cbody">'+body+'</div>'+(CH.err?'<div class="msg tc-err" style="margin:0 12px 10px">'+esc(CH.err)+'</div>':'')+pw+inp+'</div>'}
533
+ function grow(t){t.style.height="auto";t.style.height=Math.min(140,t.scrollHeight)+"px"}
534
+ function chatAfter(){var b=document.getElementById("cbody");if(!b)return;CH.seen=CH.seen||{};((S.ai||{}).messages||[]).forEach(function(m){CH.seen[ckey(m)]=1});var n=((S.ai||{}).messages||[]).length+((S.ai||{}).busy||CH.sending?1:0);if(n!==CH.n||CH.top===null){var ms=b.querySelectorAll(".tc-cm"),last=ms[ms.length-1];b.style.scrollBehavior="auto";b.scrollTop=last&&last.offsetHeight>b.clientHeight-16?last.offsetTop-8:b.scrollHeight;b.style.scrollBehavior=""}else b.scrollTop=CH.top;CH.n=n;b.onscroll=function(){CH.top=b.scrollTop};var t=document.getElementById("chin");if(t){grow(t);if(CH.refocus!==undefined&&CH.refocus!==null){t.focus();try{t.setSelectionRange(CH.refocus,CH.refocus)}catch(e){}CH.refocus=null}}}
535
+ function chatSend(text){var t=(text!==undefined?text:CH.draft).trim();if(!t||CH.sending)return;CH.sending=true;CH.err=null;if(text===undefined)CH.draft="";S.ai.messages=(S.ai.messages||[]).concat([{role:"you",text:t,ts:new Date().toISOString()}]);render();
536
+ api("/ai/chat",{text:t}).then(function(){return refresh()}).catch(function(e){CH.err=e.message;if(text===undefined)CH.draft=t}).then(function(){CH.sending=false;render()})}
537
+ function chatPw(){api("/ai/password",{password:CH.pw}).then(function(){CH.pw="";CH.err=null;return refresh()}).catch(function(e){CH.err=e.message;render()})}
538
+ function setCheckin(h){api("/ai/checkin",{hours:Number(h)}).then(function(){flash=[1,Number(h)?"Check-ins on. "+aiName()+" will review your rules and suggest changes for you to approve.":"Check-ins off."];refresh()}).catch(function(e){flash=[0,e.message];render()})}
539
+ function newChat(){if(!CH.confirmNew){CH.confirmNew=true;LASTAP=null;render();clearTimeout(CH.cnT);CH.cnT=setTimeout(function(){CH.confirmNew=false;LASTAP=null;render()},4000);return}
540
+ clearTimeout(CH.cnT);CH.confirmNew=false;api("/ai/new",{}).then(function(){CH.n=-1;CH.top=null;flash=[1,"New chat started. Your rules are unchanged."];return refresh()}).catch(function(e){flash=[0,e.message];render()})}
541
+ function tokIcon(sym,chain){var k=String(sym||"").toUpperCase();if(k==="WETH")k="ETH";var svg=COIN.coin[k];return '<div class="ic tok">'+(svg||'<span class="tl">'+esc(k.slice(0,4))+'</span>')+(chain&&COIN.chain[chain]?'<span class="cb">'+COIN.chain[chain]+'</span>':"")+'</div>'}
155
542
  function chainIcon(chain,extra){return '<div class="ic tok">'+(COIN.chain[chain]||esc(String(chain||"?")[0].toUpperCase()))+(extra&&COIN.chain[extra]?'<span class="cb">'+COIN.chain[extra]+'</span>':"")+'</div>'}
156
- function kindIcon(e){var m=/^[\d.,]+ ([A-Za-z]+)/.exec(e.detail||"");if(e.kind!=="payment"&&m)return tokIcon(m[1],e.chain);return '<div class="ic tok"><span class="glyph">'+(e.kind==="payment"?"P":e.kind==="trade"?"S":"W")+'</span>'+(COIN.chain[e.chain]?'<span class="cb">'+COIN.chain[e.chain]+'</span>':"")+'</div>'}
543
+ function kindIcon(e){if(e.kind==="stake")return tokIcon("SOL","solana");var m=/^[\d.,]+ ([A-Za-z]+)/.exec(e.detail||"");if(e.kind!=="payment"&&m)return tokIcon(m[1],e.chain);return '<div class="ic tok"><span class="glyph">'+(e.kind==="payment"?"P":e.kind==="trade"?"S":"W")+'</span>'+(COIN.chain[e.chain]?'<span class="cb">'+COIN.chain[e.chain]+'</span>':"")+'</div>'}
157
544
  function amt(x){var n=Number(x);return isFinite(n)?n.toLocaleString(undefined,{maximumFractionDigits:6}):String(x)}
158
545
  var NAMES={ETH:"Ethereum",WETH:"Wrapped Ether",USDC:"USD Coin",USDT:"Tether",SOL:"Solana"},CHAIN_NAMES={base:"Base",ethereum:"Ethereum",solana:"Solana"},prevVal={};
159
546
  function priceOf(sym){var p=S&&S.prices&&S.prices.prices;return p&&p[String(sym).toUpperCase()]||null}
@@ -164,17 +551,20 @@ function stLabel(s){return {proposed:"Waiting for you",active:"On",paused:"Pause
164
551
  function away(r){var p=r.state&&r.state.last_price;if(!p)return "";var t=r.kind==="trailing_stop"&&r.state.high?r.state.high*(1-r.trail_pct/100):r.trigger_price;if(!t)return " · now "+fmtUsd(p);var d=(p-t)/p*100;return " · now "+fmtUsd(p)+", trigger "+fmtUsd(t)+" ("+Math.abs(d).toFixed(1)+"% "+(d>0?"above":"below")+")"}
165
552
  function ruleCard(r){var fills=(r.state&&r.state.fills||[]).filter(function(f){return f.ok});var last=fills[fills.length-1];var btn=function(id,st,lbl,cls){return '<button class="b '+cls+'" style="padding:7px 11px;font-size:12px" onclick="setRule(\''+id+'\',\''+st+'\')">'+lbl+'</button>'};
166
553
  var acts=r.status==="proposed"?btn(r.id,"active","Turn on","ok")+btn(r.id,"cancelled","Dismiss","no"):r.status==="active"?btn(r.id,"paused","Pause","g")+btn(r.id,"cancelled","Cancel","no"):(r.status==="paused"||r.status==="error")?btn(r.id,"active","Turn on","ok")+btn(r.id,"cancelled","Cancel","no"):"";
167
- return '<div class="row rule-'+r.status+'" style="display:block"><div style="display:flex;gap:10px;align-items:center">'+tokIcon(r.token,r.chain)+'<div style="min-width:0"><div>'+esc(r.description)+'</div><div class="sm mut">'+esc(stLabel(r.status))+(r.created_by==="agent"?" · from agent":"")+(r.group&&(S.rules||[]).filter(function(x){return x.group===r.group&&x.id!==r.id}).length?" · linked: when one fills, the other cancels":"")+(r.status==="active"?esc(away(r)):"")+(fills.length?" · "+fills.length+" fill"+(fills.length>1?"s":""):"")+(last&&last.tx&&EXP[r.chain]&&!last.paper?' · <a target="_blank" rel="noopener" href="'+EXP[r.chain]+esc(last.tx)+'">last tx</a>':"")+'</div>'+(r.state&&r.state.last_error&&r.status!=="done"?'<div class="sm warn">'+esc(r.state.last_error)+'</div>':"")+'</div></div>'+(acts?'<div style="display:flex;gap:6px;margin-top:10px;justify-content:flex-end">'+acts+'</div>':"")+'</div>'}
168
- function autopilot(){var R=S.rules||[],A=S.autopilot||{};var st=A.here?'<span class="ok">● Running here</span> · checks prices every 15s':A.running?'<span class="ok">● Running</span> in another window ('+esc(A.mode||"live")+')':'<span class="warn">● Not running</span>';
169
- var prop=R.filter(function(r){return r.status==="proposed"}),act=R.filter(function(r){return r.status==="active"||r.status==="paused"||r.status==="error"}),hist=R.filter(function(r){return ["done","cancelled","expired"].indexOf(r.status)>=0}).slice(0,15);
554
+ return '<div class="row rule-'+r.status+'" style="display:block"><div style="display:flex;gap:10px;align-items:center">'+tokIcon(r.token,r.chain)+'<div style="min-width:0"><div>'+esc(r.description)+'</div><div class="sm mut">'+esc(stLabel(r.status))+(r.created_by==="agent"?" · suggested by AI":"")+(r.group&&(S.rules||[]).filter(function(x){return x.group===r.group&&x.id!==r.id}).length?" · linked: when one fills, the other cancels":"")+(r.status==="active"?esc(away(r)):"")+(fills.length?" · "+fills.length+" fill"+(fills.length>1?"s":""):"")+(last&&last.tx&&EXP[r.chain]&&!last.paper?' · <a target="_blank" rel="noopener" href="'+EXP[r.chain]+esc(last.tx)+'">last tx</a>':"")+'</div>'+(r.state&&r.state.last_error&&r.status!=="done"?'<div class="sm warn">'+esc(r.state.last_error)+'</div>':"")+(r.reason?'<div class="tc-why">'+esc(r.reason)+'</div>':"")+(r.replaces?'<div class="sm mut">Replaces: '+esc(((S.rules||[]).filter(function(x){return x.id===r.replaces})[0]||{description:r.replaces}).description)+'</div>':"")+(r.state&&r.state.note?'<div class="sm mut">'+esc(r.state.note)+'</div>':"")+'</div></div>'+(acts?'<div style="display:flex;gap:6px;margin-top:10px;justify-content:flex-end">'+acts+'</div>':"")+'</div>'}
555
+ function autopilot(){var R=S.rules||[],A=S.autopilot||{};var st=A.here?'<span class="ok">● Autopilot on</span> · checks prices every 15s':A.running?'<span class="ok">● Autopilot on</span> in another window ('+esc(A.mode||"live")+')':'<span class="warn">● Autopilot not running</span>';
556
+ var inChat={};((S.ai||{}).messages||[]).forEach(function(m){(m.rules||[]).forEach(function(id){inChat[id]=1})});
557
+ var prop=R.filter(function(r){return r.status==="proposed"&&!inChat[r.id]}),act=R.filter(function(r){return r.status==="active"||r.status==="paused"||r.status==="error"}),hist=R.filter(function(r){return ["done","cancelled","expired"].indexOf(r.status)>=0}).slice(0,15);
170
558
  var pv=PV?'<div class="ap" style="margin-top:10px"><div class="sm mut" style="margin-bottom:6px">Preview: nothing runs until you turn it on</div>'+(PV.rules.length?PV.rules.map(function(r){return '<div style="margin:4px 0">• '+esc(r.description)+(r.problem?' <span class="bad sm">('+esc(r.problem)+')</span>':"")+'</div>'}).join(""):'<div class="bad">No rules understood.</div>')+(PV.unparsed.length?'<div class="sm warn" style="margin-top:6px">Not understood: '+esc(PV.unparsed.join(" · "))+'</div>':"")+'<div class="btns"><button class="b g" onclick="PV=null;render()">Discard</button><button class="b ok"'+(PV.rules.length&&!PV.rules.some(function(r){return r.problem})?"":" disabled")+' onclick="addRules()">Turn on</button></div></div>':"";
171
- return '<h3>Autopilot</h3><div class="sm">'+st+'</div><div class="sm mut" style="margin-top:4px">Every fill follows your limits and approvals and is charged the same 0.5% fee as any swap. '+(A.agent_rules==="auto"?"Agents can turn rules on without asking you.":"Rules an agent suggests wait here for you.")+'</div>'+
172
- '<h3>New strategy</h3><textarea id="stxt" rows="3" placeholder="e.g. Buy $25 of SOL every Monday. Sell half my ETH if it rises 10% and all if it falls 8%." style="width:100%;background:var(--surf);border:1px solid var(--line);color:var(--tx);border-radius:10px;padding:10px;font:inherit;resize:vertical"></textarea>'+
173
- '<div class="toggles" style="margin-top:6px">'+TEMPL.map(function(t,i){return '<button class="chip" style="border:0;cursor:pointer" onclick="document.getElementById(\'stxt\').value=TEMPL['+i+']">'+esc(t)+'</button>'}).join("")+'</div>'+
174
- '<div style="margin-top:10px"><button class="b" onclick="preview()">Preview</button></div>'+pv+msg()+
559
+ var manualOpen=CH.manual||!S.ai||!(S.ai||{}).provider;
560
+ return '<div class="sm">'+st+'</div><div class="sm mut" style="margin-top:3px">Every fill follows your limits and approvals, with the same 0.5% fee as any swap.</div>'+msg()+
561
+ chatBox()+
175
562
  (prop.length?'<h3>Waiting for you</h3>'+prop.map(ruleCard).join(""):"")+
176
- '<h3>Rules</h3>'+(act.length?act.map(ruleCard).join(""):'<div class="mut sm">No rules on yet.</div>')+
177
- (hist.length?'<h3>History</h3>'+hist.map(ruleCard).join(""):"")}
563
+ '<h3>Your rules</h3>'+(act.length?act.map(ruleCard).join(""):'<div class="mut sm">No rules on yet.</div>')+
564
+ (hist.length?'<h3>History</h3>'+hist.map(ruleCard).join(""):"")+
565
+ '<details class="mrule"'+(manualOpen?" open":"")+' ontoggle="CH.manual=this.open"><summary>Write a rule yourself</summary><textarea id="stxt" rows="3" placeholder="e.g. Buy $25 of SOL every Monday. Sell half my ETH if it rises 10% and all if it falls 8%." style="width:100%;margin-top:8px;background:var(--surf);border:1px solid var(--line);color:var(--tx);border-radius:10px;padding:10px;font:inherit;resize:vertical"></textarea>'+
566
+ '<div class="toggles" style="margin-top:6px">'+TEMPL.map(function(t,i){return '<button class="chip" style="border:0;cursor:pointer" onclick="document.getElementById(\'stxt\').value=TEMPL['+i+']">'+esc(t)+'</button>'}).join("")+'</div>'+
567
+ '<div style="margin-top:10px"><button class="b" onclick="preview()">Preview</button></div>'+pv+'</details>'}
178
568
  function preview(){var t=document.getElementById("stxt").value;api("/rules/preview",{text:t}).then(function(r){PV=r;flash=null;render()}).catch(function(e){flash=[0,e.message];render()})}
179
569
  function addRules(){var rules=PV.rules.map(function(r){var o={};for(var k in r)if(k!=="description"&&k!=="problem")o[k]=r[k];return o});api("/rules",{rules:rules}).then(function(){PV=null;flash=[1,"Turned on. The autopilot is watching prices."];refresh()}).catch(function(e){flash=[0,e.message];render()})}
180
570
  function setRule(id,st){api("/rules/status",{id:id,status:st}).then(function(){flash=[1,st==="active"?"Turned on.":st==="paused"?"Paused.":"Cancelled."];refresh()}).catch(function(e){flash=[0,e.message];render()})}
@@ -196,10 +586,12 @@ function cpBtn(a){return '<button class="cp" title="Copy full address" onclick="
196
586
  function cp(btn,a){var done=function(){btn.classList.add("done");btn.innerHTML=CPI+"Copied";setTimeout(function(){btn.classList.remove("done");btn.innerHTML=CPI+"Copy"},1500)};
197
587
  if(navigator.clipboard&&navigator.clipboard.writeText)navigator.clipboard.writeText(a).then(done,function(){fallback(a);done()});else{fallback(a);done()}}
198
588
  function fallback(a){var t=document.createElement("textarea");t.value=a;t.style.position="fixed";t.style.opacity="0";document.body.appendChild(t);t.select();try{document.execCommand("copy")}catch(e){}t.remove()}
589
+ function shortWhy(r){r=String(r);var m=/reverted: "([^"]+)"/.exec(r);if(m)r="Transaction would fail: "+m[1];r=r.split(" (action=")[0];return r.length>140?r.slice(0,137)+"…":r}
590
+ function rowUsd(e){if(Number(e.usd)>0)return usd(e.usd);var m=(e.kind==="stake"?/([\d.]+) (SOL)/:/^([\d.]+) ([A-Za-z]+)/).exec(e.detail||""),p=m&&priceOf(m[2]==="WETH"?"ETH":m[2]);return p&&p.usd?'<span title="at today\'s price">≈'+usd(Number(m[1])*p.usd)+'</span>':usd(e.usd)}
199
591
  function usd(n){n=Number(n)||0;return "$"+n.toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:n<1&&n>0?4:2})}
200
592
  function ago(ts){var s=(Date.now()-Date.parse(ts))/1000;if(s<60)return "just now";if(s<3600)return Math.floor(s/60)+"m ago";if(s<86400)return Math.floor(s/3600)+"h ago";return new Date(ts).toLocaleDateString()}
201
- var ICONS={autopilot:'<path d="M13 2L4 14h7l-1 8 9-12h-7z"/>',home:'<path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/>',activity:'<path d="M3 12h4l3-8 4 16 3-8h4"/>',approvals:'<path d="M5 13l4 4L19 7"/>',limits:'<path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7z"/>',withdraw:'<path d="M12 4v12m0 0l-5-5m5 5l5-5M5 20h14"/>'};
202
- var TABS=["home","activity","autopilot","approvals","limits","withdraw"],lastBadge=-1,lastAuto=-1;
593
+ var ICONS={autopilot:'<path d="M13 2L4 14h7l-1 8 9-12h-7z"/>',home:'<path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/>',activity:'<path d="M3 12h4l3-8 4 16 3-8h4"/>',approvals:'<path d="M5 13l4 4L19 7"/>',settings:'<path d="M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12"/><circle cx="16" cy="6" r="2"/><circle cx="10" cy="12" r="2"/><circle cx="18" cy="18" r="2"/>',withdraw:'<path d="M12 4v12m0 0l-5-5m5 5l5-5M5 20h14"/>'};
594
+ var TABS=["home","activity","autopilot","approvals","settings","withdraw"],lastBadge=-1,lastAuto=-1;
203
595
  function nav(){var el=document.getElementById("nav");if(!el.querySelector("button")){el.innerHTML='<div class="ind" id="ind"></div>'+TABS.map(function(k){return '<button data-k="'+k+'" onclick="go(\''+k+'\')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'+ICONS[k]+'</svg>'+k[0].toUpperCase()+k.slice(1)+'</button>'}).join("")}
204
596
  el.querySelectorAll("button").forEach(function(b){b.classList.toggle("on",b.getAttribute("data-k")===tab)});
205
597
  document.getElementById("ind").style.transform="translateX("+(TABS.indexOf(tab)*100)+"%)";
@@ -207,8 +599,8 @@ var na=S&&S.rules?S.rules.filter(function(r){return r.status==="proposed"}).leng
207
599
  var n=S?S.pending.length:0;if(n!==lastBadge){lastBadge=n;var ab=el.querySelector('[data-k="approvals"]'),old=ab.querySelector(".badge");if(old)old.remove();if(n){var sp=document.createElement("span");sp.className="badge";sp.textContent=n;ab.appendChild(sp)}}}
208
600
  function go(k){if(k===tab&&!flash)return;tab=k;flash=null;render(true)}
209
601
  function msg(){return flash?'<div class="msg '+(flash[0]?"okk":"err")+'">'+esc(flash[1])+'</div>':""}
210
- function render(enter){nav();if(!S)return;var a=S.approvals||"none";var ap=document.getElementById("appr");ap.classList.toggle("live",a!=="none"||!!S.live);ap.textContent=(S.live?"Live":"")+(S.live?" · ":"")+(a==="none"?"Approvals off":"Approvals on");ap.title=a==="none"?"Set up Telegram or keep this page open":"Answered via "+a.replace("+"," and ");var m=document.getElementById("main");
211
- if(tab==="home")m.innerHTML=home();else if(tab==="activity")m.innerHTML=activity();else if(tab==="approvals")m.innerHTML=approvals();else if(tab==="limits")m.innerHTML=limits();else if(tab==="autopilot")m.innerHTML=autopilot();else{m.innerHTML=withdraw();tokOpts()}
602
+ function render(enter){nav();if(!S)return;var a=S.approvals||"none";var ap=document.getElementById("appr");ap.classList.toggle("live",a!=="none"||!!S.live);ap.textContent=(S.live?"Live":"")+(S.live?" · ":"")+(a==="none"?"Approvals off":"Approvals on");ap.title=a==="none"?"Set up Telegram or keep this page open":"Answered via "+a.replace("+"," and ");ap.onclick=function(){go("settings")};ap.style.cursor="pointer";var m=document.getElementById("main");
603
+ if(tab!=="autopilot")LASTAP=null;if(tab!=="settings"){LIMD={};LIMPW=""}var prevTab=m.dataset.tab;m.dataset.tab=tab;if(tab==="home")m.innerHTML=home();else if(tab==="activity")m.innerHTML=activity();else if(tab==="approvals")m.innerHTML=approvals();else if(tab==="settings")m.innerHTML=settings();else if(tab==="autopilot"){var h=autopilot();if(!enter&&h===LASTAP&&prevTab==="autopilot"){chatAfter();return}m.innerHTML=h;LASTAP=h;chatAfter()}else{m.innerHTML=withdraw();tokOpts()}
212
604
  after(m,enter)}
213
605
  var shown=null;
214
606
  function after(m,enter){m.querySelectorAll(".row,.ap").forEach(function(r){r.classList.add("spot")});
@@ -222,26 +614,61 @@ function count(to){if(cnt&&cnt.to===to)return;var from=shown===null?0:shown;if(f
222
614
  cnt={from:from,to:to,t0:performance.now(),d:1400};var c=cnt;(function f(now){if(cnt!==c)return;var k=Math.min(1,(now-c.t0)/c.d),e=1-Math.pow(1-k,4);shown=c.from+(c.to-c.from)*(k===1?1:e);var el=document.getElementById("tot");if(el)el.textContent=fmtUsd(shown);if(k<1)requestAnimationFrame(f);else cnt=null})(c.t0)}
223
615
  function home(){var tot=0,chg=0,hasUnpriced=false,rows="";var BL=(S.live&&S.live.balances&&Object.keys(S.live.balances).length)?S.live.balances:B;if(BL){Object.keys(BL).forEach(function(c){var v=BL[c];if(v.error){rows+='<div class="row">'+chainIcon(c)+'<div>'+c+'<div class="sm bad">'+esc(v.error)+'</div></div></div>';return}
224
616
  v.forEach(function(t){var a=Number(t.amount),pr=priceOf(t.token),val=pr?a*pr.usd:null;if(val!==null){tot+=val;if(pr.change_24h!==null)chg+=val-val/(1+pr.change_24h)}else hasUnpriced=true;var key=c+":"+t.token,dir=val!==null&&prevVal[key]!==undefined&&Math.abs(val-prevVal[key])>1e-9?(val>prevVal[key]?" up":" down"):"";if(val!==null)prevVal[key]=val;
225
- rows+='<div class="row">'+tokIcon(t.token,c)+'<div style="min-width:0"><div class="tname">'+esc(NAMES[t.token]||t.token)+'</div><div class="sm">'+(pr&&pr.change_24h!==null&&!/^USD/.test(t.token)?'<span class="'+(pr.change_24h>=0?"ok":"bad")+'">'+(pr.change_24h>=0?"+":"")+(pr.change_24h*100).toFixed(2)+'%</span><span class="mut"> · '+esc(CHAIN_NAMES[c]||c)+'</span>':'<span class="mut">'+esc(CHAIN_NAMES[c]||c)+'</span>')+'</div></div><div class="r"><div class="usdv'+dir+'">'+(val===null?"—":fmtUsd(val))+'</div><div class="sm mut">'+esc(fmtAmt(a))+' '+esc(t.token)+'</div></div></div>'})})}
617
+ rows+='<div class="row'+rowAttrs(coinOf(c,t.token),t.token,c,NAMES[t.token]||t.token)+'>'+tokIcon(t.token,c)+'<div style="min-width:0"><div class="tname">'+esc(NAMES[t.token]||t.token)+'</div><div class="sm">'+(pr&&pr.change_24h!==null&&!/^USD/.test(t.token)?'<span class="'+(pr.change_24h>=0?"ok":"bad")+'">'+(pr.change_24h>=0?"+":"")+(pr.change_24h*100).toFixed(2)+'%</span><span class="mut"> · '+esc(CHAIN_NAMES[c]||c)+'</span>':'<span class="mut">'+esc(CHAIN_NAMES[c]||c)+'</span>')+'</div></div><div class="r"><div class="usdv'+dir+'">'+(val===null?"—":fmtUsd(val))+'</div><div class="sm mut">'+esc(fmtAmt(a))+' '+esc(t.token)+'</div></div></div>'})})}
226
618
  var p=S.policy,sp=S.spent_today;function lim(lbl,used,max){return '<div class="row" style="display:block"><div style="display:flex"><span>'+lbl+'</span><span class="r mut">'+usd(used)+' of '+usd(max)+'</span></div><div class="bar"><i data-w="'+Math.min(100,used/max*100).toFixed(2)+'"></i></div></div>'}
227
- var chgPct=tot-chg>0?chg/(tot-chg):0;
619
+ var HR=holdRows();rows+=HR.rows;tot+=HR.tot;chg+=HR.chg;var chgPct=tot-chg>0?chg/(tot-chg):0;
228
620
  return '<div class="mut sm">Total balance</div><div class="big">'+(BL?'<span id="tot" data-v="'+tot+'">'+fmtUsd(shown===null?0:shown)+'</span>':'<span class="skel"></span>')+'</div><div class="sm daychg">'+(BL&&S.prices&&S.prices.at?'<span class="'+(chg>=0?"ok":"bad")+'">'+(Math.abs(chg)<0.01?(chg>=0?"+":"−")+"$0.00":(chg>=0?"+":"−")+fmtUsd(Math.abs(chg)))+' ('+(chg>=0?"+":"")+(chgPct*100).toFixed(2)+'%)</span><span class="mut"> today</span>':'<span class="mut">Prices loading…</span>')+'</div>'+
229
- liveSection()+'<h3>Today</h3>'+lim("Trades",sp.trades_usd,p.daily_trades_usd)+lim("Payments",sp.payments_usd,p.daily_payments_usd)+
230
- '<h3>Balances</h3>'+(rows||'<div class="mut">Loading…</div>')+
231
- '<h3>Deposit addresses</h3><div class="row">'+chainIcon("base","ethereum")+'<div><div class="sm mut">Base / Ethereum</div><div class="addr" title="'+esc(S.addresses.evm)+'">'+esc(short(S.addresses.evm))+'</div></div>'+cpBtn(S.addresses.evm)+'</div><div class="row">'+chainIcon("solana")+'<div><div class="sm mut">Solana</div><div class="addr" title="'+esc(S.addresses.solana)+'">'+esc(short(S.addresses.solana))+'</div></div>'+cpBtn(S.addresses.solana)+'</div>'}
232
- function activity(){var r=S.receipts;if(!r.length)return '<div class="empty">No trades or payments yet.</div>';return '<div style="display:flex;align-items:center"><h3 style="margin:0">Activity</h3><a class="sm" style="margin-left:auto" href="/api/receipts.csv?t='+T+'">Export CSV</a></div>'+r.map(function(e){var name=e.kind==="trade"?"Swap":e.kind==="payment"?"Payment":"Withdrawal";var st=e.status==="done"?"":(e.status==="failed"?' <span class="chip bad">failed</span>':' <span class="chip warn">pending</span>');var link=(e.tx&&EXP[e.chain]?' · <a target="_blank" rel="noopener" href="'+EXP[e.chain]+esc(e.tx)+'">tx</a>':"")+confChip(liveFor(e.tx));
233
- return '<div class="row">'+kindIcon(e)+'<div style="min-width:0"><div>'+name+st+'</div><div class="sm mut" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px">'+esc(e.kind==="payment"?e.seller:(e.detail||""))+'</div><div class="sm mut">'+esc(e.chain||"")+' · '+ago(e.ts)+link+(e.reason?'<div class="sm bad">'+esc(e.reason)+'</div>':"")+'</div></div><div class="r">'+usd(e.usd)+(e.fee_usd?'<div class="sm mut">fee '+usd(e.fee_usd)+'</div>':"")+'</div></div>'}).join("")}
234
- function approvals(){var p=S.pending;var head='<h3>Waiting for you</h3>';if(!p.length)return head+'<div class="empty">Nothing to approve.<div class="sm" style="margin-top:6px">Trades over '+usd(S.policy.approval_above_usd)+' show up here and in Telegram while this page is open.</div></div>'+msg();
621
+ walletActions()+bkBanner()+aiBtn()+liveSection()+'<h3>Today</h3>'+lim("Trades",sp.trades_usd,p.daily_trades_usd)+lim("Payments",sp.payments_usd,p.daily_payments_usd)+
622
+ '<h3>Balances</h3>'+(rows||'<div class="mut">Loading…</div>')+holdFoot()+
623
+ '<h3>Deposit addresses</h3><div class="row">'+chainIcon("base","ethereum")+'<div><div class="sm mut">Base / Ethereum</div><div class="addr" title="'+esc(S.addresses.evm)+'">'+esc(short(S.addresses.evm))+'</div></div>'+cpBtn(S.addresses.evm)+'</div><div class="row">'+chainIcon("solana")+'<div><div class="sm mut">Solana</div><div class="addr" title="'+esc(S.addresses.solana)+'">'+esc(short(S.addresses.solana))+'</div></div>'+cpBtn(S.addresses.solana)+'</div>'+bkLink()}
624
+ function activity(){var r=S.receipts;if(!r.length)return '<div class="empty">No trades or payments yet.</div>';return '<div style="display:flex;align-items:center"><h3 style="margin:0">Activity</h3><a class="sm" style="margin-left:auto" href="/api/receipts.csv?t='+T+'">Export CSV</a></div>'+r.map(function(e){var name=e.kind==="trade"?"Swap":e.kind==="payment"?"Payment":e.kind==="stake"?(e.tool==="stake"?"Stake":e.tool==="unstake"?"Unstake":"Stake withdrawn"):e.kind==="move"?"Move":e.kind==="dapp"?"App: "+(e.seller||""):e.tool==="cash out"?"Cash out":"Withdrawal";var st=e.status==="done"?"":(e.status==="failed"?' <span class="chip bad">failed</span>':' <span class="chip warn">pending</span>');var link=(e.tx&&EXP[e.chain]?' · <a target="_blank" rel="noopener" href="'+EXP[e.chain]+esc(e.tx)+'">tx</a>':"")+confChip(liveFor(e.tx));
625
+ return '<div class="row">'+kindIcon(e)+'<div style="min-width:0"><div>'+name+st+'</div><div class="sm mut" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px">'+esc(e.kind==="payment"?e.seller:(e.detail||""))+'</div><div class="sm mut">'+esc(e.chain||"")+' · '+ago(e.ts)+link+(e.reason?'<div class="sm bad" style="overflow-wrap:anywhere" title="'+esc(e.reason)+'">'+esc(shortWhy(e.reason))+'</div>':"")+'</div></div><div class="r">'+rowUsd(e)+(e.fee_usd?'<div class="sm mut">fee '+usd(e.fee_usd)+'</div>':"")+'</div></div>'}).join("")}
626
+ function approvals(){var p=S.pending;var ids=p.map(function(x){return x.id}).join();if(ids!==APIDS){APIDS=ids;APLOCK=Date.now()+700}var head='<h3>Waiting for you</h3>';if(!p.length)return head+'<div class="empty">Nothing to approve.<div class="sm" style="margin-top:6px">Trades over '+usd(S.policy.approval_above_usd)+' show up here and in Telegram while this page is open.</div></div>'+msg();
235
627
  return head+p.map(function(x){return '<div class="ap">'+esc(x.text)+'<div class="sm mut" style="margin-top:6px">Expires '+new Date(x.expires).toLocaleTimeString()+'</div><div class="btns"><button class="b no" onclick="ans(\''+x.id+'\',\'denied\')">Deny</button><button class="b ok" onclick="ans(\''+x.id+'\',\'approved\')">Approve</button></div></div>'}).join("")+msg()}
236
- function ans(id,d){api("/approve",{id:id,decision:d}).then(function(){flash=[1,d==="approved"?"Approved.":"Denied."];refresh()}).catch(function(e){flash=[0,e.message];render()})}
237
- var F=[["per_trade_usd","Per trade ($)"],["daily_trades_usd","Trades per day ($)"],["approval_above_usd","Ask me above ($)"],["max_slippage_bps","Max slippage (bps)"],["per_payment_usd","Per payment ($)"],["daily_payments_usd","Payments per day ($)"],["per_seller_daily_usd","Per seller per day ($)"]];
238
- function limits(){var p=S.policy;return '<h3>Limits</h3><div class="mut sm">The agent can\'t change these. Saving needs your password.</div><div class="grid2">'+F.map(function(f){return '<div><label>'+f[1]+'</label><input id="f_'+f[0]+'" type="number" min="0" step="any" value="'+p[f[0]]+'"></div>'}).join("")+'</div>'+
239
- '<label>Chains</label><div class="toggles">'+["base","solana","ethereum"].map(function(c){return '<label><input type="checkbox" id="c_'+c+'"'+(p.chains.indexOf(c)>=0?" checked":"")+'> '+c+'</label>'}).join("")+'</div>'+
240
- '<div class="toggles" style="margin-top:8px"><label><input type="checkbox" id="f_bsw"'+(p.block_seller_warnings?" checked":"")+'> Block sellers that fail the seller check</label></div>'+
241
- '<div class="toggles" style="margin-top:8px"><label><input type="checkbox" id="f_ar"'+(p.agent_rules==="auto"?" checked":"")+'> Let agents turn on autopilot rules without asking me</label></div>'+
242
- '<label>Password</label><input id="pw1" type="password" autocomplete="current-password"><div style="margin-top:12px"><button class="b" onclick="saveLim()">Save limits</button></div>'+msg()}
243
- function saveLim(){var p={};F.forEach(function(f){p[f[0]]=Number(document.getElementById("f_"+f[0]).value)});p.max_slippage_bps=Math.round(p.max_slippage_bps);p.chains=["base","solana","ethereum"].filter(function(c){return document.getElementById("c_"+c).checked});p.block_seller_warnings=document.getElementById("f_bsw").checked;p.agent_rules=document.getElementById("f_ar").checked?"auto":"propose";
244
- api("/policy",{password:document.getElementById("pw1").value,policy:p}).then(function(){flash=[1,"Saved."];refresh()}).catch(function(e){flash=[0,e.message];render()})}
628
+ var APIDS="",APLOCK=0;
629
+ function ans(id,d){if(Date.now()<APLOCK)return;api("/approve",{id:id,decision:d}).then(function(){flash=[1,d==="approved"?"Approved.":"Denied."];refresh()}).catch(function(e){flash=[0,e.message];render()})}
630
+ var LIMD={},LIMPW="";
631
+ function lv(id,def){return Object.prototype.hasOwnProperty.call(LIMD,id)?LIMD[id]:def}
632
+ function limIn(el){if(el.id==="pw1"){LIMPW=el.value;return}LIMD[el.id]=el.type==="checkbox"?el.checked:el.value;var u=document.getElementById("limun");if(u)u.hidden=false}
633
+ function limF(k,lab,hint,spent){var p=S.policy,v=lv("f_"+k,String(p[k])),sub="";
634
+ if(spent!=null){var pct=p[k]>0?Math.min(100,Math.round(spent/p[k]*100)):0;sub='<div class="bar"><i style="width:'+pct+'%"></i></div><div class="sm mut sth">Spent today '+usd(spent)+'</div>'}else if(hint)sub='<div class="sm mut sth">'+hint+'</div>';
635
+ return '<div><label for="f_'+k+'">'+lab+'</label><input id="f_'+k+'" type="number" min="0" step="any" inputmode="decimal" value="'+esc(v)+'" oninput="limIn(this)">'+sub+'</div>'}
636
+ function limT(id,on,text){var c=lv(id,on);return '<label><input type="checkbox" id="'+id+'"'+(c?" checked":"")+' onchange="limIn(this)"> '+text+'</label>'}
637
+ function setRow(icon,title,sub,btn){return '<div class="row">'+icon+'<div style="min-width:0"><div>'+title+'</div><div class="sm mut">'+sub+'</div></div><div class="r">'+btn+'</div></div>'}
638
+ var SICO={ai:'<path d="M12 3v3M12 18v3M3 12h3M18 12h3"/><rect x="7" y="7" width="10" height="10" rx="2"/>',tg:'<path d="M21 4L3 11l6 2 2 6 3-4 5 4z"/>',bk:'<path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7z"/>',ext:'<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.7 3.8 5.7 3.8 9s-1.3 6.3-3.8 9c-2.5-2.7-3.8-5.7-3.8-9S9.5 5.7 12 3z"/>'};
639
+ function sIco(k){return '<div class="ic"><svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'+SICO[k]+'</svg></div>'}
640
+ function settings(){var p=S.policy,sp=S.spent_today||{},dirty=Object.keys(LIMD).length>0;
641
+ var lim='<h3>Spending limits</h3><div class="mut sm">Your AI and autopilot have to stay inside these and can\'t change them. Saving needs your password.</div>'+
642
+ '<div class="sgrp">Swaps</div><div class="grid2">'+limF("per_trade_usd","Per swap ($)","Largest single swap")+limF("daily_trades_usd","Swaps per day ($)",null,sp.trades_usd||0)+limF("approval_above_usd","Ask me above ($)","Bigger swaps wait for your OK")+limF("max_slippage_bps","Max slippage (bps)","100 bps = 1%")+'</div>'+
643
+ '<div class="sgrp">Paying for tools</div><div class="grid2">'+limF("per_payment_usd","Per payment ($)","Largest single payment")+limF("daily_payments_usd","Payments per day ($)",null,sp.payments_usd||0)+limF("per_seller_daily_usd","Per seller per day ($)","Most to any one seller")+'</div>'+
644
+ '<div class="sgrp">Networks</div><div class="toggles">'+["base","solana","ethereum"].map(function(c){return limT("c_"+c,p.chains.indexOf(c)>=0,CHAIN_NAMES[c])}).join("")+'</div>'+
645
+ '<div class="sgrp">Safety</div><div class="toggles stg">'+limT("f_bsw",p.block_seller_warnings,"Block sellers that fail the seller check")+limT("f_ar",p.agent_rules==="auto","Let agents turn on autopilot rules without asking me")+'</div>'+
646
+ '<label for="pw1">Password</label><input id="pw1" type="password" autocomplete="current-password" value="'+esc(LIMPW)+'" oninput="limIn(this)">'+
647
+ '<div class="ssave"><button class="b" onclick="saveLim()">Save limits</button><span id="limun" class="sm warn"'+(dirty?"":" hidden")+'>Unsaved changes</span></div>'+msg();
648
+ var on=((S.connect||{}).apps||[]).filter(function(a){return a.added}).map(function(a){return (AIMETA[a.id]||[0,a.id])[1]});
649
+ var ai=setRow(sIco("ai"),"Connected AI",on.length?esc(on.join(", ")):"None yet",'<button class="b g sbtn" onclick="openAI()">'+(on.length?"Manage":"Connect")+'</button>');
650
+ var tgOn=/telegram/.test(S.approvals||"");
651
+ var tg=setRow(sIco("tg"),"Telegram approvals",tgOn?'<span class="ok">On.</span> Swaps over '+usd(p.approval_above_usd)+' ask you there too.':'Off. Approvals only show on this page while it\'s open.',"")+
652
+ (tgOn?"":'<div class="sm mut sth">To get them on your phone, make a bot with @BotFather, then run:</div><div class="row"><code class="addr">tollkit-wallet telegram &lt;bot-token&gt; &lt;chat-id&gt;</code></div>');
653
+ var bk=S.backup||{},bkSub=bk.done?'<span class="ok">Backed up.</span>':(bk.kind==="keys"?"Not backed up yet. This wallet's backup is its two private keys.":"Not backed up yet.");
654
+ var bkr=setRow(sIco("bk"),"Backup",bkSub,'<button class="b g sbtn" onclick="openBk()">'+(bk.done?"Show again":"Back up")+'</button>');
655
+ var ex=S.ext||{paired:0,sites:[]};if(EXC&&ex.paired>EXCN)EXC=null;
656
+ var exr=setRow(sIco("ext"),"Browser extension",ex.paired?'<span class="ok">Paired</span> with '+ex.paired+' browser'+(ex.paired>1?"s":"")+'. Sites ask here, in Approvals.':"Lets websites connect to this wallet in Chrome, like MetaMask or Phantom.",'<button class="b g sbtn" onclick="extCode()">Pair a browser</button>')+
657
+ (EXC?'<div class="row" style="flex-wrap:wrap"><div style="min-width:0"><div class="sm mut">Paste this code into the Tollkit extension. It works once, for 5 minutes.</div><div class="addr" style="font-size:17px;letter-spacing:.06em;margin-top:4px">'+esc(EXC)+'</div></div><div class="r">'+cpBtn(EXC)+'</div></div>':'')+
658
+ (ex.sites.length?ex.sites.map(function(x){var h=x.origin.replace(/^https?:\/\//,"");return '<div class="row"><div style="min-width:0"><div>'+esc(h)+'</div><div class="sm mut">'+[x.evm?"Base/Ethereum":"",x.solana?"Solana":""].filter(Boolean).join(" + ")+'</div></div><div class="r"><button class="b no sbtn" data-extoff="'+esc(x.origin)+'">Disconnect</button></div></div>'}).join(""):'')+
659
+ (ex.paired?'<div class="sm" style="margin-top:6px"><a href="#" onclick="extUnpair();return false">Unpair all browsers</a></div>':'');
660
+ return lim+'<h3>AI</h3>'+ai+'<h3>Websites</h3>'+exr+'<h3>Approvals</h3>'+tg+'<h3>Recovery</h3>'+bkr}
661
+ var EXC=null,EXCN=0;
662
+ function extCode(){api("/extmgr/code",{}).then(function(r){EXC=r.code;EXCN=(S.ext||{}).paired||0;render()}).catch(function(e){flash=[0,e.message];render()})}
663
+ document.addEventListener("click",function(e){var b=e.target.closest&&e.target.closest("[data-extoff]");if(b)extOff(b.getAttribute("data-extoff"))});
664
+ function extOff(o){api("/extmgr/disconnect",{origin:o}).then(function(){refresh().then(function(){render()})})}
665
+ function extUnpair(){api("/extmgr/unpair",{}).then(function(){EXC=null;refresh().then(function(){render()})})}
666
+ function saveLim(){var F=["per_trade_usd","daily_trades_usd","approval_above_usd","max_slippage_bps","per_payment_usd","daily_payments_usd","per_seller_daily_usd"],p={},bad=null;F.forEach(function(k){var v=document.getElementById("f_"+k).value.trim();if(v===""||!isFinite(Number(v))||Number(v)<0)bad=bad||k;p[k]=Number(v)});
667
+ if(bad){flash=[0,"Enter a number for every limit."];render();return}
668
+ p.max_slippage_bps=Math.round(p.max_slippage_bps);p.chains=["base","solana","ethereum"].filter(function(c){return document.getElementById("c_"+c).checked});if(!p.chains.length){flash=[0,"Keep at least one network on."];render();return}
669
+ p.block_seller_warnings=document.getElementById("f_bsw").checked;p.agent_rules=document.getElementById("f_ar").checked?"auto":"propose";
670
+ var pw=document.getElementById("pw1").value;if(!pw){flash=[0,"Enter your password to save."];render();return}
671
+ api("/policy",{password:pw,policy:p}).then(function(){LIMD={};LIMPW="";flash=[1,"Saved."];refresh().then(function(){render()})}).catch(function(e){LIMPW="";flash=[0,e.message];render()})}
245
672
  var conf=null;
246
673
  function withdraw(){var W=S.policy.withdraw_addresses;var saved=W.length?W.map(function(a){return '<div class="row">'+chainIcon(a.chain)+'<div style="min-width:0"><div>'+esc(a.label||a.chain)+'</div><div class="addr mut" title="'+esc(a.address)+'">'+esc(short(a.address))+'</div></div><div class="r" style="display:flex;gap:6px">'+cpBtn(a.address)+'<button class="b no" style="padding:6px 9px;font-size:12px" onclick="rmAddr(\''+a.chain+'\',\''+esc(a.address)+'\')">Remove</button></div></div>'}).join(""):'<div class="mut sm">None yet. Funds can only be withdrawn to an address saved here.</div>';
247
674
  var opts=W.map(function(a,i){return '<option value="'+i+'">'+esc((a.label?a.label+" · ":"")+a.chain+" · "+a.address.slice(0,6)+"…"+a.address.slice(-4))+'</option>'}).join("");
@@ -253,7 +680,7 @@ function addAddr(){api("/addresses",{password:document.getElementById("pw3").val
253
680
  function rmAddr(c,a){var pw=prompt("Password to remove this address:");if(!pw)return;api("/addresses",{password:pw,action:"remove",chain:c,address:a}).then(function(){flash=[1,"Removed."];refresh()}).catch(function(e){flash=[0,e.message];render()})}
254
681
  function prepW(){var a=S.policy.withdraw_addresses[Number(document.getElementById("w_to").value)];conf={chain:a.chain,to:a.address,token:document.getElementById("w_tok").value,amount:document.getElementById("w_amt").value.trim(),pw:document.getElementById("pw2").value};if(!conf.amount||!conf.pw){flash=[0,"Enter an amount and your password."];conf=null}render();if(conf){var s=document.getElementById("w_to");s.value=String(S.policy.withdraw_addresses.indexOf(a));tokOpts();document.getElementById("w_tok").value=conf.token;document.getElementById("w_amt").value=conf.amount;document.getElementById("pw2").value=conf.pw}}
255
682
  function doW(){var c=conf;conf=null;flash=[1,"Sending…"];render();api("/withdraw",{password:c.pw,chain:c.chain,token:c.token,to:c.to,amount:c.amount}).then(function(r){flash=r.ok?[1,"Sent. Transaction "+r.tx]:[0,r.refused.reason];refresh();loadBal()}).catch(function(e){flash=[0,e.message];render()})}
256
- function busy(){var el=document.activeElement;return el&&(el.tagName==="INPUT"||el.tagName==="SELECT"||el.tagName==="TEXTAREA")||conf}
683
+ function busy(){var el=document.activeElement;if(el&&el.id==="chin"){CH.refocus=el.selectionStart;return !!conf}return el&&(el.tagName==="INPUT"||el.tagName==="SELECT"||el.tagName==="TEXTAREA")||conf}
257
684
  var lastS="";
258
685
  function refresh(){return api("/state").then(function(s){var j=JSON.stringify(s);var first=!S;S=s;if(j===lastS)return;lastS=j;notifyLive();if(!busy())render(first);else nav()}).catch(function(e){document.getElementById("main").innerHTML='<div class="empty bad">'+esc(e.message)+'<div class="sm mut">Is <code>tollkit-wallet ui</code> still running?</div></div>'})}
259
686
  function loadBal(){B=null;if(tab==="home")render();api("/balances").then(function(b){B=b;if(tab==="home"&&!busy())render()})}