memoscan
← all memos

Memo 0x0ea674b2…fdf83d on Ethereum

0x68575b07…67c7·#25,913,807·view on Etherscan
`Awaiting recipient · recoverable in ${fmtTime(t.unlockTime+GRACE-n)}`, act:null}; } const progressOf = t => t.delay>0 ? Math.max(0,Math.min(100,((nw()-t.timestamp)/t.delay)*100)) : 100; const RANK = {ready:0, recover:1, pending:2, settling:3}; function paintTabs(){ qsa('.tab[data-tab]').forEach(t=>t.setAttribute('aria-selected',String(t.dataset.tab===S.tab))); } function renderList(){ const c = el.list; el.tabUnlocked.hidden = !S.unlocked.length; paintTabs(); if(S.tab==='unlocked' && !S.unlocked.length) S.tab='inbound'; const list = S.tab==='outbound' ? S.out : S.inb; c.replaceChildren(); if(S.account && S.tab==='unlocked'){ renderUnlocked(); return; } if(!S.account){ c.appendChild(emptyState('Connect a wallet to see your transfers.','Connect',()=>connect(true))); return; } if(S.loading){ const w=document.createElement('div'); w.className='empty'; w.appendChild(loaderNode()); const p=document.createElement('p'); p.textContent=`Reading ${cfg().name}…`; w.appendChild(p); c.appendChild(w); return; } if(S.slowDeployed[S.chain]===false){ c.appendChild(emptyState(`SLOW is not deployed on ${cfg().name} yet — there is nothing to read here.`, 'Switch network', ()=>el.chainBtn.click())); return; } if(S.listError){ c.appendChild(emptyState(S.listError, 'Try again', ()=>loadTransfers(true))); return; } if(!list.length){ c.appendChild(S.tab==='outbound' ? emptyState('Nothing sent yet.','Send one',()=>setView('send')) : emptyState('Nothing inbound.')); return; } const sorted=[...list].sort((a,b)=>{ const ra=RANK[statusOf(a).key], rb=RANK[statusOf(b).key]; return ra!==rb ? ra-rb : a.unlockTime-b.unlockTime; }); for(const t of sorted) c.appendChild(makeRow(t)); startTimer(); } function renderUnlocked(){ const c=el.list; c.replaceChildren(); if(!S.unlocked.length){ c.appendChild(emptyState('Nothing unlocked.')); return; } const note=document.createElement('div'); note.className='empty'; note.style.padding='0 0 6px'; note.textContent = S.hasGuardian ? 'Settled, and held as a wrapper. Your guardian approves each withdrawal before it can leave.' : 'Settled, and held as a wrapper. Withdraw to receive the underlying.'; c.appendChild(note); for(const u of S.unlocked){ const row=document.createElement('div'); row.className='tx'; const bar=document.createElement('span'); bar.className='tx-bar'; bar.style.background=railOf(u.symbol,u.token); const open=document.createElement('button'); open.type='button'; open.className='tx-open'; const l1=document.createElement('span'); l1.className='tx-line'; const amt=document.createElement('span'); amt.className='tx-amt'; amt.textContent=`${u.amount} ${u.symbol==='???'?shortAddr(u.token):u.symbol}`; const party=document.createElement('span'); party.className='tx-party'; party.textContent='unlocked'; l1.append(amt,party); const meta=document.createElement('span'); meta.className='tx-meta'; const st=document.createElement('span'); st.className='tx-status'; st.textContent = S.hasGuardian ? 'Needs a guardian-approved withdrawal' : 'Ready to withdraw'; meta.appendChild(st); open.append(l1,meta); open.onclick=()=>showExit(u); const act=document.createElement('button'); act.type='button'; act.className='tx-act'; act.textContent='Withdraw'; act.onclick=e=>{ e.stopPropagation(); doWithdraw(u); }; row.append(bar,open,act); c.appendChild(row); } } function emptyState(text, cta, fn){ const w=document.createElement('div'); w.className='empty'; const p=document.createElement('p'); p.textContent=text; w.appendChild(p); if(cta){ const b=document.createElement('button'); b.className='btn'; b.style.width='auto'; b.style.padding='0 22px'; b.textContent=cta; b.onclick=fn; w.appendChild(b); } return w; } function makeRow(t){ const st=statusOf(t); const sym = t.symbol==='???' ? shortAddr(t.token) : t.symbol; const row=document.createElement('div'); row.className='tx'+(st.key==='ready'?' act':st.key==='recover'?' act warn-row':''); row.dataset.tid=t.id; const bar=document.createElement('span'); bar.className='tx-bar'; bar.style.background=railOf(t.symbol,t.token); row.appendChild(bar); const open=document.createElement('button'); open.type='button'; open.className='tx-open'; open.setAttribute('aria-label',`${t.amount} ${sym} ${S.tab==='outbound'?'to':'from'} ${shortAddr(S.tab==='outbound'?t.to:t.from)} — ${st.text}. Details.`); const l1=document.createElement('span'); l1.className='tx-line'; const amt=document.createElement('span'); amt.className='tx-amt'; amt.textContent=`${t.amount} ${sym}`; const party=document.createElement('span'); party.className='tx-party'; party.textContent=(S.tab==='outbound'?'→ ':'← ')+shortAddr(S.tab==='outbound'?t.to:t.from); l1.append(amt,party); const track=document.createElement('span'); track.className='tx-track'; const fill=document.createElement('span'); fill.className='tx-fill'; fill.style.width=progressOf(t)+'%'; fill.dataset.tid=t.id; track.appendChild(fill); const l2=document.createElement('span'); l2.className='tx-meta'; const status=document.createElement('span'); status.className='tx-status'; status.textContent=st.text; const flags=document.createElement('span'); const f=[]; if(t.tipped) f.push('keeper'); if(t.guardianPending) f.push('guardian'); flags.textContent=f.join(' · '); l2.append(status,flags); open.append(l1,track,l2); open.onclick=()=>openDetail(t,sym); row.appendChild(open); if(st.act){ const act=document.createElement('button'); act.type='button'; act.className='tx-act'; act.textContent=st.act; act.setAttribute('aria-label',`${st.act} ${t.amount} ${sym}`); act.onclick=e=>{ e.stopPropagation(); runAction(t,st.key); }; row.appendChild(act); } return row; } function runAction(t,key){ if(key==='ready') return doClaim(t); if(key==='recover') return doClawback(t); if(key==='pending' && S.tab==='outbound') return doReverse(t); } function detailRows(t,sym){ const st=statusOf(t); const rows=[ ['Amount', `${t.amount} ${sym}`], [S.tab==='outbound'?'To':'From', S.tab==='outbound'?t.to:t.from], ['Status', st.text], ['Timelock', fmtTime(t.delay)], ['Sent', new Date(t.timestamp*1000).toLocaleString()], ]; if(t.guardianPending && S.tab==='outbound' && nw()<t.unlockTime) rows.push(['Guardian','Required to reclaim']); if(S.hasGuardian && S.tab==='inbound' && nw()>=t.unlockTime) rows.push(['Guardian','Required to withdraw']); if(t.tipped) rows.push(['Keeper tip', S.tab==='inbound'?'A keeper will settle this':'Attached, refundable']); return rows; } function openDetail(t,sym){ S.detail={t,sym}; el.detailShare.hidden=false; el.detailTitle.textContent = S.tab==='outbound'?'Outbound':'Inbound'; el.detailRows.replaceChildren(); for(const [k,v] of detailRows(t,sym)){ const row=document.createElement('div'); row.className='kv'; const dt=document.createElement('dt'), dd=document.createElement('dd'); dt.textContent=k; dd.textContent=v; row.append(dt,dd); el.detailRows.appendChild(row); } const mine = !!S.account && !!t.to && t.to.toLowerCase()===S.account.toLowerCase(); el.detailForget.hidden = !(S.tab==='inbound' && mine); el.detailForget.onclick = el.detailForget.hidden ? null : ()=>doForgetInbound(t); const st=statusOf(t), btn=el.detailAction; btn.className='btn'+(st.key==='recover'?' danger':''); if(st.act){ btn.hidden=false; btn.disabled=false; btn.textContent=`${st.act} ${t.amount} ${sym}`; btn.onclick=()=>{ hideModal(el.detailModal); runAction(t,st.key); }; } else { btn.hidden=true; btn.onclick=null; } showModal(el.detailModal); } function tick(){ if(S.view!=='take'||document.hidden) return; const list=S.tab==='outbound'?S.out:S.inb; const byId=new Map(list.map(t=>[t.id,t])); let restructure=false; for(const f of qsa('.tx-fill')){ const t=byId.get(f.dataset.tid); if(!t) continue; f.style.width=progressOf(t)+'%'; } for(const r of qsa('.tx')){ const t=byId.get(r.dataset.tid); if(!t) continue; const st=statusOf(t); const node=r.querySelector('.tx-status'); if(node && node.textContent!==st.text) node.textContent=st.text; const wasAct=r.classList.contains('act'); if(wasAct!==(st.key==='ready'||st.key==='recover')) restructure=true; } if(restructure) renderList(); } function startTimer(){ if(!S.timer) S.timer=setInterval(tick,5000); } function stopTimer(){ if(S.timer){ clearInterval(S.timer); S.timer=null; } } document.addEventListener('visibilitychange',()=>{ if(document.hidden){ stopTimer(); return; } if(S.view==='take'){ startTimer(); tick(); loadTransfers(false); } }); const wardsKey = () => `slow.wards.${S.chain}.${(S.account||'').toLowerCase()}`; function loadWards(){ try { return JSON.parse(localStorage.getItem(wardsKey())||'[]').filter(isAddr); } catch(e){ return []; } } function saveWards(list){ try { localStorage.setItem(wardsKey(), JSON.stringify([...new Set(list.map(a=>a.toLowerCase()))])); } catch(e){} } async function wardsFromIndex(){ const key='idx:'+S.chain; if(S.guard[key]===false) return []; try { if(S.guard[key]===undefined){ const code=await rpc('eth_getCode',[SLOW,'latest'],S.chain); S.guard[key] = typeof code==='string' && code.includes(SEL.wardsOf.slice(2)); if(!S.guard[key]) return []; } const [r]=await mc([{to:SLOW,data:cd(SEL.wardsOf,['address'],[S.account])}]); if(!r) return []; const d=strip(r), n=parseInt(d.slice(64,128),16); const out=[]; for(let i=0;i<n;i++) out.push('0x'+d.slice(128+i*64+24,128+(i+1)*64)); return out; } catch(e){ return []; } } async function wardsFromLogs(){ const c=cfg(); try { const logs=await rpc('eth_getLogs',[{ address:SLOW, topics:[TOPIC_GUARDIAN_SET, null, '0x'+word(S.account)], fromBlock:'0x'+(c.deployBlock||0).toString(16), toBlock:'latest', }],c.id); if(!Array.isArray(logs)) return []; return logs.map(l=>'0x'+String(l.topics?.[1]||'').slice(-40)).filter(isAddr); } catch(e){ return []; } } async function scanWards(){ if(!S.account) return; S.guard.scanning=true; renderGuard(); try { const [a,b]=await Promise.all([wardsFromIndex(),wardsFromLogs()]); const found=[...a,...b].map(x=>x.toLowerCase()).filter(isAddr); const before=loadWards(); const merged=[...new Set([...before,...found])]; saveWards(merged); const added=merged.length-before.length; toast(added ? `Found ${added} more.` : (found.length ? 'Nothing new.' : 'Nothing found — add an address instead.')); } finally { S.guard.scanning=false; await loadGuard(); } } async function loadGuard(){ if(!S.account) return; const acct=S.account, chain=S.chain; const stale=()=>S.account!==acct||S.chain!==chain; S.guard.loading=true; renderGuard(); try { const [gh,ph] = await mc([ {to:SLOW,data:cd(SEL.guardians,['address'],[acct])}, {to:SLOW,data:cd(SEL.pendingGuardian,['address'],[acct])}, ]); if(stale()) return; const guardian = (gh && strip(gh).length>=40) ? '0x'+gh.slice(-40) : ZERO; S.guard.guardian = (!isAddr(guardian) || guardian.toLowerCase()===ZERO) ? null : guardian; S.guard.pending = null; if(ph){ const [g,at] = decode(['address','uint256'], ph); if(at!==0n) S.guard.pending={guardian:g, at:Number(at)}; } const stored=loadWards(); if(stored.length){ const res=await mc(stored.map(w=>({to:SLOW,data:cd(SEL.guardians,['address'],[w])}))); if(stale()) return; S.guard.wards = stored.map((w,i)=>({ addr:w, mine: !!res[i] && ('0x'+res[i].slice(-40)).toLowerCase()===acct.toLowerCase(), txs:null, })); } else S.guard.wards=[]; } catch(e){ if(!stale()) toast('Could not read guardian state — '+errText(e),5000); } finally { if(!stale()){ S.guard.loading=false; renderGuard(); } } } async function loadWardTransfers(w){ const acct=S.account; try { const [oh, nh] = await mc([ {to:SLOW,data:cd(SEL.getOut,['address'],[w.addr])}, {to:SLOW,data:cd(SEL.guardianNonces,['address'],[w.addr])}, ]); w.nonce = nh ? decode(['uint256'],nh)[0] : undefined; const ids = oh ? decode(['uint256[]'],oh)[0] : []; const pts = ids.length ? await mc(ids.map(id=>({to:SLOW,data:cd(SEL.pendingTransfers,['uint256'],[id])}))) : []; const live=[]; ids.forEach((id,i)=>{ if(!pts[i]) return; try { const [ts,from,to,tid,amt]=decode(['uint256','address','address','uint256','uint256'],pts[i]); if(ts!==0n) live.push({id:id.toString(),from,to,tid,amt,ts:Number(ts)}); } catch(e){} }); const tokens=[...new Set(live.map(t=>decodeId(t.tid).token))]; await Promise.all(tokens.map(t=>tokenMeta(t).catch(()=>{}))); if(S.account!==acct) return; w.txs=live.map(t=>{ const {token,delay}=decodeId(t.tid); const m=S.meta[token.toLowerCase()]||{symbol:'???',decimals:18}; return {...t, token, symbol:m.symbol, decimals:m.decimals, amount:fmtAmt(t.amt,m.decimals), delay, unlockTime:t.ts+delay}; }); renderGuard(); } catch(e){ w.txs=[]; renderGuard(); toast('Could not read that account'); } } async function guardApprove(w){ if(!guardWritable()) return; const to=(el.gaTo.value||'').trim(); const idRaw=(el.gaId.value||'').trim(); const amtRaw=(el.gaAmt.value||'').trim(); if(!isAddr(to)){ setNote(el.gaNote,'Enter the address the funds will go to','err'); return; } if(!/^\d+$/.test(idRaw)){ setNote(el.gaNote,'Enter the position id they will draw from','err'); return; } let amount; try { amount=BigInt(amtRaw); if(amount<=0n) throw 0; } catch(e){ setNote(el.gaNote,'Enter the exact amount in base units','err'); return; } try { setNote(el.gaNote,'Deriving the operation id…'); const sel = el.gaKind.value==='transfer' ? SEL.predictTransferId : SEL.predictWithdrawalId; const [r] = await mc([{to:SLOW, data:cd(sel,['address','address','uint256','uint256'],[w.addr,to,BigInt(idRaw),amount])}]); if(!r) throw new Error('Could not derive the operation id'); const opId = decode(['uint256'],r)[0]; hideModal(el.gaModal); await slowTx(cd(SEL.approveTransfer,['address','uint256'],[w.addr,opId]), 'Approving one operation','Confirm in your wallet'); toast('Approved. Good for exactly this operation, once.',6000); loadGuard(); } catch(e){ fail(e); setNote(el.gaNote,''); } } function openApprove(w){ el.gaModal.dataset.ward=w.addr; el.gaTo.value=w.addr; el.gaId.value=''; el.gaAmt.value=''; setNote(el.gaNote,''); el.gaWard.textContent=shortAddr(w.addr); el.gaNonce.textContent = w.nonce!==undefined ? `their nonce is ${w.nonce}` : ''; showModal(el.gaModal); } function renderGuard(){ const g=S.guard; el.gNotDeployed.hidden = S.slowDeployed[S.chain]!==false; if(!el.gNotDeployed.hidden) el.gNotDeployed.textContent = `SLOW is not deployed on ${cfg().name} yet. Guardian settings there do not exist.`; el.gHint.textContent = g.scanning ? 'scanning…' : g.loading ? 'reading…' : ''; el.gScan.disabled = !!(g.loading||g.scanning); const cur=el.gCurrent; cur.replaceChildren(); cur.classList.toggle('none',!g.guardian); if(g.guardian){ const a=document.createElement('span'); a.textContent=g.guardian; const b=document.createElement('span'); b.className='sub'; b.textContent='Must co-sign every outflow from your account.'; cur.append(a,b); } else { const a=document.createElement('span'); a.textContent='None set'; const b=document.createElement('span'); b.className='sub'; b.textContent='Outflows need no second signature. Setting the first guardian takes effect immediately.'; cur.append(a,b); } el.gRemove.disabled=!g.guardian; const pf=el.gPendingField; pf.hidden=!g.pending; if(g.pending){ const ready = nw()>=g.pending.at; const p=el.gPending; p.replaceChildren(); const a=document.createElement('span'); a.textContent = g.pending.guardian.toLowerCase()===ZERO ? 'Remove the guardian' : `→ ${g.pending.guardian}`; const b=document.createElement('span'); b.className='sub'; b.textContent = ready ? 'Ready to commit. You or your current guardian can land it.' : `Commits in ${fmtTime(g.pending.at-nw())}. Either you or your current guardian can still cancel.`; p.append(a,b); el.gCommit.disabled=!ready; el.gCancel.disabled=ready; } const wrap=el.gWards; wrap.replaceChildren(); if(g.loading||g.scanning){ const w=document.createElement('div'); w.className='empty loader-sm'; w.appendChild(loaderNode()); const p=document.createElement('p'); p.textContent = g.scanning ? 'Looking for accounts that named you…' : `Reading ${cfg().name}…`; w.appendChild(p); wrap.appendChild(w); return; } el.gWardHint.textContent = g.wards.length ? `${g.wards.filter(w=>w.mine).length} active` : ''; if(!g.wards.length){ const e=document.createElement('div'); e.className='empty'; e.textContent='None. Add an address that named you as its guardian.'; wrap.appendChild(e); } for(const w of g.wards){ const box=document.createElement('div'); box.className='ward'; const h=document.createElement('button'); h.type='button'; h.className='ward-h'; const nm=document.createElement('span'); nm.textContent=shortAddr(w.addr); const cnt=document.createElement('span'); cnt.className='cnt'; cnt.textContent = !w.mine ? 'not guarded by you' : w.txs===null ? 'tap to load' : `${w.txs.length} pending`; h.append(nm,cnt); h.onclick=()=>{ if(!w.mine){ if(confirmDrop(w.addr)) return; return; } g.open = g.open===w.addr ? null : w.addr; renderGuard(); if(g.open===w.addr && w.txs===null) loadWardTransfers(w); }; box.appendChild(h); if(g.open===w.addr && w.mine && w.txs){ const b=document.createElement('div'); b.className='ward-b'; if(!w.txs.length){ const e=document.createElement('div'); e.className='empty'; e.style.padding='22px'; e.textContent='No pending transfers.'; b.appendChild(e); } for(const t of w.txs){ const r=document.createElement('div'); r.className='wtx'; const i=document.createElement('div'); i.className='wtx-i'; i.textContent=`${t.amount} ${t.symbol==='???'?shortAddr(t.token):t.symbol}`; const sub=document.createElement('span'); sub.textContent=`→ ${shortAddr(t.to)} · ${nw()>=t.unlockTime?'unlocked':fmtTime(t.unlockTime-nw())+' left'}`; i.appendChild(sub); r.appendChild(i); b.appendChild(r); } const foot=document.createElement('div'); foot.className='wtx'; const note=document.createElement('div'); note.className='wtx-i'; note.textContent='Approve an outflow'; const ns=document.createElement('span'); ns.textContent='Pre-authorises one operation. Their next one, in order.'; note.appendChild(ns); const ab=document.createElement('button'); ab.type='button'; ab.textContent='Approve…'; ab.onclick=()=>openApprove(w); foot.append(note,ab); b.appendChild(foot); box.appendChild(b); } wrap.appendChild(box); } } function confirmDrop(addr){ saveWards(loadWards().filter(a=>a!==addr.toLowerCase())); S.guard.wards=S.guard.wards.filter(w=>w.addr!==addr.toLowerCase()); toast('Removed — you are not that account’s guardian'); renderGuard(); return true; } function openGuardModal(mode){ el.guardModal.dataset.mode=mode; el.guardInput.value=''; el.guardInput.hidden = mode==='remove'; setNote(el.guardNote,''); el.guardTitle.textContent = mode==='ward' ? 'Account you guard' : mode==='remove' ? 'Remove guardian' : (S.guard.guardian?'Change guardian':'Set guardian'); el.guardApply.textContent = mode==='remove' ? 'Remove' : 'Apply'; const warn=el.guardWarn; if(mode==='ward') warn.textContent='The address must already have named you as its guardian on chain.'; else if(mode==='remove') warn.textContent=`Removal is staged for ${fmtTime(GUARD_DELAY)} and then has to be committed. Until it commits, your guardian can cancel it.`; else warn.textContent = S.guard.guardian ? `Changing an active guardian is staged for ${fmtTime(GUARD_DELAY)}. Either of you can cancel it in that window.` : 'The first guardian takes effect immediately. It cannot be your own address — a stolen key could approve its own withdrawals.'; showModal(el.guardModal); } async function applyGuardModal(){ const mode=el.guardModal.dataset.mode; if(mode==='remove'){ hideModal(el.guardModal); return setGuardianTx(ZERO); } const raw=el.guardInput.value.trim(); let addr=raw; if(!isAddr(raw)){ if(!raw.includes('.')){ setNote(el.guardNote,'Enter an address or a name','err'); return; } setNote(el.guardNote,'Resolving…'); addr=await resolveName(raw); if(!addr||!isAddr(addr)){ setNote(el.guardNote,`No address set for ${raw}`,'err'); return; } } if(mode==='ward'){ const [r]=await mc([{to:SLOW,data:cd(SEL.guardians,['address'],[addr])}]); const who = r ? '0x'+r.slice(-40) : ZERO; if(who.toLowerCase()!==(S.account||'').toLowerCase()){ setNote(el.guardNote,'That account has not named you as its guardian','err'); return; } saveWards([...loadWards(),addr]); hideModal(el.guardModal); loadGuard(); return; } if(addr.toLowerCase()===(S.account||'').toLowerCase()){ setNote(el.guardNote,'A guardian cannot be your own address','err'); return; } hideModal(el.guardModal); setGuardianTx(addr); } function guardWritable(){ if(S.slowDeployed[S.chain]===false){ toast(`SLOW is not deployed on ${cfg().name} — there is nothing to set a guardian on.`, 6000); return false; } return true; } async function setGuardianTx(addr){ if(!guardWritable()) return; const cur = (S.guard.guardian||'').toLowerCase(); const same = !!cur && addr.toLowerCase()===cur; const staged = !!S.guard.guardian && !same; const hadPending = !!S.guard.pending; try { await slowTx(cd(SEL.setGuardian,['address'],[addr]), same ? 'Keeping your guardian…' : staged ? 'Staging the change…' : 'Setting your guardian…', 'Confirm in your wallet'); toast(same ? (hadPending ? 'Kept. The pending change was cancelled.' : 'No change — that is already your guardian.') : staged ? `Staged. Commit it after ${fmtTime(GUARD_DELAY)}.` : (addr.toLowerCase()===ZERO?'Guardian removed.':'Guardian set.'), 6000); loadGuard(); } catch(e){ fail(e); } } async function commitGuardianTx(){ if(!guardWritable()) return; try { await slowTx(cd(SEL.commitGuardian,['address'],[S.account]),'Committing…','Confirm in your wallet'); toast('Committed.'); loadGuard(); } catch(e){ fail(e); } } async function cancelGuardianTx(){ if(!guardWritable()) return; try { await slowTx(cd(SEL.cancelGuardianChange,['address'],[S.account]),'Cancelling…','Confirm in your wallet'); toast('Change cancelled.'); loadGuard(); } catch(e){ fail(e); } } function setNote(node,text,cls){ node.textContent=text; node.className='note'+(cls?' '+cls:''); const owner=node.id && qs(`[aria-describedby="${node.id}"]`); if(owner) owner.setAttribute('aria-invalid', String(cls==='err')); } async function resolveRecipient(input){ const seq=++S.resolveSeq; const stale=()=>S.resolveSeq!==seq; setNote(el.ens,'Resolving…'); S.resolving=true; paintFoot(); try { if(isAddr(input)){ if(!checksumOk(input)){ S.resolved=null; S.resolvedName=null; setNote(el.ens, 'That address fails its checksum — a character is wrong. Re-copy it from the source.', 'err'); updateSummary(); return false; } const key=input.toLowerCase(); const name = key in S.ens ? S.ens[key] : await reverseAny(input); if(stale()) return false; S.ens[key]=name||null; S.resolved=toChecksum(input); S.resolvedName=name||null; setNote(el.ens, name?`${name} · ${shortAddr(S.resolved)}`:'Valid address','ok'); updateSummary(); return true; } if(input.includes('.')){ const key=input.toLowerCase(); const a = key in S.ens ? S.ens[key] : await resolveName(input); if(stale()) return false; S.ens[key]=a||null; if(a&&isAddr(a)){ S.resolved=toChecksum(a); S.resolvedName=input; setNote(el.ens,`${input} · ${shortAddr(S.resolved)}`,'ok'); updateSummary(); return true; } setNote(el.ens,`No address set for ${input}`,'err'); return false; } setNote(el.ens,'Enter an address, a .eth name, or a .wei name','err'); return false; } catch(e){ if(!stale()) setNote(el.ens,'Could not reach a name resolver','err'); return false; } finally { if(!stale()){ S.resolving=false; paintFoot(); } } } function updateSummary(){ renderReview(); paintPreview(); } const PLATE_D = 'M0 23.7C0 15.6 0 11.5 1.5 8.4C3 5.4 5.4 3 8.4 1.5C11.5 0 15.6 0 23.7 0H276.3C284.4 0 288.5 0 291.6 1.5C294.6 3 297 5.4 298.5 8.4C300 11.5 300 15.6 300 23.7V276.3C300 284.4 300 288.5 298.5 291.6C297 294.6 294.6 297 291.6 298.5C288.5 300 284.4 300 276.3 300H23.7C15.6 300 11.5 300 8.4 298.5C5.4 297 3 294.6 1.5 291.6C0 288.5 0 284.4 0 276.3V23.7Z'; const fit = (len, box, max, min) => { if(!len) return max; const size = Math.floor((box*10)/(len*6)); return size>max ? max : size<min ? min : size; }; const blen = s => new TextEncoder().encode(String(s)).length; function formatDelay(d){ d=BigInt(d); const u=(n,w)=>`${d/n} ${w}${d/n===1n?'':'s'}`; for(const [n,w] of [[31536000n,'year'],[2592000n,'month'],[86400n,'day'],[3600n,'hour'],[60n,'minute']]) if(d>=n && d%n===0n) return u(n,w); return `${d} second${d===1n?'':'s'}`; } const escHTML = t => String(t).replace(/&/g,'&amp;').replace(/'/g,'&#39;') .replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); function clipForDisplay(str, maxBytes){ const b = new TextEncoder().encode(str); if(b.length<=maxBytes) return str; let cut=maxBytes; while(cut>0 && (b[cut]&0xc0)===0x80) cut--; return new TextDecoder().decode(b.slice(0,cut))+'...'; } function renderNFT(token, delay, symbol,