theseus/extensions/js/bns-register.js
Local Dev 8fb940ec80 feat: community extensions — publish with a BCDN name, install from Settings, theseus.x catalog
Anyone who owns a BCDN name can now publish a Theseus extension, and every
Theseus can install it with the publisher's signature verified locally.

Gateway (Argus/src/gateway/public-gateway.mjs):
  PUT /api/ext/<name>/<id>/<version> takes the gzipped tar, checks two BCH
  message signatures against the name's current NFT owner (one authorises
  the upload, one is stored in the channel), inspects the package
  (addon.json at the root, id/version/main match, 8 MB cap), enforces
  first-publisher ownership of an id and monotonic versions, and writes the
  tarball, the extension's updates.json and community/catalog.json to Sia.
  GET /api/ext/catalog reads the catalog back with CORS.

Theseus:
  lib/publisher-sig.mjs recovers the signer of a channel entry; main.js
  compares it with the publisher name's owner from Theseus's own chain
  index before installing or updating, so neither the relay nor a tampered
  catalog can pass off code under a trusted name. addon-updater.js gains
  installCommunity() and accepts publisher-signed entries in the regular
  update check (operator Ed25519 entries unchanged). Settings › Extensions
  shows the community catalog with Install / Update; Settings › Plug-ins
  links to theseus.x/plug-ins.

theseus.x:
  /plug-ins/ is a separate page for the first-party plug-ins (Aegis,
  Ariadne's Thread) with live versions and hashes; /extensions/ lists the
  bundled extensions, the community catalog, and how to build and publish;
  /extensions/publish/ signs and uploads a package in the browser with the
  wallet that holds the publisher's name (session helper + wallet bundle
  copied alongside).
2026-09-20 15:26:30 +02:00

7 lines
49 KiB
JavaScript

import{generateBip39Mnemonic as qe,deriveSeedFromBip39Mnemonic as ne,deriveHdPrivateNodeFromSeed as Ke,deriveHdPath as We,secp256k1 as oe,hash160 as ze,encodeCashAddress as no,lockingBytecodeToCashAddress as Ge,cashAddressToLockingBytecode as oo,binToHex as je,hexToBin as so,utf8ToBin as ie,binToBase64 as Nt,base64ToBin as Dt}from"@bitauth/libauth";var mt="bchtest";var $t="m/44'/145'/0'",Je=5,kt="bns.wallet.v1";function ae(e){let t=ze(e);return Uint8Array.from([118,169,20,...t,136,172])}function se(e,t,n){let o=Ge({bytecode:ae(e),prefix:t,tokenSupport:n});if(typeof o=="string")throw new Error(`address encoding failed: ${o}`);return o.address}var Rt=class e{constructor(t,n,o=mt,s=$t){this.mnemonic=t,this.prefix=o,this.accountPath=s,this._root=Ke(n),this._keys=new Map,this._derive()}static async create(t=mt,n=$t){let o=qe();return new e(o,await ne(o),t,n)}static async fromMnemonic(t,n=mt,o=$t){let s=String(t).trim().toLowerCase().replace(/\s+/g," "),r=await ne(s);if(typeof r=="string")throw new Error(`invalid recovery phrase: ${r}`);return new e(s,r,n,o)}_derive(){this.receive=[],this.change=[];for(let[t,n]of[[0,this.receive],[1,this.change]])for(let o=0;o<Je;o++){let s=`${this.accountPath}/${t}/${o}`,r=We(this._root,s);if(typeof r=="string")throw new Error(`HD derivation failed at ${s}: ${r}`);let a=oe.derivePublicKeyCompressed(r.privateKey);if(typeof a=="string")throw new Error(`pubkey derivation failed: ${a}`);let i=ae(a),l={path:s,branch:t,index:o,privateKey:r.privateKey,publicKey:a,lockingBytecode:i,address:se(a,this.prefix,!1),tokenAddress:se(a,this.prefix,!0)};n.push(l),this._keys.set(je(i),l)}}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}signMessage(t,n){if(!(t instanceof Uint8Array)||t.length!==32)throw new Error("signMessage expects a 32-byte Uint8Array digest");let o=n||this.address,s=[...this.receive,...this.change].find(h=>h.address===o);if(!s)throw new Error(`no key for address ${o}`);let r=oe.signMessageHashRecoverableCompact(s.privateKey,t);if(typeof r=="string")throw new Error(`signMessage failed: ${r}`);let{signature:a}=r,i=r.recoveryId??r.recovery;if(typeof i!="number")throw new Error("signMessage: no recovery id in signature result");let l=31+i,c=new Uint8Array(65);return c[0]=l,c.set(a,1),c}async encrypt(t){let n=crypto.getRandomValues(new Uint8Array(16)),o=crypto.getRandomValues(new Uint8Array(12)),s=await re(t,n),r=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:o},s,ie(this.mnemonic)));return JSON.stringify({v:1,kdf:"PBKDF2-SHA256",iterations:Ft,prefix:this.prefix,salt:Nt(n),iv:Nt(o),ct:Nt(r)})}static async decrypt(t,n){let o=typeof t=="string"?JSON.parse(t):t,s=await re(n,Dt(o.salt),o.iterations??Ft),r;try{r=await crypto.subtle.decrypt({name:"AES-GCM",iv:Dt(o.iv)},s,Dt(o.ct))}catch{throw new Error("wrong password")}return e.fromMnemonic(new TextDecoder().decode(r),o.prefix??mt)}async save(t,n=globalThis.localStorage){n.setItem(kt,await this.encrypt(t))}static async load(t,n=globalThis.localStorage){let o=n.getItem(kt);return o?e.decrypt(o,t):null}static exists(t=globalThis.localStorage){return!!t?.getItem(kt)}static forget(t=globalThis.localStorage){t.removeItem(kt)}},Ft=25e4;async function re(e,t,n=Ft){let o=await crypto.subtle.importKey("raw",ie(String(e)),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:t,iterations:n,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}var bt=["wss://silentmode.st/electrum","wss://chipnet.bch.ninja:50004","wss://chipnet.imaginary.cash:50004","wss://tbch4.loping.net:62002"],Lt=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 o=new this.WS(this.url);this.ws=o;let s=async()=>{try{await this.call("server.version",["bns-web","1.4.2"]),t(this)}catch(i){n(i)}},r=i=>n(new Error("electrum ws error: "+(i?.message||this.url))),a=i=>this._onData(typeof i.data=="string"?i.data:i.data.toString());o.addEventListener?(o.addEventListener("open",s),o.addEventListener("error",r),o.addEventListener("message",a)):(o.onopen=s,o.onerror=r,o.onmessage=a)})}_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 s=this.buf.slice(0,n).trim();if(this.buf=this.buf.slice(n+1),s)try{this._handle(JSON.parse(s))}catch{}}let o=this.buf.trim();if(o)try{let s=JSON.parse(o);this._handle(s),this.buf=""}catch{}}call(t,n=[]){let o=++this.id;return new Promise((s,r)=>{this.pending.set(o,{resolve:s,reject:r}),this.ws.send(JSON.stringify({id:o,method:t,params:n})+`
`),setTimeout(()=>{this.pending.has(o)&&(this.pending.delete(o),r(new Error(`electrum timeout: ${t}`)))},2e4)})}close(){try{this.ws.close()}catch{}}};async function ce(e=bt,t){let n=[];for(let o of e)try{return await new Lt(o,t).connect()}catch(s){n.push(`${o}: ${s?.message??s}`)}throw Pe(e.length,n)}function Pe(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 Ye,lockingBytecodeToCashAddress as Xe,sha256 as Ve,hash160 as co,binToHex as Qe,hexToBin as lo}from"@bitauth/libauth";function le(e){return Qe(Ve.hash(e).slice().reverse())}function _(e){let t=Ye(e);if(typeof t=="string")throw new Error(`bad address "${e}": ${t}`);return t.bytecode}function Ze(e){return le(_(e))}function M(e){let t=_(e),n=String(e).includes(":")?e.split(":")[0]:"bchtest",o=Xe({bytecode:t,prefix:n,tokenSupport:!0});if(typeof o=="string")throw new Error(`cannot token-encode "${e}": ${o}`);return o.address}async function ue(e,t){let n=_(t),o=le(n);return(await e.call("blockchain.scripthash.listunspent",[o,"include_tokens"])).map(r=>({txid:r.tx_hash,vout:r.tx_pos,satoshis:BigInt(r.value),height:r.height,address:t,lockingBytecode:n,token:r.token_data?{category:r.token_data.category,amount:BigInt(r.token_data.amount??0),nft:r.token_data.nft?{capability:r.token_data.nft.capability,commitment:r.token_data.nft.commitment}:void 0}:void 0}))}async function z(e,t){let n=[];for(let o of t)n.push(...await ue(e,o));return n}async function V(e,t){return e.call("blockchain.transaction.broadcast",[t])}import{binToHex as vt,hexToBin as j,utf8ToBin as st,binToUtf8 as de,encodeTransaction as tn,decodeTransaction as po}from"@bitauth/libauth";var en="BNS1",Tt="bchtest:qp8njaa9a3ahg7259s5qajnce526n0rttsq8qwx4jl",Mt="bchtest:qzc4c76vtw9nd49g6x8nmvc0jz62uvk4dc7hmpmerh",I=600n,F=1000n,U=546n,Ct="bch",Ht=200,nn=/^[a-z0-9-]{1,32}$/,he=/^[a-z0-9-]{1,16}$/;function ht(e){let t=String(e).toLowerCase().trim().replace(/^\.+/,"").replace(/\.+$/,"");if(!he.test(t))throw new Error(`invalid TLD "${e}" (allowed: [a-z0-9-]{1,16})`);return t}var St=class extends Error{constructor(t){super(t),this.name="NoGenesisInputError",this.code="NO_GENESIS_INPUT"}},J=class extends Error{constructor(t,{needSats:n,haveSats:o}={}){super(t),this.name="InsufficientFundsError",this.code="INSUFFICIENT_FUNDS",this.needSats=n,this.haveSats=o}};function P(e){let t=String(e).toLowerCase().trim().replace(/\.$/,"").split(".").filter(Boolean),n,o;if(t.length===0)throw new Error(`invalid name "${e}"`);if(t.length===1?(n=t[0],o=Ct):(o=t[t.length-1],n=t[t.length-2]),!nn.test(n)||!he.test(o))throw new Error(`invalid name "${e}" (allowed: <label>.<tld>, [a-z0-9-])`);return o===Ct?n:`${n}.${o}`}function Y(e){return e.includes(".")?e:`${e}.${Ct}`}function it(e,t,n){let o=JSON.stringify({o:e,n:t,r:n??{}}),s=st(en+o);if(s.length>Ht)throw new Error(`records too large (${s.length} > ${Ht} bytes)`);return s}function Ut(e,t="REG"){return Ht-it(t,e,{}).length}function on(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 pt(e){return Uint8Array.from([106,...on(e)])}function qt(){return Uint8Array.from([65,...new Uint8Array(65),33,...new Uint8Array(33)])}var sn=e=>!e.token;function q(e,t,{requireGenesis:n=!0}={}){let o=e.filter(sn),s=[],r=0n;if(n){let i=o.filter(l=>l.vout===0).sort((l,c)=>c.satoshis>l.satoshis?1:-1);if(i.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)");s.push(i[0]),r+=i[0].satoshis}let a=o.filter(i=>!s.some(l=>l.txid===i.txid&&l.vout===i.vout)).sort((i,l)=>l.satoshis>i.satoshis?1:-1);for(let i of a){if(r>=t)break;s.push(i),r+=i.satoshis}return{inputs:s,totalSats:r}}function rt(e){return{outpointTransactionHash:j(e.txid),outpointIndex:e.vout,sequenceNumber:4294967294,unlockingBytecode:qt()}}function dt(e){return{...rt(e),lockingBytecode:e.lockingBytecode,valueSatoshis:e.satoshis,...e.token?{token:{amount:e.token.amount??0n,category:j(e.token.category),...e.token.nft?{nft:{capability:e.token.nft.capability,commitment:j(e.token.nft.commitment||"")}}:{}}}:{}}}function et(e){return tn(e).length}function Kt({name:e,ownerAddress:t,ownerTokenAddress:n,changeAddress:o,records:s={},utxos:r,feeAddress:a=null,feeSats:i=0n,tldFeeAddress:l=null,tldFeeSats:c=0n,beaconAddress:h=Tt,feeRate:u=1,addressToLockingBytecode:d,cosignUtxo:f=null,cosignTokenAddress:m=null}){if(typeof d!="function")throw new Error("addressToLockingBytecode must be injected (see chain.js)");let g=P(e),p=it("REG",g,s),y=BigInt(i??0n),w=BigInt(c??0n);if(f&&!f.token?.nft)throw new Error("cosignUtxo does not carry a TLD certificate");let x=f?BigInt(f.satoshis):0n,v=f?d(m??n??t):null;if(y>0n&&!a)throw new Error("feeSats set but feeAddress missing");if(w>0n&&!l)throw new Error("tldFeeSats set but tldFeeAddress missing");let b=d(o??t),T=d(n??t),L=d(h),N=y>0n?d(a):null,k=w>0n?d(l):null,C=F+I+y+w+x,E=q(r,C+300n-x),S=0n,A=0n,H=!0;for(let B=0;B<6;B++){let{inputs:K}=E,$=f?[...K,f]:K,R=E.totalSats+x,Z=$[0],X=j(Z.txid),W=(lt,ut)=>{let G=[];return lt&&G.push({lockingBytecode:b,valueSatoshis:ut}),G.push({lockingBytecode:T,valueSatoshis:F,token:{amount:0n,category:X,nft:{capability:"none",commitment:st(g)}}}),G.push({lockingBytecode:pt(p),valueSatoshis:0n}),G.push({lockingBytecode:L,valueSatoshis:I}),k&&G.push({lockingBytecode:k,valueSatoshis:w}),N&&G.push({lockingBytecode:N,valueSatoshis:y}),f&&G.push({lockingBytecode:v,valueSatoshis:x,token:{amount:BigInt(f.token.amount??0),category:j(f.token.category),nft:{capability:f.token.nft.capability,commitment:j(f.token.nft.commitment||"")}}}),{version:2,locktime:0,inputs:$.map(rt),outputs:G}},He=W(!0,1000000n),Me=et(He);S=BigInt(Math.ceil(Me*u));let yt=C+S;if(R<yt){let lt=q(r,yt+200n-x);if(lt.inputs.length>K.length){E=lt;continue}throw new J(`not enough funds: need ${yt} sat, have ${R} sat`,{needSats:yt,haveSats:R})}if(A=R-yt,H=A>=U,!H){let lt=W(!1,0n);S=R-C;let ut=BigInt(Math.ceil(et(lt)*u));if(S<ut){let G=q(r,C+ut+U+200n-x);if(G.inputs.length>K.length){E=G;continue}throw new J(`not enough funds: need ${C+ut} sat, have ${R} sat`,{needSats:C+ut,haveSats:R})}A=0n}let Ue=W(H,A),tt={},ot=0;return H&&(tt.change=ot++),tt.certificate=ot++,tt.opReturn=ot++,tt.beacon=ot++,k&&(tt.tldOwnerFee=ot++),N&&(tt.serviceFee=ot++),f&&(tt.cosignCertificate=ot++),{name:g,displayName:Y(g),category:vt(X),transaction:Ue,sourceOutputs:$.map(dt),inputUtxos:$,outputMap:tt,payload:p,costs:{certificateDustSats:F,beaconDustSats:I,serviceFeeSats:y,tldOwnerFeeSats:w,chainFeeSats:S,changeSats:A,totalInputSats:R,netCostSats:y+w+S+I}}}throw new Error("could not converge on a fee \u2014 please retry")}function Et({tld:e,ownerAddress:t,ownerTokenAddress:n,changeAddress:o,records:s={},utxos:r,feeAddress:a=null,feeSats:i=0n,beaconAddress:l=Mt,feeRate:c=1,addressToLockingBytecode:h}){if(typeof h!="function")throw new Error("addressToLockingBytecode must be injected (see chain.js)");let u=ht(e),d=it("TREG",u,s),f=BigInt(i??0n);if(f>0n&&!a)throw new Error("feeSats set but feeAddress missing");let m=h(o??t),g=h(n??t),p=h(l),y=f>0n?h(a):null,w=F+I+f,x=q(r,w+300n),v=0n,b=0n,T=!0;for(let L=0;L<6;L++){let{inputs:N,totalSats:k}=x,C=N[0],E=j(C.txid),S=(Z,X)=>{let W=[];return Z&&W.push({lockingBytecode:m,valueSatoshis:X}),W.push({lockingBytecode:g,valueSatoshis:F,token:{amount:0n,category:E,nft:{capability:"none",commitment:st(u)}}}),W.push({lockingBytecode:pt(d),valueSatoshis:0n}),W.push({lockingBytecode:p,valueSatoshis:I}),y&&W.push({lockingBytecode:y,valueSatoshis:f}),{version:2,locktime:0,inputs:N.map(rt),outputs:W}},A=S(!0,1000000n),H=et(A);v=BigInt(Math.ceil(H*c));let B=w+v;if(k<B){let Z=q(r,B+200n);if(Z.inputs.length>N.length){x=Z;continue}throw new J(`not enough funds: need ${B} sat, have ${k} sat`,{needSats:B,haveSats:k})}if(b=k-B,T=b>=U,!T){let Z=S(!1,0n);v=k-w;let X=BigInt(Math.ceil(et(Z)*c));if(v<X){let W=q(r,w+X+U+200n);if(W.inputs.length>N.length){x=W;continue}throw new J(`not enough funds: need ${w+X} sat, have ${k} sat`,{needSats:w+X,haveSats:k})}b=0n}let K=S(T,b),$={},R=0;return T&&($.change=R++),$.certificate=R++,$.opReturn=R++,$.beacon=R++,y&&($.serviceFee=R++),{tld:u,category:vt(E),transaction:K,sourceOutputs:N.map(dt),inputUtxos:N,outputMap:$,payload:d,costs:{certificateDustSats:F,beaconDustSats:I,serviceFeeSats:f,chainFeeSats:v,changeSats:b,totalInputSats:k,netCostSats:f+v+I}}}throw new Error("could not converge on a fee \u2014 please retry")}function At({name:e,ownerAddress:t,ownerTokenAddress:n,records:o={},certificateUtxo:s,utxos:r,beaconAddress:a=Tt,feeRate:i=1,addressToLockingBytecode:l}){if(typeof l!="function")throw new Error("addressToLockingBytecode must be injected (see chain.js)");let c=P(e);if(!s?.token?.nft)throw new Error("certificateUtxo does not carry a certificate");let h=vt(st(c));if(s.token.nft.commitment!==h)throw new Error(`certificate commitment mismatch: holds "${de(j(s.token.nft.commitment))}", asked to update "${c}"`);let u=it("UPD",c,o),d=l(t),f=l(n??t),m=l(a),g=F+I,p=q(r,g+400n,{requireGenesis:!1}),y=0n,w=0n,x=!0;for(let v=0;v<6;v++){let b=[s,...p.inputs],T=s.satoshis+p.totalSats,L=(A,H)=>{let B=[];return A&&B.push({lockingBytecode:d,valueSatoshis:H}),B.push({lockingBytecode:f,valueSatoshis:F,token:{amount:0n,category:j(s.token.category),nft:{capability:"none",commitment:st(c)}}}),B.push({lockingBytecode:pt(u),valueSatoshis:0n}),B.push({lockingBytecode:m,valueSatoshis:I}),{version:2,locktime:0,inputs:b.map(rt),outputs:B}},N=et(L(!0,1000000n));y=BigInt(Math.ceil(N*i));let k=g+y;if(T<k){let A=q(r,k+200n,{requireGenesis:!1});if(A.inputs.length>p.inputs.length){p=A;continue}throw new J(`not enough funds: need ${k} sat, have ${T} sat`,{needSats:k,haveSats:T})}w=T-k,x=w>=U,x||(y=T-g,w=0n);let C=L(x,w),E={},S=0;return x&&(E.change=S++),E.certificate=S++,E.opReturn=S++,E.beacon=S++,{name:c,displayName:Y(c),category:s.token.category,transaction:C,sourceOutputs:b.map(dt),inputUtxos:b,outputMap:E,payload:u,costs:{certificateDustSats:F,beaconDustSats:I,serviceFeeSats:0n,chainFeeSats:y,changeSats:w,totalInputSats:T,netCostSats:y+I}}}throw new Error("could not converge on a fee \u2014 please retry")}function pe({tld:e,ownerAddress:t,ownerTokenAddress:n,records:o={},certificateUtxo:s,utxos:r,beaconAddress:a=Mt,feeRate:i=1,addressToLockingBytecode:l}){if(typeof l!="function")throw new Error("addressToLockingBytecode must be injected (see chain.js)");let c=ht(e);if(!s?.token?.nft)throw new Error("certificateUtxo does not carry a certificate");let h=vt(st(c));if(s.token.nft.commitment!==h)throw new Error(`TLD certificate commitment mismatch: holds "${de(j(s.token.nft.commitment))}", asked to update ".${c}"`);let u=it("TUPD",c,o),d=l(t),f=l(n??t),m=l(a),g=F+I,p=q(r,g+400n,{requireGenesis:!1}),y=0n,w=0n,x=!0;for(let v=0;v<6;v++){let b=[s,...p.inputs],T=s.satoshis+p.totalSats,L=(A,H)=>{let B=[];return A&&B.push({lockingBytecode:d,valueSatoshis:H}),B.push({lockingBytecode:f,valueSatoshis:F,token:{amount:0n,category:j(s.token.category),nft:{capability:"none",commitment:st(c)}}}),B.push({lockingBytecode:pt(u),valueSatoshis:0n}),B.push({lockingBytecode:m,valueSatoshis:I}),{version:2,locktime:0,inputs:b.map(rt),outputs:B}},N=et(L(!0,1000000n));y=BigInt(Math.ceil(N*i));let k=g+y;if(T<k){let A=q(r,k+200n,{requireGenesis:!1});if(A.inputs.length>p.inputs.length){p=A;continue}throw new J(`not enough funds: need ${k} sat, have ${T} sat`,{needSats:k,haveSats:T})}w=T-k,x=w>=U,x||(y=T-g,w=0n);let C=L(x,w),E={},S=0;return x&&(E.change=S++),E.certificate=S++,E.opReturn=S++,E.beacon=S++,{tld:c,displayName:`.${c}`,category:s.token.category,transaction:C,sourceOutputs:b.map(dt),inputUtxos:b,outputMap:E,payload:u,costs:{certificateDustSats:F,beaconDustSats:I,serviceFeeSats:0n,chainFeeSats:y,changeSats:w,totalInputSats:T,netCostSats:y+I}}}throw new Error("could not converge on a fee \u2014 please retry")}function ft({ownerAddress:e,utxos:t,amountSats:n=10000n,feeRate:o=1,addressToLockingBytecode:s}){let r=s(e),a=q(t,n+400n,{requireGenesis:!1});if(a.inputs.length===0)throw new J("wallet is empty");for(let i=0;i<6;i++){let{inputs:l,totalSats:c}=a,h=(m,g)=>({version:2,locktime:0,inputs:l.map(rt),outputs:[{lockingBytecode:r,valueSatoshis:n},...m?[{lockingBytecode:r,valueSatoshis:g}]:[]]}),u=BigInt(Math.ceil(et(h(!0,1000000n))*o));if(c<n+u){let m=BigInt(Math.ceil(et(h(!1,0n))*o));if(c<=m+U)throw new J(`not enough funds to prepare the wallet: have ${c} sat`,{needSats:m+U,haveSats:c});return{transaction:{version:2,locktime:0,inputs:l.map(rt),outputs:[{lockingBytecode:r,valueSatoshis:c-m}]},sourceOutputs:l.map(dt),inputUtxos:l,costs:{chainFeeSats:m}}}let d=c-n-u,f=d>=U;return{transaction:h(f,d),sourceOutputs:l.map(dt),inputUtxos:l,costs:{chainFeeSats:f?u:c-n}}}throw new Error("could not converge on a fee \u2014 please retry")}function at(e){return e.some(t=>t.vout===0&&!t.token)}function fe({transaction:e,sourceOutputs:t},{broadcast:n=!0,userPrompt:o}={}){return{transaction:e,sourceOutputs:t,broadcast:n,...o?{userPrompt:o}:{}}}import{generateSigningSerializationBCH as rn,hash256 as ye,secp256k1 as an,encodeTransaction as cn,binToHex as Wt}from"@bitauth/libauth";var ge=65;function nt(e,t,n){let o={...e,inputs:e.inputs.map((r,a)=>{let i=t[a],l=n(Wt(i.lockingBytecode));if(!l)throw new Error(`no key for input ${a} \u2014 cannot sign`);let c=rn({inputIndex:a,sourceOutputs:t,transaction:e},{coveredBytecode:i.lockingBytecode,signingSerializationType:Uint8Array.of(ge)}),h=ye(c),u=an.signMessageHashSchnorr(l.privateKey,h);if(typeof u=="string")throw new Error(`signing failed on input ${a}: ${u}`);let d=Uint8Array.from([...u,ge]);return{...r,unlockingBytecode:Uint8Array.from([d.length,...d,l.publicKey.length,...l.publicKey])}})},s=cn(o);return{transaction:o,hex:Wt(s),txid:zt(s)}}function zt(e){return Wt(ye(e).slice().reverse())}function Gt(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 zt(t)}var me="e1cdbba719436f49acb4bf245b44df875c6d6b405eda46a9d3e736d138102f3b";var we="c85db10e106606273422b2733b2a6fb8b35a4d0e7dbfb20cf59bdb0833c008b8",xe="BNS1";var ln=/^[a-z0-9-]{1,32}$/,be=/^[a-z0-9-]{1,16}$/,un=/^[a-z0-9-]{1,32}(\.[a-z0-9-]{1,16})?$/;function dn(e){return e.includes(".")?e:e+".bch"}var hn=[{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 pn=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},Jt=e=>[...new TextEncoder().encode(e)].map(t=>t.toString(16).padStart(2,"0")).join("");function fn(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 o,s;if(n.length===1?(o=n[0],s="bch"):(s=n[n.length-1],o=n[n.length-2]),!ln.test(o)||!be.test(s))throw new Error(`invalid name "${e}" (allowed: <label>.<tld>, [a-z0-9-])`);return`${o}.${s}`}var Bt=class{constructor(t,n,o){this.url=t,this.WS=n||globalThis.WebSocket,this.options=o,this.id=0,this.pending=new Map,this.buf=""}connect(){return new Promise((t,n)=>{let o=this.options?new this.WS(this.url,this.options):new this.WS(this.url);this.ws=o;let s=async()=>{try{await this.call("server.version",["bns-web","1.4.2"]),t(this)}catch(i){n(i)}},r=i=>n(new Error("electrum ws error: "+(i?.message||this.url))),a=i=>this._onData(typeof i.data=="string"?i.data:i.data.toString());o.addEventListener?(o.addEventListener("open",s),o.addEventListener("error",r),o.addEventListener("message",a)):(o.onopen=s,o.onerror=r,o.onmessage=a)})}_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 s=this.buf.slice(0,n).trim();if(this.buf=this.buf.slice(n+1),s)try{this._handle(JSON.parse(s))}catch{}}let o=this.buf.trim();if(o)try{let s=JSON.parse(o);this._handle(s),this.buf=""}catch{}}call(t,n=[]){let o=++this.id;return new Promise((s,r)=>{this.pending.set(o,{resolve:s,reject:r}),this.ws.send(JSON.stringify({id:o,method:t,params:n})+`
`),setTimeout(()=>{this.pending.has(o)&&(this.pending.delete(o),r(new Error(`electrum timeout: ${t}`)))},2e4)})}close(){try{this.ws.close()}catch{}}},gn=e=>typeof e=="string"?{url:e}:e;async function yn(e,t,{directIP:n=!1}={}){let o;for(let s of e){let r=gn(s);try{return await new Bt(r.url,t).connect()}catch(a){o=a}if(n&&r.ip)try{let a=new URL(r.url),i=`${a.protocol}//${r.ip}:${a.port}`;return await new Bt(i,t,{servername:a.hostname}).connect()}catch(a){o=a}}throw o??new Error("no electrum server reachable")}function Se(e){if(!e||!e.startsWith("6a"))return null;let t=pn(e),n=1,o=[];for(;n<t.length;){let r=t[n++],a;if(r<=75)a=r;else if(r===76)a=t[n++];else if(r===77)a=t[n]|t[n+1]<<8,n+=2;else return null;o.push(t.slice(n,n+a)),n+=a}let s=new TextDecoder().decode(o[0]??new Uint8Array);if(!s.startsWith(xe))return null;try{let r=JSON.parse(s.slice(xe.length));if(mn.has(r.o)&&typeof r.n=="string")return r}catch{}return null}var mn=new Set(["REG","UPD","TREG","TUPD"]);function ve(e,t){let n=e.height>0?e.height:1/0,o=t.height>0?t.height:1/0;return n-o||e.tx_hash.localeCompare(t.tx_hash)}function Te(e,t){let n=new Set(e.map(i=>i.tx_hash)),o=new Map(e.map(i=>{let l=new Set;for(let c of t.get(i.tx_hash)?.vin??[])c.txid&&c.txid!==i.tx_hash&&n.has(c.txid)&&l.add(c.txid);return[i.tx_hash,l]})),s=new Set,r=[],a=[...e];for(;a.length;){let i=a.findIndex(c=>[...o.get(c.tx_hash)].every(h=>s.has(h)));i<0&&(i=0);let[l]=a.splice(i,1);s.add(l.tx_hash),r.push(l)}return r}function jt(e){let t=e.lastIndexOf(".");return t<0?e:e.slice(t+1)}function wn(e,t,n){let o=n.get(e);return o?o.regHeight>0&&t>0?o.regHeight<=t:!0:!1}async function Pt(e){let t=typeof e.unionHistory=="function"?(await e.unionHistory(we)).history:await e.call("blockchain.scripthash.get_history",[we]);t.sort(ve);let n=new Map;for(let{tx_hash:s}of t)try{n.set(s,await e.call("blockchain.transaction.get",[s,!0]))}catch{}let o=new Map;for(let{tx_hash:s,height:r}of Te(t.filter(a=>n.has(a.tx_hash)),n)){let i=n.get(s).vout||[],l=null;for(let h of i)l=Se(h.scriptPubKey?.hex)??l;if(!l||typeof l.n!="string"||!be.test(l.n))continue;let c=l.n;if(l.o==="TREG"){if(o.has(c))continue;let h=Jt(c),u=i.find(d=>d.tokenData?.nft&&d.tokenData.nft.commitment===h);if(!u)continue;o.set(c,{category:u.tokenData.category,regHeight:r,regTxid:s,mintScriptHex:u.scriptPubKey?.hex??null,records:l.r??{},updates:[{height:r,txid:s,records:l.r??{}}]})}else if(l.o==="TUPD"){let h=o.get(c);if(!h||!i.some(u=>u.tokenData&&u.tokenData.category===h.category))continue;h.records=l.r??{},h.updates.push({height:r,txid:s,records:h.records})}}return o}function xn(e,t){if(!e)return{};let n=e.updates||[];if(!n.length)return e.records||{};let o={};for(let s of n)t>0&&(s.height<=0||s.height>t)||(o=s.records||{});return o}function kn(e,t){let n=xn(e,t);return n.policy==="cosign"||n.hidden===1||n.hidden===!0||n.hidden==="1"}function bn(e,t,n){let o=Jt(n);return(e||[]).some(s=>s.tokenData&&s.tokenData.category===t.category&&s.tokenData.nft&&s.tokenData.nft.commitment===o)}function ke(e){if(!e||!e.scriptPubKey)return null;let t=e.scriptPubKey;return typeof t.address=="string"?t.address:Array.isArray(t.addresses)&&t.addresses.length?t.addresses[0]:null}async function gt({electrum:e=hn,WebSocket:t,client:n,directIP:o=!1,enforceTld:s=!1,cosignGate:r=!0}={}){let a=n??await yn(e,t,{directIP:o});try{let i=null;if(s||r)try{i=await Pt(a)}catch(d){if(s)throw d;i=null}let l=null,c;if(typeof a.unionHistory=="function"){let d=await a.unionHistory(me);c=d.history,l={servers:d.servers,unreachable:d.unreachable,disagreements:d.disagreements}}else c=await a.call("blockchain.scripthash.get_history",[me]);c.sort(ve);let h=new Map;for(let{tx_hash:d}of c)try{h.set(d,await a.call("blockchain.transaction.get",[d,!0]))}catch{}let u=new Map;for(let{tx_hash:d,height:f}of Te(c.filter(m=>h.has(m.tx_hash)),h)){let g=h.get(d).vout||[],p=null;for(let x of g)p=Se(x.scriptPubKey?.hex)??p;if(!p)continue;let y=p.n;if(!un.test(y))continue;let w=dn(y);if(p.o==="REG"){if(u.has(w)||s&&!wn(jt(w),f,i))continue;if(r&&i){let b=i.get(jt(w));if(b&&kn(b,f)&&!bn(g,b,jt(w)))continue}let x=Jt(y),v=g.find(b=>b.tokenData?.nft&&b.tokenData.nft.commitment===x);if(!v)continue;u.set(w,{name:w,category:v.tokenData.category,records:p.r??{},txid:d,height:f,updatedTxid:null,owner:ke(v)})}else if(p.o==="UPD"){let x=u.get(w);if(!x)continue;let v=g.find(b=>b.tokenData&&b.tokenData.category===x.category);if(!v)continue;x.records=p.r??{},x.updatedTxid=d,x.owner=ke(v)??x.owner}}return l&&Object.defineProperty(u,"integrity",{value:l,enumerable:!1}),u}finally{n||a.close()}}async function Sn(e,t={}){let n=fn(e);return(await gt(t)).get(n)??null}var O={network:"chipnet",addressPrefix:"bchtest",selfHosted:null,get electrum(){return this.selfHosted?[this.selfHosted,...bt]:bt},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 vn(e){let t=r=>(Number(r)/1e8).toFixed(8).replace(/0+$/,"").replace(/\.$/,""),n=[],o=BigInt(e.tldOwnerFeeSats??0n),s=BigInt(e.serviceFeeSats??0n);return o>0n?(n.push({label:"Service fee \u2014 TLD owner (90%)",sats:o,bch:t(o),note:"goes to whoever holds this TLD's certificate on chain"}),n.push({label:"Service fee \u2014 platform (10%)",sats:s,bch:t(s),note:"covers the resolver + gateway + indexer"})):n.push({label:"Service fee",sats:s,bch:t(s)}),n.push({label:"Network fee",sats:e.chainFeeSats,bch:t(e.chainFeeSats)}),n.push({label:"Discovery beacon",sats:e.beaconDustSats,bch:t(e.beaconDustSats)}),n.push({label:"Held in the certificate",sats:e.certificateDustSats,bch:t(e.certificateDustSats),note:"stays yours \u2014 it lives in the certificate you now own"}),n.push({label:"Total",sats:e.netCostSats,bch:t(e.netCostSats),total:!0}),n}import{hexToBin as De,binToHex as ct,decodeTransaction as On,lockingBytecodeToCashAddress as Nn,sha256 as Dn,utf8ToBin as $n,binToBase64 as Rn}from"@bitauth/libauth";import{encodeTransaction as Xt,decodeTransaction as Ee,generateSigningSerializationBCH as Ae,hash256 as Vt,hash160 as Tn,secp256k1 as Be,hexToBin as Q,binToHex as D,utf8ToBin as _e}from"@bitauth/libauth";var En=65,Yt=195,Qt=(e,t)=>({outpointTransactionHash:Q(e.txid),outpointIndex:e.vout,sequenceNumber:4294967294,unlockingBytecode:t??qt()}),wt=e=>e?{amount:BigInt(e.amount??0),category:Q(e.category),...e.nft?{nft:{capability:e.nft.capability,commitment:Q(e.nft.commitment||"")}}:{}}:void 0,Zt=e=>({lockingBytecode:e.lockingBytecode instanceof Uint8Array?D(e.lockingBytecode):String(e.lockingBytecode),valueSatoshis:String(e.valueSatoshis),...e.token?{token:{category:e.token.category instanceof Uint8Array?D(e.token.category):String(e.token.category),amount:String(e.token.amount??0),...e.token.nft?{nft:{capability:e.token.nft.capability,commitment:e.token.nft.commitment instanceof Uint8Array?D(e.token.nft.commitment):String(e.token.nft.commitment||"")}}:{}}}:{}}),te=e=>({lockingBytecode:Q(e.lockingBytecode),valueSatoshis:BigInt(e.valueSatoshis??e.satoshis),...e.token?{token:wt(e.token)}:{}}),Ie=(e,t)=>({...Qt(e,t),lockingBytecode:e.lockingBytecode instanceof Uint8Array?e.lockingBytecode:Q(e.lockingBytecode),valueSatoshis:BigInt(e.satoshis),...e.token?{token:wt(e.token)}:{}});function Oe(e,t,n,o,s){let r=t[n],a=Ae({inputIndex:n,sourceOutputs:t,transaction:e},{coveredBytecode:r.lockingBytecode,signingSerializationType:Uint8Array.of(s)}),i=Vt(a),l=Be.signMessageHashSchnorr(o.privateKey,i);if(typeof l=="string")throw new Error(`signing failed on input ${n}: ${l}`);let c=Uint8Array.from([...l,s]);return Uint8Array.from([c.length,...c,o.publicKey.length,...o.publicKey])}function _t(e,t,n,{sighash:o=En,skip:s=[]}={}){let r={...e,inputs:e.inputs.map((i,l)=>{if(s.includes(l))return i;let c=n(D(t[l].lockingBytecode));return c?{...i,unlockingBytecode:Oe(e,t,l,c,o)}:i})},a=Xt(r);return{transaction:r,hex:D(a),txid:D(Vt(a).slice().reverse())}}function An({name:e,certificateUtxo:t,priceSats:n,sellerAddress:o,addressToLockingBytecode:s,keyFor:r}){let a=P(e),i=BigInt(n);if(i<U)throw new Error(`price must be at least ${U} sat`);if(!t?.token?.nft)throw new Error("certificateUtxo does not carry a certificate");if(t.token.nft.commitment!==D(_e(a)))throw new Error("certificate commitment does not match the name");let l=s(o),c=Ie(t),h={version:2,locktime:0,inputs:[Qt(t)],outputs:[{lockingBytecode:l,valueSatoshis:i}]},u=r(D(c.lockingBytecode));if(!u)throw new Error("this wallet does not hold the key for the certificate");return h.inputs[0].unlockingBytecode=Oe(h,[c],0,u,Yt),{v:1,name:a,seller:o,price_sats:i.toString(),category:t.token.category,partial_tx:D(Xt(h)),source_output:{txid:t.txid,vout:t.vout,satoshis:BigInt(t.satoshis).toString(),lockingBytecode:D(c.lockingBytecode),token:{category:t.token.category,amount:String(t.token.amount??0),nft:t.token.nft?{capability:t.token.nft.capability,commitment:t.token.nft.commitment}:void 0}},created_at:new Date().toISOString()}}function Bn(e){let t=e[0],n=e.slice(1,1+t),o=e[1+t],s=e.slice(2+t,2+t+o);return{sig:n,pubkey:s}}var _n=e=>Uint8Array.from([118,169,20,...Tn(e),136,172]);function Ne(e,t={}){let n=o=>({ok:!1,error:o});try{if(!e||e.v!==1)return n("unsupported listing version");let o=P(e.name),s=Ee(Q(e.partial_tx));if(typeof s=="string")return n("partial_tx does not decode: "+s);if(s.inputs.length!==1||s.outputs.length<1)return n("partial_tx must have exactly one input and a payment output");let r=e.source_output;if(!r?.token?.nft)return n("source_output carries no certificate");if(D(s.inputs[0].outpointTransactionHash)!==r.txid||s.inputs[0].outpointIndex!==r.vout)return n("input does not spend source_output");if(r.token.nft.commitment!==D(_e(o)))return n("certificate commitment does not match the name");if(t.category&&r.token.category!==t.category)return n("certificate category does not match the registry");let a=s.outputs[0].valueSatoshis;if(a<U)return n("price below dust");if(BigInt(e.price_sats)!==a)return n("price_sats does not match output 0");let{sig:i,pubkey:l}=Bn(s.inputs[0].unlockingBytecode);if(i.length!==65||i[64]!==Yt)return n("input 0 is not signed SINGLE|ANYONECANPAY");let c=Q(r.lockingBytecode);if(D(_n(l))!==D(c))return n("public key does not own the certificate output");if(t.ownerLockingBytecode&&D(c)!==t.ownerLockingBytecode)return n("certificate is not held by the current owner");if(D(s.outputs[0].lockingBytecode)!==D(c))return n("payment does not go to the certificate holder");let h={...s.inputs[0],lockingBytecode:c,valueSatoshis:BigInt(r.satoshis),token:wt(r.token)},u=Ae({inputIndex:0,sourceOutputs:[h],transaction:s},{coveredBytecode:c,signingSerializationType:Uint8Array.of(Yt)});return Be.verifySignatureSchnorr(i.slice(0,64),l,Vt(u))?{ok:!0,name:o,price_sats:a,seller_lock:D(c),category:r.token.category}:n("seller signature is invalid")}catch(o){return n(String(o?.message||o))}}function In({listing:e,buyerAddress:t,buyerTokenAddress:n,utxos:o,records:s={},beaconAddress:r=Tt,feeRate:a=1,addressToLockingBytecode:i}){let l=Ne(e);if(!l.ok)throw new Error("bad listing: "+l.error);let c=l.name,h=Ee(Q(e.partial_tx)),u=e.source_output,d=h.inputs[0],f={...d,lockingBytecode:Q(u.lockingBytecode),valueSatoshis:BigInt(u.satoshis),token:wt(u.token)},m=h.outputs[0],g=m.valueSatoshis,p=i(t),y=i(n??t),w=i(r),x=it("UPD",c,s),v=g+F+I-BigInt(u.satoshis),b=q(o,v+400n,{requireGenesis:!1}),T=L=>Xt(L).length;for(let L=0;L<6;L++){let{inputs:N,totalSats:k}=b,C=(K,$)=>({version:2,locktime:0,inputs:[d,...N.map(R=>Qt(R))],outputs:[m,{lockingBytecode:y,valueSatoshis:F,token:wt(u.token)},{lockingBytecode:pt(x),valueSatoshis:0n},{lockingBytecode:w,valueSatoshis:I},...K?[{lockingBytecode:p,valueSatoshis:$}]:[]]}),E=BigInt(Math.ceil(T(C(!0,1000000n))*a)),S=v+E;if(k<S){let K=q(o,S+200n,{requireGenesis:!1});if(K.inputs.length>N.length){b=K;continue}let $=new Error(`not enough funds: need ${S} sat, have ${k} sat`);throw $.needSats=S,$.haveSats=k,$}let A=k-S,H=A>=U;H||(A=0n);let B=C(H,A);return{name:c,transaction:B,sourceOutputs:[f,...N.map(K=>Ie(K))],sellerInputIndex:0,costs:{priceSats:g,beaconDustSats:I,certificateDustSats:F,chainFeeSats:H?E:k-v,changeSats:A,totalInputSats:k}}}throw new Error("could not converge on a fee \u2014 please retry")}var Fn=90n,Ln=100n;async function $e(e,t,n=O.addressPrefix??"bchtest"){let s=(await Pt(e)).get(String(t).toLowerCase());if(!s?.mintScriptHex)return null;try{let r=De(s.mintScriptHex),a=Nn({bytecode:r,prefix:n,tokenSupport:!1});return typeof a=="string"?null:a.address}catch{return null}}function Re(e){let t=BigInt(e??0n),n=t*Fn/Ln,o=t-n;return{tldOwnerFeeSats:n,platformFeeSats:o}}var Cn=/silentmode\.st\/electrum|coinspectrum\.duckdns\.org:50011/;async function Hn({electrum:e=O.electrum,WebSocket:t,beaconOk:n=!1}={}){let o=n?e:e.filter(s=>!Cn.test(typeof s=="string"?s:s.url||""));return ce(o,t)}async function It(e,t){let n=P(t),o=Y(n),r=(await gt({client:e})).get(o)??null;return{name:o,rawName:n,available:!r,entry:r,recordBudgetBytes:Ut(n)}}async function Ot(e,{name:t,ownerAddress:n,records:o={},utxos:s,serviceFee:r=O.serviceFee,feeRate:a=O.feeRate,cosignUtxo:i=null,cosignTokenAddress:l=null}){let c=s??await z(e,[n]),h=P(t),u=h.indexOf("."),d=u<0?"bch":h.slice(u+1),f=BigInt(r.sats??0n),m=null,g=0n,p=f;if(f>0n){try{m=await $e(e,d)}catch{m=null}if(m&&m!==n){let y=Re(f);g=y.tldOwnerFeeSats,p=y.platformFeeSats}}return Kt({name:t,ownerAddress:n,ownerTokenAddress:M(n),records:o,utxos:c,feeAddress:p>0n?r.address:null,feeSats:p,tldFeeAddress:g>0n?m:null,tldFeeSats:g,beaconAddress:O.beacon,feeRate:a,addressToLockingBytecode:_,cosignUtxo:i,cosignTokenAddress:l})}function Fe(e,{tld:t,category:n}){let o=[...new TextEncoder().encode(String(t).toLowerCase())].map(s=>s.toString(16).padStart(2,"0")).join("");return e.find(s=>s.token?.nft&&(!n||s.token.category===n)&&s.token.nft.commitment===o)??null}async function Le(e,{wallet:t,utxos:n,feeRate:o,onProgress:s}){if(at(n))return{utxos:n,prepTxid:null};s("preparing-wallet");let r=ft({ownerAddress:t.address,utxos:n,feeRate:o,addressToLockingBytecode:_}),a=nt(r.transaction,r.sourceOutputs,c=>t.keyFor(c)),i=await V(e,a.hex);return s("prepared",{txid:i}),{utxos:a.transaction.outputs.slice(0,2).map((c,h)=>({txid:i,vout:h,satoshis:c.valueSatoshis,address:t.address,lockingBytecode:_(t.address)})),prepTxid:i}}async function Mn(e,{wallet:t,name:n,records:o={},serviceFee:s=O.serviceFee,feeRate:r=O.feeRate,tldCert:a,onProgress:i=()=>{}}){if(!a?.utxo?.token?.nft||!a.owner)throw new Error("tldCert must be the /api/tld-cert response ({ owner, utxo })");i("checking-availability");let l=await It(e,n);if(!l.available)throw new Error(`"${l.name}" is already registered`);i("loading-coins");let c=await z(e,t.watchedAddresses);if(c.filter(p=>!p.token).reduce((p,y)=>p+y.satoshis,0n)===0n)throw new Error("wallet is empty \u2014 fund the address first");let u=null;({utxos:c,prepTxid:u}=await Le(e,{wallet:t,utxos:c,feeRate:r,onProgress:i})),i("building");let d=await Ot(e,{name:n,ownerAddress:t.address,records:o,utxos:c,serviceFee:s,feeRate:r,cosignUtxo:{...a.utxo,address:a.owner},cosignTokenAddress:M(a.owner)}),f=d.transaction.inputs.length-1;i("signing");let m={...d.transaction,inputs:d.transaction.inputs.map((p,y)=>y===f?{...p,unlockingBytecode:new Uint8Array}:p)},g=_t(m,d.sourceOutputs,p=>t.keyFor(p),{skip:[f]});return{name:d.name,displayName:d.displayName,category:d.category,tx:g.hex,sourceOutputs:d.sourceOutputs.map(Zt),cosignInputIndex:f,costs:d.costs,prepTxid:u}}function Un(e,t){let n=On(De(String(e.tx||"")));if(typeof n=="string")throw new Error("invalid transaction: "+n);let o=(e.sourceOutputs||[]).map(te);if(o.length!==n.inputs.length)throw new Error("sourceOutputs do not match the transaction inputs");let s=[],r=0;n.inputs.forEach((i,l)=>{let c=o[l];if(!t(ct(c.lockingBytecode))){s.push(l);return}if(c.token?.nft&&!n.outputs.some(u=>u.token&&ct(u.token.category)===ct(c.token.category)&&ct(u.token.nft?.commitment??new Uint8Array)===ct(c.token.nft.commitment??new Uint8Array)&&ct(u.lockingBytecode)===ct(c.lockingBytecode)&&u.valueSatoshis>=c.valueSatoshis))throw new Error("refusing to sign: the request would not return your TLD certificate to you");r+=1});let a=_t(n,o,t,{skip:s});return{hex:a.hex,txid:a.txid,signedInputs:r}}async function qn(e,{wallet:t,name:n,records:o={},serviceFee:s=O.serviceFee,feeRate:r=O.feeRate,cosign:a=null,onProgress:i=()=>{}}){i("checking-availability");let l=await It(e,n);if(!l.available)throw new Error(`"${l.name}" is already registered`);i("loading-coins");let c=await z(e,t.watchedAddresses);if(c.filter(p=>!p.token).reduce((p,y)=>p+y.satoshis,0n)===0n)throw new Error("wallet is empty \u2014 fund the address first");let u=null;if(a&&(u=Fe(c,a),!u))throw new Error(`this wallet does not hold the certificate for .${a.tld} \u2014 only the TLD owner can co-sign`);let d=null;({utxos:c,prepTxid:d}=await Le(e,{wallet:t,utxos:c,feeRate:r,onProgress:i})),i("building");let f=await Ot(e,{name:n,ownerAddress:t.address,records:o,utxos:c,serviceFee:s,feeRate:r,cosignUtxo:u,cosignTokenAddress:u?M(t.address):null});i("signing");let m=nt(f.transaction,f.sourceOutputs,p=>t.keyFor(p));i("broadcasting");let g=await V(e,m.hex);return i("registered",{txid:g,category:f.category}),{name:f.name,displayName:f.displayName,category:f.category,txid:g,prepTxid:d,ownerAddress:t.address,certificateAddress:M(t.address),costs:f.costs,hex:m.hex}}async function Kn(e,{wallet:t,tld:n,records:o={},serviceFee:s={address:null,sats:0n},feeRate:r=O.feeRate,tldListUrl:a=null,onProgress:i=()=>{}}){let l=ht(n);if(i("checking-availability"),a)try{let g=await fetch(a);if(g.ok){let p=await g.json();if(Array.isArray(p.tlds)&&p.tlds.some(w=>(w.tld||w)===l))throw new Error(`".${l}" is already registered as a TLD`)}}catch(g){if(/already registered/.test(g.message))throw g}i("loading-coins");let c=await z(e,t.watchedAddresses);if(c.filter(g=>!g.token).reduce((g,p)=>g+p.satoshis,0n)===0n)throw new Error("wallet is empty \u2014 fund the address first");let u=null;if(!at(c)){i("preparing-wallet");let g=ft({ownerAddress:t.address,utxos:c,feeRate:r,addressToLockingBytecode:_}),p=nt(g.transaction,g.sourceOutputs,y=>t.keyFor(y));u=await V(e,p.hex),i("prepared",{txid:u}),c=[{txid:u,vout:0,satoshis:p.transaction.outputs[0].valueSatoshis,address:t.address,lockingBytecode:_(t.address)},...p.transaction.outputs.length>1?[{txid:u,vout:1,satoshis:p.transaction.outputs[1].valueSatoshis,address:t.address,lockingBytecode:_(t.address)}]:[]]}i("building");let d=Et({tld:l,ownerAddress:t.address,ownerTokenAddress:M(t.address),changeAddress:t.address,records:o,utxos:c,feeAddress:s.address||null,feeSats:BigInt(s.sats??0n),feeRate:r,addressToLockingBytecode:_});i("signing");let f=nt(d.transaction,d.sourceOutputs,g=>t.keyFor(g));i("broadcasting");let m=await V(e,f.hex);return i("registered",{txid:m,category:d.category}),{tld:d.tld,category:d.category,txid:m,prepTxid:u,ownerAddress:t.address,certificateAddress:M(t.address),costs:d.costs,hex:f.hex}}async function Wn(e,{session:t,tld:n,records:o={},serviceFee:s={address:null,sats:0n},feeRate:r=O.feeRate,tldListUrl:a=null,onProgress:i=()=>{}}){let l=ht(n);if(i("checking-availability"),a)try{let p=await fetch(a);if(p.ok){let y=await p.json();if(Array.isArray(y.tlds)&&y.tlds.some(x=>(x.tld||x)===l))throw new Error(`".${l}" is already registered as a TLD`)}}catch(p){if(/already registered/.test(p.message))throw p}i("reading-wallet");let c=await t.getAddresses();if(!c.length)throw new Error("the wallet exposed no addresses");let h=c[0];i("loading-coins");let u=await z(e,c);if(u.filter(p=>!p.token).reduce((p,y)=>p+y.satoshis,0n)===0n)throw new Error("the connected wallet has no spendable coins");let d=null;if(!at(u)){i("preparing-wallet");let p=ft({ownerAddress:h,utxos:u,feeRate:r,addressToLockingBytecode:_}),y=await t.signTransaction(p,{userPrompt:"One-time setup so your wallet can create a TLD certificate"});d=await xt(e,t,y,i),i("prepared",{txid:d}),u=[{txid:d,vout:0,satoshis:p.transaction.outputs[0].valueSatoshis,address:h,lockingBytecode:_(h)},...u.filter(w=>!w.token&&!p.inputUtxos.some(x=>x.txid===w.txid&&x.vout===w.vout))]}i("building");let f=Et({tld:l,ownerAddress:h,ownerTokenAddress:M(h),changeAddress:h,records:o,utxos:u,feeAddress:s.address||null,feeSats:BigInt(s.sats??0n),feeRate:r,addressToLockingBytecode:_});i("awaiting-approval");let m=await t.signTransaction(f,{userPrompt:`Register .${l} \u2014 the TLD certificate is minted to your own address`}),g=await xt(e,t,m,i);return i("registered",{txid:g,category:f.category}),{tld:f.tld,category:f.category,txid:g,prepTxid:d,ownerAddress:h,certificateAddress:M(h),costs:f.costs}}async function xt(e,t,n,o){let s=n.signedTransactionHash??Gt(n.signedTransaction);if(t.walletBroadcasts)return s;o("broadcasting");try{return await V(e,n.signedTransaction)}catch(r){let a=String(r?.message??r);if(/already|duplicate|txn-already|in mempool/i.test(a))return s;throw r}}async function zn(e,{session:t,name:n,ownerAddress:o,records:s={},serviceFee:r=O.serviceFee,feeRate:a=O.feeRate,onProgress:i=()=>{}}){i("checking-availability");let l=await It(e,n);if(!l.available)throw new Error(`"${l.name}" is already registered`);i("reading-wallet");let c=await t.getAddresses();if(!c.length)throw new Error("the wallet exposed no addresses");let h=o??c[0];i("loading-coins");let u=await z(e,c);if(!u.some(g=>!g.token))throw new Error("the connected wallet has no spendable coins");if(!at(u)){i("preparing-wallet");let g=ft({ownerAddress:h,utxos:u,feeRate:a,addressToLockingBytecode:_}),p=await t.signTransaction(g,{userPrompt:"One-time setup so your wallet can create a name certificate"}),y=await xt(e,t,p,i);i("prepared",{txid:y}),u=[{txid:y,vout:0,satoshis:g.transaction.outputs[0].valueSatoshis,address:h,lockingBytecode:_(h)},...u.filter(w=>!w.token&&!g.inputUtxos.some(x=>x.txid===w.txid&&x.vout===w.vout))]}i("building");let d=await Ot(e,{name:n,ownerAddress:h,records:s,utxos:u,serviceFee:r,feeRate:a});i("awaiting-approval");let f=await t.signTransaction(d,{userPrompt:`Register ${d.displayName} \u2014 the certificate is minted to your own address`}),m=await xt(e,t,f,i);return i("registered",{txid:m,category:d.category}),{name:d.name,displayName:d.displayName,category:d.category,txid:m,ownerAddress:h,certificateAddress:M(h),costs:d.costs}}async function Gn(e,{addresses:t,category:n,rawName:o}){let s=await z(e,t),r=[...new TextEncoder().encode(o)].map(a=>a.toString(16).padStart(2,"0")).join("");return s.find(a=>a.token?.category===n&&a.token?.nft?.commitment===r)??null}async function jn(e,{wallet:t,name:n,records:o,feeRate:s=O.feeRate,onProgress:r=()=>{}}){r("resolving");let a=P(n),l=(await gt({client:e})).get(Y(a));if(!l)throw new Error(`"${Y(a)}" is not registered`);r("loading-coins");let c=await z(e,t.watchedAddresses),h=c.find(m=>m.token?.category===l.category&&m.token?.nft);if(!h)throw new Error(`this wallet does not hold the certificate for "${Y(a)}" \u2014 only the owner can change its records`);r("building");let u=At({name:n,ownerAddress:t.address,ownerTokenAddress:M(t.address),records:o,certificateUtxo:h,utxos:c.filter(m=>!m.token),beaconAddress:O.beacon,feeRate:s,addressToLockingBytecode:_});r("signing");let d=nt(u.transaction,u.sourceOutputs,m=>t.keyFor(m));r("broadcasting");let f=await V(e,d.hex);return r("updated",{txid:f}),{name:u.name,displayName:u.displayName,category:u.category,txid:f,records:o,costs:u.costs}}async function Jn(e,{session:t,name:n,records:o,feeRate:s=O.feeRate,onProgress:r=()=>{}}){r("resolving");let a=P(n),l=(await gt({client:e})).get(Y(a));if(!l)throw new Error(`"${Y(a)}" is not registered`);r("reading-wallet");let c=await t.getAddresses(),h=await z(e,c),u=h.find(g=>g.token?.category===l.category&&g.token?.nft);if(!u)throw new Error(`this wallet does not hold the certificate for "${Y(a)}" \u2014 only the owner can change its records`);r("building");let d=At({name:n,ownerAddress:u.address,ownerTokenAddress:M(u.address),records:o,certificateUtxo:u,utxos:h.filter(g=>!g.token),beaconAddress:O.beacon,feeRate:s,addressToLockingBytecode:_});r("awaiting-approval");let f=await t.signTransaction(d,{userPrompt:`Point ${d.displayName} somewhere new`}),m=await xt(e,t,f,r);return r("updated",{txid:m}),{name:d.name,displayName:d.displayName,category:d.category,txid:m,records:o,costs:d.costs}}async function Pn(e,{wallet:t,tld:n,records:o,category:s,feeRate:r=O.feeRate,onProgress:a=()=>{}}){if(!s)throw new Error("setTldRecords requires the TLD certificate's category id");a("loading-coins");let i=await z(e,t.watchedAddresses),l=i.find(d=>d.token?.category===s&&d.token?.nft);if(!l)throw new Error(`this wallet does not hold the TLD certificate for ".${n}" \u2014 only the owner can change its policy`);a("building");let c=pe({tld:n,ownerAddress:t.address,ownerTokenAddress:M(t.address),records:o,certificateUtxo:l,utxos:i.filter(d=>!d.token),feeRate:r,addressToLockingBytecode:_});a("signing");let h=nt(c.transaction,c.sourceOutputs,d=>t.keyFor(d));a("broadcasting");let u=await V(e,h.hex);return a("updated",{txid:u}),{tld:c.tld,displayName:c.displayName,category:c.category,txid:u,records:o,costs:c.costs}}async function Yn(e,t){if(!t||t.v!==1)throw new Error("manifest.v must be 1");if(!t.name)throw new Error("manifest.name is required");if(typeof t.seq!="number"||t.seq<0)throw new Error("manifest.seq must be a non-negative integer");let n={...t};delete n.sig;let o=JSON.stringify(ee(n)),s=Dn.hash($n(o)),r=e.signMessage(s);return{...t,sig:Rn(r)}}function ee(e){if(e!=null){if(Array.isArray(e))return e.map(ee).filter(t=>t!==void 0);if(typeof e=="object"){let t={};for(let n of Object.keys(e).sort()){let o=ee(e[n]);o!==void 0&&(t[n]=o)}return t}return e}}async function Xn(e,t){let n=await z(e,t),o=n.filter(s=>!s.token);return{sats:o.reduce((s,r)=>s+r.satoshis,0n),confirmedSats:o.filter(s=>s.height>0).reduce((s,r)=>s+r.satoshis,0n),coins:o.length,certificates:n.filter(s=>s.token?.nft),hasGenesisInput:at(n)}}import{hash160 as Vn,lockingBytecodeToCashAddress as Qn,binToHex as $o}from"@bitauth/libauth";function Ce(e){return Uint8Array.from([118,169,20,...Vn(e),136,172])}function Zn(e,t="bchtest",n=!1){let o=Qn({bytecode:Ce(e),prefix:t,tokenSupport:n});if(typeof o=="string")throw new Error(`address encoding failed: ${o}`);return o.address}function to(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 Jo,hexToBin as Po}from"@bitauth/libauth";import{stringify as Xo}from"@bitauth/libauth";export{Rt as BuiltInWallet,mt as CHIPNET_PREFIX,J as InsufficientFundsError,St as NoGenesisInputError,O as REGISTRAR,Mt as TLD_BEACON,_ as addressToLockingBytecode,Ze as addressToScripthash,Jo as binToHex,V as broadcast,Mn as buildCosignRequest,gt as buildIndex,An as buildListing,ft as buildPrepTx,Kt as buildRegistrationTx,Et as buildTldRegistrationTx,At as buildUpdateTx,It as checkAvailability,In as completeSale,Hn as connect,to as describeGenesisRequirement,Y as displayName,Gn as findCertificateUtxo,Fe as findTldCertificateUtxo,$e as findTldOwnerAddress,Xn as getBalance,ue as getUtxos,z as getUtxosForAddresses,at as hasGenesisInput,Po as hexToBin,P as normalizeName,ht as normalizeTld,Ut as payloadBudget,vn as priceSummary,Zn as pubkeyToAddress,Ce as pubkeyToLockingBytecode,Ot as quoteRegistration,Kn as registerTldWithBuiltInWallet,Wn as registerTldWithExternalWallet,qn as registerWithBuiltInWallet,zn as registerWithExternalWallet,Sn as resolveName,jn as setRecordsWithBuiltInWallet,Jn as setRecordsWithExternalWallet,Pn as setTldRecordsWithBuiltInWallet,Un as signCosignRequest,_t as signInputs,Yn as signRecordsManifest,nt as signTransaction,te as sourceOutputFromJson,Zt as sourceOutputToJson,Re as splitServiceFee,Xo as stringify,M as toTokenAddress,fe as toWcTransactionObject,zt as txidOf,Gt as txidOfHex,Ne as verifyListing};