site/js/bns-register.js

8 lines
70 KiB
JavaScript
Raw Normal View History

import{generateBip39Mnemonic as Zn,deriveSeedFromBip39Mnemonic as We,deriveHdPrivateNodeFromSeed as to,deriveHdPath as eo,secp256k1 as Ge,hash160 as no,encodeCashAddress as Bs,lockingBytecodeToCashAddress as oo,cashAddressToLockingBytecode as Is,binToHex as so,hexToBin as Ps,utf8ToBin as ze,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:[]}},Le="chipnet";function Pn(){let t=typeof globalThis<"u"?globalThis.BNS_NETWORK:null,e=typeof process<"u"&&process.env?process.env.BNS_NETWORK:null;return String(t||e||Le).trim().toLowerCase()}function Et(t){if(t&&typeof t=="object")return t;let e=t||Pn(),n=Tt[e];if(!n)throw new Error(`unknown network "${e}" (known: ${Object.keys(Tt).join(", ")})`);return n}var E=Et();function zt(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 ws=E.beacon,xs=E.beaconScripthash,bs=E.tldBeacon,ks=E.tldBeaconScripthash,Ss=E.electrum,Ts=Tt.chipnet.electrum,Es=E.electrumListName,Ae="BNS1";var De="bch",Nn=/^[a-z0-9-]{1,32}$/,Ce=/^[a-z0-9-]{1,16}$/,Ue=/^[a-z0-9-]{1,32}(\.[a-z0-9-]{1,16})?$/;function Fe(t){return t.includes(".")?t:t+"."+De}var $e=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},Yt=t=>[...new TextEncoder().encode(t)].map(e=>e.toString(16).padStart(2,"0")).join("");function Rn(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{}}},Ln=t=>typeof t=="string"?{url:t}:t;async function Dt(t,e,{directIP:n=!1}={}){let o;for(let s of t){let r=Ln(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 Dn(t){if(!t||!t.startsWith("6a"))return null;let e=$e(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(!Cn.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}function Vt(t){let e=null;for(let n of t||[]){let o=Dn(n&&n.scriptPubKey?n.scriptPubKey.hex:null);if(o){if(e)return null;e=o}}return e}var Cn=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(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 Me(t,e){let n=e&&e.tokenData;if(!n||!n.nft||typeof n.category!="string")return!1;let o=n.category;if(!(t.vin||[]).some(a=>a&&a.txid===o&&a.vout===0)||(t.vout||[]).filter(a=>a.tokenData&&a.tokenData.category===o).length!==1)return!1;let s=n.nft.capability;if(s!=null&&s!==""&&s!=="none")return!1;let r;try{r=BigInt(n.amount??0)}catch{return!1}return r===0n}function qt(t){let e=t.lastIndexOf(".");return e<0?t:t.slice(e+1)}function Un(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}=zt(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 l=s.get(a),c=l.vout||[],h=Vt(c);if(!h||typeof h.n!="string"||!Ce.test(h.n))continue;let d=h.n;if(h.o==="TREG"){if(r.has(d))continue;let u=Yt(d),f=c.find(y=>y.tokenData?.nft&&y.tokenData.nft.commitment===u);if(!f||!Me(l,f))continue;r.set(d,{category:f.tokenData.category,regHeight:i,regTxid:a,mintScriptHex:f.scriptPubKey?.hex??null,records:h.r??{},updates:[{height:i,txid:a,records:h.r??{}}]})}else if(h.o==="TUPD"){let u=r.get(d);if(!u||!c.some(f=>f.tokenData&&f.tokenData.category===u.category))continue;u.records=h.r??{},u.updates.push({height:i,txid:a,records:u.records})}}return r}function Fn(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 $n(t,e){let n=Fn(t,e);return n.policy==="cosign"||n.hidden===1||n.hidden===!0||n.hidden==="1"}function Mn(t,e,n){let o=Yt(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 He(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 Hn(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=He(
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 Ve(t=Ut,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 co(t.length,n)}function co(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 lo,lockingBytecodeToCashAddress as uo,sha256 as fo,hash160 as Fs,binToHex as ho,hexToBin as $s}from"@bitauth/libauth";function Xe(t){return ho(fo.hash(t).slice().reverse())}function D(t){let e=lo(t);if(typeof e=="string")throw new Error(`bad address "${t}": ${e}`);return e.bytecode}function po(t){return Xe(D(t))}function j(t){let e=D(t),n=String(t).includes(":")?t.split(":")[0]:E.prefix,o=uo({bytecode:e,prefix:n,tokenSupport:!0});if(typeof o=="string")throw new Error(`cannot token-encode "${t}": ${o}`);return o.address}async function Je(t,e){let n=D(e),o=Xe(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 Je(t,o));return n}async function tt(t,e){return t.call("blockchain.transaction.broadcast",[e])}async function Qe(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 go,decodeTransaction as Ws}from"@bitauth/libauth";var yo="BNS1",ct=E.beacon,le=E.tldBeacon,B=600n,R=1000n,K=546n,ie="bch",ae=200,mo=/^[a-z0-9-]{1,32}$/,Ze=/^[a-z0-9-]{1,16}$/;function Ot(t){let e=String(t).toLowerCase().trim().replace(/^\.+/,"").replace(/\.+$/,"");if(!Ze.test(e))throw new Error(`invalid TLD "${t}" (allowed: [a-z0-9-]{1,16})`);return e}var Ft=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 C(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]),!mo.test(n)||!Ze.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(yo+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 wo(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,...wo(t)])}function de(){return Uint8Array.from([65,...new Uint8Array(65),33,...new Uint8Array(33)])}var xo=t=>!t.token;function F(t,e,{requireGenesis:n=!0}={}){let o=t.filter(xo),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 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)");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 go(t).length}function fe({name:t,ownerAddress:e,ownerTokenAddress:n,changeAddress:o,records:s={},utxos:r,feeAddress:a=null,feeSats:i=0n,tldFeeAddress:l=null,tld