Răsfoiți Sursa

Add `electrum-cash` to handle cluster access (from server).

Shomari 1 lună în urmă
părinte
comite
0f425e747d

+ 4 - 80
web/components/Wallet/Welcome.vue

@@ -39,85 +39,9 @@ const balance = computed(() => {
 const cashout = async () => {
     // alert('WIP?? sorry...')
 
-    /* Initialize locals. */
-    let inputs
-    let keys
-    let outputs
-    let rawTx
-    let response
-    let session
-
-    // FIXME
-    const sessionid = '4e9654f9-3de9-4f9a-8169-3834f40847f5'
-
-    /* Request session details. */
-    session = await $fetch(`http://localhost:39159/v1/fusion/${sessionid}`)
-        .catch(err => console.error(err))
-    console.log('SESSION', session)
-
-    /* Set inputs. */
-    inputs = session.inputs
-    // console.log('INPUTS', inputs)
-
-    /* Initialize keys. */
-    keys = []
-
-    /* Handle (input) keys. */
-    Object.keys(inputs).forEach(_inputid => {
-        keys.push(_inputid)
-    })
-    // console.log('KEYS', keys)
-
-    /* Sort (input) keys. */
-    keys.sort()
-    // console.log('KEYS (sorted)', keys)
-
-    /* Initialize sorted inputs. */
-    const sortedInputs = []
-
-    /* Handle (input) keys. */
-    keys.forEach(_keyid => {
-        /* Add input. */
-        sortedInputs.push(inputs[_keyid])
-    })
-    // console.log('INPUTS (sorted)', sortedInputs)
-
-    outputs = session.outputs
-    // console.log('OUTPUTS', outputs)
-
-    /* Initialize (output) keys. */
-    keys = []
-
-    /* Handle keys. */
-    Object.keys(outputs).forEach(_outputid => {
-        keys.push(_outputid)
-    })
-    // console.log('KEYS', keys)
-
-    /* Sort (output) keys. */
-    keys.sort()
-    // console.log('KEYS (sorted)', keys)
-
-    /* Initialize sorted outputs. */
-    const sortedOutputs = []
-
-    /* Handle (output) keys. */
-    keys.forEach(_keyid => {
-        if (outputs[_keyid].value >= DUST_VAL) {
-            /* Add input. */
-            sortedOutputs.push(outputs[_keyid])
-        }
-    })
-    // console.log('OUTPUTS (sorted)', sortedOutputs)
-
-    /* Sign shared transaction. */
-    rawTx = signSharedTx(
-        sessionid, Wallet.mnemonic, sortedInputs, sortedOutputs)
-    console.log('RAW TX', rawTx)
-
-    response = await Wallet.broadcast('BCH', rawTx)
+    const response = Wallet.completeFusion()
         .catch(err => console.error(err))
-    console.log('BROADCAST (response)', response)
+    console.log('COMPLETE FUSION', response)
 }
 
 const consolidate = () => {
@@ -163,8 +87,8 @@ onMounted(() => {
             LP Wallets
         </h2>
 
-<!-- <pre class="text-xs">{{Wallet.fusionInputs}}</pre> -->
-<!-- <hr /> -->
+<pre class="text-xs">{{Wallet.fusionInputs}}</pre>
+<hr class="my-10" />
 <pre class="text-xs">{{Wallet.fusionAddrs}}</pre>
 
         <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">

+ 3 - 0
web/handlers/initFusions.ts

@@ -13,6 +13,7 @@ const init = async (_fusionsDb) => {
     fusionsDb['4e9654f9-3de9-4f9a-8169-3834f40847f5'] = {
         tierid: 888000,
         progress: 0.1,
+        buildHash: null,
         numGuests: 0,
         numInputs: 0,
         numOutputs: 0,
@@ -29,6 +30,7 @@ const init = async (_fusionsDb) => {
     fusionsDb['6f765750-2267-4601-87be-80a416143a28'] = {
         tierid: 820000,
         progress: 100.0,
+        buildHash: '9e3e7d52e195827b9061ceaa5b1a79d710ad4a8a',
         numGuests: 20,
         numInputs: 36,
         numOutputs: 68,
@@ -45,6 +47,7 @@ const init = async (_fusionsDb) => {
     fusionsDb['4eff6293-60e9-4a5a-83de-4b91da1f7de4'] = {
         tierid: 1800000,
         progress: 100.0,
+        buildHash: '9e3e7d52e195827b9061ceaa5b1a79d710ad4a8a',
         numGuests: 22,
         numInputs: 51,
         numOutputs: 181,

+ 3 - 1
web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "hushyourmoney",
-  "version": "24.8.21",
+  "version": "24.8.25",
   "license": "MIT",
   "description": "Spend Privately. Fearlessly!",
   "author": "Shomari <[email protected]>",
@@ -16,7 +16,9 @@
     "@nexajs/crypto": "24.8.16",
     "@pinia/nuxt": "0.4.7",
     "@psf/bch-js": "6.7.4",
+    "async-mutex": "0.5.0",
     "bitcoinjs-lib": "5.2.0",
+    "electrum-cash": "3.2.0",
     "moment": "2.30.1",
     "nexajs": "24.8.15",
     "numeral": "2.0.6",

+ 108 - 0
web/server/api/electrum.post.ts

@@ -0,0 +1,108 @@
+/* Import modules. */
+import { sha256 } from '@nexajs/crypto'
+import {
+    binToHex,
+    hexToBin,
+} from '@nexajs/utils'
+import BCHJS from '@psf/bch-js'
+import { ElectrumCluster } from 'electrum-cash'
+
+/* Initialize BCHJS. */
+const bchjs = new BCHJS()
+
+/* Initialize globals. */
+let electrum
+
+;(async () => {
+    // Initialize an electrum cluster where 2 out of 3 needs to be consistent,
+    // polled randomly with fail-over (default).
+    electrum = new ElectrumCluster('Electrum cluster example', '1.4.1', 2, 3)
+
+    // Add some servers to the cluster.
+    electrum.addServer('bch.imaginary.cash')
+    electrum.addServer('electroncash.de')
+    electrum.addServer('electroncash.dk')
+    electrum.addServer('electron.jochen-hoenicke.de', 51002)
+    electrum.addServer('electrum.imaginary.cash')
+
+    // Wait for enough connections to be available.
+    await electrum.ready()
+})()
+
+
+export default defineEventHandler(async (event) => {
+    /* Initialize locals. */
+    let address
+    let body
+    let hash160
+    let method
+    let params
+    let response
+    let script
+    let scriptHash
+
+    /* Set (request) body. */
+    body = await readBody(event)
+    // console.log('BODY', body)
+
+    if (!body || !body.method || !body.params) {
+        return `Request FAILED!`
+    }
+
+    /* Set method. */
+    method = body.method
+
+    /* Handle method. */
+    switch(method) {
+    case 'blockchain.transaction.get':
+        /* Handle parameters. */
+        if (typeof body.params == 'string') {
+            params = body.params
+        } else {
+            params = body.params[0]
+        }
+
+        break
+    case 'blockchain.scripthash.listunspent':
+        /* Handle parameters. */
+        if (typeof body.params == 'string') {
+            params = body.params
+        } else {
+            params = body.params[0]
+        }
+
+        /* Set address. */
+        address = params
+
+        /* Convert to script hash. */
+        hash160 = bchjs.Address.toHash160(params)
+        console.log('HASH160', hash160)
+
+        /* Set script. */
+        // FIXME Use `OP` and TypedArray.
+        script = `76a914${hash160}88ac`
+        // console.log('SCRIPT', script)
+
+        /* Conver to hex. */
+        script = hexToBin(script)
+
+        /* Generate script hash. */
+        scriptHash = sha256(script)
+
+        scriptHash = scriptHash.reverse()
+
+        /* Set params. */
+        params = binToHex(scriptHash)
+
+        break
+    default:
+        setResponseStatus(event, 401)
+        return 'Oops! Invalid request!'
+    }
+
+    // Request the full transaction hex for the transaction ID.
+    response = await electrum.request(body.method, params)
+    // console.log('RESPONSE', response)
+
+    return response
+})

+ 2 - 5
web/server/middleware/04-fusions.ts

@@ -58,8 +58,8 @@ const init = async () => {
 // txObj.ins[1].script = txObj2.ins[1].script
 // txObj.ins[2].script = txObj3.ins[2].script
 
-                    /* Add to database. */
-                    // fusionsDb.rawTx
+                    /* Save to database. */
+                    // fusionsDb[???].rawTx
                 }
             })
             console.log('NEW LAST UPDATE', lastUpdate)
@@ -74,7 +74,4 @@ export default defineEventHandler((event) => {
 
     /* Set fusions database. */
     fusionsDb = Db.fusions
-
-    /* Inject wallet into server context. */
-    event.context.Wallet = wallet
 })

+ 41 - 134
web/stores/wallet.ts

@@ -10,9 +10,11 @@ import { binToHex } from '@nexajs/utils'
 import { Wallet } from '@nexajs/wallet'
 
 import _broadcast from './wallet/broadcast.ts'
+import _completeFusion from './wallet/completeFusion.ts'
 import _setEntropy from './wallet/setEntropy.ts'
 import _setupKeychain from './wallet/setupKeychain.ts'
 import _setupHushKeychain from './wallet/setupHushKeychain.ts'
+import _startFusions from './wallet/startFusions.ts'
 
 /* Initialize constants. */
 const HUSH_PROTOCOL_ID = 0x48555348
@@ -26,7 +28,9 @@ const bchjs = new BCHJS({
 })
 
 /* Set constants. */
-const UPDATE_UTXOS_INTERVAL = 8000 // Allows for ~7 (REST) requests per minute.
+// const UPDATE_UTXOS_INTERVAL = 8000 // Allows for ~7 (REST) requests per minute.
+const UPDATE_UTXOS_INTERVAL = 15000 // Allows for ~8 (Double REST) requests per minute.
+
 
 /**
  * Wallet Store
@@ -142,6 +146,7 @@ export const useWalletStore = defineStore('wallet', {
             }
 
             const collection = _state._utxos
+            // console.log('COLLECTION', collection)
 
             const mainList = []
 
@@ -237,7 +242,7 @@ _setupHushKeychain.bind(this)()
             })
 
             // FIXME ADDED FOR BITCOIN CASH SUPPORT
-            setInterval(this.updateUtxos, UPDATE_UTXOS_INTERVAL)
+            // setInterval(this.updateUtxos, UPDATE_UTXOS_INTERVAL)
 
             /* Update ALL chains. */
             this.updateUtxos(true)
@@ -303,6 +308,7 @@ _setupHushKeychain.bind(this)()
             let coins
             let data
             let hushAddresses
+            let usedAddresses
             let utxos
             let walletAddresses
 
@@ -319,15 +325,37 @@ _setupHushKeychain.bind(this)()
                 const coin = coins[_coinid]
                 return coin.address
             })
-            // console.log('SAVED ADDRESSES', hushAddresses)
+            // console.log('HUSH ADDRESSES', hushAddresses)
 
+            /* Request UTXO data. */
             data = await bchjs.Electrumx.utxo(hushAddresses.slice(0, 20))
-            // console.log('HUSH DATA', data)
-            // const utxos = data.utxos
+                .catch(err => console.error(err))
+            console.log('HUSH UTXOS', data)
+
+            // FIXME Update the deltas ONLY!
+            // this._utxos[HUSH_PROTOCOL_ID] = data?.utxos
+
+            /* Request history data. */
+            // data = await bchjs.Electrumx.transactions(hushAddresses.slice(0, 5))
+            //     .catch(err => console.error(err))
+            // console.log('HUSH TX HISTORY', data)
+
+            // usedAddresses = data?.utxos.map(_utxo => {
+            //     if (_utxo.utxos.length > 0) {
+            //         return _utxo.address
+            //     }
+            // })
+            // console.log('USED ADDRESSES', usedAddresses)
 
-            // FIXME Update the delata ONLY!
-            this._utxos[HUSH_PROTOCOL_ID] = data?.utxos
+            // Object.keys(coins).forEach(_coinid => {
+            //     const coin = coins[_coinid]
 
+            //     if (usedAddresses.includes(coin.address)) {
+            //         coin.isUsed = true
+            //     }
+            // })
+
+            /* Validate chain handler flag. */
             if (_allChains) {
                 let bchAddress1
                 let bchAddress2
@@ -390,134 +418,13 @@ _setupHushKeychain.bind(this)()
         },
 
         async startFusions() {
-            console.log('Starting fusions...')
-
-            /* Initialize locals. */
-            let blindComponents
-            let components
-            let cipherTokens
-            let fusionInputs
-            let inputAmount
-            let publicKey
-            let rawTx
-            let inputs
-            let outputs
-            let response
-            let clubWallet
-            let tierScale
-
-            const feeOffset = 1034//10034
-            const maxOutputCount = 17
-
-            /* Calculate input amount. */
-            // inputAmount = this.fusionInputs.reduce(
-            //     (acc, utxo) => (utxo.value > 10000) ? acc + utxo.value : 0, 0
-            // )
-            // console.log('INPUT AMOUNT', inputAmount)
-
-            /* Clone fusion inputs. */
-            fusionInputs = [ ...this.fusionInputs ]
-
-            /* Add inputs to components. */
-            components = [ ...fusionInputs ]
-
-            const tierScales = [
-                10000,      12000,      15000,      18000,      22000,      27000,      33000,      39000,      47000,      56000,      68000,      82000,
-                100000,     120000,     150000,     180000,     220000,     270000,     330000,     390000,     470000,     560000,     680000,     820000,
-                1000000,    1200000,    1500000,    1800000,    2200000,    2700000,    3300000,    3900000,    4700000,    5600000,    6800000,    8200000,
-                10000000,   12000000,   15000000,   18000000,   22000000,   27000000,   33000000,   39000000,   47000000,   56000000,   68000000,   82000000,
-                100000000,  120000000,  150000000,  180000000,  220000000,  270000000,  330000000,  390000000,  470000000,  560000000,  680000000,  820000000,
-                1000000000, 1200000000, 1500000000, 1800000000, 2200000000, 2700000000, 3300000000, 3900000000, 4700000000, 5600000000, 6800000000, 8200000000,
-            ]
-
-            const bestTiers = {}
-
-            for (let i = 0; i < fusionInputs.length; i++) {
-                /* Set input amount. */
-                inputAmount = fusionInputs[i].value
-                console.log('INPUT AMOUNT', inputAmount)
-
-                /* Handle ALL tier scales. */
-                tierScales.forEach(_tierScale => {
-                    try {
-                        /* Request (random) outputs. */
-                        response = randomOutputsForTier(
-                            inputAmount,
-                            _tierScale,
-                            feeOffset,
-                            maxOutputCount,
-                        )
-
-                        /* Validate tier outputs. */
-                        if (response && response.length > 1) {
-                            console.log('TIER', _tierScale, 'INPUT #', i, response)
-
-                            /* Test for the best tiers. */
-                            if (typeof bestTiers[i] === 'undefined' || response.length > bestTiers[i]?.outputs.length) {
-                                const numOutputs = response.length
-                                // console.log('NUM OUTPUTS', numOutputs)
-
-                                // const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: numOutputs })
-                                const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 })
-                                // console.log('FEE', fee)
-
-                                const outputs = response.map(_outputValue => {
-                                    return {
-                                        address: this.getFusionAddress(),
-                                        // value: (_outputValue - Math.ceil(fee / numOutputs)),
-                                        value: (_outputValue - fee),
-                                    }
-                                })
-
-                                bestTiers[i] = {
-                                    tierid: _tierScale,
-                                    outputs,
-                                }
-                            }
-                        }
-                    } catch (err) {
-                        // console.error(err)
-                    }
-                })
-            }
-            console.log('BEST TIERS', bestTiers)
-// return
-
-            /* Add best tiers to components. */
-            Object.keys(bestTiers).forEach(_tierid => {
-                const tier = bestTiers[_tierid]
-
-                /* Add (output) tier. */
-                components.push(tier)
-            })
-
-            /* Prepare components for encryption. */
-            components = JSON.stringify(components)
-            // console.log('FUSION (components)', components)
-
-            // TODO Handle any filtering required BEFORE submitting for fusion.
+            /* Start fusions. */
+            return _startFusions.bind(this)()
+        },
 
-            clubWallet = await $fetch('/api/wallet')
-                .catch(err => console.error(err))
-            // console.log('CLUB WALLET', clubWallet)
-
-            // FIXME Retrieve public key from a "public" endpoint.
-            publicKey = clubWallet.publicKey
-            // console.log('CLUB PUBLIC KEY', publicKey)
-
-            /* Generate blind components. */
-            blindComponents = encryptForPubkey(publicKey, components)
-            // console.log('BLINDED COMPONENTS', blindComponents)
-
-            response = await $fetch('/v1', {
-                method: 'POST',
-                body: {
-                    authid: binToHex(this.wallet.publicKey),
-                    components: blindComponents,
-                },
-            })
-            .catch(err => console.error(err))
-            console.log('RESPONSE', response)
+        async completeFusion() {
+            /* Start fusions. */
+            return _completeFusion.bind(this)()
         },
 
         async transfer(_receiver, _satoshis) {

+ 82 - 0
web/stores/wallet/completeFusion.ts

@@ -0,0 +1,82 @@
+
+export default async function () {
+    /* Initialize locals. */
+    let inputs
+    let keys
+    let outputs
+    let rawTx
+    let response
+    let session
+
+    // FIXME
+    const sessionid = '4e9654f9-3de9-4f9a-8169-3834f40847f5'
+
+    /* Request session details. */
+    session = await $fetch(`http://localhost:39159/v1/fusion/${sessionid}`)
+        .catch(err => console.error(err))
+    console.log('SESSION', session)
+
+    /* Set inputs. */
+    inputs = session.inputs
+    // console.log('INPUTS', inputs)
+
+    /* Initialize keys. */
+    keys = []
+
+    /* Handle (input) keys. */
+    Object.keys(inputs).forEach(_inputid => {
+        keys.push(_inputid)
+    })
+    // console.log('KEYS', keys)
+
+    /* Sort (input) keys. */
+    keys.sort()
+    // console.log('KEYS (sorted)', keys)
+
+    /* Initialize sorted inputs. */
+    const sortedInputs = []
+
+    /* Handle (input) keys. */
+    keys.forEach(_keyid => {
+        /* Add input. */
+        sortedInputs.push(inputs[_keyid])
+    })
+    // console.log('INPUTS (sorted)', sortedInputs)
+
+    outputs = session.outputs
+    // console.log('OUTPUTS', outputs)
+
+    /* Initialize (output) keys. */
+    keys = []
+
+    /* Handle keys. */
+    Object.keys(outputs).forEach(_outputid => {
+        keys.push(_outputid)
+    })
+    // console.log('KEYS', keys)
+
+    /* Sort (output) keys. */
+    keys.sort()
+    // console.log('KEYS (sorted)', keys)
+
+    /* Initialize sorted outputs. */
+    const sortedOutputs = []
+
+    /* Handle (output) keys. */
+    keys.forEach(_keyid => {
+        if (outputs[_keyid].value >= DUST_VAL) {
+            /* Add input. */
+            sortedOutputs.push(outputs[_keyid])
+        }
+    })
+    // console.log('OUTPUTS (sorted)', sortedOutputs)
+
+    /* Sign shared transaction. */
+    rawTx = signSharedTx(
+        sessionid, Wallet.mnemonic, sortedInputs, sortedOutputs)
+    console.log('RAW TX', rawTx)
+
+    response = await Wallet.broadcast('BCH', rawTx)
+        .catch(err => console.error(err))
+    console.log('BROADCAST (response)', response)
+}

+ 131 - 0
web/stores/wallet/startFusions.ts

@@ -0,0 +1,131 @@
+
+export default async function () {
+    console.log('Starting fusions...')
+
+    /* Initialize locals. */
+    let blindComponents
+    let components
+    let cipherTokens
+    let fusionInputs
+    let inputAmount
+    let publicKey
+    let rawTx
+    let inputs
+    let outputs
+    let response
+    let clubWallet
+    let tierScale
+
+    const feeOffset = 1034//10034
+    const maxOutputCount = 17
+
+    /* Calculate input amount. */
+    // inputAmount = this.fusionInputs.reduce(
+    //     (acc, utxo) => (utxo.value > 10000) ? acc + utxo.value : 0, 0
+    // )
+    // console.log('INPUT AMOUNT', inputAmount)
+
+    /* Clone fusion inputs. */
+    fusionInputs = [ ...this.fusionInputs ]
+
+    /* Add inputs to components. */
+    components = [ ...fusionInputs ]
+
+    const tierScales = [
+        10000,      12000,      15000,      18000,      22000,      27000,      33000,      39000,      47000,      56000,      68000,      82000,
+        100000,     120000,     150000,     180000,     220000,     270000,     330000,     390000,     470000,     560000,     680000,     820000,
+        1000000,    1200000,    1500000,    1800000,    2200000,    2700000,    3300000,    3900000,    4700000,    5600000,    6800000,    8200000,
+        10000000,   12000000,   15000000,   18000000,   22000000,   27000000,   33000000,   39000000,   47000000,   56000000,   68000000,   82000000,
+        100000000,  120000000,  150000000,  180000000,  220000000,  270000000,  330000000,  390000000,  470000000,  560000000,  680000000,  820000000,
+        1000000000, 1200000000, 1500000000, 1800000000, 2200000000, 2700000000, 3300000000, 3900000000, 4700000000, 5600000000, 6800000000, 8200000000,
+    ]
+
+    const bestTiers = {}
+
+    for (let i = 0; i < fusionInputs.length; i++) {
+        /* Set input amount. */
+        inputAmount = fusionInputs[i].value
+        console.log('INPUT AMOUNT', inputAmount)
+
+        /* Handle ALL tier scales. */
+        tierScales.forEach(_tierScale => {
+            try {
+                /* Request (random) outputs. */
+                response = randomOutputsForTier(
+                    inputAmount,
+                    _tierScale,
+                    feeOffset,
+                    maxOutputCount,
+                )
+
+                /* Validate tier outputs. */
+                if (response && response.length > 1) {
+                    console.log('TIER', _tierScale, 'INPUT #', i, response)
+
+                    /* Test for the best tiers. */
+                    if (typeof bestTiers[i] === 'undefined' || response.length > bestTiers[i]?.outputs.length) {
+                        const numOutputs = response.length
+                        // console.log('NUM OUTPUTS', numOutputs)
+
+                        // const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: numOutputs })
+                        const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 })
+                        // console.log('FEE', fee)
+
+                        const outputs = response.map(_outputValue => {
+                            return {
+                                address: this.getFusionAddress(),
+                                // value: (_outputValue - Math.ceil(fee / numOutputs)),
+                                value: (_outputValue - fee),
+                            }
+                        })
+
+                        bestTiers[i] = {
+                            tierid: _tierScale,
+                            outputs,
+                        }
+                    }
+                }
+            } catch (err) {
+                // console.error(err)
+            }
+        })
+    }
+    console.log('BEST TIERS', bestTiers)
+// return
+
+    /* Add best tiers to components. */
+    Object.keys(bestTiers).forEach(_tierid => {
+        const tier = bestTiers[_tierid]
+
+        /* Add (output) tier. */
+        components.push(tier)
+    })
+
+    /* Prepare components for encryption. */
+    components = JSON.stringify(components)
+    // console.log('FUSION (components)', components)
+
+    // TODO Handle any filtering required BEFORE submitting for fusion.
+
+    clubWallet = await $fetch('/api/wallet')
+        .catch(err => console.error(err))
+    // console.log('CLUB WALLET', clubWallet)
+
+    // FIXME Retrieve public key from a "public" endpoint.
+    publicKey = clubWallet.publicKey
+    // console.log('CLUB PUBLIC KEY', publicKey)
+
+    /* Generate blind components. */
+    blindComponents = encryptForPubkey(publicKey, components)
+    // console.log('BLINDED COMPONENTS', blindComponents)
+
+    response = await $fetch('/v1', {
+        method: 'POST',
+        body: {
+            authid: binToHex(this.wallet.publicKey),
+            components: blindComponents,
+        },
+    })
+    .catch(err => console.error(err))
+    console.log('RESPONSE', response)
+}

+ 49 - 1
web/yarn.lock

@@ -1148,6 +1148,11 @@
   dependencies:
     lines-and-columns "^1.1.6"
 
+"@monsterbitar/isomorphic-ws@^5.3.0":
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/@monsterbitar/isomorphic-ws/-/isomorphic-ws-5.3.1.tgz#acd6be86c7568682d8146f8b5fadbec74bf8789e"
+  integrity sha512-BWfWUffbg3uO4K6Cyokg9ff43lPaXAOZcCnNe1lcjCjUMDVrRAb5qEHG5qeJp3ud2SPYbORaNsls5as6SR3oig==
+
 "@netlify/functions@^2.8.0":
   version "2.8.1"
   resolved "https://registry.yarnpkg.com/@netlify/functions/-/functions-2.8.1.tgz#67cd94f929551e156225fb50d2efba603b97e138"
@@ -2050,6 +2055,13 @@
   resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
   integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
 
+"@types/ws@^8.5.5":
+  version "8.5.12"
+  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
+  integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==
+  dependencies:
+    "@types/node" "*"
+
 "@unhead/[email protected]", "@unhead/dom@^1.9.16":
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.10.0.tgz#85d0f157702d2e0a57b2adbe5fd768018f12acbe"
@@ -2532,6 +2544,20 @@ ast-walker-scope@^0.6.2:
     "@babel/parser" "^7.25.3"
     ast-kit "^1.0.1"
 
+async-mutex@^0.4.0:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.1.tgz#bccf55b96f2baf8df90ed798cb5544a1f6ee4c2c"
+  integrity sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==
+  dependencies:
+    tslib "^2.4.0"
+
+async-mutex@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482"
+  integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==
+  dependencies:
+    tslib "^2.4.0"
+
 async-sema@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/async-sema/-/async-sema-3.1.1.tgz#e527c08758a0f8f6f9f15f799a173ff3c40ea808"
@@ -3991,6 +4017,18 @@ electron-to-chromium@^1.5.4:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6"
   integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==
 
+electrum-cash@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/electrum-cash/-/electrum-cash-3.2.0.tgz#8c0b56f4df29e7594e07c4ffc4106340f465e7a7"
+  integrity sha512-vT7oV9A1O/Chj5w4BBQg3aEBvcE6mHumiBUuOdaTB4WfqD5LPjJ9iwQ4ueJmKyf/cekcWDIqSAxrM2pMgVLXMA==
+  dependencies:
+    "@monsterbitar/isomorphic-ws" "^5.3.0"
+    "@types/ws" "^8.5.5"
+    async-mutex "^0.4.0"
+    debug "^4.3.2"
+    lossless-json "^2.0.11"
+    ws "^8.13.0"
+
 [email protected]:
   version "6.5.4"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@@ -5730,6 +5768,11 @@ [email protected], lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
+lossless-json@^2.0.11:
+  version "2.0.11"
+  resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-2.0.11.tgz#3137684c93fd99481c6f99c985efc9c9c5cc76a5"
+  integrity sha512-BP0vn+NGYvzDielvBZaFain/wgeJ1hTvURCqtKvhr1SCPePdaaTanmmcplrHfEJSJOUql7hk4FHwToNJjWRY3g==
+
 lru-cache@^10.2.0:
   version "10.4.3"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
@@ -8277,6 +8320,11 @@ ts-interface-checker@^0.1.9:
   resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
   integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
 
+tslib@^2.4.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
+  integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
+
 [email protected]:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
@@ -8944,7 +8992,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
   integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
 
-ws@^8.18.0:
+ws@^8.13.0, ws@^8.18.0:
   version "8.18.0"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
   integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==