From c791c3e3d2b33fda4c889502e072d998e04cf266 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 27 Sep 2026 21:18:37 +0200 Subject: [PATCH] bns: one network table instead of chipnet constants copied into fifteen places MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every client hard-coded the chipnet beacons, address prefix and electrum servers on its own: resolver, registrar config, wallets, gateway, indexer, mirror scripts, the browser bundle and the mobile Java. A mainnet launch would have meant finding all of them and hoping none was missed. The table now lives in resolver-web.js, the one file every client already shares, so it stays a single-file drop-in. BNS_NETWORK selects the record; unset means chipnet, so nothing changes today: the live index resolves the same 60 names and 20 TLDs, the 67 offline tests pass, and the dashboard, market and studio load the same values through BNS.NETWORK. The mainnet record carries the verified public servers, the prefix and its own Sia bucket, but its beacons, start height and operator address are deliberately null: requireBeacons() refuses to scan until they are pinned in the order ROADMAP-MAINNET.md ยง6 requires. Bns.java reads a generated BnsNetwork.java so the phone cannot drift from the desktop clients. NETWORK-CONFIG.md records what reads the table and what a launch still pins. --- js/bns-register.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/bns-register.js b/js/bns-register.js index 47c31ba..7d8435d 100644 --- a/js/bns-register.js +++ b/js/bns-register.js @@ -1,7 +1,7 @@ -import{generateBip39Mnemonic as dn,deriveSeedFromBip39Mnemonic as ye,deriveHdPrivateNodeFromSeed as hn,deriveHdPath as pn,secp256k1 as me,hash160 as fn,encodeCashAddress as Ko,lockingBytecodeToCashAddress as gn,cashAddressToLockingBytecode as Go,binToHex as yn,hexToBin as Wo,utf8ToBin as ke,binToBase64 as Ht,base64ToBin as Kt}from"@bitauth/libauth";var Et="bchtest";var Gt="m/44'/145'/0'",mn=5,At="bns.wallet.v1";function be(t){let e=fn(t);return Uint8Array.from([118,169,20,...e,136,172])}function we(t,e,n){let o=gn({bytecode:be(t),prefix:e,tokenSupport:n});if(typeof o=="string")throw new Error(`address encoding failed: ${o}`);return o.address}var Wt=class t{constructor(e,n,o=Et,s=Gt){this.mnemonic=e,this.prefix=o,this.accountPath=s,this._root=hn(n),this._keys=new Map,this._derive()}static async create(e=Et,n=Gt){let o=dn();return new t(o,await ye(o),e,n)}static async fromMnemonic(e,n=Et,o=Gt){let s=String(e).trim().toLowerCase().replace(/\s+/g," "),r=await ye(s);if(typeof r=="string")throw new Error(`invalid recovery phrase: ${r}`);return new t(s,r,n,o)}_derive(){this.receive=[],this.change=[];for(let[e,n]of[[0,this.receive],[1,this.change]])for(let o=0;oe.address)}keyFor(e){let n=this._keys.get(e);return n?{privateKey:n.privateKey,publicKey:n.publicKey}:null}signMessage(e,n){if(!(e instanceof Uint8Array)||e.length!==32)throw new Error("signMessage expects a 32-byte Uint8Array digest");let o=n||this.address,s=[...this.receive,...this.change].find(h=>h.address===o);if(!s)throw new Error(`no key for address ${o}`);let r=me.signMessageHashRecoverableCompact(s.privateKey,e);if(typeof r=="string")throw new Error(`signMessage failed: ${r}`);let{signature:a}=r,i=r.recoveryId??r.recovery;if(typeof i!="number")throw new Error("signMessage: no recovery id in signature result");let l=31+i,c=new Uint8Array(65);return c[0]=l,c.set(a,1),c}async encrypt(e){let n=crypto.getRandomValues(new Uint8Array(16)),o=crypto.getRandomValues(new Uint8Array(12)),s=await xe(e,n),r=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:o},s,ke(this.mnemonic)));return JSON.stringify({v:1,kdf:"PBKDF2-SHA256",iterations:qt,prefix:this.prefix,salt:Ht(n),iv:Ht(o),ct:Ht(r)})}static async decrypt(e,n){let o=typeof e=="string"?JSON.parse(e):e,s=await xe(n,Kt(o.salt),o.iterations??qt),r;try{r=await crypto.subtle.decrypt({name:"AES-GCM",iv:Kt(o.iv)},s,Kt(o.ct))}catch{throw new Error("wrong password")}return t.fromMnemonic(new TextDecoder().decode(r),o.prefix??Et)}async save(e,n=globalThis.localStorage){n.setItem(At,await this.encrypt(e))}static async load(e,n=globalThis.localStorage){let o=n.getItem(At);return o?t.decrypt(o,e):null}static exists(e=globalThis.localStorage){return!!e?.getItem(At)}static forget(e=globalThis.localStorage){e.removeItem(At)}},qt=25e4;async function xe(t,e,n=qt){let o=await crypto.subtle.importKey("raw",ke(String(t)),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:e,iterations:n,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}var Bt=["wss://silentmode.st/electrum","wss://chipnet.bch.ninja:50004","wss://chipnet.imaginary.cash:50004","wss://tbch4.loping.net:62002"],zt=class{constructor(e,n){this.url=e,this.WS=n||globalThis.WebSocket,this.id=0,this.pending=new Map,this.buf=""}connect(){return new Promise((e,n)=>{let o=new this.WS(this.url);this.ws=o;let s=async()=>{try{await this.call("server.version",["bns-web","1.4.2"]),e(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());o.addEventListener?(o.addEventListener("open",s),o.addEventListener("error",r),o.addEventListener("message",a)):(o.onopen=s,o.onerror=r,o.onmessage=a)})}_handle(e){let n=this.pending.get(e.id);n&&(this.pending.delete(e.id),e.error?n.reject(new Error(JSON.stringify(e.error))):n.resolve(e.result))}_onData(e){this.buf+=e;let n;for(;(n=this.buf.indexOf(` +import{generateBip39Mnemonic as Bn,deriveSeedFromBip39Mnemonic as Ae,deriveHdPrivateNodeFromSeed as In,deriveHdPath as Pn,secp256k1 as Be,hash160 as Nn,encodeCashAddress as ss,lockingBytecodeToCashAddress as Rn,cashAddressToLockingBytecode as rs,binToHex as Ln,hexToBin as is,utf8ToBin as Ne,binToBase64 as Yt,base64ToBin as Vt}from"@bitauth/libauth";var kt={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://s3.silentmode.st:8600/bns/name-list.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},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}},Se="chipnet";function gn(){let t=typeof globalThis<"u"?globalThis.BNS_NETWORK:null,e=typeof process<"u"&&process.env?process.env.BNS_NETWORK:null;return String(t||e||Se).trim().toLowerCase()}function Pt(t){if(t&&typeof t=="object")return t;let e=t||gn(),n=kt[e];if(!n)throw new Error(`unknown network "${e}" (known: ${Object.keys(kt).join(", ")})`);return n}var T=Pt();function zt(t=T){let e=Pt(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 jo=T.beacon,Yo=T.beaconScripthash,Vo=T.tldBeacon,Xo=T.tldBeaconScripthash,Jo=T.electrum,Qo=kt.chipnet.electrum,Zo=T.electrumListName,ke="BNS1";var Te="bch",mn=/^[a-z0-9-]{1,32}$/,Ee=/^[a-z0-9-]{1,16}$/,yn=/^[a-z0-9-]{1,32}(\.[a-z0-9-]{1,16})?$/;function wn(t){return t.includes(".")?t:t+"."+Te}var xn=t=>{let e=new Uint8Array(t.length/2);for(let n=0;n[...new TextEncoder().encode(t)].map(e=>e.toString(16).padStart(2,"0")).join("");function kn(t){let n=String(t).toLowerCase().trim().replace(/^[a-z]+:\/\//,"").split("/")[0].replace(/\.$/,"").split(".").filter(Boolean);if(n.length===0)throw new Error(`invalid name "${t}"`);let o,s;if(n.length===1?(o=n[0],s=Te):(s=n[n.length-1],o=n[n.length-2]),!mn.test(o)||!Ee.test(s))throw new Error(`invalid name "${t}" (allowed: