site/js/bns-register.js
Local Dev 81bc43e581 sirius.x portal: /api/holdings endpoint + favicon paths + roadmap link
gateway/public-gateway.mjs:
  * GET /api/holdings/<scripthash> and POST /api/holdings {scripthashes:[…]}
    — server-side wallet-holdings lookup for the portal. Bns-indexer is
    beacon-only so it can't answer listunspent for arbitrary addresses; the
    gateway does the electrum roundtrip and cross-joins with the cached BCNR
    index. Client posts scripthashes (no libauth needed on the server) and
    gets back the names owned.
  * elConnect now falls through to whole-buffer JSON.parse when a Fulcrum
    response lacks a trailing newline — chipnet.bch.ninja does this and
    otherwise every elCall to it times out. Same tolerance pattern as
    bns-indexer.test.mjs's tiny electrum client.
  * verifyElectrum picks a full electrum with a 3s server.version probe so a
    hung server fails over to the next in seconds instead of stalling 20.
  * Skips silentmode.st/electrum and coinspectrum.duckdns.org:50011 for
    /api/holdings — both are bns-indexer routes that don't do listunspent.

web/register-entry.js: export addressToScripthash so the portal can derive
the scripthash for each of its wallet's watched addresses before POSTing.
Browser bundle rebuilt.

site-sirius-x pages:
  * favicon + logo hrefs made relative (./assets/… on index/portal,
    ../assets/… on docs/theseus). Absolute /sirius-x/… broke on the sirius.x
    BCNR route where the site is served from root instead of under /sirius-x/.
  * Roadmap nav link added back to portal, docs, theseus — the landing has an
    anchor to a section on itself, so subpages now link to it explicitly.
  * portal.html: loadNames rewritten to POST scripthashes to /api/holdings
    instead of doing client-side electrum. Works on browsers whose networks
    block chipnet electrum ports; same-origin HTTPS on 443.
  * portal.html: bundle import ?v= bumped so Chrome's in-memory ES module
    map picks up the new bundle instead of a stale cached copy.
2026-08-31 05:40:47 +02:00

7 lines
28 KiB
JavaScript

import{generateBip39Mnemonic as Xt,deriveSeedFromBip39Mnemonic as At,deriveHdPrivateNodeFromSeed as Vt,deriveHdPath as Qt,secp256k1 as Zt,hash160 as te,encodeCashAddress as Je,lockingBytecodeToCashAddress as ee,cashAddressToLockingBytecode as Ye,binToHex as ne,hexToBin as Xe,utf8ToBin as Nt,binToBase64 as dt,base64ToBin as ut}from"@bitauth/libauth";var Y="bchtest";var se="m/44'/145'/0'",oe=5,tt="bns.wallet.v1";function $t(e){let t=te(e);return Uint8Array.from([118,169,20,...t,136,172])}function _t(e,t,n){let s=ee({bytecode:$t(e),prefix:t,tokenSupport:n});if(typeof s=="string")throw new Error(`address encoding failed: ${s}`);return s.address}var ht=class e{constructor(t,n,s=Y){this.mnemonic=t,this.prefix=s,this._root=Vt(n),this._keys=new Map,this._derive()}static async create(t=Y){let n=Xt();return new e(n,await At(n),t)}static async fromMnemonic(t,n=Y){let s=String(t).trim().toLowerCase().replace(/\s+/g," "),r=await At(s);if(typeof r=="string")throw new Error(`invalid recovery phrase: ${r}`);return new e(s,r,n)}_derive(){this.receive=[],this.change=[];for(let[t,n]of[[0,this.receive],[1,this.change]])for(let s=0;s<oe;s++){let r=`${se}/${t}/${s}`,o=Qt(this._root,r);if(typeof o=="string")throw new Error(`HD derivation failed at ${r}: ${o}`);let i=Zt.derivePublicKeyCompressed(o.privateKey);if(typeof i=="string")throw new Error(`pubkey derivation failed: ${i}`);let a=$t(i),c={path:r,branch:t,index:s,privateKey:o.privateKey,publicKey:i,lockingBytecode:a,address:_t(i,this.prefix,!1),tokenAddress:_t(i,this.prefix,!0)};n.push(c),this._keys.set(ne(a),c)}}get address(){return this.receive[0].address}get tokenAddress(){return this.receive[0].tokenAddress}get watchedAddresses(){return[...this.receive,...this.change].map(t=>t.address)}keyFor(t){let n=this._keys.get(t);return n?{privateKey:n.privateKey,publicKey:n.publicKey}:null}async encrypt(t){let n=crypto.getRandomValues(new Uint8Array(16)),s=crypto.getRandomValues(new Uint8Array(12)),r=await Bt(t,n),o=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:s},r,Nt(this.mnemonic)));return JSON.stringify({v:1,kdf:"PBKDF2-SHA256",iterations:pt,prefix:this.prefix,salt:dt(n),iv:dt(s),ct:dt(o)})}static async decrypt(t,n){let s=typeof t=="string"?JSON.parse(t):t,r=await Bt(n,ut(s.salt),s.iterations??pt),o;try{o=await crypto.subtle.decrypt({name:"AES-GCM",iv:ut(s.iv)},r,ut(s.ct))}catch{throw new Error("wrong password")}return e.fromMnemonic(new TextDecoder().decode(o),s.prefix??Y)}async save(t,n=globalThis.localStorage){n.setItem(tt,await this.encrypt(t))}static async load(t,n=globalThis.localStorage){let s=n.getItem(tt);return s?e.decrypt(s,t):null}static exists(t=globalThis.localStorage){return!!t?.getItem(tt)}static forget(t=globalThis.localStorage){t.removeItem(tt)}},pt=25e4;async function Bt(e,t,n=pt){let s=await crypto.subtle.importKey("raw",Nt(String(e)),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:t,iterations:n,hash:"SHA-256"},s,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}var et=["wss://silentmode.st/electrum","wss://chipnet.bch.ninja:50004","wss://chipnet.imaginary.cash:50004","wss://tbch4.loping.net:62002"],ft=class{constructor(t,n){this.url=t,this.WS=n||globalThis.WebSocket,this.id=0,this.pending=new Map,this.buf=""}connect(){return new Promise((t,n)=>{let s=new this.WS(this.url);this.ws=s;let r=async()=>{try{await this.call("server.version",["bns-web","1.4.2"]),t(this)}catch(a){n(a)}},o=a=>n(new Error("electrum ws error: "+(a?.message||this.url))),i=a=>this._onData(typeof a.data=="string"?a.data:a.data.toString());s.addEventListener?(s.addEventListener("open",r),s.addEventListener("error",o),s.addEventListener("message",i)):(s.onopen=r,s.onerror=o,s.onmessage=i)})}_handle(t){let n=this.pending.get(t.id);n&&(this.pending.delete(t.id),t.error?n.reject(new Error(JSON.stringify(t.error))):n.resolve(t.result))}_onData(t){this.buf+=t;let n;for(;(n=this.buf.indexOf(`
`))>=0;){let r=this.buf.slice(0,n).trim();if(this.buf=this.buf.slice(n+1),r)try{this._handle(JSON.parse(r))}catch{}}let s=this.buf.trim();if(s)try{let r=JSON.parse(s);this._handle(r),this.buf=""}catch{}}call(t,n=[]){let s=++this.id;return new Promise((r,o)=>{this.pending.set(s,{resolve:r,reject:o}),this.ws.send(JSON.stringify({id:s,method:t,params:n})+`
`),setTimeout(()=>{this.pending.has(s)&&(this.pending.delete(s),o(new Error(`electrum timeout: ${t}`)))},2e4)})}close(){try{this.ws.close()}catch{}}};async function It(e=et,t){let n=[];for(let s of e)try{return await new ft(s,t).connect()}catch(r){n.push(`${s}: ${r?.message??r}`)}throw re(e.length,n)}function re(e,t){let n=new Error(`no electrum server reachable (tried ${e}). These use non-standard ports, which some networks and VPNs block:
${t.join(`
`)}`);return n.code="NO_ELECTRUM",n.failures=t,n}import{cashAddressToLockingBytecode as ie,lockingBytecodeToCashAddress as ae,sha256 as ce,hash160 as tn,binToHex as le,hexToBin as en}from"@bitauth/libauth";function Rt(e){return le(ce.hash(e).slice().reverse())}function v(e){let t=ie(e);if(typeof t=="string")throw new Error(`bad address "${e}": ${t}`);return t.bytecode}function de(e){return Rt(v(e))}function U(e){let t=v(e),n=String(e).includes(":")?e.split(":")[0]:"bchtest",s=ae({bytecode:t,prefix:n,tokenSupport:!0});if(typeof s=="string")throw new Error(`cannot token-encode "${e}": ${s}`);return s.address}async function Dt(e,t){let n=v(t),s=Rt(n);return(await e.call("blockchain.scripthash.listunspent",[s,"include_tokens"])).map(o=>({txid:o.tx_hash,vout:o.tx_pos,satoshis:BigInt(o.value),height:o.height,address:t,lockingBytecode:n,token:o.token_data?{category:o.token_data.category,amount:BigInt(o.token_data.amount??0),nft:o.token_data.nft?{capability:o.token_data.nft.capability,commitment:o.token_data.nft.commitment}:void 0}:void 0}))}async function N(e,t){let n=[];for(let s of t)n.push(...await Dt(e,s));return n}async function K(e,t){return e.call("blockchain.transaction.broadcast",[t])}import{binToHex as Ot,hexToBin as G,utf8ToBin as nt,binToUtf8 as ue,encodeTransaction as he,decodeTransaction as on}from"@bitauth/libauth";var pe="BNS1",Ut="bchtest:qp8njaa9a3ahg7259s5qajnce526n0rttsq8qwx4jl",D=600n,W=1000n,j=546n,gt="bch",yt=200,fe=/^[a-z0-9-]{1,32}$/,ge=/^[a-z0-9-]{1,16}$/;var st=class extends Error{constructor(t){super(t),this.name="NoGenesisInputError",this.code="NO_GENESIS_INPUT"}},O=class extends Error{constructor(t,{needSats:n,haveSats:s}={}){super(t),this.name="InsufficientFundsError",this.code="INSUFFICIENT_FUNDS",this.needSats=n,this.haveSats=s}};function C(e){let t=String(e).toLowerCase().trim().replace(/\.$/,"").split(".").filter(Boolean),n,s;if(t.length===0)throw new Error(`invalid name "${e}"`);if(t.length===1?(n=t[0],s=gt):(s=t[t.length-1],n=t[t.length-2]),!fe.test(n)||!ge.test(s))throw new Error(`invalid name "${e}" (allowed: <label>.<tld>, [a-z0-9-])`);return s===gt?n:`${n}.${s}`}function A(e){return e.includes(".")?e:`${e}.${gt}`}function mt(e,t,n){let s=JSON.stringify({o:e,n:t,r:n??{}}),r=nt(pe+s);if(r.length>yt)throw new Error(`records too large (${r.length} > ${yt} bytes)`);return r}function wt(e,t="REG"){return yt-mt(t,e,{}).length}function ye(e){return e.length<=75?Uint8Array.from([e.length,...e]):e.length<=255?Uint8Array.from([76,e.length,...e]):Uint8Array.from([77,e.length&255,e.length>>8&255,...e])}function Ct(e){return Uint8Array.from([106,...ye(e)])}function me(){return Uint8Array.from([65,...new Uint8Array(65),33,...new Uint8Array(33)])}var we=e=>!e.token;function q(e,t,{requireGenesis:n=!0}={}){let s=e.filter(we),r=[],o=0n;if(n){let a=s.filter(c=>c.vout===0).sort((c,d)=>d.satoshis>c.satoshis?1:-1);if(a.length===0)throw new st("wallet has no coin at output index 0, which CashTokens requires to create a new certificate category \u2014 send a small amount to yourself first (buildPrepTx)");r.push(a[0]),o+=a[0].satoshis}let i=s.filter(a=>!r.some(c=>c.txid===a.txid&&c.vout===a.vout)).sort((a,c)=>c.satoshis>a.satoshis?1:-1);for(let a of i){if(o>=t)break;r.push(a),o+=a.satoshis}return{inputs:r,totalSats:o}}function X(e){return{outpointTransactionHash:G(e.txid),outpointIndex:e.vout,sequenceNumber:4294967294,unlockingBytecode:me()}}function ot(e){return{...X(e),lockingBytecode:e.lockingBytecode,valueSatoshis:e.satoshis,...e.token?{token:{amount:e.token.amount??0n,category:G(e.token.category),...e.token.nft?{nft:{capability:e.token.nft.capability,commitment:G(e.token.nft.commitment||"")}}:{}}}:{}}}function V(e){return he(e).length}function xt({name:e,ownerAddress:t,ownerTokenAddress:n,changeAddress:s,records:r={},utxos:o,feeAddress:i=null,feeSats:a=0n,beaconAddress:c=Ut,feeRate:d=1,addressToLockingBytecode:u}){if(typeof u!="function")throw new Error("addressToLockingBytecode must be injected (see chain.js)");let l=C(e),h=mt("REG",l,r),y=BigInt(a??0n);if(y>0n&&!i)throw new Error("feeSats set but feeAddress missing");let p=u(s??t),f=u(n??t),b=u(c),m=y>0n?u(i):null,g=W+D+y,w=q(o,g+300n),x=0n,_=0n,T=!0;for(let P=0;P<6;P++){let{inputs:$,totalSats:k}=w,ct=$[0],I=G(ct.txid),R=(H,M)=>{let B=[];return H&&B.push({lockingBytecode:p,valueSatoshis:M}),B.push({lockingBytecode:f,valueSatoshis:W,token:{amount:0n,category:I,nft:{capability:"none",commitment:nt(l)}}}),B.push({lockingBytecode:Ct(h),valueSatoshis:0n}),B.push({lockingBytecode:b,valueSatoshis:D}),m&&B.push({lockingBytecode:m,valueSatoshis:y}),{version:2,locktime:0,inputs:$.map(X),outputs:B}},F=R(!0,1000000n),lt=V(F);x=BigInt(Math.ceil(lt*d));let E=g+x;if(k<E){let H=q(o,E+200n);if(H.inputs.length>$.length){w=H;continue}throw new O(`not enough funds: need ${E} sat, have ${k} sat`,{needSats:E,haveSats:k})}if(_=k-E,T=_>=j,!T){let H=R(!1,0n);x=k-g;let M=BigInt(Math.ceil(V(H)*d));if(x<M){let B=q(o,g+M+j+200n);if(B.inputs.length>$.length){w=B;continue}throw new O(`not enough funds: need ${g+M} sat, have ${k} sat`,{needSats:g+M,haveSats:k})}_=0n}let Yt=R(T,_),L={},J=0;return T&&(L.change=J++),L.certificate=J++,L.opReturn=J++,L.beacon=J++,m&&(L.serviceFee=J++),{name:l,displayName:A(l),category:Ot(I),transaction:Yt,sourceOutputs:$.map(ot),inputUtxos:$,outputMap:L,payload:h,costs:{certificateDustSats:W,beaconDustSats:D,serviceFeeSats:y,chainFeeSats:x,changeSats:_,totalInputSats:k,netCostSats:y+x+D}}}throw new Error("could not converge on a fee \u2014 please retry")}function rt({name:e,ownerAddress:t,ownerTokenAddress:n,records:s={},certificateUtxo:r,utxos:o,beaconAddress:i=Ut,feeRate:a=1,addressToLockingBytecode:c}){if(typeof c!="function")throw new Error("addressToLockingBytecode must be injected (see chain.js)");let d=C(e);if(!r?.token?.nft)throw new Error("certificateUtxo does not carry a certificate");let u=Ot(nt(d));if(r.token.nft.commitment!==u)throw new Error(`certificate commitment mismatch: holds "${ue(G(r.token.nft.commitment))}", asked to update "${d}"`);let l=mt("UPD",d,s),h=c(t),y=c(n??t),p=c(i),f=W+D,b=q(o,f+400n,{requireGenesis:!1}),m=0n,g=0n,w=!0;for(let x=0;x<6;x++){let _=[r,...b.inputs],T=r.satoshis+b.totalSats,P=(F,lt)=>{let E=[];return F&&E.push({lockingBytecode:h,valueSatoshis:lt}),E.push({lockingBytecode:y,valueSatoshis:W,token:{amount:0n,category:G(r.token.category),nft:{capability:"none",commitment:nt(d)}}}),E.push({lockingBytecode:Ct(l),valueSatoshis:0n}),E.push({lockingBytecode:p,valueSatoshis:D}),{version:2,locktime:0,inputs:_.map(X),outputs:E}},$=V(P(!0,1000000n));m=BigInt(Math.ceil($*a));let k=f+m;if(T<k){let F=q(o,k+200n,{requireGenesis:!1});if(F.inputs.length>b.inputs.length){b=F;continue}throw new O(`not enough funds: need ${k} sat, have ${T} sat`,{needSats:k,haveSats:T})}g=T-k,w=g>=j,w||(m=T-f,g=0n);let ct=P(w,g),I={},R=0;return w&&(I.change=R++),I.certificate=R++,I.opReturn=R++,I.beacon=R++,{name:d,displayName:A(d),category:r.token.category,transaction:ct,sourceOutputs:_.map(ot),inputUtxos:_,outputMap:I,payload:l,costs:{certificateDustSats:W,beaconDustSats:D,serviceFeeSats:0n,chainFeeSats:m,changeSats:g,totalInputSats:T,netCostSats:m+D}}}throw new Error("could not converge on a fee \u2014 please retry")}function it({ownerAddress:e,utxos:t,amountSats:n=10000n,feeRate:s=1,addressToLockingBytecode:r}){let o=r(e),i=q(t,n+400n,{requireGenesis:!1});if(i.inputs.length===0)throw new O("wallet is empty");for(let a=0;a<6;a++){let{inputs:c,totalSats:d}=i,u=(p,f)=>({version:2,locktime:0,inputs:c.map(X),outputs:[{lockingBytecode:o,valueSatoshis:n},...p?[{lockingBytecode:o,valueSatoshis:f}]:[]]}),l=BigInt(Math.ceil(V(u(!0,1000000n))*s));if(d<n+l){let p=BigInt(Math.ceil(V(u(!1,0n))*s));if(d<=p+j)throw new O(`not enough funds to prepare the wallet: have ${d} sat`,{needSats:p+j,haveSats:d});return{transaction:{version:2,locktime:0,inputs:c.map(X),outputs:[{lockingBytecode:o,valueSatoshis:d-p}]},sourceOutputs:c.map(ot),inputUtxos:c,costs:{chainFeeSats:p}}}let h=d-n-l,y=h>=j;return{transaction:u(y,h),sourceOutputs:c.map(ot),inputUtxos:c,costs:{chainFeeSats:y?l:d-n}}}throw new Error("could not converge on a fee \u2014 please retry")}function Q(e){return e.some(t=>t.vout===0&&!t.token)}function Ft({transaction:e,sourceOutputs:t},{broadcast:n=!0,userPrompt:s}={}){return{transaction:e,sourceOutputs:t,broadcast:n,...s?{userPrompt:s}:{}}}import{generateSigningSerializationBCH as xe,hash256 as Ht,secp256k1 as be,encodeTransaction as ke,binToHex as bt}from"@bitauth/libauth";var Lt=65;function Z(e,t,n){let s={...e,inputs:e.inputs.map((o,i)=>{let a=t[i],c=n(bt(a.lockingBytecode));if(!c)throw new Error(`no key for input ${i} \u2014 cannot sign`);let d=xe({inputIndex:i,sourceOutputs:t,transaction:e},{coveredBytecode:a.lockingBytecode,signingSerializationType:Uint8Array.of(Lt)}),u=Ht(d),l=be.signMessageHashSchnorr(c.privateKey,u);if(typeof l=="string")throw new Error(`signing failed on input ${i}: ${l}`);let h=Uint8Array.from([...l,Lt]);return{...o,unlockingBytecode:Uint8Array.from([h.length,...h,c.publicKey.length,...c.publicKey])}})},r=ke(s);return{transaction:s,hex:bt(r),txid:kt(r)}}function kt(e){return bt(Ht(e).slice().reverse())}function St(e){let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return kt(t)}var Mt="e1cdbba719436f49acb4bf245b44df875c6d6b405eda46a9d3e736d138102f3b";var Kt="c85db10e106606273422b2733b2a6fb8b35a4d0e7dbfb20cf59bdb0833c008b8",Wt="BNS1";var Se=/^[a-z0-9-]{1,32}$/,jt=/^[a-z0-9-]{1,16}$/,ve=/^[a-z0-9-]{1,32}(\.[a-z0-9-]{1,16})?$/;function Te(e){return e.includes(".")?e:e+".bch"}var Ee=[{url:"wss://silentmode.st/electrum",ip:"195.184.247.106"},{url:"wss://coinspectrum.duckdns.org:50011",ip:"195.184.247.106"},{url:"wss://chipnet.bch.ninja:50004",ip:"135.148.236.244"},{url:"wss://chipnet.imaginary.cash:50004",ip:"66.29.152.199"},{url:"wss://tbch4.loping.net:62002",ip:"183.89.202.207"}];var Ae=e=>{let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t},qt=e=>[...new TextEncoder().encode(e)].map(t=>t.toString(16).padStart(2,"0")).join("");function _e(e){let n=String(e).toLowerCase().trim().replace(/^[a-z]+:\/\//,"").split("/")[0].replace(/\.$/,"").split(".").filter(Boolean);if(n.length===0)throw new Error(`invalid name "${e}"`);let s,r;if(n.length===1?(s=n[0],r="bch"):(r=n[n.length-1],s=n[n.length-2]),!Se.test(s)||!jt.test(r))throw new Error(`invalid name "${e}" (allowed: <label>.<tld>, [a-z0-9-])`);return`${s}.${r}`}var at=class{constructor(t,n,s){this.url=t,this.WS=n||globalThis.WebSocket,this.options=s,this.id=0,this.pending=new Map,this.buf=""}connect(){return new Promise((t,n)=>{let s=this.options?new this.WS(this.url,this.options):new this.WS(this.url);this.ws=s;let r=async()=>{try{await this.call("server.version",["bns-web","1.4.2"]),t(this)}catch(a){n(a)}},o=a=>n(new Error("electrum ws error: "+(a?.message||this.url))),i=a=>this._onData(typeof a.data=="string"?a.data:a.data.toString());s.addEventListener?(s.addEventListener("open",r),s.addEventListener("error",o),s.addEventListener("message",i)):(s.onopen=r,s.onerror=o,s.onmessage=i)})}_handle(t){let n=this.pending.get(t.id);n&&(this.pending.delete(t.id),t.error?n.reject(new Error(JSON.stringify(t.error))):n.resolve(t.result))}_onData(t){this.buf+=t;let n;for(;(n=this.buf.indexOf(`
`))>=0;){let r=this.buf.slice(0,n).trim();if(this.buf=this.buf.slice(n+1),r)try{this._handle(JSON.parse(r))}catch{}}let s=this.buf.trim();if(s)try{let r=JSON.parse(s);this._handle(r),this.buf=""}catch{}}call(t,n=[]){let s=++this.id;return new Promise((r,o)=>{this.pending.set(s,{resolve:r,reject:o}),this.ws.send(JSON.stringify({id:s,method:t,params:n})+`
`),setTimeout(()=>{this.pending.has(s)&&(this.pending.delete(s),o(new Error(`electrum timeout: ${t}`)))},2e4)})}close(){try{this.ws.close()}catch{}}},Be=e=>typeof e=="string"?{url:e}:e;async function Ne(e,t,{directIP:n=!1}={}){let s;for(let r of e){let o=Be(r);try{return await new at(o.url,t).connect()}catch(i){s=i}if(n&&o.ip)try{let i=new URL(o.url),a=`${i.protocol}//${o.ip}:${i.port}`;return await new at(a,t,{servername:i.hostname}).connect()}catch(i){s=i}}throw s??new Error("no electrum server reachable")}function Gt(e){if(!e||!e.startsWith("6a"))return null;let t=Ae(e),n=1,s=[];for(;n<t.length;){let o=t[n++],i;if(o<=75)i=o;else if(o===76)i=t[n++];else if(o===77)i=t[n]|t[n+1]<<8,n+=2;else return null;s.push(t.slice(n,n+i)),n+=i}let r=new TextDecoder().decode(s[0]??new Uint8Array);if(!r.startsWith(Wt))return null;try{let o=JSON.parse(r.slice(Wt.length));if($e.has(o.o)&&typeof o.n=="string")return o}catch{}return null}var $e=new Set(["REG","UPD","TREG","TUPD"]);function zt(e,t){let n=e.height>0?e.height:1/0,s=t.height>0?t.height:1/0;return n-s||e.tx_hash.localeCompare(t.tx_hash)}function Pt(e,t){let n=new Set(e.map(a=>a.tx_hash)),s=new Map(e.map(a=>{let c=new Set;for(let d of t.get(a.tx_hash)?.vin??[])d.txid&&d.txid!==a.tx_hash&&n.has(d.txid)&&c.add(d.txid);return[a.tx_hash,c]})),r=new Set,o=[],i=[...e];for(;i.length;){let a=i.findIndex(d=>[...s.get(d.tx_hash)].every(u=>r.has(u)));a<0&&(a=0);let[c]=i.splice(a,1);r.add(c.tx_hash),o.push(c)}return o}function Ie(e){let t=e.lastIndexOf(".");return t<0?e:e.slice(t+1)}function Re(e,t,n){let s=n.get(e);return s?s.regHeight>0&&t>0?s.regHeight<=t:!0:!1}async function De(e){let t=typeof e.unionHistory=="function"?(await e.unionHistory(Kt)).history:await e.call("blockchain.scripthash.get_history",[Kt]);t.sort(zt);let n=new Map;for(let{tx_hash:r}of t)try{n.set(r,await e.call("blockchain.transaction.get",[r,!0]))}catch{}let s=new Map;for(let{tx_hash:r,height:o}of Pt(t.filter(i=>n.has(i.tx_hash)),n)){let a=n.get(r).vout||[],c=null;for(let u of a)c=Gt(u.scriptPubKey?.hex)??c;if(!c||typeof c.n!="string"||!jt.test(c.n))continue;let d=c.n;if(c.o==="TREG"){if(s.has(d))continue;let u=qt(d),l=a.find(h=>h.tokenData?.nft&&h.tokenData.nft.commitment===u);if(!l)continue;s.set(d,{category:l.tokenData.category,regHeight:o,regTxid:r})}}return s}async function z({electrum:e=Ee,WebSocket:t,client:n,directIP:s=!1,enforceTld:r=!1}={}){let o=n??await Ne(e,t,{directIP:s});try{let i=r?await De(o):null,a=null,c;if(typeof o.unionHistory=="function"){let l=await o.unionHistory(Mt);c=l.history,a={servers:l.servers,unreachable:l.unreachable,disagreements:l.disagreements}}else c=await o.call("blockchain.scripthash.get_history",[Mt]);c.sort(zt);let d=new Map;for(let{tx_hash:l}of c)try{d.set(l,await o.call("blockchain.transaction.get",[l,!0]))}catch{}let u=new Map;for(let{tx_hash:l,height:h}of Pt(c.filter(y=>d.has(y.tx_hash)),d)){let p=d.get(l).vout||[],f=null;for(let g of p)f=Gt(g.scriptPubKey?.hex)??f;if(!f)continue;let b=f.n;if(!ve.test(b))continue;let m=Te(b);if(f.o==="REG"){if(u.has(m)||r&&!Re(Ie(m),h,i))continue;let g=qt(b),w=p.find(x=>x.tokenData?.nft&&x.tokenData.nft.commitment===g);if(!w)continue;u.set(m,{name:m,category:w.tokenData.category,records:f.r??{},txid:l,height:h,updatedTxid:null})}else if(f.o==="UPD"){let g=u.get(m);if(!g||!p.some(x=>x.tokenData&&x.tokenData.category===g.category))continue;g.records=f.r??{},g.updatedTxid=l}}return a&&Object.defineProperty(u,"integrity",{value:a,enumerable:!1}),u}finally{n||o.close()}}async function Oe(e,t={}){let n=_e(e);return(await z(t)).get(n)??null}var S={network:"chipnet",addressPrefix:"bchtest",selfHosted:null,get electrum(){return this.selfHosted?[this.selfHosted,...et]:et},beacon:"bchtest:qp8njaa9a3ahg7259s5qajnce526n0rttsq8qwx4jl",serviceFee:{address:"bchtest:qzew3dqcsj0guwe5y3uxf9q2xs5ajgy9lca4z093cu",sats:10000n},feeRate:1,wizardConnect:{dappName:"Silent Mode",dappIcon:"https://silentmode.st/icon.png"},faucets:[{name:"tbch.googol.cash",url:"https://tbch.googol.cash/"},{name:"chipnet.bch.ninja faucet",url:"https://chipnet.bch.ninja/faucet"}]};function Ue(e){let t=n=>(Number(n)/1e8).toFixed(8).replace(/0+$/,"").replace(/\.$/,"");return[{label:"Service fee",sats:e.serviceFeeSats,bch:t(e.serviceFeeSats)},{label:"Network fee",sats:e.chainFeeSats,bch:t(e.chainFeeSats)},{label:"Discovery beacon",sats:e.beaconDustSats,bch:t(e.beaconDustSats)},{label:"Held in the certificate",sats:e.certificateDustSats,bch:t(e.certificateDustSats),note:"stays yours \u2014 it lives in the certificate you now own"},{label:"Total",sats:e.netCostSats,bch:t(e.netCostSats),total:!0}]}async function Ce({electrum:e=S.electrum,WebSocket:t}={}){return It(e,t)}async function Tt(e,t){let n=C(t),s=A(n),o=(await z({client:e})).get(s)??null;return{name:s,rawName:n,available:!o,entry:o,recordBudgetBytes:wt(n)}}async function Et(e,{name:t,ownerAddress:n,records:s={},utxos:r,serviceFee:o=S.serviceFee,feeRate:i=S.feeRate}){let a=r??await N(e,[n]);return xt({name:t,ownerAddress:n,ownerTokenAddress:U(n),records:s,utxos:a,feeAddress:o.sats>0n?o.address:null,feeSats:o.sats,beaconAddress:S.beacon,feeRate:i,addressToLockingBytecode:v})}async function Fe(e,{wallet:t,name:n,records:s={},serviceFee:r=S.serviceFee,feeRate:o=S.feeRate,onProgress:i=()=>{}}){i("checking-availability");let a=await Tt(e,n);if(!a.available)throw new Error(`"${a.name}" is already registered`);i("loading-coins");let c=await N(e,t.watchedAddresses);if(c.filter(p=>!p.token).reduce((p,f)=>p+f.satoshis,0n)===0n)throw new Error("wallet is empty \u2014 fund the address first");let u=null;if(!Q(c)){i("preparing-wallet");let p=it({ownerAddress:t.address,utxos:c,feeRate:o,addressToLockingBytecode:v}),f=Z(p.transaction,p.sourceOutputs,b=>t.keyFor(b));u=await K(e,f.hex),i("prepared",{txid:u}),c=[{txid:u,vout:0,satoshis:f.transaction.outputs[0].valueSatoshis,address:t.address,lockingBytecode:v(t.address)},...f.transaction.outputs.length>1?[{txid:u,vout:1,satoshis:f.transaction.outputs[1].valueSatoshis,address:t.address,lockingBytecode:v(t.address)}]:[]]}i("building");let l=await Et(e,{name:n,ownerAddress:t.address,records:s,utxos:c,serviceFee:r,feeRate:o});i("signing");let h=Z(l.transaction,l.sourceOutputs,p=>t.keyFor(p));i("broadcasting");let y=await K(e,h.hex);return i("registered",{txid:y,category:l.category}),{name:l.name,displayName:l.displayName,category:l.category,txid:y,prepTxid:u,ownerAddress:t.address,certificateAddress:U(t.address),costs:l.costs,hex:h.hex}}async function vt(e,t,n,s){let r=n.signedTransactionHash??St(n.signedTransaction);if(t.walletBroadcasts)return r;s("broadcasting");try{return await K(e,n.signedTransaction)}catch(o){let i=String(o?.message??o);if(/already|duplicate|txn-already|in mempool/i.test(i))return r;throw o}}async function Le(e,{session:t,name:n,ownerAddress:s,records:r={},serviceFee:o=S.serviceFee,feeRate:i=S.feeRate,onProgress:a=()=>{}}){a("checking-availability");let c=await Tt(e,n);if(!c.available)throw new Error(`"${c.name}" is already registered`);a("reading-wallet");let d=await t.getAddresses();if(!d.length)throw new Error("the wallet exposed no addresses");let u=s??d[0];a("loading-coins");let l=await N(e,d);if(!l.some(f=>!f.token))throw new Error("the connected wallet has no spendable coins");if(!Q(l)){a("preparing-wallet");let f=it({ownerAddress:u,utxos:l,feeRate:i,addressToLockingBytecode:v}),b=await t.signTransaction(f,{userPrompt:"One-time setup so your wallet can create a name certificate"}),m=await vt(e,t,b,a);a("prepared",{txid:m}),l=[{txid:m,vout:0,satoshis:f.transaction.outputs[0].valueSatoshis,address:u,lockingBytecode:v(u)},...l.filter(g=>!g.token&&!f.inputUtxos.some(w=>w.txid===g.txid&&w.vout===g.vout))]}a("building");let h=await Et(e,{name:n,ownerAddress:u,records:r,utxos:l,serviceFee:o,feeRate:i});a("awaiting-approval");let y=await t.signTransaction(h,{userPrompt:`Register ${h.displayName} \u2014 the certificate is minted to your own address`}),p=await vt(e,t,y,a);return a("registered",{txid:p,category:h.category}),{name:h.name,displayName:h.displayName,category:h.category,txid:p,ownerAddress:u,certificateAddress:U(u),costs:h.costs}}async function He(e,{addresses:t,category:n,rawName:s}){let r=await N(e,t),o=[...new TextEncoder().encode(s)].map(i=>i.toString(16).padStart(2,"0")).join("");return r.find(i=>i.token?.category===n&&i.token?.nft?.commitment===o)??null}async function Me(e,{wallet:t,name:n,records:s,feeRate:r=S.feeRate,onProgress:o=()=>{}}){o("resolving");let i=C(n),c=(await z({client:e})).get(A(i));if(!c)throw new Error(`"${A(i)}" is not registered`);o("loading-coins");let d=await N(e,t.watchedAddresses),u=d.find(p=>p.token?.category===c.category&&p.token?.nft);if(!u)throw new Error(`this wallet does not hold the certificate for "${A(i)}" \u2014 only the owner can change its records`);o("building");let l=rt({name:n,ownerAddress:t.address,ownerTokenAddress:U(t.address),records:s,certificateUtxo:u,utxos:d.filter(p=>!p.token),beaconAddress:S.beacon,feeRate:r,addressToLockingBytecode:v});o("signing");let h=Z(l.transaction,l.sourceOutputs,p=>t.keyFor(p));o("broadcasting");let y=await K(e,h.hex);return o("updated",{txid:y}),{name:l.name,displayName:l.displayName,category:l.category,txid:y,records:s,costs:l.costs}}async function Ke(e,{session:t,name:n,records:s,feeRate:r=S.feeRate,onProgress:o=()=>{}}){o("resolving");let i=C(n),c=(await z({client:e})).get(A(i));if(!c)throw new Error(`"${A(i)}" is not registered`);o("reading-wallet");let d=await t.getAddresses(),u=await N(e,d),l=u.find(f=>f.token?.category===c.category&&f.token?.nft);if(!l)throw new Error(`this wallet does not hold the certificate for "${A(i)}" \u2014 only the owner can change its records`);o("building");let h=rt({name:n,ownerAddress:l.address,ownerTokenAddress:U(l.address),records:s,certificateUtxo:l,utxos:u.filter(f=>!f.token),beaconAddress:S.beacon,feeRate:r,addressToLockingBytecode:v});o("awaiting-approval");let y=await t.signTransaction(h,{userPrompt:`Point ${h.displayName} somewhere new`}),p=await vt(e,t,y,o);return o("updated",{txid:p}),{name:h.name,displayName:h.displayName,category:h.category,txid:p,records:s,costs:h.costs}}async function We(e,t){let n=await N(e,t),s=n.filter(r=>!r.token);return{sats:s.reduce((r,o)=>r+o.satoshis,0n),confirmedSats:s.filter(r=>r.height>0).reduce((r,o)=>r+o.satoshis,0n),coins:s.length,certificates:n.filter(r=>r.token?.nft),hasGenesisInput:Q(n)}}import{hash160 as je,lockingBytecodeToCashAddress as qe,binToHex as bn}from"@bitauth/libauth";function Jt(e){return Uint8Array.from([118,169,20,...je(e),136,172])}function Ge(e,t="bchtest",n=!1){let s=qe({bytecode:Jt(e),prefix:t,tokenSupport:n});if(typeof s=="string")throw new Error(`address encoding failed: ${s}`);return s.address}function ze(e){return e?{ready:!0,approvals:1,explanation:"Your wallet can mint the certificate directly."}:{ready:!1,approvals:2,explanation:"Your wallet needs one small setup transaction to itself before it can create a certificate \u2014 a rule of the Bitcoin Cash token system, not a fee we charge. You will be asked to approve it, then the registration. This happens only once."}}import{binToHex as Rn,hexToBin as Dn}from"@bitauth/libauth";import{stringify as Un}from"@bitauth/libauth";export{ht as BuiltInWallet,Y as CHIPNET_PREFIX,O as InsufficientFundsError,st as NoGenesisInputError,S as REGISTRAR,v as addressToLockingBytecode,de as addressToScripthash,Rn as binToHex,K as broadcast,z as buildIndex,it as buildPrepTx,xt as buildRegistrationTx,rt as buildUpdateTx,Tt as checkAvailability,Ce as connect,ze as describeGenesisRequirement,A as displayName,He as findCertificateUtxo,We as getBalance,Dt as getUtxos,N as getUtxosForAddresses,Q as hasGenesisInput,Dn as hexToBin,C as normalizeName,wt as payloadBudget,Ue as priceSummary,Ge as pubkeyToAddress,Jt as pubkeyToLockingBytecode,Et as quoteRegistration,Fe as registerWithBuiltInWallet,Le as registerWithExternalWallet,Oe as resolveName,Me as setRecordsWithBuiltInWallet,Ke as setRecordsWithExternalWallet,Z as signTransaction,Un as stringify,U as toTokenAddress,Ft as toWcTransactionObject,kt as txidOf,St as txidOfHex};