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,'&').replace(/'/g,''')
.replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>');
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,