0x68575b07…67c7·#25,913,386·view on Etherscan
— click to unmute"};
sd.onclick=()=>{snd=!snd;try{LS.snd=snd?"1":"0"}catch{}syncSnd();if(snd)sBlip()};
syncSnd();
wn.onclick=()=>{sBlip();wnSet(!wnMode)};
const cpHex=b=>{let s="";for(const x of b)s+=x.toString(16).padStart(2,"0");return s},cpU8=s=>new TextEncoder().encode(s);
const CP_POOL="0x0000000000047DD77CeCEfE5Dc015EB7bFa9C677",CP_ROUTER="0x000000004c5BF191225F9049b385d6F3820E09BC",
CP_ETH="0x3cba71e1114af183cdeacc6b8457a474d17529fd28704480ca799d0d03126f34",CP_SCALE=10000000000n,CP_BLOCK=25892003,
CP_RELAY="https://api.tacit.finance",CP_LOGS=["https://mainnet.gateway.tenderly.co","https://eth.drpc.org"],
CP_NI="0x00000000000000000000000000000000000000C8",CP_KEY="zswap:cpk:",CP_NOTES="zswap:cpn:",CP_CACHE="zswap:cppool";
const CP_L2={8453:{bridge:"0x3154Cf16ccdb4C6d922629664174b904d80F2C35",peer:"0x4200000000000000000000000000000000000010",gas:800000n},
4663:{inbox:"0x1A07cc4BD17E0118BdB54D70990D2158AbAD7a2D",peer:"0xDf8755334ce7A73cCF6b581C02eA649AE3E864b3",gas:1000000n}};
const SEL_CPESCROW="2bf0cda2",SEL_CPACT="1699fd5b",SEL_CPRECL="02edf635",SEL_CPEXIT="acad0634",SEL_CPWRAP="859a9cee",
SEL_CPDEP="7da9874f",SEL_CPDEPTO="9a2ac6d5",SEL_CPRETRY="679b6ded",SEL_CPSUBFEE="a66b327d",SEL_CPESTRT="c3dc5879",
SEL_CPIMPL="93228617",SEL_CPSETTLE="717fd7f2",SEL_CPNEXT="0be4f422",SEL_CPASSET="9fda5b66",SEL_CPOTHER="7f46ddb2",SEL_CPBRIDGE="e78cea92";
const CP_T_LEAVES="0x7783fb256f5b4e1d4d8b79583488756286326ae15d9997d4098ce5432ed2708b",
CP_T_SPENT="0x576d91547505afce99e7ebe2baf1a0948b5915598105a55f40ba72fea86e875e",
CP_T_WRAP="0xf5d1711d21af6f42622ab6237626933cefc42cc9f0663d81b7c4c7bc5ce99e44";
const CP_MSG="zSwap private bridge\n\nSign to derive the key that owns your shielded deposits. This costs nothing and moves nothing.\n\nAccount: ";
const S2K=[0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2];
const sha2=m=>{const l=m.length,n=((l+9+63)>>6)<<6,b=new Uint8Array(n);b.set(m);b[l]=128;
const dv=new DataView(b.buffer);dv.setUint32(n-4,(l*8)>>>0);dv.setUint32(n-8,Math.floor(l/536870912));
const h=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],w=new Uint32Array(64),rr=(x,k)=>(x>>>k)|(x<<(32-k));
for(let o=0;o<n;o+=64){for(let i=0;i<16;i++)w[i]=dv.getUint32(o+i*4);
for(let i=16;i<64;i++){const p=w[i-15],q=w[i-2];w[i]=w[i-16]+(rr(p,7)^rr(p,18)^(p>>>3))+w[i-7]+(rr(q,17)^rr(q,19)^(q>>>10))}
let[a0,a1,a2,a3,a4,a5,a6,a7]=h;
for(let i=0;i<64;i++){const t1=(a7+(rr(a4,6)^rr(a4,11)^rr(a4,25))+((a4&a5)^(~a4&a6))+S2K[i]+w[i])>>>0,t2=((rr(a0,2)^rr(a0,13)^rr(a0,22))+((a0&a1)^(a0&a2)^(a1&a2)))>>>0;
a7=a6;a6=a5;a5=a4;a4=(a3+t1)>>>0;a3=a2;a2=a1;a1=a0;a0=(t1+t2)>>>0}
h[0]=(h[0]+a0)>>>0;h[1]=(h[1]+a1)>>>0;h[2]=(h[2]+a2)>>>0;h[3]=(h[3]+a3)>>>0;h[4]=(h[4]+a4)>>>0;h[5]=(h[5]+a5)>>>0;h[6]=(h[6]+a6)>>>0;h[7]=(h[7]+a7)>>>0}
const o=new Uint8Array(32);for(let i=0;i<8;i++){o[i*4]=h[i]>>>24;o[i*4+1]=(h[i]>>>16)&255;o[i*4+2]=(h[i]>>>8)&255;o[i*4+3]=h[i]&255}return o};
const FP=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,FN=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
const GP=[0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n,0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n,1n],PZ=[0n,1n,0n];
const fmod=(a,m)=>{a%=m;return a<0n?a+m:a};
const fpow=(b,e,m)=>{let r=1n;b=fmod(b,m);for(;e>0n;e>>=1n){if(e&1n)r=r*b%m;b=b*b%m}return r};
const pdbl=([x,y,z])=>{if(!z||!y)return PZ;const ys=y*y%FP,s=4n*x*ys%FP,m=3n*x*x%FP,x3=fmod(m*m-2n*s,FP);
return[x3,fmod(m*(s-x3)-8n*ys*ys,FP),2n*y*z%FP]};
const padd=(P,Q)=>{if(!P[2])return Q;if(!Q[2])return P;
const[x1,y1,z1]=P,[x2,y2,z2]=Q,z1s=z1*z1%FP,z2s=z2*z2%FP,u1=x1*z2s%FP,u2=x2*z1s%FP,s1=y1*z2s%FP*z2%FP,s2=y2*z1s%FP*z1%FP;
if(u1===u2)return s1===s2?pdbl(P):PZ;
const h=fmod(u2-u1,FP),r=fmod(s2-s1,FP),h2=h*h%FP,h3=h2*h%FP,v=u1*h2%FP,x3=fmod(r*r-h3-2n*v,FP);
return[x3,fmod(r*(v-x3)-s1*h3,FP),h*z1%FP*z2%FP]};
const pmul=(P,k)=>{let R=PZ,Q=P;for(k=fmod(k,FN);k>0n;k>>=1n){if(k&1n)R=padd(R,Q);Q=pdbl(Q)}return R};
const paff=([x,y,z])=>{if(!z)return[0n,0n];const zi=fpow(z,FP-2n,FP),z2=zi*zi%FP;return[x*z2%FP,y*z2%FP*zi%FP]};
const pcomp=P=>{const[x,y]=paff(P);return(y&1n?"03":"02")+x.toString(16).padStart(64,"0")};
const pfromx=(x,odd)=>{if(x>=FP)return null;const y2=(x*x%FP*x+7n)%FP,y=fpow(y2,(FP+1n)/4n,FP);
return y*y%FP===y2?[x,Number(y&1n)!==(odd?1:0)?FP-y:y,1n]:null};
const cpCat=(...a)=>{const o=new Uint8Array(a.reduce((n,x)=>n+x.length,0));let p=0;for(const x of a){o.set(x,p);p+=x.length}return o};
const b32=v=>hexToBytes(typeof v==="bigint"?v.toString(16).padStart(64,"0"):strip0x(String(v)).padStart(64,"0"));
const beN=(n,len)=>{const o=new Uint8Array(len);let v=BigInt(n);for(let i=len-1;i>=0;i--){o[i]=Number(v&255n);v>>=8n}return o};
const big=b=>BigInt("0x"+cpHex(b));
const cpH=(()=>{const s=sha2(cpU8("tacit-generator-H-v1"));for(let c=0;c<256;c++){const P=pfromx(big(sha2(cpCat(s,[c]))),0);if(P)return P}})();
const cpCommit=(v,r)=>padd(pmul(cpH,BigInt(v)),pmul(GP,BigInt(r)));
const cpXY=P=>{const[x,y]=paff(P);return{cx:"0x"+x.toString(16).padStart(64,"0"),cy:"0x"+y.toString(16).padStart(64,"0")}};
const cpDerive=(seed,i)=>{const base=cpCat(b32(seed),cpU8("tacit-evm-cnote-v1"),b32(CP_ETH),beN(i,8));
return{secret:"0x"+cpHex(sha2(cpCat(base,[0]))),blinding:fmod(big(sha2(cpCat(base,[1]))),FN)||1n}};
const cpOwner=nk=>keccak(cpCat(b32(nk),cpU8("tacit-native-owner-v1")));
const cpLeaf=(cx,cy,o)=>keccak(cpCat(b32(CP_ETH),b32(cx),b32(cy),b32(o)));
const cpDepCommit=(cx,cy,o)=>keccak(cpCat(b32(cx),b32(cy),b32(o)));
const cpDepId=(v,c)=>keccak(cpCat(b32(CP_ETH),beN(v,32),b32(c)));
const cpNu=(nk,leaf)=>keccak(cpCat(b32(nk),b32(leaf),cpU8("tacit-native-nullifier-v1")));
const cpBinding=()=>keccak(hexToBytes(pad32("1")+strip0x(CP_POOL).toLowerCase()));
const cpCtx=(tag,cb,a,b,notes,amts)=>keccak(cpCat(cpU8(tag),b32(cb),b32(a),b32(b),...notes.flatMap(n=>n.map(b32)),...amts.map(x=>beN(x,8))));
const cpNonce=(r,ctx,role)=>fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-nonce-v1"),b32(fmod(BigInt(r),FN)),b32(ctx),cpU8(role))))),FN)||1n;
const cpSigma=(v,r,ctx,k)=>{const rs=fmod(BigInt(r),FN),ks=fmod(BigInt(k),FN),Cc=cpCommit(v,rs),R=pmul(GP,ks);
const e=fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-sigma-v1"),beN(v,8),b32(ctx),hexToBytes(pcomp(Cc)),hexToBytes(pcomp(R)))))),FN);
return{R:"0x"+pcomp(R),z:"0x"+fmod(ks+e*rs,FN).toString(16).padStart(64,"0")}};
const cpSeal=(pub,n,e)=>{const P=pfromx(BigInt("0x"+pub.slice(2)),pub.slice(0,2)==="03");
const ss=sha2(hexToBytes(pcomp(pmul(P,e)))),plain=cpCat(beN(n.value,8),b32(n.blinding),b32(n.secret),b32(CP_ETH),b32(n.owner)),o=new Uint8Array(plain.length);
for(let i=0;i<plain.length;i+=32){const k=sha2(cpCat(ss,[i/32]));for(let j=0;j<32&&i+j<plain.length;j++)o[i+j]=plain[i+j]^k[j]}
return"0x"+pcomp(pmul(GP,e))+cpHex(o)};
const cpZeros=(()=>{const z=[new Uint8Array(32)];for(let i=1;i<32;i++)z.push(hexToBytes(keccak(cpCat(z[i-1],z[i-1]))));return z})();
const cpTree=(leaves,idx)=>{let lv=leaves.map(hexToBytes);const path=[];
for(let i=0;i<32;i++){const sib=(idx>>>i)^1;path.push("0x"+cpHex(sib<lv.length?lv[sib]:cpZeros[i]));const nx=[];
for(let k=0;k*2<lv.length;k++)nx.push(hexToBytes(keccak(cpCat(lv[2*k],2*k+1<lv.length?lv[2*k+1]:cpZeros[i]))));
lv=nx.length?nx:[cpZeros[i+1]||cpZeros[0]]}
return{root:"0x"+cpHex(lv[0]),path}};
const cpRandom=()=>{for(let t=0;t<32;t++){const b=new Uint8Array(32);crypto.getRandomValues(b);const s=big(b);if(s&&s<FN)return s}throw Error("no randomness")};
const cpEncCall=c=>encAddr(c.target)+encUint(c.value)+encAddr(c.token)+encUint(c.amount)+encBool(c.push)+pad32("c0")+encBytes(c.data||"0x");
const cpEncRecipe=r=>{const calls=r.calls.map(cpEncCall);let off=calls.length*32,head=encUint(calls.length);
for(const c of calls){head+=encUint(off);off+=c.length/2}
const ca=head+calls.join(""),sw=encUint(r.sweepTokens.length)+r.sweepTokens.map(encAddr).join(""),mo=encUint(r.minOuts.length)+r.minOuts.map(encUint).join("");
return pad32(r.exitedAsset)+encAddr(r.feeAsset)+encAddr(r.finalRecipient)+encUint(r.deadline)+encUint(r.nonce)
+encUint(256)+encUint(256+ca.length/2)+encUint(256+(ca.length+sw.length)/2)+ca+sw+mo};
const cpEscrow=(impl,r)=>{const salt=keccak(hexToBytes(pad32("20")+cpEncRecipe(r)));
const ih=keccak(hexToBytes("602d5f8160095f39f35f5f365f5f37365f73"+strip0x(impl).toLowerCase()+"5af43d5f5f3e6029573d5ffd5b3d5ff3"));
return"0x"+strip0x(keccak(hexToBytes("ff"+strip0x(CP_ROUTER)+strip0x(salt)+strip0x(ih)))).slice(24)};
const cpActData=r=>"0x"+SEL_CPACT+pad32("20")+cpEncRecipe(r);
const cpReclData=r=>{const b=cpEncRecipe(r);return"0x"+SEL_CPRECL+pad32("40")+encUint(64+b.length/2)+b+pad32("0")};
const cpExitData=(pv,pr,r)=>{const a=encBytes(pv),b=encBytes(pr),c=pad32("0"),d=cpEncRecipe(r);
return"0x"+SEL_CPEXIT+pad32("80")+encUint(128+a.length/2)+encUint(128+(a.length+b.length)/2)+encUint(128+(a.length+b.length+c.length)/2)+a+b+c+d};
const cpRecipe=x=>{const l2=CP_L2[x.ch],r={exitedAsset:CP_ETH,feeAsset:ZERO,finalRecipient:x.fin,deadline:BigInt(x.dl),nonce:BigInt(x.nonce),
sweepTokens:[ZERO],minOuts:[0n]};
r.calls=[x.ch===8453?{target:l2.bridge,value:BigInt(x.wei),token:ZERO,amount:0n,push:false,
data:"0x"+SEL_CPDEPTO+encAddr(x.to)+encUint(200000)+pad32("60")+pad32("0")}
:{target:l2.inbox,value:BigInt(x.wei),token:ZERO,amount:0n,push:false,
data:"0x"+SEL_CPRETRY+encAddr(x.to)+encUint(x.l2v)+encUint(x.sc)+encAddr(x.to)+encAddr(x.to)+encUint(x.gl)+encUint(x.mf)+pad32("100")+pad32("0")}];
return r};
const cpRelayBase=()=>{try{const u=LS["zswap:cprelay"];if(/^https:\/\/[\w.-]+(:\d+)?(\/[\w./-]*)?$/.test(u||""))return u.replace(/\/$/,"")}catch{}return CP_RELAY};
const relayFetch=async(path,body,base)=>{const ac=new AbortController(),k=setTimeout(()=>ac.abort(),20000);
try{const r=await fetch((base||cpRelayBase())+path,body?{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body),signal:ac.signal}:{signal:ac.signal});
let j=null;try{j=await r.json()}catch{}
if(r.status===404&&!body)return{status:"unknown"};
if(!r.ok)throw Error((j&&j.error)||"The relay answered "+r.status+".");
return j||{}}
catch(e){if(e&&e.name==="AbortError")throw Error("The relay did not answer.");
if(e instanceof TypeError)throw Error("The relay could not be reached from this page.");throw e}
finally{clearTimeout(k)}};
let cpSeed="",cpFp="",cpPub="",cpNotes=[],cpPool=null,cpSyncAt=0,cpBusy=0,pvMode=false,pvTimer=0,pvTick=0;
const cpNoteOf=n=>{const{secret,blinding}=cpDerive(cpSeed,n.i),owner=cpOwner(secret),{cx,cy}=cpXY(cpCommit(n.v,blinding)),leaf=cpLeaf(cx,cy,owner);
return{secret,blinding,owner,cx,cy,leaf,nu:cpNu(secret,leaf),value:BigInt(n.v),dep:cpDepId(n.v,cpDepCommit(cx,cy,owner))}};
const pneg=([x,y,z])=>[x,fmod(-y,FP),z];
const cpOnCurve=(x,y)=>x<FP&&y<FP&&y*y%FP===(x*x%FP*x+7n)%FP;
const cpVerifySigma=(cx,cy,v,Rh,zh,ctx)=>{const x=BigInt(cx),y=BigInt(cy);if(!cpOnCurve(x,y))return false;
const R=pfromx(BigInt("0x"+strip0x(Rh).slice(2)),strip0x(Rh).slice(0,2)==="03");if(!R||!/^0x0[23][0-9a-fA-F]{64}$/.test(Rh))return false;
const Cc=[x,y,1n],e=fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-sigma-v1"),beN(v,8),b32(ctx),hexToBytes(pcomp(Cc)),hexToBytes(pcomp(R)))))),FN);
const X=padd(Cc,pneg(pmul(cpH,BigInt(v))));return pcomp(pmul(GP,BigInt(zh)))===pcomp(padd(R,pmul(X,e)))};
const cpSettleData=(pv,pr)=>{const a=encBytes(pv),b=encBytes(pr);return"0x"+SEL_CPSETTLE+pad32("60")+encUint(96+a.length/2)+encUint(96+(a.length+b.length)/2)+a+b+pad32("0")};
const cpSaveNotes=()=>{try{LS[CP_NOTES+cpFp]=JSON.stringify(cpNotes)}catch{}};
const cpLoadNotes=()=>{try{const a=JSON.parse(LS[CP_NOTES+cpFp]||"[]");return Array.isArray(a)?a.filter(n=>Number.isInteger(n.i)&&/^\d+$/.test(n.v||"")):[]}catch{return[]}};
const cpKeyFor=a=>{try{return LS[CP_KEY+a.toLowerCase()]}catch{return""}};
const cpUse=s=>{cpSeed=s;cpFp=cpHex(sha2(cpU8("zswap-cp-v1:"+s))).slice(0,16);cpPub=pcomp(pmul(GP,BigInt(s)));cpNotes=cpLoadNotes()};
const cpUnlock=async()=>{let s=cpKeyFor(account);
if(!/^0x[0-9a-f]{64}$/i.test(s||"")){
const sig=await rpc("personal_sign",["0x"+cpHex(cpU8(CP_MSG+account.toLowerCase()+"\nVersion: 1")),account]);
if(!/^0x[0-9a-fA-F]{130}$/.test(sig||""))throw Error("The wallet returned an unusable signature.");
s="0x"+(fmod(BigInt(keccak(hexToBytes(sig))),FN)||1n).toString(16).padStart(64,"0");
try{LS[CP_KEY+account.toLowerCase()]=s}catch{}}
cpUse(s)};
const cpRescue=i=>{const k=fmod(big(sha2(cpCat(cpU8("zswap-exit-rescue-v1"),b32(cpSeed),beN(i,8)))),FN)||1n,[x,y]=paff(pmul(GP,k));
return{key:"0x"+k.toString(16).padStart(64,"0"),addr:"0x"+strip0x(keccak(cpCat(beN(x,32),beN(y,32)))).slice(24)}};
const cpLogsOk=r=>Array.isArray(r)&&r.every(l=>l&&Array.isArray(l.topics)&&l.topics.every(t=>/^0x[0-9a-f]{64}$/i.test(t))
&&/^0x([0-9a-f]{2})*$/i.test(l.data||"0x")&&/^0x[0-9a-f]+$/i.test(l.blockNumber||"")&&/^0x[0-9a-f]+$/i.test(l.logIndex||""));
const cpGetLogs=async(from,to,depth)=>{
const f={address:CP_POOL,fromBlock:toHex(from),toBlock:toHex(to),topics:[[CP_T_LEAVES,CP_T_SPENT,CP_T_WRAP]]};
try{const r=await cfgRead("eth_getLogs",[f]);if(cpLogsOk(r))return r}catch{}
for(const u of CP_LOGS){try{const r=await httpRead(u,"eth_getLogs",[f]);if(cpLogsOk(r))return r}catch{}}
if(to-from<2000||depth>12)throw Error("No node would serve the pool's history.");
const m=from+((to-from)>>1);return(await cpGetLogs(from,m,depth+1)).concat(await cpGetLogs(m+1,to,depth+1))};
const cpWord=(d,i)=>d.slice(i*64,i*64+64);
const cpArr=(d,off)=>{const n=Number(BigInt("0x"+cpWord(d,off)||"0x0")),o=[];for(let i=0;i<n;i++)o.push("0x"+cpWord(d,off+1+i));return o};
const cpSync=async(force)=>{
if(!force&&cpPool&&Date.now()-cpSyncAt<60000)return cpPool;
let c=cpPool;if(!c)try{c=JSON.parse(LS[CP_CACHE]||"null")}catch{}
if(!c||!Array.isArray(c.leaves)||!Array.isArray(c.spent)||!Array.isArray(c.wraps)||!(c.to>=CP_BLOCK))c={to:CP_BLOCK-1,leaves:[],spent:[],wraps:[]};
const cached=c.to>=CP_BLOCK;
const head=parseInt(await cfgRead("eth_blockNumber",[]),16);
if(head>c.to){const logs=await cpGetLogs(c.to+1,head,0);
logs.sort((a,b)=>(parseInt(a.blockNumber,16)-parseInt(b.blockNumber,16))||(parseInt(a.logIndex,16)-parseInt(b.logIndex,16)));
for(const l of logs){const t=l.topics[0].toLowerCase(),d=strip0x(l.data||"");
if(t===CP_T_LEAVES&&l.topics[1]){const first=Number(BigInt(l.topics[1])),off=Number(BigInt("0x"+cpWord(d,0)))/32;
cpArr(d,off).forEach((x,i)=>{c.leaves[first+i]=x})}
else if(t===CP_T_SPENT)c.spent.push(...cpArr(d,Number(BigInt("0x"+cpWord(d,0)))/32));
else if(t===CP_T_WRAP&&l.topics[1])c.wraps.push([l.topics[1].toLowerCase(),"0x"+cpWord(d,0)])}
c.to=head}
const next=Number(BigInt(await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPNEXT},L])||"0x0"));
if(c.leaves.length<next||c.leaves.slice(0,next).some(x=>!x)){
if(cached){cpPool=null;try{delete LS[CP_CACHE]}catch{}return cpSync(true)}
throw Error("The pool's history came back incomplete.")}
cpPool=c;cpSyncAt=Date.now();try{LS[CP_CACHE]=JSON.stringify(c)}catch{}return c};
const cpLadder=v=>{let x=BigInt(v);if(x<=0n)return 0n;const d=x.toString().length;if(d<=2)return x;const s=10n**BigInt(d-2);return(x+s-1n)/s*s};
const cpFee=async v=>{const gp=BigInt(await cfgRead("eth_gasPrice",[])||"0x0");
let fee=cpLadder((450000n*gp+40000000000000n)*135n/100n/CP_SCALE);if(fee>v)fee=v;return fee};
const cpPins=async ch=>{
const impl=retAddr(await cfgRead(C,[{to:CP_ROUTER,data:"0x"+SEL_CPIMPL},L]));
if(!impl||impl===ZERO)throw Error("The router did not answer.");
const a=strip0x(await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPASSET+pad32(CP_ETH)},L])||"");
if(BigInt("0x"+(cpWord(a,0)||"0"))!==1n||BigInt("0x"+(cpWord(a,1)||"0"))!==0n)throw Error("Native ETH is not registered in the pool.");
const l2=CP_L2[ch];
const peer=retAddr(await cfgRead(C,[{to:l2.bridge||l2.inbox,data:"0x"+(l2.bridge?SEL_CPOTHER:SEL_CPBRIDGE)},L]));
if(peer!==l2.peer.toLowerCase())throw Error("The "+CHAINS[ch].name+" bridge did not answer as expected — refusing to continue.");
if(ch===4663){const id=await httpRead(CHAINS[4663].rpcs[0],I,[]);if(parseInt(id,16)!==4663)throw Error("The Robinhood node answered for another chain.")}
return impl};
const cpRhGas=async to=>{const rh=CHAINS[4663].rpcs[0],data="0x"+SEL_CPESTRT+encAddr(CP_ROUTER)+encUint(10n**18n+1n)+encAddr(to)+encUint(1)+encAddr(to)+encAddr(to)+pad32("e0")+pad32("0");
const est=BigInt(await httpRead(rh,"eth_estimateGas",[{to:CP_NI,data}])||"0x0");
const l1=BigInt(await cfgRead("eth_gasPrice",[])||"0x0");
const sc=BigInt(await cfgRead(C,[{to:CP_L2[4663].inbox,data:"0x"+SEL_CPSUBFEE+pad32("0")+encUint(l1*2n)},L])||"0x0");
const l2=BigInt(await httpRead(rh,"eth_gasPrice",[])||"0x0");
if(!est||!sc||!l2)throw Error("Robinhood gas could not be quoted.");
return{gl:est*3n/2n,sc,mf:l2*2n}};
const cpDry=async(x,r,esc)=>{const data=cpActData(r),req={to:CP_ROUTER,data,gas:toHex(CP_L2[x.ch].gas)},ov={[esc]:{balance:toHex(BigInt(x.wei))}};
const tries=[()=>cfgRead(C,[req,L,ov]),...CP_LOGS.map(u=>()=>httpRead(u,C,[req,L,ov]))];let last="";
for(const t of tries){try{if(strip0x(await t()||"")==="")return"";last=""}catch(e){const m=String(e&&e.message||"");if(/revert|execution|out of gas|BadTarget|Expired/i.test(m))throw Error("The bridge call would fail: "+m.slice(0,80));last=m}}
return"Could not dry-run the bridge call ("+(last||"no node")+")."};
const cpEnsureBalance=async(esc,need)=>BigInt(await cfgRead("eth_getBalance",[esc,L])||"0x0")>=need;
const cpStatus=n=>{if(n.p)return{k:null,li:-1,s:n.js==="settled"?"paid":n.js==="failed"?"payfailed":"paying"};
const k=cpNoteOf(n),c=cpPool;
const li=c?c.leaves.indexOf(k.leaf):-1,spent=!!c&&c.spent.includes(k.nu);
const x=n.ex,dl=x?Number(x.dl):0,now=Math.floor(Date.now()/1000);
if(x&&x.ch===1)return{k,li,s:spent?"withdrawn":"exiting"};
if(x&&(x.atx||x.ptx||x.rtx))return{k,li,s:x.rtx?"reclaimed":"bridged"};
if(x&&spent)return{k,li,s:now>dl?"expired":"funded"};
if(x)return{k,li,s:"exiting"};
if(spent)return{k,li,s:"spent"};
if(li>=0)return{k,li,s:"ready"};
return{k,li,s:"pending"}};
const cpRelayPoll=async n=>{const job=n.ex?n.ex.job:n.job;if(!job)return;
const tgt=n.ex||n,st=await relayFetch("/confidential/status?id="+encodeURIComponent(job),null,tgt.rb);tgt.js=st.status||"unknown";
if(st.txHash)tgt.jtx=st.txHash;if(st.error)tgt.jerr=String(st.error).slice(0,120);
const pr=st.proof||st.proofBytes;if(st.status==="proven"&&/^0x[0-9a-fA-F]+$/.test(st.publicValues||"")&&/^0x[0-9a-fA-F]+$/.test(pr||"")){tgt.pv=st.publicValues;tgt.pr=pr}
cpSaveNotes()};
const cpSettleWrap=async n=>{const r=await relayFetch("/confidential/submit",{type:"wrap",op:n.op,memos:[n.memo],mode:"settle"});
if(!r.jobId)throw Error("The relay did not take the deposit.");n.job=r.jobId;n.js=r.status||"pending";n.rb=cpRelayBase();cpSaveNotes()};
const cpAmount=parts=>{const wei=parseUnits(pvAmt.value||"",18);
if(wei<=0n)throw Error("How much ether?");
if(wei%(CP_SCALE*BigInt(parts)))throw Error(parts>1?"That amount does not split evenly into "+parts+" notes.":"The pool keeps eight decimals — round the amount.");
const v=wei/CP_SCALE/BigInt(parts);if(v>=1n<<64n)throw Error("Too large for one note.");return{wei,v}};
const cpFresh=async v=>{let i=cpNotes.reduce((m,n)=>Number.isInteger(n.i)&&n.i>=0?Math.max(m,n.i+1):m,0),k;
for(let t=0;;i++){k=cpNoteOf({i,v:String(v)});
const st=await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPDEP+pad32(k.dep)},L]);
if(BigInt(st||"0x0")===0n)return{i,k};if(++t>20)throw Error("Could not find a fresh note slot.")}};
const cpNote=(i,k)=>{const v=k.value,cb=cpBinding(),ctx=cpCtx("tacit-wrap-intent-v1",cb,CP_ETH,k.dep,[[k.cx,k.cy,k.owner]],[v]),sig=cpSigma(v,k.blinding,ctx,cpNonce(k.blinding,ctx,"wrap"));
return{i,v:String(v),at:Math.floor(Date.now()/1000),memo:cpSeal(cpPub,k,cpRandom()),
op:{chainBinding:cb,asset:CP_ETH,value:String(v),cx:k.cx,cy:k.cy,owner:k.owner,sigR:sig.R,sigZ:sig.z}}};
const cpDepositOne=async(v,label)=>{const{i,k}=await cpFresh(v),n=cpNote(i,k);
const req={from:account,to:CP_ROUTER,value:toHex(v*CP_SCALE),data:"0x"+SEL_CPWRAP+pad32(cpDepCommit(k.cx,k.cy,k.owner))};
await rpc(C,[req,L]);
n.tx=await sendTx([req]);cpNotes.push(n);cpSaveNotes();cpPaint();
await settle(n.tx,true);
stat.textContent=label+" Asking the relay to settle it…";
await cpSettleWrap(n);return n};
const cpDeposit=async()=>{
const parts=+pvSplit.value||1,{wei,v}=cpAmount(parts);
if(wei>fromBalance)throw Error("Not enough ETH.");
for(let p=1;p<=parts;p++)await cpDepositOne(v,parts>1?"Deposit "+p+" of "+parts+" sent.":"Deposited.");
pvAmt.value="";refreshBalance();
stat.textContent=(parts>1?"Deposited as "+parts+" notes.":"Deposited.")+" The relay is settling them into the pool.";sGot()};
const cpRequest=async()=>{const{v}=cpAmount(1),{i,k}=await cpFresh(v),n=cpNote(i,k);n.inv=1;
cpNotes.push(n);cpSaveNotes();cpPaint();
const inv={v:1,chainBinding:n.op.chainBinding,assetId:CP_ETH,underlying:ZERO,ticker:"cETH",amount:String(v*CP_SCALE),value:String(v),
cx:k.cx,cy:k.cy,owner:k.owner,commit:cpDepCommit(k.cx,k.cy,k.owner),depositId:k.dep,leaf:k.leaf,memo:n.memo,witness:n.op};
pvAmt.value="";stat.textContent="Request built. Send it to whoever is paying; the note settles once they pay.";
prompt("Your payment request. Anyone can pay it; only this key can spend the result.",JSON.stringify(inv))};
const cpPay=async()=>{let inv;try{inv=JSON.parse((prompt("Paste the payment request (JSON).")||"").trim())}catch{}
const h32=x=>/^0x[0-9a-fA-F]{64}$/.test(x||"");
if(!inv||inv.v!==1||!inv.witness)throw Error("That is not a payment request.");
if(String(inv.assetId).toLowerCase()!==CP_ETH||String(inv.underlying||ZERO).toLowerCase()!==ZERO)throw Error("This request is not for ether.");
if(![inv.cx,inv.cy,inv.owner,inv.commit,inv.depositId,inv.leaf,inv.chainBinding].every(h32)||!/^\d+$/.test(inv.value||"")||!/^\d+$/.test(inv.amount||""))throw Error("The request is malformed.");
const v=BigInt(inv.value),wei=BigInt(inv.amount),w=inv.witness;
if(wei!==v*CP_SCALE||v<=0n||v>=1n<<64n)throw Error("The request's amounts disagree.");
if(String(inv.chainBinding).toLowerCase()!==cpBinding()||w.chainBinding!==inv.chainBinding||String(w.asset).toLowerCase()!==CP_ETH||w.value!==inv.value||w.cx!==inv.cx||w.cy!==inv.cy||w.owner!==inv.owner)throw Error("The request is for another pool.");
const commit=cpDepCommit(inv.cx,inv.cy,inv.owner);
if(commit!==String(inv.commit).toLowerCase()||cpLeaf(inv.cx,inv.cy,inv.owner)!==String(inv.leaf).toLowerCase()||cpDepId(v,commit)!==String(inv.depositId).toLowerCase())throw Error("The request does not add up — refusing to pay it.");
const ctx=cpCtx("tacit-wrap-intent-v1",inv.chainBinding,CP_ETH,inv.depositId,[[inv.cx,inv.cy,inv.owner]],[v]);
if(!cpVerifySigma(inv.cx,inv.cy,v,w.sigR,w.sigZ,ctx))throw Error("The request is not claimable by its author — refusing to pay it.");
if(!/^0x0[23][0-9a-fA-F]{64}[0-9a-fA-F]{272}$/.test(inv.memo||""))throw Error("The request carries no recovery memo.");
const st=await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPDEP+pad32(inv.depositId)},L]);
if(BigInt(st||"0x0")!==0n)throw Error("This request was already paid.");
if(wei>fromBalance)throw Error("Not enough ETH.");
const n={p:1,i:-1,v:String(v),dep:inv.depositId,at:Math.floor(Date.now()/1000),op:w,memo:inv.memo};
const req={from:account,to:CP_ROUTER,value:toHex(wei),data:"0x"+SEL_CPWRAP+pad32(commit)};
await rpc(C,[req,L]);
n.tx=await sendTx([req]);cpNotes.unshift(n);cpSaveNotes();cpPaint();
await settle(n.tx,true);
stat.textContent="Paid. Asking the relay to settle it for the recipient…";
await cpSettleWrap(n);refreshBal