site/js/bns-register.js

8 lines
74 KiB
JavaScript
Raw Normal View History

import{generateBip39Mnemonic as lo,deriveSeedFromBip39Mnemonic as ze,deriveHdPrivateNodeFromSeed as uo,deriveHdPath as fo,secp256k1 as Ye,hash160 as ho,encodeCashAddress as Ms,lockingBytecodeToCashAddress as po,cashAddressToLockingBytecode as $s,binToHex as go,hexToBin as Hs,utf8ToBin as Je,binToBase64 as oe,base64ToBin as se}from"@bitauth/libauth";var vt={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"}],covenant:{version:1,fundLock:"76a914b2e8b418849e8e3b34247864940a3429d92085fe88ac",steward:"02817a308db60d949f5a0543085f6757578dab169d4381934e997cbaef52c34c0d",blocksPerYear:52560,graceBlocks:26280,tiers:[{maxLabel:2,renewalSats:2e6,head:"011e0350cd0002a86604809698000400e1f5050380841e"},{maxLabel:4,renewalSats:5e5,head:"011e0350cd0002a86603a025260440787d010320a107"},{maxLabel:7,renewalSats:2e5,head:"011e0350cd0002a8660340420f048096980003400d03"},{maxLabel:255,renewalSats:1e5,head:"011e0350cd0002a8660320a10703404b4c03a08601"}],tail:"105279a97b887b7bad51b275827701209c2102817a308db60d949f5a0543085f6757578dab169d4381934e997cbaef52c34c0d1976a914b2e8b418849e8e3b34247864940a3429d92085fe88ac5b79009c635c79a95b79885d7a5d7aad5c7951a269597a815c79597a959376040065cd1d9f6901145a7a7e547e7c54807ec1011a7f777eaa00cd02aa207b7e01877e8800d1c0ce8800d2c0cf8800ccc0c6a26951cd8851cc537a597a95a26951d100886d6d6d7551675b79519c635e7a820114885d79a95c7a885d7a5d7aad01147c7e547e5a7a7ec1011a7f777eaa00cd02aa207b7e01877e8800d1c0ce8800d2c0cf8800ccc0c6a2696d6d6d6d6d51675b79529c635c7a820114885a7a8176b175c576040065cd1d9f697c597993587900709f635a795a795c79945479567994955d7996937b757c785e799501649677686079827701209dc0cf01117a7eaa011456797e01207e7c7e587a7e51c702aa207baa7e01877e8853795c7993040065cd1d9f690114557a7e547e547a5b7a9354807ec1011a7f777eaa00cd02aa207b7e01877e8800d1c0ce8800d2c0cf8800ccc0c6a26951cd547a8851cc7b527994a26951d100887600a06352cd0376a9145a797e0288ac7e8852cc78a26952d10088686d6d6d6d6d51675b79539c635c79a95b7a885c7a5c7aadc0cec454a16900d178879169c451a06351d17887916968c452a06352d17887916968c453a06353d178879169686d6d6d6d6d6d51675b7a549d5b79a95b79885c7a5c7aadc1011a7f77aa5c7a7c5d79aa7eaa537abb01145a7a7e547e597a7e597a7eaa00cd02aa207b7e01877e8800d1c0ce8800d2c0cf8800ccc0c6a2696d6d6d6d5168686868"}},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.
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{}}},Fn=t=>typeof t=="string"?{url:t}:t;async function Ct(t,e,{directIP:n=!1}={}){let o;for(let s of t){let r=Fn(s);try{return await new Lt(r.url,e).connect()}catch(i){o=i}if(n&&r.ip)try{let i=new URL(r.url),a=`${i.protocol}//${r.ip}:${i.port}`;return await new Lt(a,e,{servername:i.hostname}).connect()}catch(i){o=i}}throw o??new Error("no electrum server reachable")}function Mn(t){if(!t||!t.startsWith("6a"))return null;let e=Ut(t),n=1,o=[];for(;n<e.length;){let r=e[n++],i;if(r<=75)i=r;else if(r===76)i=e[n++];else if(r===77)i=e[n]|e[n+1]<<8,n+=2;else return null;o.push(e.slice(n,n+i)),n+=i}let s=new TextDecoder().decode(o[0]??new Uint8Array);if(!s.startsWith(Pe))return null;try{let r=JSON.parse(s.slice(Pe.length));if(!$n.has(r.o))return null;if(typeof r.n=="string"||r.o==="RPT"&&Array.isArray(r.n)&&r.n.every(i=>typeof i=="string"))return r}catch{}return null}function Qt(t){let e=null;for(let n of t||[]){let o=Mn(n&&n.scriptPubKey?n.scriptPubKey.hex:null);if(o){if(e)return null;e=o}}return e}var $n=new Set(["REG","UPD","REL","TREG","TUPD","RPT","BLK","UBK"]);function Zt(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 te(t,e){let n=new Set(t.map(a=>a.tx_hash)),o=new Map(t.map(a=>{let l=new Set;for(let c of e.get(a.tx_hash)?.vin??[])c.txid&&c.txid!==a.tx_hash&&n.has(c.txid)&&l.add(c.txid);return[a.tx_hash,l]})),s=new Set,r=[],i=[...t];for(;i.length;){let a=i.findIndex(c=>[...o.get(c.tx_hash)].every(h=>s.has(h)));a<0&&(a=0);let[l]=i.splice(a,1);s.add(l.tx_hash),r.push(l)}return r}function We(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(i=>i&&i.txid===o&&i.vout===0)||(t.vout||[]).filter(i=>i.tokenData&&i.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 Yt(t){let e=t.lastIndexOf(".");return e<0?t:t.slice(e+1)}function Hn(t,e,n){let o=n.get(t);return o?o.regHeight>0&&e>0?o.regHeight<=e:!0:!1}async function ee(t,e=S){let{tldBeaconScripthash:n}=Xt(e),o=typeof t.unionHistory=="function"?(await t.unionHistory(n)).history:await t.call("blockchain.scripthash.get_history",[n]);o.sort(Zt);let s=new Map;for(let{tx_hash:i}of o)try{s.set(i,await t.call("blockchain.transaction.get",[i,!0]))}catch{}let r=new Map;for(let{tx_hash:i,height:a}of te(o.filter(l=>s.has(l.tx_hash)),s)){let l=s.get(i),c=l.vout||[],h=Qt(c);if(!h||typeof h.n!="string"||!$e.test(h.n))continue;let d=h.n;if(h.o==="TREG"){if(r.has(d))continue;let u=Jt(d),f=c.find(y=>y.tokenData?.nft&&y.tokenData.nft.commitment===u);if(!f||!We(l,f))continue;r.set(d,{category:f.tokenData.category,regHeight:a,regTxid:i,mintScriptHex:f.scriptPubKey?.hex??null,records:h.r??{},updates:[{height:a,txid:i,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:a,txid:i,records:u.records})}}return r}function Kn(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 Wn(t,e){let n=Kn(t,e);return n.policy==="cosign"||n.hidden===1||n.hidden===!0||n.hidden==="1"}function Gn(t,e,n){let o=Jt(n);return(t||[]).some(s=>s.tokenData&&s.tokenData.category===e.category&&s.tokenData.nft&&s.tokenData.nft.commitment===o)}function Ne(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 Ge(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 jn(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=Ge(
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=Mt,e){let n=[];for(let o of t)try{return await new ce(o,e).connect()}catch(s){n.push(`${o}: ${s?.message??s}`)}throw wo(t.length,n)}function wo(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 bo,lockingBytecodeToCashAddress as ko,sha256 as So,hash160 as Ys,binToHex as To,hexToBin as Vs}from"@bitauth/libauth";function tn(t){return To(So.hash(t).slice().reverse())}function C(t){let e=bo(t);if(typeof e=="string")throw new Error(`bad address "${t}": ${e}`);return e.bytecode}function vo(t){return tn(C(t))}function j(t){let e=C(t),n=String(t).includes(":")?t.split(":")[0]:S.prefix,o=ko({bytecode:e,prefix:n,tokenSupport:!0});if(typeof o=="string")throw new Error(`cannot token-encode "${t}": ${o}`);return o.address}async function en(t,e){let n=C(e),o=tn(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 en(t,o));return n}async function tt(t,e){return t.call("blockchain.transaction.broadcast",[e])}async function nn(t,e,n=!0){return t.call("blockchain.transaction.get",[e,n])}import{binToHex as Pt,hexToBin as V,utf8ToBin as yt,binToUtf8 as de,encodeTransaction as Eo,decodeTransaction as Zs}from"@bitauth/libauth";var Oo="BNS1",lt=S.beacon,fe=S.tldBeacon,I=600n,R=1000n,K=546n,le="bch",ue=200,_o=/^[a-z0-9-]{1,32}$/,on=/^[a-z0-9-]{1,16}$/;function Et(t){let e=String(t).toLowerCase().trim().replace(/^\.+/,"").replace(/\.+$/,"");if(!on.test(e))throw new Error(`invalid TLD "${t}" (allowed: [a-z0-9-]{1,16})`);return e}var $t=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=le):(o=e[e.length-1],n=e[e.length-2]),!_o.test(n)||!on.test(o))throw new Error(`invalid name "${t}" (allowed: <label>.<tld>, [a-z0-9-])`);return o===le?n:`${n}.${o}`}function z(t){return t.includes(".")?t:`${t}.${le}`}function J(t,e,n,o=null){let s=JSON.stringify(o?{o:t,n:e,r:n??{},c:o}:{o:t,n:e,r:n??{}}),r=yt(Oo+s);if(r.length>ue)throw new Error(`records too large (${r.length} > ${ue} bytes)`);return r}function he(t,e="REG"){return ue-J(e,t,{}).length}function Ao(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,...Ao(t)])}function pe(){return Uint8Array.from([65,...new Uint8Array(65),33,...new Uint8Array(33)])}var Bo=t=>!t.token;function F(t,e,{requireGenesis:n=!0}={}){let o=t.filter(Bo),s=[],r=0n;if(n){let a=o.filter(l=>l.vout===0).sort((l,c)=>c.satoshis>l.satoshis?1:-1);if(a.length===0)throw new $t("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(a[0]),r+=a[0].satoshis}let i=o.filter(a=>!s.some(l=>l.txid===a.txid&&l.vout===a.vout)).sort((a,l)=>l.satoshis>a.satoshis?1:-1);for(let a of i){if(r>=e)break;s.push(a),r+=a.satoshis}return{inputs:s,totalSats:r}}function mt(t){return{outpointTransactionHash:V(t.txid),outpointIndex:t.vout,sequenceNumber:4294967294,unlockingBytecode:pe()}}function bt(t){return{...mt(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 rt(t){return Eo(t).length}function ge({name:t,ownerAddress:e,ownerTokenAddress:n,changeAddress:o,records:s={},utxos:r,feeAddress:i=null,feeSat