tollkit-wallet 0.2.1 → 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 (61) hide show
  1. package/README.md +49 -2
  2. package/THIRD_PARTY_NOTICES.md +4 -0
  3. package/dist/actions.d.ts +34 -33
  4. package/dist/actions.js +5 -1
  5. package/dist/actions.js.map +1 -1
  6. package/dist/bridge.d.ts +148 -0
  7. package/dist/bridge.js +460 -0
  8. package/dist/bridge.js.map +1 -0
  9. package/dist/buy.d.ts +17 -0
  10. package/dist/buy.js +23 -0
  11. package/dist/buy.js.map +1 -0
  12. package/dist/cli.js +68 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/dapp.d.ts +89 -0
  15. package/dist/dapp.js +505 -0
  16. package/dist/dapp.js.map +1 -0
  17. package/dist/dashboard.d.ts +2 -0
  18. package/dist/dashboard.js +145 -2
  19. package/dist/dashboard.js.map +1 -1
  20. package/dist/holdings.d.ts +92 -0
  21. package/dist/holdings.js +200 -0
  22. package/dist/holdings.js.map +1 -0
  23. package/dist/keystore.d.ts +58 -11
  24. package/dist/keystore.js +87 -11
  25. package/dist/keystore.js.map +1 -1
  26. package/dist/ledger.d.ts +1 -1
  27. package/dist/mcp.d.ts +1 -1
  28. package/dist/mcp.js +1 -1
  29. package/dist/netguard.d.ts +8 -0
  30. package/dist/netguard.js +44 -0
  31. package/dist/netguard.js.map +1 -0
  32. package/dist/paths.d.ts +1 -0
  33. package/dist/paths.js +1 -0
  34. package/dist/paths.js.map +1 -1
  35. package/dist/policy.d.ts +4 -4
  36. package/dist/rules.d.ts +4 -4
  37. package/dist/sell.d.ts +60 -0
  38. package/dist/sell.js +71 -0
  39. package/dist/sell.js.map +1 -0
  40. package/dist/stake.d.ts +84 -0
  41. package/dist/stake.js +158 -0
  42. package/dist/stake.js.map +1 -0
  43. package/dist/ui.js +306 -32
  44. package/dist/ui.js.map +1 -1
  45. package/dist/wallet.d.ts +57 -2
  46. package/dist/wallet.js +185 -4
  47. package/dist/wallet.js.map +1 -1
  48. package/extension/PRIVACY.md +19 -0
  49. package/extension/README.md +16 -0
  50. package/extension/background.js +88 -0
  51. package/extension/content.js +34 -0
  52. package/extension/icons/icon.svg +1 -0
  53. package/extension/icons/icon128.png +0 -0
  54. package/extension/icons/icon16.png +0 -0
  55. package/extension/icons/icon32.png +0 -0
  56. package/extension/icons/icon48.png +0 -0
  57. package/extension/inpage.js +129 -0
  58. package/extension/manifest.json +15 -0
  59. package/extension/popup.html +35 -0
  60. package/extension/popup.js +39 -0
  61. package/package.json +3 -2
package/dist/ui.js CHANGED
@@ -5,7 +5,7 @@ const COIN_JSON = JSON.stringify(COIN_ICONS).replace(/</g, "\\u003c");
5
5
  const AI_ICON_JSON = JSON.stringify(AI_ICONS).replace(/</g, "\\u003c");
6
6
  export const PAGE = String.raw `<!doctype html>
7
7
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
8
- <title>Tollkit Wallet</title>
8
+ <title>Tollkit</title>
9
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">
10
10
  <style>
11
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}
@@ -41,13 +41,14 @@ input,select{width:100%;background:var(--surf);border:1px solid var(--line);colo
41
41
  input:focus,select:focus{outline:2px solid var(--acc);border-color:transparent}
42
42
  .grid2{display:grid;grid-template-columns:1fr 1fr;gap:8px}
43
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)}
44
- .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}
45
45
  .ap .btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}
46
46
  .empty{text-align:center;color:var(--mut);padding:40px 10px}
47
47
  a{color:var(--acc)}
48
48
  .ok{color:var(--ok)}.bad{color:var(--bad)}.warn{color:var(--warn)}
49
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}
50
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}
51
52
 
52
53
  /* ── Motion, in the same language as tollkit.dev (motion.css) ─────────── */
53
54
  :root{--ease:cubic-bezier(.16,1,.3,1);--glow:74,142,255}
@@ -177,11 +178,11 @@ input,select{transition:border-color .2s ease,box-shadow .25s ease}input:focus,s
177
178
  @keyframes aifade{from{opacity:0}to{opacity:1}}
178
179
  @media (prefers-reduced-motion:reduce){.aio,.aidlg,.airow{animation:none}}
179
180
  .tc-chat{background:var(--surf);border:1px solid var(--line);border-radius:18px;margin-top:12px;overflow:hidden}
180
- .tc-chd{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 12px;border-bottom:1px solid var(--line)}
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)}
181
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)}
182
- .tc-chr{display:flex;align-items:center;gap:6px}.tc-chk{display:flex;align-items:center;gap:4px;color:var(--mut)}
183
- .tc-chk select{max-width:130px;background:var(--surf2);color:var(--tx);border:1px solid var(--line);border-radius:9px;padding:5px 6px;font:inherit;font-size:12px}
184
- .tc-chx{width:32px;height:32px;padding:0;border-radius:9px;display:flex;align-items:center;justify-content:center;flex:none;font-size:20px;font-weight:500;line-height:1}.tc-csend svg{margin:0}
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}
185
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}
186
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}
187
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%}
@@ -208,10 +209,65 @@ input,select{transition:border-color .2s ease,box-shadow .25s ease}input:focus,s
208
209
  .tc-why{font-size:12.5px;color:var(--tx);opacity:.85;margin-top:5px;line-height:1.45}
209
210
  .mrule summary{cursor:pointer;color:var(--mut);font-size:13px;margin-top:14px}
210
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}
211
267
  </style></head><body>
212
268
  <div class="aur" aria-hidden="true"><div class="drift"><i></i><i></i><i></i></div></div>
213
269
  <div class="wrap"><div class="card">
214
- <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>
215
271
  <div id="toasts" aria-live="polite"></div><main id="main"><div class="empty">Loading…</div></main>
216
272
  <nav id="nav"></nav>
217
273
  </div><p class="mut sm" style="text-align:center">Running on this computer only. Keys never leave it.</p></div>
@@ -273,6 +329,188 @@ function aiDo(act,id){if(act!=="remove"&&!AIPW){AIMSG[id]=[0,"Type your wallet p
273
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()})}
274
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()}}}
275
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()}
276
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?"];
277
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("")}
278
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"}):"")}
@@ -285,7 +523,7 @@ return '<div class="tc-cm tc-ai'+nw+(m.error?" tc-err":"")+'"><div class="tc-cav
285
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>'}
286
524
  function chatBox(){var A=S.ai;if(!A)return "";var M=A.messages||[],busy=A.busy||CH.sending;
287
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>'+
288
- '<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" onclick="newChat()" title="New chat" aria-label="New chat">+</button>':'')+'</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>';
289
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>';
290
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>';
291
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>';
@@ -298,10 +536,11 @@ function chatSend(text){var t=(text!==undefined?text:CH.draft).trim();if(!t||CH.
298
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()})}
299
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()})}
300
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()})}
301
- function newChat(){api("/ai/new",{}).then(function(){CH.n=-1;CH.top=null;refresh()}).catch(function(e){flash=[0,e.message];render()})}
302
- 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>'}
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>'}
303
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>'}
304
- 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>'}
305
544
  function amt(x){var n=Number(x);return isFinite(n)?n.toLocaleString(undefined,{maximumFractionDigits:6}):String(x)}
306
545
  var NAMES={ETH:"Ethereum",WETH:"Wrapped Ether",USDC:"USD Coin",USDT:"Tether",SOL:"Solana"},CHAIN_NAMES={base:"Base",ethereum:"Ethereum",solana:"Solana"},prevVal={};
307
546
  function priceOf(sym){var p=S&&S.prices&&S.prices.prices;return p&&p[String(sym).toUpperCase()]||null}
@@ -348,11 +587,11 @@ function cp(btn,a){var done=function(){btn.classList.add("done");btn.innerHTML=C
348
587
  if(navigator.clipboard&&navigator.clipboard.writeText)navigator.clipboard.writeText(a).then(done,function(){fallback(a);done()});else{fallback(a);done()}}
349
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()}
350
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}
351
- function rowUsd(e){if(Number(e.usd)>0)return usd(e.usd);var m=/^([\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)}
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)}
352
591
  function usd(n){n=Number(n)||0;return "$"+n.toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:n<1&&n>0?4:2})}
353
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()}
354
- 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"/>'};
355
- 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;
356
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("")}
357
596
  el.querySelectorAll("button").forEach(function(b){b.classList.toggle("on",b.getAttribute("data-k")===tab)});
358
597
  document.getElementById("ind").style.transform="translateX("+(TABS.indexOf(tab)*100)+"%)";
@@ -360,8 +599,8 @@ var na=S&&S.rules?S.rules.filter(function(r){return r.status==="proposed"}).leng
360
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)}}}
361
600
  function go(k){if(k===tab&&!flash)return;tab=k;flash=null;render(true)}
362
601
  function msg(){return flash?'<div class="msg '+(flash[0]?"okk":"err")+'">'+esc(flash[1])+'</div>':""}
363
- 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");
364
- if(tab!=="autopilot")LASTAP=null;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==="limits")m.innerHTML=limits();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()}
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()}
365
604
  after(m,enter)}
366
605
  var shown=null;
367
606
  function after(m,enter){m.querySelectorAll(".row,.ap").forEach(function(r){r.classList.add("spot")});
@@ -375,26 +614,61 @@ function count(to){if(cnt&&cnt.to===to)return;var from=shown===null?0:shown;if(f
375
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)}
376
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}
377
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;
378
- 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>'})})}
379
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>'}
380
- 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;
381
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>'+
382
- aiBtn()+liveSection()+'<h3>Today</h3>'+lim("Trades",sp.trades_usd,p.daily_trades_usd)+lim("Payments",sp.payments_usd,p.daily_payments_usd)+
383
- '<h3>Balances</h3>'+(rows||'<div class="mut">Loading…</div>')+
384
- '<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>'}
385
- 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));
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));
386
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("")}
387
- 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();
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();
388
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()}
389
- 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()})}
390
- 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 ($)"]];
391
- 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>'+
392
- '<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>'+
393
- '<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>'+
394
- '<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>'+
395
- '<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()}
396
- 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";
397
- 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()})}
398
672
  var conf=null;
399
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>';
400
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("");