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