From 9e9f23ff7bd710d49eebc4459dfe817f7af310be Mon Sep 17 00:00:00 2001 From: Deggen Date: Wed, 24 Jun 2026 15:07:13 -0500 Subject: [PATCH] feat: add TeraTestNet (ttn) network support Bump @bsv/* deps to latest (wallet-toolbox 2.1.30 -> 2.3.2, which adds 'ttn' to the Chain type and auto-configures ttn service endpoints). Widen the app's network type from 'main' | 'test' to 'main' | 'test' | 'ttn' across IPC, storage, and wallet plumbing; add a TeraTestNet option to the network selector; and route WhatsOnChain calls through a new chain-keyed helper so funding/legacy-bridge flows hit the ttn WoC host. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01QTzRUkGAVsfXyD5fMT1UXY --- electron/main.ts | 8 +- electron/monitor-worker.ts | 2 +- electron/preload.ts | 16 +- electron/storage.ts | 22 +- package-lock.json | 233 ++++++++++++------ package.json | 12 +- src/global.d.ts | 8 +- src/lib/StorageElectronIPC.ts | 4 +- src/lib/WalletContext.tsx | 6 +- src/lib/components/FundingHandler.tsx | 3 +- src/lib/components/WalletConfig.tsx | 10 +- src/lib/components/WalletFundingFlow.tsx | 11 +- src/lib/hooks/useWalletService.ts | 4 +- src/lib/i18n/translations.ts | 12 + src/lib/pages/Dashboard/Apps/index.tsx | 7 +- .../pages/Dashboard/LegacyBridge/index.tsx | 7 +- src/lib/pages/Greeter/index.tsx | 4 +- src/lib/services/WalletService.ts | 4 +- src/lib/utils/getBeefForTxid.ts | 5 +- src/lib/utils/woc.ts | 19 ++ 20 files changed, 262 insertions(+), 135 deletions(-) create mode 100644 src/lib/utils/woc.ts diff --git a/electron/main.ts b/electron/main.ts index 036d80c..2b40f3a 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -445,7 +445,7 @@ ipcMain.on('http-response', (_event, response) => { // ===== Storage IPC Handlers ===== // Check if storage can be made available -ipcMain.handle('storage:is-available', async (_event, identityKey: string, chain: 'main' | 'test') => { +ipcMain.handle('storage:is-available', async (_event, identityKey: string, chain: 'main' | 'test' | 'ttn') => { try { const manager = await getStorageManager(); return await manager.isAvailable(identityKey, chain); @@ -456,7 +456,7 @@ ipcMain.handle('storage:is-available', async (_event, identityKey: string, chain }); // Make storage available (initialize database) -ipcMain.handle('storage:make-available', async (_event, identityKey: string, chain: 'main' | 'test') => { +ipcMain.handle('storage:make-available', async (_event, identityKey: string, chain: 'main' | 'test' | 'ttn') => { try { const manager = await getStorageManager(); const settings = await manager.makeAvailable(identityKey, chain); @@ -468,7 +468,7 @@ ipcMain.handle('storage:make-available', async (_event, identityKey: string, cha }); // Call a storage method -ipcMain.handle('storage:call-method', async (_event, identityKey: string, chain: 'main' | 'test', method: string, args: any[]) => { +ipcMain.handle('storage:call-method', async (_event, identityKey: string, chain: 'main' | 'test' | 'ttn', method: string, args: any[]) => { try { const manager = await getStorageManager(); const result = await manager.callStorageMethod(identityKey, chain, method, args); @@ -480,7 +480,7 @@ ipcMain.handle('storage:call-method', async (_event, identityKey: string, chain: }); // Initialize services on storage -ipcMain.handle('storage:initialize-services', async (_event, identityKey: string, chain: 'main' | 'test') => { +ipcMain.handle('storage:initialize-services', async (_event, identityKey: string, chain: 'main' | 'test' | 'ttn') => { try { const manager = await getStorageManager(); await manager.initializeServices(identityKey, chain); diff --git a/electron/monitor-worker.ts b/electron/monitor-worker.ts index 5f375b0..025c472 100644 --- a/electron/monitor-worker.ts +++ b/electron/monitor-worker.ts @@ -29,7 +29,7 @@ function getCreateKnex() { interface MonitorConfig { identityKey: string; - chain: 'main' | 'test'; + chain: 'main' | 'test' | 'ttn'; } let monitor: Monitor | null = null; diff --git a/electron/preload.ts b/electron/preload.ts index af09a72..e82601b 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -35,13 +35,13 @@ contextBridge.exposeInMainWorld('electronAPI', { // Storage operations storage: { - isAvailable: (identityKey: string, chain: 'main' | 'test') => + isAvailable: (identityKey: string, chain: 'main' | 'test' | 'ttn') => ipcRenderer.invoke('storage:is-available', identityKey, chain), - makeAvailable: (identityKey: string, chain: 'main' | 'test') => + makeAvailable: (identityKey: string, chain: 'main' | 'test' | 'ttn') => ipcRenderer.invoke('storage:make-available', identityKey, chain), - initializeServices: (identityKey: string, chain: 'main' | 'test') => + initializeServices: (identityKey: string, chain: 'main' | 'test' | 'ttn') => ipcRenderer.invoke('storage:initialize-services', identityKey, chain), - callMethod: (identityKey: string, chain: 'main' | 'test', method: string, args: any[]) => + callMethod: (identityKey: string, chain: 'main' | 'test' | 'ttn', method: string, args: any[]) => ipcRenderer.invoke('storage:call-method', identityKey, chain, method, args) }, @@ -96,10 +96,10 @@ export interface ElectronAPI { sendHttpResponse: (response: any) => void; removeHttpRequestListener: () => void; storage: { - isAvailable: (identityKey: string, chain: 'main' | 'test') => Promise; - makeAvailable: (identityKey: string, chain: 'main' | 'test') => Promise<{ success: boolean; settings?: any; error?: string }>; - initializeServices: (identityKey: string, chain: 'main' | 'test') => Promise<{ success: boolean; error?: string }>; - callMethod: (identityKey: string, chain: 'main' | 'test', method: string, args: any[]) => Promise<{ success: boolean; result?: any; error?: string }>; + isAvailable: (identityKey: string, chain: 'main' | 'test' | 'ttn') => Promise; + makeAvailable: (identityKey: string, chain: 'main' | 'test' | 'ttn') => Promise<{ success: boolean; settings?: any; error?: string }>; + initializeServices: (identityKey: string, chain: 'main' | 'test' | 'ttn') => Promise<{ success: boolean; error?: string }>; + callMethod: (identityKey: string, chain: 'main' | 'test' | 'ttn', method: string, args: any[]) => Promise<{ success: boolean; result?: any; error?: string }>; }; secrets: { getAll: () => Promise>; diff --git a/electron/storage.ts b/electron/storage.ts index 246118e..307b269 100644 --- a/electron/storage.ts +++ b/electron/storage.ts @@ -89,7 +89,7 @@ class StorageManager { /** * Get or create a storage instance for the given identity key */ - async getOrCreateStorage(identityKey: string, chain: 'main' | 'test'): Promise { + async getOrCreateStorage(identityKey: string, chain: 'main' | 'test' | 'ttn'): Promise { const key = `${identityKey}-${chain}`; if (this.storages.has(key)) { @@ -171,7 +171,7 @@ class StorageManager { /** * Check if storage is available for the given identity key */ - async isAvailable(identityKey: string, chain: 'main' | 'test'): Promise { + async isAvailable(identityKey: string, chain: 'main' | 'test' | 'ttn'): Promise { // Storage is always available once created await this.getOrCreateStorage(identityKey, chain); return true; @@ -181,7 +181,7 @@ class StorageManager { * Make storage available (initialize database tables) * Returns TableSettings from the storage */ - async makeAvailable(identityKey: string, chain: 'main' | 'test'): Promise { + async makeAvailable(identityKey: string, chain: 'main' | 'test' | 'ttn'): Promise { const storage = await this.getOrCreateStorage(identityKey, chain); const settings = await storage.makeAvailable(); console.log(`[Storage] Storage made available for ${identityKey}-${chain}`); @@ -194,7 +194,7 @@ class StorageManager { */ async initializeServices( identityKey: string, - chain: 'main' | 'test' + chain: 'main' | 'test' | 'ttn' ): Promise { const storage = await this.getOrCreateStorage(identityKey, chain); const key = `${identityKey}-${chain}`; @@ -209,7 +209,11 @@ class StorageManager { // Create Services instance in the backend const options = Services.createDefaultOptions(chain); - options.chaintracks = new ChaintracksServiceClient(chain, chain === 'main' ? 'https://chaintracks-us-1.bsvb.tech' : 'https://chaintracks-testnet-us-1.bsvb.tech') + // For main/test, point ChainTracks at the bsvb.tech endpoints. TeraTestNet ('ttn') + // keeps the toolbox default (arcade-v2-ttn ChainTracks) set by createDefaultOptions. + if (chain !== 'ttn') { + options.chaintracks = new ChaintracksServiceClient(chain, chain === 'main' ? 'https://chaintracks-us-1.bsvb.tech' : 'https://chaintracks-testnet-us-1.bsvb.tech') + } const services = new Services(options); // Type assertion to access setServices method @@ -234,7 +238,7 @@ class StorageManager { */ async startMonitorWorker( identityKey: string, - chain: 'main' | 'test' + chain: 'main' | 'test' | 'ttn' ): Promise { const key = `${identityKey}-${chain}`; @@ -348,7 +352,7 @@ class StorageManager { /** * Stop Monitor worker process */ - async stopMonitorWorker(identityKey: string, chain: 'main' | 'test'): Promise { + async stopMonitorWorker(identityKey: string, chain: 'main' | 'test' | 'ttn'): Promise { const key = `${identityKey}-${chain}`; const worker = this.monitorWorkers.get(key); @@ -388,7 +392,7 @@ class StorageManager { */ async callStorageMethod( identityKey: string, - chain: 'main' | 'test', + chain: 'main' | 'test' | 'ttn', method: string, args: any[] ): Promise { @@ -430,7 +434,7 @@ class StorageManager { for (const [key] of this.monitorWorkers.entries()) { const [identityKey, chain] = key.split('-'); workerStopPromises.push( - this.stopMonitorWorker(identityKey, chain as 'main' | 'test') + this.stopMonitorWorker(identityKey, chain as 'main' | 'test' | 'ttn') ); } await Promise.all(workerStopPromises); diff --git a/package-lock.json b/package-lock.json index ceda504..bb550ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,16 @@ "version": "2.3.3", "license": "Apache-2.0", "dependencies": { - "@bsv/amountinator": "^2.0.2", - "@bsv/btms": "^1.0.2", - "@bsv/btms-permission-module": "^1.0.2", + "@bsv/amountinator": "^2.1.0", + "@bsv/btms": "^1.1.0", + "@bsv/btms-permission-module": "^1.1.0", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", - "@bsv/message-box-client": "^2.1.2", + "@bsv/message-box-client": "^2.2.0", "@bsv/sdk": "^2.1.6", "@bsv/uhrp-react": "^1.0.6", - "@bsv/wallet-toolbox": "^2.1.30", - "@bsv/wallet-toolbox-client": "^2.1.30", + "@bsv/wallet-toolbox": "^2.3.2", + "@bsv/wallet-toolbox-client": "^2.3.2", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.8", @@ -357,31 +357,39 @@ } }, "node_modules/@bsv/amountinator": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@bsv/amountinator/-/amountinator-2.0.2.tgz", - "integrity": "sha512-zcCFvuw8I4eD4hNGXKmvjIO9YEWJ/1YrsTYskGyG10GMHCKfupJuJYn3reyy9lngIboWwC51I19j2YCcXjkTBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@bsv/amountinator/-/amountinator-2.1.0.tgz", + "integrity": "sha512-tG05O59BfTBmPx83DOVrQVVi4K7aLfDij8h4Fy02f9XQg8noEbM3CoXopQ0RxCjRYNG+Rffm3ewOcDNaLnUHrQ==", "license": "Open BSV License", "dependencies": { - "@bsv/sdk": "^2.1.2", - "@bsv/wallet-toolbox-client": "^2.1.27" + "@bsv/wallet-toolbox-client": "^2.1.30" + }, + "peerDependencies": { + "@bsv/sdk": "^2" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, "node_modules/@bsv/auth-express-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@bsv/auth-express-middleware/-/auth-express-middleware-2.0.6.tgz", - "integrity": "sha512-PXtb3hxcyyFG3N44Ab/Ey3yNBlMksLBbfrzLFn7vebwZPC+RhisQi/zL5/33PmUh0EsRkl4cxNAexzACE6DMIQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@bsv/auth-express-middleware/-/auth-express-middleware-2.1.0.tgz", + "integrity": "sha512-pdVCr8GBx+xs39KHcoiyIcyuBXTpIB+VKZNykIaVfZzhUyTIMEv1zQ+y8WhonCsvc9B9jcFAeYVI7kZ8IF4Gxw==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { - "@bsv/sdk": "2.1.3", - "express": "^5.1.0" + "express": "^5.2.1" + }, + "peerDependencies": { + "@bsv/sdk": "^2" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, - "node_modules/@bsv/auth-express-middleware/node_modules/@bsv/sdk": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-2.1.3.tgz", - "integrity": "sha512-nmni2Q762/TeWz6MfbHdWRSTwzPV8o5t34789/u8MA+kPNnoZfs/BQOw4c5FtlXYcu3Soxbhup3+02zdrP+drg==", - "license": "SEE LICENSE IN LICENSE.txt" - }, "node_modules/@bsv/auth-express-middleware/node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -396,21 +404,34 @@ } }, "node_modules/@bsv/auth-express-middleware/node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -706,33 +727,50 @@ } }, "node_modules/@bsv/authsocket-client": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@bsv/authsocket-client/-/authsocket-client-2.0.2.tgz", - "integrity": "sha512-da7ON4zqdShM9QFYxQzcuJoVfau1sm1dwrpsYtU3JowwDXMYYzcbvwvWzYBJS1380ijn1T50n1IAJlAaLqVNgg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@bsv/authsocket-client/-/authsocket-client-2.1.0.tgz", + "integrity": "sha512-dm2HkXhIgGUDlBKCd7gfSnCJqM4PC3a5QbchT0d3IBizD7GJT8jBcFsnhPQ31EoV0NBFRUgBdptJucS8w8weCQ==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { - "@bsv/sdk": "^2.0.4", "socket.io-client": "^4.8.1" + }, + "peerDependencies": { + "@bsv/sdk": "^2" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, "node_modules/@bsv/btms": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bsv/btms/-/btms-1.0.2.tgz", - "integrity": "sha512-65o0ZUp5feVC2qcGvzKohxZWPsHD4O2kKo6mXfyxj8EjT+Sqk2IQUYSu0QiUP/2dpGZJFDMr8CGmc34Fl07ZJA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@bsv/btms/-/btms-1.1.0.tgz", + "integrity": "sha512-eQBfAwg+0lLSmEirbrAMS6qAKxpkQAq0F5WjLnPPzKREe65Zn3VkXmjP165CqAlvtXPL0aE9GeGL2uHuSRyc6Q==", "license": "SEE LICENSE IN LICENSE.txt", "peerDependencies": { - "@bsv/sdk": "^2.1.2" + "@bsv/sdk": "^2" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, "node_modules/@bsv/btms-permission-module": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bsv/btms-permission-module/-/btms-permission-module-1.0.2.tgz", - "integrity": "sha512-ZDyrPABMitFCnMUZZ8In2w6+ERJFAaf+OLtzd6saifYw+e7nKIaPFUff7ffnDRoswZVfdbU9+A11d145U8SP9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@bsv/btms-permission-module/-/btms-permission-module-1.1.0.tgz", + "integrity": "sha512-WnGe6KCFfdebSbqlUi5ESMDMt+bhrOByVTM8HOB6Ax/jnC/2c8g2CE0wEaBJjyoiA33/3g2LR9/N5aaZlcpK4Q==", "license": "Open BSV", "peerDependencies": { - "@bsv/btms": "^1.0.1", - "@bsv/sdk": "^2.1.2", - "@bsv/wallet-toolbox-client": "^2.1.28" + "@bsv/btms": "^1.0.2", + "@bsv/sdk": "^2", + "@bsv/wallet-toolbox-client": "^2.1.30" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, "node_modules/@bsv/btms-permission-module-ui": { @@ -777,31 +815,39 @@ } }, "node_modules/@bsv/message-box-client": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-2.1.2.tgz", - "integrity": "sha512-UCau2/sML+grGED0og7mfxpMYNf31DYViUuS5Kh++XxyKlXOsC0mJWdxSr2nHDWndQuQHrKbcUDoRZjlkHkQyg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-2.2.0.tgz", + "integrity": "sha512-l/bJOI9p2wetnsYaGcRbtn53soUuQxyqtH0AliyXWuKUY+aXUArAVjCmxy1Fkv+FGsa/uws8N0w/LyI3B7uDsA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { - "@bsv/authsocket-client": "^2.0.2", - "@bsv/sdk": "^2.1.2" + "@bsv/authsocket-client": "^2.0.3" + }, + "peerDependencies": { + "@bsv/sdk": "^2" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, "node_modules/@bsv/payment-express-middleware": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@bsv/payment-express-middleware/-/payment-express-middleware-2.0.3.tgz", - "integrity": "sha512-ezhdp9veMg6GSBWMLRonDBTDp2iyguPO9d29LDEqYuPkZznNeTqHvBbTyYmuVwSWkrGNZ9t/tqeiAFb5xXKm4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@bsv/payment-express-middleware/-/payment-express-middleware-2.1.0.tgz", + "integrity": "sha512-tiYwQeT6nnVi0pas+SaxPwaOQH1230BbI1T61TQ0btj+/QykE5Jwc+Ae0X1VMh5uj0jiHyJrMkxQ6rbX0fQ3dg==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { - "@bsv/sdk": "2.1.3", - "express": "^5.1.0" + "express": "^5.2.1" + }, + "peerDependencies": { + "@bsv/sdk": "^2" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, - "node_modules/@bsv/payment-express-middleware/node_modules/@bsv/sdk": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-2.1.3.tgz", - "integrity": "sha512-nmni2Q762/TeWz6MfbHdWRSTwzPV8o5t34789/u8MA+kPNnoZfs/BQOw4c5FtlXYcu3Soxbhup3+02zdrP+drg==", - "license": "SEE LICENSE IN LICENSE.txt" - }, "node_modules/@bsv/payment-express-middleware/node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -816,21 +862,34 @@ } }, "node_modules/@bsv/payment-express-middleware/node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -1145,14 +1204,13 @@ } }, "node_modules/@bsv/wallet-toolbox": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox/-/wallet-toolbox-2.1.30.tgz", - "integrity": "sha512-uREWUHoQ8pxfK9VONI5lHlVApBdCsLARsJxPOalCHTBlrpFohAVvGXBE/D1XX8ZfFZD/nRfZiSVPP3v75BiT0w==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox/-/wallet-toolbox-2.3.2.tgz", + "integrity": "sha512-MZTENl5s59kubfsHcFB48Qdjz/WgqatBYANQgDYDwjTTb4VjnCIjFWNX1MAujJIGq1rj1hfHro8LWR/LmIckNw==", "license": "SEE LICENSE IN license.md", "dependencies": { - "@bsv/auth-express-middleware": "^2.0.6", - "@bsv/payment-express-middleware": "^2.0.3", - "@bsv/sdk": "^2.1.3", + "@bsv/auth-express-middleware": "^2.1.0", + "@bsv/payment-express-middleware": "^2.1.0", "better-sqlite3": "^12.10.1", "express": "^5.2.1", "hash-wasm": "^4.12.0", @@ -1160,17 +1218,32 @@ "knex": "^3.2.10", "mysql2": "^3.22.5", "ws": "^8.21.0" + }, + "peerDependencies": { + "@bsv/sdk": "^2.1.6" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, "node_modules/@bsv/wallet-toolbox-client": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-2.1.30.tgz", - "integrity": "sha512-PaFXZWHfX8brz0feooGDmNUCi54MzGO0ua010vt3FFLGDKrLeQCR/Tf/nkuBM70gDe/VdDXa+J6HSRPLHNmoIg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-2.3.2.tgz", + "integrity": "sha512-0WUXdQ0dLgYbsvnsUe/HAdfv8rUrJ9WyV2JYPoEGhQkDQNjoWe7nhwrvcm6dhizONfjIPL1YSMdk1eQVsdxXmw==", "license": "SEE LICENSE IN license.md", "dependencies": { - "@bsv/sdk": "^2.1.3", "hash-wasm": "^4.12.0", "idb": "^8.0.2" + }, + "peerDependencies": { + "@bsv/sdk": "^2.1.6" + }, + "peerDependenciesMeta": { + "@bsv/sdk": { + "optional": false + } } }, "node_modules/@bsv/wallet-toolbox/node_modules/accepts": { diff --git a/package.json b/package.json index d2f490b..27f0f35 100644 --- a/package.json +++ b/package.json @@ -23,16 +23,16 @@ "test:perf": "vitest run --config vitest.config.electron.ts" }, "dependencies": { - "@bsv/amountinator": "^2.0.2", - "@bsv/btms": "^1.0.2", - "@bsv/btms-permission-module": "^1.0.2", + "@bsv/amountinator": "^2.1.0", + "@bsv/btms": "^1.1.0", + "@bsv/btms-permission-module": "^1.1.0", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", - "@bsv/message-box-client": "^2.1.2", + "@bsv/message-box-client": "^2.2.0", "@bsv/sdk": "^2.1.6", "@bsv/uhrp-react": "^1.0.6", - "@bsv/wallet-toolbox": "^2.1.30", - "@bsv/wallet-toolbox-client": "^2.1.30", + "@bsv/wallet-toolbox": "^2.3.2", + "@bsv/wallet-toolbox-client": "^2.3.2", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.8", diff --git a/src/global.d.ts b/src/global.d.ts index 43b8f07..56c5bc8 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -13,10 +13,10 @@ export interface ElectronAPI { sendHttpResponse: (response: any) => void; removeHttpRequestListener: () => void; storage: { - isAvailable: (identityKey: string, chain: 'main' | 'test') => Promise; - makeAvailable: (identityKey: string, chain: 'main' | 'test') => Promise<{ success: boolean; settings?: any; error?: string }>; - callMethod: (identityKey: string, chain: 'main' | 'test', method: string, args: any[]) => Promise<{ success: boolean; result?: any; error?: string }>; - initializeServices: (identityKey: string, chain: 'main' | 'test') => Promise<{ success: boolean; error?: string }>; + isAvailable: (identityKey: string, chain: 'main' | 'test' | 'ttn') => Promise; + makeAvailable: (identityKey: string, chain: 'main' | 'test' | 'ttn') => Promise<{ success: boolean; settings?: any; error?: string }>; + callMethod: (identityKey: string, chain: 'main' | 'test' | 'ttn', method: string, args: any[]) => Promise<{ success: boolean; result?: any; error?: string }>; + initializeServices: (identityKey: string, chain: 'main' | 'test' | 'ttn') => Promise<{ success: boolean; error?: string }>; }; secrets: { getAll: () => Promise>; diff --git a/src/lib/StorageElectronIPC.ts b/src/lib/StorageElectronIPC.ts index e292a9c..4096383 100644 --- a/src/lib/StorageElectronIPC.ts +++ b/src/lib/StorageElectronIPC.ts @@ -25,11 +25,11 @@ import type { WalletStorageProvider, WalletServices } from '@bsv/wallet-toolbox- export class StorageElectronIPC implements WalletStorageProvider { private identityKey: string; - private chain: 'main' | 'test'; + private chain: 'main' | 'test' | 'ttn'; private services?: WalletServices; private settings?: any; - constructor(identityKey: string, chain: 'main' | 'test') { + constructor(identityKey: string, chain: 'main' | 'test' | 'ttn') { this.identityKey = identityKey; this.chain = chain; diff --git a/src/lib/WalletContext.tsx b/src/lib/WalletContext.tsx index 40b5a6e..c7c7144 100644 --- a/src/lib/WalletContext.tsx +++ b/src/lib/WalletContext.tsx @@ -102,7 +102,7 @@ export interface WABConfig { wabUrl: string; wabInfo: any; method: string; - network: 'main' | 'test'; + network: 'main' | 'test' | 'ttn'; storageUrl: string; messageBoxUrl: string; loginType?: LoginType; @@ -124,6 +124,9 @@ export interface WalletContextValue { settings: WalletSettings; updateSettings: (newSettings: WalletSettings) => Promise; network: 'mainnet' | 'testnet'; + /** Raw selected chain. Distinguishes TeraTestNet ('ttn') from plain testnet, + * which `network` collapses to 'testnet'. Use for picking service endpoints. */ + chain: 'main' | 'test' | 'ttn'; activeProfile: WalletProfile | null; setActiveProfile: (profile: WalletProfile | null) => void; logout: () => void; @@ -185,6 +188,7 @@ export const WalletContext = createContext({ settings: DEFAULT_SETTINGS, updateSettings: async () => {}, network: 'mainnet', + chain: 'main', activeProfile: null, setActiveProfile: () => {}, logout: () => {}, diff --git a/src/lib/components/FundingHandler.tsx b/src/lib/components/FundingHandler.tsx index f2d31e5..e6028ef 100644 --- a/src/lib/components/FundingHandler.tsx +++ b/src/lib/components/FundingHandler.tsx @@ -29,7 +29,7 @@ const TabPanel: React.FC = ({ children, value, index }) => { const FundingHandler: React.FC = () => { const { t } = useTranslation() - const { setWalletFunder, network } = useContext(WalletContext) + const { setWalletFunder, network, chain } = useContext(WalletContext) const [open, setOpen] = useState(false) const [identityKey, setIdentityKey] = useState('') const [paymentTX, setPaymentTX] = useState('') @@ -110,6 +110,7 @@ const FundingHandler: React.FC = () => { wallet={wallet} adminOriginator={adminOriginator} network={network === 'mainnet' ? 'mainnet' : 'testnet'} + chain={chain} onFundingComplete={handleFundingComplete} /> )} diff --git a/src/lib/components/WalletConfig.tsx b/src/lib/components/WalletConfig.tsx index 8c951ae..4601d37 100644 --- a/src/lib/components/WalletConfig.tsx +++ b/src/lib/components/WalletConfig.tsx @@ -49,7 +49,7 @@ const WalletConfig: React.FC = ({ autoExpand = false, hideLog faucetAmount: number; } | null>(null) const [method, setMethod] = useState("") - const [network, setNetwork] = useState<'main' | 'test'>(DEFAULT_CHAIN) + const [network, setNetwork] = useState<'main' | 'test' | 'ttn'>(DEFAULT_CHAIN) const [storageUrl, setStorageUrl] = useState('') const [loginType, setLoginType] = useState(contextLoginType) const [useRemoteStorage, setUseRemoteStorage] = useState(false) @@ -251,6 +251,14 @@ const WalletConfig: React.FC = ({ autoExpand = false, hideLog > {t('wallet_config_testnet')} + diff --git a/src/lib/components/WalletFundingFlow.tsx b/src/lib/components/WalletFundingFlow.tsx index 7bcc12d..49afe0b 100644 --- a/src/lib/components/WalletFundingFlow.tsx +++ b/src/lib/components/WalletFundingFlow.tsx @@ -27,6 +27,7 @@ import { import { toast } from 'react-toastify' import getBeefForTxid from '../utils/getBeefForTxid' import { wocFetch } from '../utils/RateLimitedFetch' +import { wocApiBase } from '../utils/woc' const brc29ProtocolID: WalletProtocol = [2, '3241645161d8'] @@ -54,6 +55,7 @@ interface WalletFundingFlowProps { wallet: WalletInterface adminOriginator: string network: 'mainnet' | 'testnet' + chain: 'main' | 'test' | 'ttn' onFundingComplete: () => void } @@ -61,6 +63,7 @@ const WalletFundingFlow: React.FC = ({ wallet, adminOriginator, network, + chain, onFundingComplete }) => { const { t } = useTranslation() @@ -104,7 +107,7 @@ const WalletFundingFlow: React.FC = ({ // Fetch UTXOs for the payment address const getUtxosForAddress = async (address: string): Promise => { const response = await wocFetch.fetch( - `https://api.whatsonchain.com/v1/bsv/${network === 'mainnet' ? 'main' : 'test'}/address/${address}/unspent/all` + `${wocApiBase(chain)}/address/${address}/unspent/all` ) const rp: WoCAddressUnspentAll = await response.json() const utxos: Utxo[] = rp.result @@ -132,7 +135,7 @@ const WalletFundingFlow: React.FC = ({ } finally { setIsCheckingPayment(false) } - }, [paymentAddress, network]) + }, [paymentAddress, network, chain]) // Process the payment and internalize const processPayment = useCallback(async () => { @@ -157,7 +160,7 @@ const WalletFundingFlow: React.FC = ({ // Merge BEEF for all inputs const beef = new Beef() for (const txid of txids) { - const b = await getBeefForTxid(txid, network === 'mainnet' ? 'main' : 'test') + const b = await getBeefForTxid(txid, chain) beef.mergeBeef(b) } @@ -258,7 +261,7 @@ const WalletFundingFlow: React.FC = ({ } finally { setIsProcessingPayment(false) } - }, [paymentAddress, balance, wallet, adminOriginator, network, derivationPrefix, derivationSuffix, onFundingComplete]) + }, [paymentAddress, balance, wallet, adminOriginator, network, chain, derivationPrefix, derivationSuffix, onFundingComplete]) // Auto-generate address on mount useEffect(() => { diff --git a/src/lib/hooks/useWalletService.ts b/src/lib/hooks/useWalletService.ts index 8e6fdee..60ae4ca 100644 --- a/src/lib/hooks/useWalletService.ts +++ b/src/lib/hooks/useWalletService.ts @@ -358,7 +358,9 @@ export function useWalletService() { // Settings settings: walletState.settings, updateSettings, - network: walletState.selectedNetwork === 'test' ? 'testnet' as const : 'mainnet' as const, + // 'ttn' (TeraTestNet) uses testnet-style addresses, so it collapses to 'testnet' here. + network: walletState.selectedNetwork === 'main' ? 'mainnet' as const : 'testnet' as const, + chain: walletState.selectedNetwork, // Profile activeProfile: walletState.activeProfile, setActiveProfile, diff --git a/src/lib/i18n/translations.ts b/src/lib/i18n/translations.ts index 2406e2c..2a64aa6 100644 --- a/src/lib/i18n/translations.ts +++ b/src/lib/i18n/translations.ts @@ -560,6 +560,7 @@ const en = { wallet_config_network_label: 'BSV Network:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'Wallet Login Type', wallet_config_login_type_description: 'Choose how you want to authenticate and manage your wallet keys.', wallet_config_login_wab: 'I prefer to use WAB', @@ -1486,6 +1487,7 @@ const es = { wallet_config_network_label: 'Red BSV:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'Tipo de Inicio de Sesión', wallet_config_login_type_description: 'Elige cómo deseas autenticarte y gestionar las claves de tu billetera.', wallet_config_login_wab: 'Prefiero usar WAB', @@ -2424,6 +2426,7 @@ const fr = { wallet_config_network_label: 'Réseau BSV :', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'Type de Connexion au Portefeuille', wallet_config_login_type_description: 'Choisissez comment vous souhaitez vous authentifier et gérer les clés de votre portefeuille.', wallet_config_login_wab: 'Je préfère utiliser WAB', @@ -3362,6 +3365,7 @@ const pt = { wallet_config_network_label: 'Rede BSV:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'Tipo de Login da Carteira', wallet_config_login_type_description: 'Escolha como deseja autenticar e gerenciar as chaves da sua carteira.', wallet_config_login_wab: 'Prefiro usar WAB', @@ -4300,6 +4304,7 @@ const zh = { wallet_config_network_label: 'BSV 网络:', wallet_config_mainnet: '主网', wallet_config_testnet: '测试网', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: '钱包登录类型', wallet_config_login_type_description: '选择您希望如何验证身份和管理钱包密钥。', wallet_config_login_wab: '我倾向于使用 WAB', @@ -5238,6 +5243,7 @@ const hi = { wallet_config_network_label: 'BSV नेटवर्क:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'वॉलेट लॉगिन प्रकार', wallet_config_login_type_description: 'चुनें कि आप कैसे प्रमाणीकृत होना चाहते हैं और अपनी वॉलेट चाबियाँ प्रबंधित करना चाहते हैं।', wallet_config_login_wab: 'मैं WAB का उपयोग करना पसंद करता/करती हूं', @@ -6176,6 +6182,7 @@ const bn = { wallet_config_network_label: 'BSV নেটওয়ার্ক:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'ওয়ালেট লগইন ধরন', wallet_config_login_type_description: 'আপনি কীভাবে প্রমাণীকরণ করতে এবং আপনার ওয়ালেট কী পরিচালনা করতে চান তা বেছে নিন।', wallet_config_login_wab: 'আমি WAB ব্যবহার করতে পছন্দ করি', @@ -7114,6 +7121,7 @@ const ar = { wallet_config_network_label: 'شبكة BSV:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'نوع تسجيل الدخول للمحفظة', wallet_config_login_type_description: 'اختر كيفية المصادقة وإدارة مفاتيح محفظتك.', wallet_config_login_wab: 'أفضّل استخدام WAB', @@ -8052,6 +8060,7 @@ const ru = { wallet_config_network_label: 'Сеть BSV:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'Тип входа в кошелёк', wallet_config_login_type_description: 'Выберите способ аутентификации и управления ключами кошелька.', wallet_config_login_wab: 'Предпочитаю использовать WAB', @@ -8990,6 +8999,7 @@ const id = { wallet_config_network_label: 'Jaringan BSV:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'Jenis Login Dompet', wallet_config_login_type_description: 'Pilih cara Anda ingin mengautentikasi dan mengelola kunci dompet.', wallet_config_login_wab: 'Saya lebih suka menggunakan WAB', @@ -9991,6 +10001,7 @@ const ja = { wallet_config_network_label: 'BSVネットワーク:', wallet_config_mainnet: 'メインネット', wallet_config_testnet: 'テストネット', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'ウォレットログインタイプ', wallet_config_login_type_description: '認証方法とウォレットキーの管理方法を選択してください。', wallet_config_login_wab: 'WABを使用する', @@ -10975,6 +10986,7 @@ const pl = { wallet_config_network_label: 'Sieć BSV:', wallet_config_mainnet: 'Mainnet', wallet_config_testnet: 'Testnet', + wallet_config_teratestnet: 'TeraTestNet', wallet_config_login_type_label: 'Typ logowania do portfela', wallet_config_login_type_description: 'Wybierz sposób uwierzytelniania i zarządzania kluczami portfela.', wallet_config_login_wab: 'Wolę używać WAB', diff --git a/src/lib/pages/Dashboard/Apps/index.tsx b/src/lib/pages/Dashboard/Apps/index.tsx index 75f4d1e..1426e23 100644 --- a/src/lib/pages/Dashboard/Apps/index.tsx +++ b/src/lib/pages/Dashboard/Apps/index.tsx @@ -16,6 +16,7 @@ import OpenInNewIcon from '@mui/icons-material/OpenInNew' import ContentCopyIcon from '@mui/icons-material/ContentCopy' import { toast } from 'react-toastify' import { WalletContext } from '../../../WalletContext' +import { wocExplorerBase } from '../../../utils/woc' import AmountDisplay from '../../../components/AmountDisplay' import type { WalletAction } from '@bsv/sdk' @@ -44,7 +45,7 @@ function getStatusChip(status: string, t: (key: string) => string) { const Transactions: React.FC = () => { const { t } = useTranslation() - const { managers, adminOriginator, network } = useContext(WalletContext) + const { managers, adminOriginator, chain } = useContext(WalletContext) const [actions, setActions] = useState([]) const [hasMore, setHasMore] = useState(false) @@ -95,9 +96,7 @@ const Transactions: React.FC = () => { } const handleExplorerLink = (txid: string) => { - const base = network === 'mainnet' - ? 'https://whatsonchain.com' - : 'https://test.whatsonchain.com' + const base = wocExplorerBase(chain) window.open(`${base}/tx/${txid}`, '_blank', 'noopener,noreferrer') } diff --git a/src/lib/pages/Dashboard/LegacyBridge/index.tsx b/src/lib/pages/Dashboard/LegacyBridge/index.tsx index d00b652..989e5d2 100644 --- a/src/lib/pages/Dashboard/LegacyBridge/index.tsx +++ b/src/lib/pages/Dashboard/LegacyBridge/index.tsx @@ -29,6 +29,7 @@ import { QRCodeSVG } from 'qrcode.react' import { PublicKey, P2PKH, Beef, Utils, Script, WalletProtocol, InternalizeActionArgs, InternalizeOutput, PrivateKey, AtomicBEEF } from '@bsv/sdk' import getBeefForTxid from '../../../utils/getBeefForTxid' import { wocFetch } from '../../../utils/RateLimitedFetch' +import { wocApiBase } from '../../../utils/woc' import { toast } from 'react-toastify' const brc29ProtocolID: WalletProtocol = [2, '3241645161d8'] @@ -85,7 +86,7 @@ const timeAgo = (ms: number): string => { export default function Payments() { const { t } = useTranslation() - const { managers, network, adminOriginator } = useContext(WalletContext) + const { managers, network, chain, adminOriginator } = useContext(WalletContext) const [paymentAddress, setPaymentAddress] = useState(null) const [balance, setBalance] = useState(-1) const [recipientAddress, setRecipientAddress] = useState('') @@ -128,7 +129,7 @@ export default function Payments() { // Fetch UTXOs for address from WhatsOnChain (rate-limited) const getUtxosForAddress = async (address: string): Promise => { - const url = `https://api.whatsonchain.com/v1/bsv/${network === 'mainnet' ? 'main' : 'test'}/address/${address}/unspent/all` + const url = `${wocApiBase(chain)}/address/${address}/unspent/all` const response = await wocFetch.fetch(url) const rp: WoCAddressUnspentAll = await response.json() if (!rp.result) return [] @@ -237,7 +238,7 @@ export default function Payments() { const beef = new Beef() for (const utxo of utxos) { if (!beef.findTxid(utxo.txid)) { - const b = await getBeefForTxid(utxo.txid, network === 'mainnet' ? 'main' : 'test') + const b = await getBeefForTxid(utxo.txid, chain) beef.mergeBeef(b) } } diff --git a/src/lib/pages/Greeter/index.tsx b/src/lib/pages/Greeter/index.tsx index e7b1f89..0f68310 100644 --- a/src/lib/pages/Greeter/index.tsx +++ b/src/lib/pages/Greeter/index.tsx @@ -500,7 +500,7 @@ const Greeter: React.FC = ({ history }) => { wabUrl: '', wabInfo: null, method: '', - network: DEFAULT_CHAIN as 'main' | 'test', + network: DEFAULT_CHAIN as 'main' | 'test' | 'ttn', storageUrl: '', messageBoxUrl: '', loginType: 'direct-key', @@ -517,7 +517,7 @@ const Greeter: React.FC = ({ history }) => { wabUrl: '', wabInfo: null, method: '', - network: DEFAULT_CHAIN as 'main' | 'test', + network: DEFAULT_CHAIN as 'main' | 'test' | 'ttn', storageUrl: '', messageBoxUrl: '', loginType: 'direct-key', diff --git a/src/lib/services/WalletService.ts b/src/lib/services/WalletService.ts index ab78cfe..efb4cbd 100644 --- a/src/lib/services/WalletService.ts +++ b/src/lib/services/WalletService.ts @@ -67,7 +67,7 @@ export type WalletServiceSnapshot = { wabUrl: string wabInfo: any selectedAuthMethod: string - selectedNetwork: 'main' | 'test' + selectedNetwork: 'main' | 'test' | 'ttn' selectedStorageUrl: string messageBoxUrl: string useRemoteStorage: boolean @@ -117,7 +117,7 @@ export class WalletService extends EventEmittable { private _wabUrl = '' private _wabInfo: any = null private _selectedAuthMethod = '' - private _selectedNetwork: 'main' | 'test' = DEFAULT_CHAIN + private _selectedNetwork: 'main' | 'test' | 'ttn' = DEFAULT_CHAIN private _selectedStorageUrl = '' private _messageBoxUrl = '' private _useRemoteStorage = false diff --git a/src/lib/utils/getBeefForTxid.ts b/src/lib/utils/getBeefForTxid.ts index 1e1ba07..e5a970b 100644 --- a/src/lib/utils/getBeefForTxid.ts +++ b/src/lib/utils/getBeefForTxid.ts @@ -1,8 +1,9 @@ import { Beef, Utils } from '@bsv/sdk' import { wocFetch } from './RateLimitedFetch' +import { wocApiBase } from './woc' -export default async function getBeefForTxid(txid: string, chain: 'main' | 'test'): Promise { - const baseUrl = `https://api.whatsonchain.com/v1/bsv/${chain}` +export default async function getBeefForTxid(txid: string, chain: 'main' | 'test' | 'ttn'): Promise { + const baseUrl = wocApiBase(chain) // Fetch BEEF from WhatsOnChain's BEEF endpoint const beefResponse = await wocFetch.fetch(`${baseUrl}/tx/${txid}/beef`) diff --git a/src/lib/utils/woc.ts b/src/lib/utils/woc.ts new file mode 100644 index 0000000..814d437 --- /dev/null +++ b/src/lib/utils/woc.ts @@ -0,0 +1,19 @@ +// WhatsOnChain endpoint helpers, keyed by BSV chain. +// +// TeraTestNet ('ttn') uses a dedicated WoC-compatible host and reuses the +// testnet path segment. mainnet/testnet use the public whatsonchain.com API. + +export type Chain = 'main' | 'test' | 'ttn' + +/** WhatsOnChain REST API base for the given chain (no trailing slash). */ +export function wocApiBase(chain: Chain): string { + return chain === 'ttn' + ? 'https://api.woc-ttn.bsvblockchain.tech/v1/bsv/test' + : `https://api.whatsonchain.com/v1/bsv/${chain}` +} + +/** WhatsOnChain explorer base for transaction links. */ +export function wocExplorerBase(chain: Chain): string { + // TeraTestNet has no public WoC explorer; fall back to the testnet explorer. + return chain === 'main' ? 'https://whatsonchain.com' : 'https://test.whatsonchain.com' +}