site/js/bns-register.js

8 lines
70 KiB
JavaScript
Raw Normal View History

import{generateBip39Mnemonic as Jn,deriveSeedFromBip39Mnemonic as He,deriveHdPrivateNodeFromSeed as Qn,deriveHdPath as Zn,secp256k1 as Ke,hash160 as to,encodeCashAddress as vs,lockingBytecodeToCashAddress as eo,cashAddressToLockingBytecode as As,binToHex as no,hexToBin as Bs,utf8ToBin as je,binToBase64 as te,base64ToBin as ee}from"@bitauth/libauth";var Tt={chipnet:{id:"chipnet",label:"chipnet",prefix:"bchtest",beacon:"bchtest:qp8njaa9a3ahg7259s5qajnce526n0rttsq8qwx4jl",beaconScripthash:"e1cdbba719436f49acb4bf245b44df875c6d6b405eda46a9d3e736d138102f3b",tldBeacon:"bchtest:qzc4c76vtw9nd49g6x8nmvc0jz62uvk4dc7hmpmerh",tldBeaconScripthash:"c85db10e106606273422b2733b2a6fb8b35a4d0e7dbfb20cf59bdb0833c008b8",electrum:[{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"}],beaconOnly:/silentmode\.st\/electrum|coinspectrum\.duckdns\.org:50011/,electrumListName:"electrum.bch",rootTldName:"tlds.bch",bootstrapTlds:["bch"],startHeight:2e5,hostingBucket:"bns",nameListUrl:"https://dl.silentmode.st/bns-name-snapshot.json",nameListMirrorUrl:"https://navigate.st/bns/silentmode.bch/bns-name-snapshot.json",cdnIndexUrl:"https://dl.silentmode.st/bns-name-index.json",nostrTags:{names:"bns-name-list",tlds:"bns-tld-list"},explorerTx:"https://chipnet.imaginary.cash/tx/",faucets:[{name:"tbch.googol.cash",url:"https://tbch.googol.cash/"},{name:"chipnet.bch.ninja faucet",url:"https://chipnet.bch.ninja/faucet"}],operatorAddress:"bchtest:qzew3dqcsj0guwe5y3uxf9q2xs5ajgy9lca4z093cu",accountPath:"m/44'/145'/0'",bchnRpcPort:48332,blocklists:[{name:"sirius-blocklist.x",beacon:"bchtest:qrrnwvuasq5fy63mdvp3umx5qfavtu7mnq950rdssw",scripthash:"fa14c5fd7a22bd1856b44168823a1ab3ff50961c917dafbcba749aeaa7b26646"}]},mainnet:{id:"mainnet",label:"mainnet",prefix:"bitcoincash",beacon:null,beaconScripthash:null,tldBeacon:null,tldBeaconScripthash:null,electrum:[{url:"wss://bch.imaginary.cash:50004",ip:"67.223.119.97"},{url:"wss://electrum.imaginary.cash:50004",ip:"15.204.113.162"},{url:"wss://electroncash.dk:50004",ip:"87.63.200.138"},{url:"wss://bch.loping.net:50004",ip:"183.89.202.207"},{url:"wss://bch.soul-dev.com:50004",ip:"174.141.196.204"},{url:"wss://cashnode.bch.ninja:50004",ip:"135.148.236.246"}],beaconOnly:/(?!)/,electrumListName:"electrum.bch",rootTldName:"tlds.bch",bootstrapTlds:["bch"],startHeight:null,hostingBucket:"bns-mainnet",nameListUrl:"https://s3.silentmode.st:8600/bns-mainnet/name-list.json",cdnIndexUrl:"https://dl.silentmode.st/bns-mainnet-name-index.json",nostrTags:{names:"bns-mainnet-name-list",tlds:"bns-mainnet-tld-list"},explorerTx:"https://blockchair.com/bitcoin-cash/transaction/",faucets:[],operatorAddress:null,accountPath:"m/44'/145'/0'",bchnRpcPort:8332,blocklists:[]}},Re="chipnet";function Bn(){let t=typeof globalThis<"u"?globalThis.BNS_NETWORK:null,e=typeof process<"u"&&process.env?process.env.BNS_NETWORK:null;return String(t||e||Re).trim().toLowerCase()}function Et(t){if(t&&typeof t=="object")return t;let e=t||Bn(),n=Tt[e];if(!n)throw new Error(`unknown network "${e}" (known: ${Object.keys(Tt).join(", ")})`);return n}var E=Et();function qt(t=E){let e=Et(t);if(!e.beaconScripthash||!e.tldBeaconScripthash)throw new Error(`${e.label}: beacon addresses are not pinned yet (see ROADMAP-MAINNET.md \xA76)`);return e}var ys=E.beacon,ms=E.beaconScripthash,ws=E.tldBeacon,xs=E.tldBeaconScripthash,bs=E.electrum,ks=Tt.chipnet.electrum,Ss=E.electrumListName,Ae="BNS1";var Le="bch",In=/^[a-z0-9-]{1,32}$/,Ce=/^[a-z0-9-]{1,16}$/,De=/^[a-z0-9-]{1,32}(\.[a-z0-9-]{1,16})?$/;function Ue(t){return t.includes(".")?t:t+"."+Le}var Fe=t=>{let e=new Uint8Array(t.length/2);for(let n=0;n<e.length;n++)e[n]=parseInt(t.substr(n*2,2),16);return e},zt=t=>[...new TextEncoder().encode(t)].map(e=>e.toString(16).padStart(2,"0")).join("");function Pn(t){let n=String(t).toLowerCase().trim().replace(/^[a-z]+:\/\//,"").split("/")[0].replace
feat(market): names can be priced in US dollars — a covenant holds the certificate, a gateway-signed oracle sets the BCH amount A BCH-priced offer is a signed transaction with a fixed amount, so a seller who wants "$500" had to relist whenever the rate moved. The new USD mode moves the certificate into a small CashScript covenant (contracts/usd-listing.cash). Anyone may buy it by paying the seller target_cents / price, where the price comes from a message signed by the gateway's oracle key — the median of five exchanges the site already uses. The payout is clamped to a floor/ceiling band the seller sets, and the seller can reclaim the certificate at any time. What the chain can and cannot enforce, stated plainly in the contract and the UI: script can require a transaction to be mined no earlier than a time, never no later, so a quote cannot expire on-chain. The band is the guarantee, and quotes are bound to the listing's outpoint so cancelling and relisting voids every quote ever issued. An offline libauth VM test covers list, buy, underpayment, foreign-listing quotes, forged quotes, band clamping, cancel and a non-seller cancel; on chipnet aloevera.test was listed at $0.30, bought by a second wallet for 119,090 sat at $251.91, listed again by the new owner and cancelled. Gateway: GET /api/price/oracle[?txid=] serves the oracle public key and per-listing signed quotes; the key lives in oracle.key beside the gateway, never in the repo. POST /api/market accepts kind "usd" after reproducing the redeem script from the stated terms and finding the certificate in that covenant; such listings are pruned once the covenant output is spent; DELETE accepts the listing's seller while the index shows the covenant as holder. Dashboard: the Sell tab has "Price is fixed in: US dollars / BCH", a band control with the resulting BCH range, and cancel through the covenant. Market: USD listings show the dollar price, today's BCH equivalent and the band; buying fetches a quote and completes the covenant spend. Studio no longer sends a needless UPD when the registry view is stale, and missing files on a name now get a real 404 page.
2026-09-20 19:20:46 +02:00
`))>=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(e,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:e,params:n})+`
`),setTimeout(()=>{this.pending.has(o)&&(this.pending.delete(o),r(new Error(`electrum timeout: ${e}`)))},2e4)})}close(){try{this.ws.close()}catch{}}},Nn=t=>typeof t=="string"?{url:t}:t;async function Yt(t,e,{directIP:n=!1}={}){let o;for(let s of t){let r=Nn(s);try{return await new Lt(r.url,e).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 Lt(i,e,{servername:a.hostname}).connect()}catch(a){o=a}}throw o??new Error("no electrum server reachable")}function Vt(t){if(!t||!t.startsWith("6a"))return null;let e=Fe(t),n=1,o=[];for(;n<e.length;){let r=e[n++],a;if(r<=75)a=r;else if(r===76)a=e[n++];else if(r===77)a=e[n]|e[n+1]<<8,n+=2;else return null;o.push(e.slice(n,n+a)),n+=a}let s=new TextDecoder().decode(o[0]??new Uint8Array);if(!s.startsWith(Ae))return null;try{let r=JSON.parse(s.slice(Ae.length));if(!Rn.has(r.o))return null;if(typeof r.n=="string"||r.o==="RPT"&&Array.isArray(r.n)&&r.n.every(a=>typeof a=="string"))return r}catch{}return null}var Rn=new Set(["REG","UPD","REL","TREG","TUPD","RPT","BLK","UBK"]);function Xt(t,e){let n=t.height>0?t.height:1/0,o=e.height>0?e.height:1/0;return n-o||t.tx_hash.localeCompare(e.tx_hash)}function Jt(t,e){let n=new Set(t.map(i=>i.tx_hash)),o=new Map(t.map(i=>{let l=new Set;for(let c of e.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=[...t];for(;a.length;){let i=a.findIndex(c=>[...o.get(c.tx_hash)].every(f=>s.has(f)));i<0&&(i=0);let[l]=a.splice(i,1);s.add(l.tx_hash),r.push(l)}return r}function jt(t){let e=t.lastIndexOf(".");return e<0?t:t.slice(e+1)}function Ln(t,e,n){let o=n.get(t);return o?o.regHeight>0&&e>0?o.regHeight<=e:!0:!1}async function Qt(t,e=E){let{tldBeaconScripthash:n}=qt(e),o=typeof t.unionHistory=="function"?(await t.unionHistory(n)).history:await t.call("blockchain.scripthash.get_history",[n]);o.sort(Xt);let s=new Map;for(let{tx_hash:a}of o)try{s.set(a,await t.call("blockchain.transaction.get",[a,!0]))}catch{}let r=new Map;for(let{tx_hash:a,height:i}of Jt(o.filter(l=>s.has(l.tx_hash)),s)){let c=s.get(a).vout||[],f=null;for(let u of c)f=Vt(u.scriptPubKey?.hex)??f;if(!f||typeof f.n!="string"||!Ce.test(f.n))continue;let d=f.n;if(f.o==="TREG"){if(r.has(d))continue;let u=zt(d),h=c.find(m=>m.tokenData?.nft&&m.tokenData.nft.commitment===u);if(!h)continue;r.set(d,{category:h.tokenData.category,regHeight:i,regTxid:a,mintScriptHex:h.scriptPubKey?.hex??null,records:f.r??{},updates:[{height:i,txid:a,records:f.r??{}}]})}else if(f.o==="TUPD"){let u=r.get(d);if(!u||!c.some(h=>h.tokenData&&h.tokenData.category===u.category))continue;u.records=f.r??{},u.updates.push({height:i,txid:a,records:u.records})}}return r}function Cn(t,e){if(!t)return{};let n=t.updates||[];if(!n.length)return t.records||{};let o={};for(let s of n)e>0&&(s.height<=0||s.height>e)||(o=s.records||{});return o}function Dn(t,e){let n=Cn(t,e);return n.policy==="cosign"||n.hidden===1||n.hidden===!0||n.hidden==="1"}function Un(t,e,n){let o=zt(n);return(t||[]).some(s=>s.tokenData&&s.tokenData.category===e.category&&s.tokenData.nft&&s.tokenData.nft.commitment===o)}function Be(t){if(!t||!t.scriptPubKey)return null;let e=t.scriptPubKey;return typeof e.address=="string"?e.address:Array.isArray(e.addresses)&&e.addresses.length?e.addresses[0]:null}function $e(t,e){let n=t&&t.txid?e.get(t.txid):null;return!n||!Array.isArray(n.vout)?null:n.vout.find(o=>o&&o.n===t.vout)??n.vout[t.vout]??null}function Fn(t,e,n){return!t||!e||!e.category||(t.vout||[]).some(o=>o.tokenData&&o.tokenData.category===e.category)?!1:(t.vin||[]).some(o=>{let s=$e(o,n);return!!(s&&s.tokenData&&s.tokenData.nft&&s.tokenData.category===e.category)})}function $n(t,e){return t===void 0?!0:t>0?!(e>0)||e>t:!1}async function pt({network:t=E,electrum:e=Et(t).electrum,WebSocket:n,client:o,directIP:s=!1,enforceTld:r=!1,cosignGate:a=!0}={}){let i=qt(t),l=o??await Yt(e,n,{directIP:s});try{let c=null;if(r||a)try{c=await Qt(l,i)}catch(p){if(r)throw p;c=null}let f=null,d;if(typeof l.unionHistory=="function"){let p=await l.unionHistory(i.beaco
feat(market): names can be priced in US dollars — a covenant holds the certificate, a gateway-signed oracle sets the BCH amount A BCH-priced offer is a signed transaction with a fixed amount, so a seller who wants "$500" had to relist whenever the rate moved. The new USD mode moves the certificate into a small CashScript covenant (contracts/usd-listing.cash). Anyone may buy it by paying the seller target_cents / price, where the price comes from a message signed by the gateway's oracle key — the median of five exchanges the site already uses. The payout is clamped to a floor/ceiling band the seller sets, and the seller can reclaim the certificate at any time. What the chain can and cannot enforce, stated plainly in the contract and the UI: script can require a transaction to be mined no earlier than a time, never no later, so a quote cannot expire on-chain. The band is the guarantee, and quotes are bound to the listing's outpoint so cancelling and relisting voids every quote ever issued. An offline libauth VM test covers list, buy, underpayment, foreign-listing quotes, forged quotes, band clamping, cancel and a non-seller cancel; on chipnet aloevera.test was listed at $0.30, bought by a second wallet for 119,090 sat at $251.91, listed again by the new owner and cancelled. Gateway: GET /api/price/oracle[?txid=] serves the oracle public key and per-listing signed quotes; the key lives in oracle.key beside the gateway, never in the repo. POST /api/market accepts kind "usd" after reproducing the redeem script from the stated terms and finding the certificate in that covenant; such listings are pruned once the covenant output is spent; DELETE accepts the listing's seller while the index shows the covenant as holder. Dashboard: the Sell tab has "Price is fixed in: US dollars / BCH", a band control with the resulting BCH range, and cancel through the covenant. Market: USD listings show the dollar price, today's BCH equivalent and the band; buying fetches a quote and completes the covenant spend. Studio no longer sends a needless UPD when the registry view is stale, and missing files on a name now get a real 404 page.
2026-09-20 19:20:46 +02:00
`))>=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(e,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:e,params:n})+`
`),setTimeout(()=>{this.pending.has(o)&&(this.pending.delete(o),r(new Error(`electrum timeout: ${e}`)))},2e4)})}close(){try{this.ws.close()}catch{}}};async function ze(t=Dt,e){let n=[];for(let o of t)try{return await new re(o,e).connect()}catch(s){n.push(`${o}: ${s?.message??s}`)}throw io(t.length,n)}function io(t,e){let n=new Error(`no electrum server reachable (tried ${t}). These use non-standard ports, which some networks and VPNs block:
${e.join(`
`)}`);return n.code="NO_ELECTRUM",n.failures=e,n}import{cashAddressToLockingBytecode as ao,lockingBytecodeToCashAddress as co,sha256 as lo,hash160 as Ds,binToHex as uo,hexToBin as Us}from"@bitauth/libauth";function Ye(t){return uo(lo.hash(t).slice().reverse())}function C(t){let e=ao(t);if(typeof e=="string")throw new Error(`bad address "${t}": ${e}`);return e.bytecode}function ho(t){return Ye(C(t))}function j(t){let e=C(t),n=String(t).includes(":")?t.split(":")[0]:E.prefix,o=co({bytecode:e,prefix:n,tokenSupport:!0});if(typeof o=="string")throw new Error(`cannot token-encode "${t}": ${o}`);return o.address}async function Ve(t,e){let n=C(e),o=Ye(n);return(await t.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:e,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 q(t,e){let n=[];for(let o of e)n.push(...await Ve(t,o));return n}async function tt(t,e){return t.call("blockchain.transaction.broadcast",[e])}async function Xe(t,e,n=!0){return t.call("blockchain.transaction.get",[e,n])}import{binToHex as Pt,hexToBin as V,utf8ToBin as gt,binToUtf8 as ce,encodeTransaction as fo,decodeTransaction as Hs}from"@bitauth/libauth";var po="BNS1",ct=E.beacon,le=E.tldBeacon,A=600n,R=1000n,K=546n,ie="bch",ae=200,go=/^[a-z0-9-]{1,32}$/,Je=/^[a-z0-9-]{1,16}$/;function Ot(t){let e=String(t).toLowerCase().trim().replace(/^\.+/,"").replace(/\.+$/,"");if(!Je.test(e))throw new Error(`invalid TLD "${t}" (allowed: [a-z0-9-]{1,16})`);return e}var Ut=class extends Error{constructor(e){super(e),this.name="NoGenesisInputError",this.code="NO_GENESIS_INPUT"}},X=class extends Error{constructor(e,{needSats:n,haveSats:o}={}){super(e),this.name="InsufficientFundsError",this.code="INSUFFICIENT_FUNDS",this.needSats=n,this.haveSats=o}};function D(t){let e=String(t).toLowerCase().trim().replace(/\.$/,"").split(".").filter(Boolean),n,o;if(e.length===0)throw new Error(`invalid name "${t}"`);if(e.length===1?(n=e[0],o=ie):(o=e[e.length-1],n=e[e.length-2]),!go.test(n)||!Je.test(o))throw new Error(`invalid name "${t}" (allowed: <label>.<tld>, [a-z0-9-])`);return o===ie?n:`${n}.${o}`}function z(t){return t.includes(".")?t:`${t}.${ie}`}function J(t,e,n){let o=JSON.stringify({o:t,n:e,r:n??{}}),s=gt(po+o);if(s.length>ae)throw new Error(`records too large (${s.length} > ${ae} bytes)`);return s}function ue(t,e="REG"){return ae-J(e,t,{}).length}function yo(t){return t.length<=75?Uint8Array.from([t.length,...t]):t.length<=255?Uint8Array.from([76,t.length,...t]):Uint8Array.from([77,t.length&255,t.length>>8&255,...t])}function et(t){return Uint8Array.from([106,...yo(t)])}function de(){return Uint8Array.from([65,...new Uint8Array(65),33,...new Uint8Array(33)])}var mo=t=>!t.token;function F(t,e,{requireGenesis:n=!0}={}){let o=t.filter(mo),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 Ut("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>=e)break;s.push(i),r+=i.satoshis}return{inputs:s,totalSats:r}}function yt(t){return{outpointTransactionHash:V(t.txid),outpointIndex:t.vout,sequenceNumber:4294967294,unlockingBytecode:de()}}function xt(t){return{...yt(t),lockingBytecode:t.lockingBytecode,valueSatoshis:t.satoshis,...t.token?{token:{amount:t.token.amount??0n,category:V(t.token.category),...t.token.nft?{nft:{capability:t.token.nft.capability,commitment:V(t.token.nft.commitment||"")}}:{}}}:{}}}function st(t){return fo(t).length}function he({name:t,ownerAddress:e,ownerTokenAddress:n,changeAddress:o,records:s={},utxos:r,feeAddress:a=null,feeSats:i=0n,tldFeeAddress:l=null,tld