site/js/bns-register.js
Local Dev 1cc7f6e828 feat(registrar): registerTldWithExternalWallet + wire into portal
Adds the external-wallet variant of registerTldWithBuiltInWallet so
WizardConnect users can mint TLDs from the sirius.x portal without
first importing a seed. Same transaction shape (TREG payload, TLD-
beacon output, service-fee output) — the wallet approves the mint
(and, if needed, a one-time prep signature to satisfy the vout-0
non-token genesis-input rule) on the user's own device.

Bundle: re-exported from src/web/register-entry.js and rebuilt
(bns-register.js: 33.3 kB, cache-buster bumped to ?v=20260907tldext
on portal.html / register.html / admin/index.html).

Portal: TLD-register submit now dispatches on wallet.source, calling
registerTldWithExternalWallet with wallet.session when the user is
signed in via WC. Removed the WC-disable guard and updated the card
copy to say TLD mints also go through the wallet's signing prompt.

Uses the same landOnChain helper as registerWithExternalWallet, so
wallet-broadcasts (walletBroadcasts:true sessions) and duplicate/
mempool responses are handled identically.
2026-09-07 01:39:16 +02:00

7 lines
33 KiB
JavaScript

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