25 Commits fb897203e1 ... 5d9f3d3cf0

Auteur SHA1 Bericht Datum
  Shomari 5d9f3d3cf0 Fix fee (bug?) 1 maand geleden
  Shomari f5a43487fe Migrate to "hosted" ElectrumX. 1 maand geleden
  Shomari 86ccd7bebe Add address to results. 1 maand geleden
  Shomari 4ed71835fa Enable multi request queries. 1 maand geleden
  Shomari 0f425e747d Add `electrum-cash` to handle cluster access (from server). 1 maand geleden
  Shomari 0217bd5371 Update middleware. 1 maand geleden
  Shomari f998ac1576 Sign shared tx + broadcast. 1 maand geleden
  Shomari 56099eb631 Process components. 1 maand geleden
  Shomari 5374413754 Add all inputs + outputs to components. 1 maand geleden
  Shomari e3933e279e Update readme. 1 maand geleden
  Shomari be6106528d Add tier endpoint. 1 maand geleden
  Shomari a6fa1cef4b Fix UTXO handling. 1 maand geleden
  Shomari b2939b4a86 Refactor fusion handlers. 1 maand geleden
  Shomari 26f85199b5 Setup keychains. 1 maand geleden
  Shomari 0398d9c699 Enable keychain on liquidity page. 1 maand geleden
  Shomari 5fd6c65375 Generate tiers. 1 maand geleden
  Shomari 7aeee2d0e4 Generate Hush addresses. 1 maand geleden
  Shomari 0aed3483c2 Update help. 1 maand geleden
  Shomari 3344c10b14 Remove JWT auth from client requests (USE ELECTRUMX). 1 maand geleden
  Shomari 506c4ce21e Enable server broadcasts. 1 maand geleden
  Shomari b92c620c6b Refactor (cleanup). 1 maand geleden
  Shomari 9b8bb581f1 Enable Fusions daemon. 1 maand geleden
  Shomari 9c5e610a0d Sign transactions. 1 maand geleden
  Shomari 1f5b1242e2 Encrypt coins. 1 maand geleden
  Shomari 816cba8639 Enable ECDH client/server comms. 1 maand geleden

+ 14 - 3
web/components/Readme/CoinJoins.vue

@@ -29,19 +29,30 @@ const props = defineProps({
         </p>
 
         <p>
-            Not every UTXO network can/will offer
+            Not every UTXO-based network can/will offer
             <NuxtLink to="https://nexa.wiki/supernet" target="_blank" class="text-blue-500 font-bold hover:underline">
                 Supernet
 
                 <svg class="inline h-5 w-auto" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
                     <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"></path>
                 </svg>
-            </NuxtLink> capabilities.
-            As a fallback to Incognito Network (Nito), CashFusion offers a Pretty Good Privacy solution for its community of users.
+            </NuxtLink>
+            capabilities; so as a fallback to Incognito Network (Nito), CashFusion has been employed as a Pretty Good Privacy solution for each network's community of users.
         </p>
 
         <p>
             Access to the protocol is permissionless, which makes it an ideal solution.
         </p>
+
+        <section>
+            <h3 class="text-xl text-red-500 font-bold uppercase">
+                Please Note
+            </h3>
+
+            <p>
+                As an alternative to TOR's anonymity network, Hush uses a <span class="font-bold">Peer-to-peer Anonymity Relay Network (PARN)</span> made up of the Guests themselves.
+                The PARN offers an assurance to each Guest that the Club Server (on its own) CANNOT compromise their financial privacy.
+            </p>
+        </section>
     </main>
 </template>

+ 117 - 116
web/components/Wallet/Welcome.vue

@@ -1,7 +1,6 @@
 <script setup lang="ts">
 /* Import modules. */
-import BCHJS from '@psf/bch-js'
-import { binToHex } from '@nexajs/utils'
+import numeral from 'numeral'
 
 /* Define properties. */
 // https://vuejs.org/guide/components/props.html#props-declaration
@@ -15,105 +14,64 @@ const props = defineProps({
 import { useWalletStore } from '@/stores/wallet'
 const Wallet = useWalletStore()
 
-// REST API servers.
-const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
+const bchAddresses = ref(null)
+const btcAddresses = ref(null)
+const hushAddresses = ref(null)
+const nexaAddresses = ref(null)
 
-const runtimeConfig = useRuntimeConfig()
-const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
+const HUSH_PROTOCOL_ID = 0x48555348
 
-const balances = ref(null)
-const cashAddress = ref(null)
 
-// Instantiate bch-js based on the network.
-const bchjs = new BCHJS({
-    restURL: BCHN_MAINNET,
-    apiToken: jwtAuthToken,
-})
-// console.log('bchjs', bchjs)
+const balance = computed(() => {
+    if (!Wallet.fusionInputs) {
+        return 0
+    }
 
+    const totalValue = Wallet.fusionInputs.reduce(
+        (acc, utxo) => acc + utxo.value, 0
+    )
+    // console.log('TOTAL VALUE', totalValue)
 
-const calculateFee = () => {
-  // Calculate miner fees.
-  // Get byte count (x inputs, pay + remainder = 2x outputs)
-  return bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 2 })
-}
+    return numeral(totalValue).format('0,0')
+})
 
-// Combine all accounts inputs and outputs in one unsigned Tx
-const buildUnsignedTx = async () => {
-    try {
-        // console.log(`inputs: ${JSON.stringify(combinedInputs, null, 2)}`)
-
-        const fee = await calculateFee()
-        const paymentAmount = props.balances.confirmed - fee
-        const satsNeeded = fee + paymentAmount
-        const receiver = 'bitcoincash:qq27zfgmy7hckrrxygjdz6rr847pjkzzyc6d0un4e4'
-        console.log(`payment (+fee): ${satsNeeded}`)
-
-        const transactionBuilder = new bchjs.TransactionBuilder()
-
-        props.utxos.forEach(async function (_utxo) {
-            const utxo = _utxo
-            console.log('UTXO', utxo);
-            transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
-            const originalAmount = utxo.value
-            const remainder = originalAmount - satsNeeded
-            console.log('REMAINDER', remainder);
-
-
-            if (remainder < 0) {
-                throw new Error('Selected UTXO does not have enough satoshis')
-            }
-
-            // Send payment
-            transactionBuilder.addOutput(receiver, satsNeeded)
-
-            // Send the BCH change back to the payment part
-            // transactionBuilder.addOutput(account.address, remainder - 300)
-        })
-
-        const tx = transactionBuilder.transaction.buildIncomplete()
-        const hex = tx.toHex()
-        console.log(`Non-signed Tx hex: ${hex}`)
-        // fs.writeFileSync('unsigned_tx.json', JSON.stringify(hex, null, 2))
-        // console.log('unsigned_tx.json written successfully.')
-    } catch (err) {
-        console.error(`Error in buildUnsignedTx(): ${err}`)
-        throw err
-    }
-}
+const cashout = async () => {
+    // alert('WIP?? sorry...')
 
+    const response = await Wallet.completeFusion()
+        .catch(err => console.error(err))
+    console.log('COMPLETE FUSION', response)
+}
 
-const cashout = () => {
+const consolidate = () => {
     alert('WIP?? sorry...')
 }
 
-const start = async () => {
-    console.log('Starting...')
-
-    console.log('FEE', calculateFee())
+const startFusion = () => {
+    Wallet.startFusion()
+}
 
-    buildUnsignedTx()
+const init = () => {
+    /* Initialize locals. */
+    let address
 
-    const readyToFuse = props.utxos
-    console.log('READY TO FUSE', readyToFuse)
+    /* Initialize #? Hush addresses. */
+    hushAddresses.value = []
 
-    // TODO Handle any filtering required BEFORE submitting for fusion.
+    for (let i = 0; i < 20; i++) {
+        // HUSH == 0x48555348 == 1,213,551,432
+        // address = await Wallet.getBchAddress(HUSH_PROTOCOL_ID, 0, i)
+        address = Wallet.keychain[HUSH_PROTOCOL_ID][i].address
+        // console.log('GET BCH ADDRESS', i, address)
+        hushAddresses.value.push(address)
+    }
 
-    const response = await $fetch('/v1', {
-        method: 'POST',
-        body: {
-            authid: binToHex(Wallet.wallet.publicKey),
-            coins: readyToFuse,
-            tokens: [],
-        },
-    })
-    .catch(err => console.error(err))
-    console.log('RESPONSE', response)
 }
 
 onMounted(() => {
-    // console.log('Mounted!', props.balances)
-    // Now it's safe to perform setup operations.
+    // console.log('KEYCHAIN', Wallet.keychain)
+    // init()
+    setTimeout(init, 1000)
 })
 
 // onBeforeUnmount(() => {
@@ -124,44 +82,87 @@ onMounted(() => {
 
 <template>
     <main>
-        <h2 class="text-base font-semibold leading-6 text-gray-900">
-            Wallets
+        <h2 class="text-6xl font-light text-gray-600">
+            LP Wallets
         </h2>
 
-        <p class="mt-1 text-sm text-gray-500">
-            You haven’t created a Wallet yet.
-            Get started by selecting a template or start from an empty project.
-        </p>
-
-        <section>
-            <h2>
-                Cash Address
-            </h2>
-
-            <h3>
-                {{cashAddress}}
-            </h3>
-
-            <h3>
-                Confirmed: {{props.balances?.confirmed}}
-            </h3>
-            <h3>
-                Unconfirmed: {{props.balances?.unconfirmed}}
-            </h3>
-
-            <div class="my-3 grid grid-cols-2 gap-4">
-                <button @click="cashout" class="px-3 py-2 bg-lime-200 border-2 border-lime-400 text-2xl text-lime-800 font-medium rounded shadow hover:bg-lime-100">
-                    Cashout Wallet
-                </button>
-
-                <button @click="start" class="px-3 py-2 bg-lime-200 border-2 border-lime-400 text-2xl text-lime-800 font-medium rounded shadow hover:bg-lime-100">
-                    Start Fusions
-                </button>
+<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">
+            <div class="w-full lg:w-fit">
+                <section class="my-3 px-3 py-2 grid grid-cols-2 items-center gap-1 bg-green-300 border-2 border-green-500 rounded-xl shadow">
+                    <h2 class="col-span-2 text-green-900 text-base font-medium uppercase tracking-widest">
+                        Bitcoin Cash Address
+                    </h2>
+
+                    <NuxtLink :to="'https://3xpl.com/bitcoin-cash/address/' + props.cashAddress.slice(12)" target="_blank" class="col-span-2 text-base sm:text-xl text-blue-500 truncate hover:underline">
+                        {{props.cashAddress.slice(12)}}
+                    </NuxtLink>
+
+                    <h3 class="text-green-900 text-sm font-medium text-right uppercase">
+                        Current Balance
+                    </h3>
+
+                    <h3 class="text-green-900 text-xl font-medium">
+                        {{balance}}
+                        <small class="text-sm">sats</small>
+                    </h3>
+                </section>
+
+                <div v-for="(address, index) of hushAddresses" :key="address" class="text-xs">
+                    <div class="grid grid-cols-4 items-center gap-3">
+                        <span class="text-right">Hush #{{(index + 1)}}</span>
+                        <NuxtLink :to="'https://3xpl.com/bitcoin-cash/address/' + address.slice(12)" target="_blank" class="col-span-3 text-blue-500 hover:underline">{{address}}</NuxtLink>
+                    </div>
+                </div>
+            </div>
+
+            <div class="w-full lg:w-fit">
+                <section class="my-3 px-3 py-2 grid grid-cols-2 items-center gap-1 bg-yellow-200 border-2 border-yellow-400 rounded-xl shadow">
+                    <h2 class="col-span-2 text-yellow-900 text-base font-medium uppercase tracking-widest">
+                        Nexa Address
+                    </h2>
+
+                    <NuxtLink :to="'https://explorer.nexa.org/address/' + Wallet.address" target="_blank" class="col-span-2 text-base sm:text-xl text-blue-500 truncate hover:underline">
+                        {{Wallet.address.slice(5)}}
+                    </NuxtLink>
+
+                    <h3 class="text-yellow-900 text-sm font-medium text-right uppercase">
+                        Current Balance
+                    </h3>
+
+                    <h3 class="text-yellow-900 text-xl font-medium">
+                        n/a
+                    </h3>
+                </section>
+
+                <!-- <div v-for="(address, index) of hushAddresses" :key="address" class="text-xs">
+                    <div class="grid grid-cols-4 items-center gap-3">
+                        <span class="text-right">Hush #{{(index + 1)}}</span>
+                        <NuxtLink :to="'https://3xpl.com/bitcoin-cash/address/' + address.slice(12)" target="_blank" class="col-span-3 text-blue-500 hover:underline">{{address}}</NuxtLink>
+                    </div>
+                </div> -->
             </div>
+        </div>
+
+        <hr />
+
+        <div class="my-3 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
+            <button @click="startFusion" class="px-3 py-2 bg-lime-200 border-2 border-lime-400 text-2xl text-lime-800 font-medium rounded-lg shadow hover:bg-lime-100">
+                Start Fusions
+            </button>
 
-            <pre class="text-xs">{{props.utxos}}</pre>
+            <button @click="consolidate" class="px-3 py-2 bg-lime-200 border-2 border-lime-400 text-2xl text-lime-800 font-medium rounded-lg shadow hover:bg-lime-100">
+                Consolidate UTXOs
+            </button>
 
-        </section>
+            <button @click="cashout" class="px-3 py-2 bg-lime-200 border-2 border-lime-400 text-2xl text-lime-800 font-medium rounded-lg shadow hover:bg-lime-100">
+                Cashout Wallet
+            </button>
+
+        </div>
 
         <ul role="list" class="mt-6 grid grid-cols-1 gap-6 border-b border-t border-gray-200 py-6 sm:grid-cols-2">
             <li class="flow-root">

+ 121 - 0
web/handlers/buildSharedTx.ts

@@ -0,0 +1,121 @@
+/* Import modules. */
+import BCHJS from '@psf/bch-js'
+import { Transaction } from 'bitcoinjs-lib'
+import { mnemonicToSeed } from '@nexajs/hdnode'
+import { encodeNullData } from '@nexajs/script'
+import { utf8ToBin } from '@nexajs/utils'
+
+const bchjs = new BCHJS()
+
+/**
+ * Build Shared Transaction
+ *
+ * Combine all participating inputs and outputs into one (signed) transaction.
+ *
+ * NOTE: ALL inputs are ALREADY signed by their respective participants.
+ */
+export default function (_inputs, _outputs) {
+    /* Initialize locals. */
+    let rawTx
+    let safeBalance
+    let utxo
+
+    const transactionBuilder = new bchjs.TransactionBuilder()
+let inputIdx
+    this.fusionInputs.forEach((_utxo) => {
+        /* Set UTXO. */
+        utxo = _utxo
+        console.log('UTXO', utxo)
+inputIdx = 0//utxo.tx_pos
+        transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
+
+        const originalAmount = utxo.value
+
+        const remainder = originalAmount - satsNeeded
+        console.log('REMAINDER', remainder);
+
+        if (remainder < 0) {
+            throw new Error('Selected UTXO does not have enough satoshis')
+        }
+
+
+        const protocolId = '1337'
+        const msg = 'building...'
+
+        const script = [
+            utf8ToBin(protocolId),
+            utf8ToBin(msg),
+        ]
+        console.log('my SCRIPT', script)
+        console.log('encodeNullData', encodeNullData(script))
+
+        // Compile the script array into a bitcoin-compliant hex encoded string.
+        // const data = bchjs.Script.encode(script)
+        const data = Buffer.from(encodeNullData(script))
+        console.log('OP_RETURN (data)', data)
+
+        // Add the OP_RETURN output.
+        // transactionBuilder.addOutput(data, 0)
+        transactionBuilder.addOutput(data, 0)
+
+
+
+        // Send payment
+        // transactionBuilder.addOutput(receiver, satsNeeded)
+        transactionBuilder.addOutput(receiver, paymentAmount)
+
+        // Send the BCH change back to the payment part
+        // transactionBuilder.addOutput(account.address, remainder - 300)
+    })
+
+    /* Initialize redeem script. */
+    // FIXME Why do we need this??
+    let redeemScript
+
+    /* Convert mnemonic to seed. */
+    const seed = mnemonicToSeed(this.mnemonic)
+
+    /* Conver to seed buffer. */
+    // FIXME Migrate to TypedArrays.
+    const seedBuffer = Buffer.from(seed, 'hex')
+
+    /* Generate master node. */
+    const masterNode = bchjs.HDNode.fromSeed(seedBuffer)
+
+/* Set account index. */
+const accountIdx = 0
+/* Set change index. */
+const changeIdx = 0
+/* Set address index. */
+const addressIdx = 0
+
+    /* Generate child node. */
+    const chidleNode = masterNode
+        .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
+
+    /* Generate wallet import format (WIF). */
+    const wif = bchjs.HDNode.toWIF(chidleNode)
+    // console.log('BCH WIF', wif)
+
+    /* Generate elliptic pair. */
+    const ecPair = bchjs.ECPair.fromWIF(wif)
+
+    /* Sign transaction. */
+    transactionBuilder.sign(
+        inputIdx,
+        ecPair,
+        redeemScript,
+        Transaction.SIGHASH_ALL,
+        utxo.value,
+    )
+
+    /* Generate (incomplete) transaction. */
+    const tx = transactionBuilder.transaction.buildIncomplete()
+    // console.log('TRANSACTION', tx)
+
+    /* Convert to (raw) hex. */
+    rawTx = tx.toHex()
+
+    /* Return raw (hex) transaction. */
+    return rawTx
+}

+ 38 - 15
web/handlers/initFusions.ts

@@ -1,3 +1,6 @@
+/* Import modules. */
+import moment from 'moment'
+
 /* Initialize globals. */
 let fusionsDb
 
@@ -8,34 +11,54 @@ const init = async (_fusionsDb) => {
     fusionsDb = _fusionsDb
 
     fusionsDb['4e9654f9-3de9-4f9a-8169-3834f40847f5'] = {
-        tierid: 270000,
-        guests: 5,
-        inputs: 0,
-        outputs: 0,
-        createdAt: 1723245503,
-        updatedAt: 1723245503,
+        tierid: 888000,
+        sessionid: null,
+        progress: 0.1,
+        numGuests: 0,
+        numInputs: 0,
+        numOutputs: 0,
+        guests: {},
+        inputs: {},
+        outputs: {},
+        rawTx: null,
+        txid: null,
+        createdAt: moment().unix(),
+        updatedAt: moment().unix(),
+        completedAt: null,
     }
 
     fusionsDb['6f765750-2267-4601-87be-80a416143a28'] = {
         tierid: 820000,
-        guests: 20,
-        inputs: 36,
-        outputs: 68,
+        sessionid: '61c13c42-a961-4ad5-8f9b-f71bea2c343c',
+        progress: 100.0,
+        numGuests: 20,
+        numInputs: 36,
+        numOutputs: 68,
+        guests: {},
+        inputs: {},
+        outputs: {},
+        rawTx: '010000000196ca5d62049e3a87a42262d424b3f6074f098ed585a768cffc8466f5aa17531c020000006441e02048ad5c08672ea824765d812f81e7d2cac73c9e4965ba1627332bbaadf0a281d4c8863a4a7664b6a69bfe6d30b75b08ba115bd85ae5448901d6295f4ca492412102bd71022b471ff54d33d0730f9ce72758832a6c2e0cab035bdc5ace4df48f2fa6ffffffff032a150000000000001976a9148f480dcea25edfab38be25d687bf4807b1065ca888ac970a0000000000001976a9147544c540698887f774f4ed3764e706a7b23e0b2088accbc89305000000001976a9145599439512e36aabca88aefd744a8506096c6cb888ac00000000',
+        txid: '01c5bb298e4ac2d92a14600a6ae5b4f2a0a146ccefaaad9ca8e3c01cfee0a80c',
         createdAt: 1723245503,
         updatedAt: 1723245503,
         completedAt: 1723245503,
-        txid: '12285e89b8aed041372f8fdfa2a057b41f37ea4fa4b4757f3c9cb3ac86623ddd',
     }
 
     fusionsDb['4eff6293-60e9-4a5a-83de-4b91da1f7de4'] = {
         tierid: 1800000,
-        guests: 22,
-        inputs: 51,
-        outputs: 181,
+        sessionid: 'c759b697-9c24-4f24-bc72-d3c08abd20d1',
+        progress: 100.0,
+        numGuests: 22,
+        numInputs: 51,
+        numOutputs: 181,
+        guests: {},
+        inputs: {},
+        outputs: {},
+        rawTx: '01000000011964909d679ca68e61a815d38073814f07aebdc0a3c8402ee11fde4ad3a10d61020000006b483045022100a0f4f5193478a110917ae20485e1b1862e0e9d6f21b67e68bc42e44631d2644a0220687713f4ffa9ef95994d55a94b7d4a802159e4c5d711dd16cc8bd87cb2aef458412103c2ceaaac7c644d0c36a2fd8763398107beff5b563468b61cfdffb75beb5d73f1ffffffff067bb13a00000000001976a9143333dbca3f997aef9224be3d2a034b507e52e03788acb5e0b104000000001976a9141cc8013ec164298b6ef743300d32e37754d6a98888acf0e0b104000000001976a9141d5c4baeee3088a0974c040a1b8e991b42fe731f88acfbe0b104000000001976a91458acf0fdf18a118f4077a5a1321c42f696303d6f88acfae0b104000000001976a91443e2bfe4a8cc4dac7877b4f06d5f0162137fc56a88acbfdfb104000000001976a9144fb80207786cf688b4c3f60736973e35f4c7c9ec88ac00000000',
+        txid: '05f98e307d97e5ed7df464b29af2e911218a268f46c8ad7b2497aa13c7544f12',
         createdAt: 1723245818,
         updatedAt: 1723245818,
-        complatedAt: 1723245818,
-        txid: 'e05a9d2dd56f7e7c0158e176131d43bbfc39ea7a8855c6913ddb08e851439c60',
+        completedAt: 1723245818,
     }
 }
 

+ 127 - 0
web/handlers/signSharedTx.ts

@@ -0,0 +1,127 @@
+/* Import modules. */
+import BCHJS from '@psf/bch-js'
+import { Transaction } from 'bitcoinjs-lib'
+import { mnemonicToSeed } from '@nexajs/hdnode'
+import { encodeNullData } from '@nexajs/script'
+import { utf8ToBin } from '@nexajs/utils'
+
+const bchjs = new BCHJS()
+
+/**
+ * Build Shared Transaction
+ *
+ * Combine all participating inputs and outputs into one (signed) transaction.
+ */
+export default function (_sessionid, _mnemonic, _inputs, _outputs) {
+console.log('SIGN SHARED TX', _sessionid, _inputs, _outputs)
+    /* Initialize locals. */
+    let accountIdx
+    let addressIdx
+    let changeIdx
+    let chidleNode
+    let data
+    let ecPair
+    let ownedInputs
+    let protocolId
+    let msg
+    let rawTx
+    let redeemScript
+    let script
+    let wif
+
+    /* Initialize transaction builde.r */
+    const transactionBuilder = new bchjs.TransactionBuilder()
+
+    /* Handle inputs. */
+    _inputs.forEach(_input => {
+        /* Add input. */
+        transactionBuilder.addInput(_input.tx_hash, _input.tx_pos)
+    })
+
+    /* Set protocol ID. */
+    protocolId = '1337'
+
+    /* Set protocol message. */
+    msg = 'finalization...'
+
+    script = [
+        utf8ToBin(protocolId),
+        utf8ToBin(msg),
+        // utf8ToBin(_sessionid),
+    ]
+    // console.log('my SCRIPT', script)
+    // console.log('encodeNullData', encodeNullData(script))
+
+    // Compile the script array into a bitcoin-compliant hex encoded string.
+    // const data = bchjs.Script.encode(script)
+    data = Buffer.from(encodeNullData(script))
+    // console.log('OP_RETURN (data)', data)
+
+    // Add the OP_RETURN output.
+    transactionBuilder.addOutput(data, 0)
+
+    /* Handle outputs. */
+    _outputs.forEach(_output => {
+        /* Add output. */
+        transactionBuilder.addOutput(_output.address, _output.value)
+    })
+
+
+
+    /* Convert mnemonic to seed. */
+    const seed = mnemonicToSeed(_mnemonic)
+
+    /* Conver to seed buffer. */
+    // FIXME Migrate to TypedArrays.
+    const seedBuffer = Buffer.from(seed, 'hex')
+
+    /* Generate master node. */
+    const masterNode = bchjs.HDNode.fromSeed(seedBuffer)
+
+/* Set account index. */
+accountIdx = 0
+/* Set change index. */
+changeIdx = 0
+/* Set address index. */
+addressIdx = 0
+
+    /* Generate child node. */
+    chidleNode = masterNode
+        .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
+
+    /* Generate wallet import format (WIF). */
+    wif = bchjs.HDNode.toWIF(chidleNode)
+    // console.log('BCH WIF', wif)
+
+    /* Generate elliptic pair. */
+    ecPair = bchjs.ECPair.fromWIF(wif)
+
+// FIXME Identify our owned inputs.
+ownedInputs = [ 0, 1, 2, 3, 4, 5, 6 ]
+
+    for (let i = 0; i < _inputs.length; i++) {
+        /* Verify input ownership. */
+        if (!ownedInputs.includes(i)) {
+            continue
+        }
+
+        /* Sign (our own) input. */
+        transactionBuilder.sign(
+            i,
+            ecPair,
+            redeemScript,
+            Transaction.SIGHASH_ALL,
+            _inputs[i].value,
+        )
+    }
+
+    /* Generate (incomplete) transaction. */
+    const tx = transactionBuilder.transaction.buildIncomplete()
+    // console.log('TRANSACTION', tx)
+
+    /* Convert to (raw) hex. */
+    rawTx = tx.toHex()
+
+    /* Return raw (hex) transaction. */
+    return rawTx
+}

+ 4 - 3
web/nuxt.config.ts

@@ -76,9 +76,10 @@ export default defineNuxtConfig({
     },
 
     runtimeConfig: {
-        public: {
-            PSF_JWT_AUTH_TOKEN: process.env.PSF_JWT_AUTH_TOKEN,
-        },
+        PSF_JWT_AUTH_TOKEN: process.env.PSF_JWT_AUTH_TOKEN,
+        // public: {
+        //     PSF_JWT_AUTH_TOKEN: process.env.PSF_JWT_AUTH_TOKEN,
+        // },
     },
 
     // FIXME Polyfills to support `@psf/bch-js` node dependencies.

+ 5 - 2
web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "hushyourmoney",
-  "version": "24.8.20",
+  "version": "24.8.25",
   "license": "MIT",
   "description": "Spend Privately. Fearlessly!",
   "author": "Shomari <[email protected]>",
@@ -13,9 +13,12 @@
     "postinstall": "nuxt prepare"
   },
   "dependencies": {
+    "@nexajs/crypto": "24.8.16",
     "@pinia/nuxt": "0.4.7",
     "@psf/bch-js": "6.7.4",
-    "bitcoinjs-lib": "6.1.6",
+    "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",

+ 35 - 17
web/pages/admin/liquidity.vue

@@ -5,8 +5,8 @@ import BCHJS from '@psf/bch-js'
 // REST API servers.
 const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
 
-const runtimeConfig = useRuntimeConfig()
-const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
+// const runtimeConfig = useRuntimeConfig()
+// const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
 
 const balances = ref(null)
 const cashAddress = ref(null)
@@ -15,7 +15,7 @@ const utxos = ref(null)
 // Instantiate bch-js based on the network.
 const bchjs = new BCHJS({
     restURL: BCHN_MAINNET,
-    apiToken: jwtAuthToken,
+    // apiToken: jwtAuthToken,
 })
 // console.log('bchjs', bchjs)
 
@@ -44,7 +44,11 @@ const init = async () => {
     let response
     let rootSeed
 
-    utxos.value = []
+    // utxos.value = []
+
+    if (typeof Wallet.mnemonic === 'undefined' || Wallet.mnemonic === null) {
+        throw new Error('NO mnemonic available to create wallet.')
+    }
 
     /* Set root seed. */
     rootSeed = await bchjs.Mnemonic.toSeed(Wallet.mnemonic)
@@ -60,24 +64,27 @@ const init = async () => {
 
     /* Set Bitcoin Cash address. */
     cashAddress.value = bchjs.HDNode.toCashAddress(childNode)
-    // console.log('cashAddress', cashAddress.value)
+    console.log('cashAddress', cashAddress.value)
 
-    response = await bchjs.Electrumx.utxo(cashAddress.value)
+    // response = await bchjs.Electrumx.utxo(cashAddress.value)
     // console.log('RESPONSE', response)
 
     /* Set UTXOs. */
-    utxos.value = response.utxos
-    console.log('UTXOS', JSON.stringify(utxos.value, null, 2))
+    // utxos.value = response.utxos
+    // utxos.value = response.utxos.filter(_utxo => {
+    //     return _utxo.value >= 10000 // NOTE: Protocol DUST limit (100 bits).
+    // })
+    // console.log('UTXOS', JSON.stringify(utxos.value, null, 2))
 
     /* Request balances. */
-    response = await bchjs.Electrumx.balance(cashAddress.value)
+    // response = await bchjs.Electrumx.balance(cashAddress.value)
 
     /* Validate response. */
-    if (response.success) {
-        /* Set balances. */
-        balances.value = response.balance
-        console.log('BALANCES', balances.value)
-    }
+    // if (response.success) {
+    //     /* Set balances. */
+    //     balances.value = response.balance
+    //     console.log('BALANCES', balances.value)
+    // }
 
 }
 
@@ -97,9 +104,20 @@ onMounted(() => {
             Liquidity Provider
         </h1>
 
-        <p>
-            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Id eius voluptatem minus natus at eveniet dolorum eos mollitia, maxime animi excepturi harum omnis illum odit recusandae pariatur! Unde, explicabo molestias.
-        </p>
+        <section class="mt-5 w-full lg:w-2/3 flex flex-col gap-4">
+            <h2 class="text-fuchsia-500 text-4xl font-light italic tracking-tight">
+                What does an LP do?
+            </h2>
+
+            <p class="text-fuchsia-800 text-2xl font-light leading-8 italic">
+                If this was a playground, LPs would be the ones turning the double dutch ropes for everyone else to enjoy a great time!
+            </p>
+
+            <p class="text-gray-600 text-lg font-light leading-7">
+                Liquidity is spread across ALL <NuxtLink to="/help/tiers" class="text-blue-500 font-normal hover:underline">Tiers</NuxtLink> to maximize its utility to the greatest number of active <NuxtLink to="/help/guests" class="text-blue-500 font-normal hover:underline">Club Guests</NuxtLink>.
+                The Hush Protocol, powered by a community of independently operated Hush Your Money (HYM) Clubs, is engineered <em>(i.e. financially incentivized)</em> to offer sub 15-seconds <NuxtLink to="/help/dances" class="text-blue-500 font-normal hover:underline">Coin Dances</NuxtLink>, consistently!
+            </p>
+        </section>
 
         <section class="w-full w-3/4 py-10 flex justify-center">
             <Loading v-if="Wallet.isLoading" />

+ 34 - 0
web/pages/help/dances.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+useHead({
+    title: `Dances — Hush Your Money`,
+    meta: [
+        { name: 'description', content: `Hush Your Money makes spending safu.` }
+    ],
+})
+
+/* Initialize stores. */
+import { useSystemStore } from '@/stores/system'
+const System = useSystemStore()
+
+// onMounted(() => {
+//     console.log('Mounted!')
+//     // Now it's safe to perform setup operations.
+// })
+
+// onBeforeUnmount(() => {
+//     console.log('Before Unmount!')
+//     // Now is the time to perform all cleanup operations.
+// })
+</script>
+
+<template>
+    <main class="max-w-5xl mx-auto py-5 flex flex-col gap-4">
+        <h1 class="text-5xl font-medium">
+            Coin Dances
+        </h1>
+
+        <p>
+            Coin Dances are the NEW (CoinJoin, CashShuffle, CashFusion).
+        </p>
+    </main>
+</template>

+ 34 - 0
web/pages/help/guests.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+useHead({
+    title: `Club Guests — Hush Your Money`,
+    meta: [
+        { name: 'description', content: `Hush Your Money makes spending safu.` }
+    ],
+})
+
+/* Initialize stores. */
+import { useSystemStore } from '@/stores/system'
+const System = useSystemStore()
+
+// onMounted(() => {
+//     console.log('Mounted!')
+//     // Now it's safe to perform setup operations.
+// })
+
+// onBeforeUnmount(() => {
+//     console.log('Before Unmount!')
+//     // Now is the time to perform all cleanup operations.
+// })
+</script>
+
+<template>
+    <main class="max-w-5xl mx-auto py-5 flex flex-col gap-4">
+        <h1 class="text-5xl font-medium">
+            Club Guests
+        </h1>
+
+        <p>
+            Club Guests are the NEW CoinJoin Players.
+        </p>
+    </main>
+</template>

+ 34 - 0
web/pages/help/tiers.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+useHead({
+    title: `Tiers — Hush Your Money`,
+    meta: [
+        { name: 'description', content: `Hush Your Money makes spending safu.` }
+    ],
+})
+
+/* Initialize stores. */
+import { useSystemStore } from '@/stores/system'
+const System = useSystemStore()
+
+// onMounted(() => {
+//     console.log('Mounted!')
+//     // Now it's safe to perform setup operations.
+// })
+
+// onBeforeUnmount(() => {
+//     console.log('Before Unmount!')
+//     // Now is the time to perform all cleanup operations.
+// })
+</script>
+
+<template>
+    <main class="max-w-5xl mx-auto py-5 flex flex-col gap-4">
+        <h1 class="text-5xl font-medium">
+            Tiers
+        </h1>
+
+        <p>
+            72 tiers are available to offer the greatest anonymity protection to <NuxtLink to="/help/guests" class="text-blue-500 font-normal hover:underline">Club Guests</NuxtLink>.
+        </p>
+    </main>
+</template>

+ 44 - 17
web/pages/v1.vue

@@ -38,51 +38,74 @@ const System = useSystemStore()
 
         <section class="flex flex-col gap-4">
             <h2 class="text-sky-600 text-2xl font-bold tracking-widest uppercase">
-                Requests
+                Client Requests
             </h2>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - getClubs
+                /v1/fusion/&lt;fusion-id&gt;
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - getFusions
+                /v1/tier/&lt;tier-id&gt;
             </h3>
 
+        </section>
+
+        <section class="flex flex-col gap-4">
+            <h2 class="text-sky-600 text-2xl font-bold tracking-widest uppercase">
+                Client Actions
+            </h2>
+
             <h3 class="text-2xl font-bold text-amber-500">
-                - getProfiles
+                addComponents <small class="text-base text-amber-400">( PublicKey, Component[] )</small>
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - getStats
+                generateAddress
             </h3>
 
+            <h3 class="text-2xl font-bold text-amber-500">
+                generateProof
+            </h3>
+
+            <h3 class="text-2xl font-bold text-amber-500">
+                submitComponents <small class="text-base text-amber-400">( PublicKey, Component[] )</small>
+            </h3>
+
+            <h3 class="text-2xl font-bold text-amber-500">
+                verifyProof
+            </h3>
         </section>
 
         <section class="flex flex-col gap-4">
             <h2 class="text-sky-600 text-2xl font-bold tracking-widest uppercase">
-                Actions
+                Batch (System) Requests
             </h2>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - addComponents <small class="text-base text-amber-400">( PublicKey, Component[] )</small>
+                /_clubs
+            </h3>
+
+            <h3 class="text-2xl font-bold text-amber-500">
+                /_fusions
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - generateAddress
+                /_profiles
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - generateProof
+                /_stats
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - submitComponents <small class="text-base text-amber-400">( PublicKey, Component[] )</small>
+                /_tiers
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - verifyProof
+                /_vaults
             </h3>
+
         </section>
 
         <section class="flex flex-col gap-4">
@@ -91,27 +114,31 @@ const System = useSystemStore()
             </h2>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - Club
+                Club
+            </h3>
+
+            <h3 class="text-2xl font-bold text-amber-500">
+                Component
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - Component
+                Fusion
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - Fusion
+                PrivateTransaction
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - PrivateTransaction
+                Profile
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - Profile
+                PublicKey
             </h3>
 
             <h3 class="text-2xl font-bold text-amber-500">
-                - PublicKey
+                Tier
             </h3>
 
         </section>

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

@@ -0,0 +1,169 @@
+/* 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 newParams
+    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
+
+    /* Set params. */
+    params = body.params
+
+    let collection = []
+
+    /* Handle method. */
+    switch(method) {
+    case 'blockchain.transaction.get':
+        if (Array.isArray(params)) {
+            for (let i = 0; i < params.length; i++) {
+                response = await electrum.request(method, params[i])
+
+                collection.push(response)
+            }
+        } else {
+            // Request the full transaction hex for the transaction ID.
+            response = await electrum.request(method, params)
+
+            collection = [ response ]
+        }
+
+        return collection
+    case 'blockchain.scripthash.listunspent':
+        if (Array.isArray(params)) {
+            for (let i = 0; i < params.length; i++) {
+                /* Set address. */
+                address = params[i]
+
+                /* Convert to script hash. */
+                hash160 = bchjs.Address.toHash160(params[i])
+
+                /* Set script. */
+                // FIXME Use `OP` and TypedArray.
+                script = `76a914${hash160}88ac`
+
+                /* Conver to hex. */
+                script = hexToBin(script)
+
+                /* Generate script hash. */
+                scriptHash = sha256(script)
+
+                scriptHash = scriptHash.reverse()
+
+                /* Set (new) params. */
+                newParams = binToHex(scriptHash)
+
+                response = await electrum.request(method, newParams)
+
+                /* Add (a reference to) address. */
+                response = {
+                    address,
+                    utxos: response,
+                }
+
+                // collection.push(response)
+                collection = [ ...collection, response ]
+            }
+        } else {
+            // Request the full transaction hex for the transaction ID.
+            response = await electrum.request(method, params)
+
+            // collection.push(response)
+            collection = [ ...response ]
+        }
+
+        return collection
+    case 'blockchain.scripthash.get_history':
+        if (Array.isArray(params)) {
+            for (let i = 0; i < params.length; i++) {
+                /* Set address. */
+                address = params[i]
+
+                /* Convert to script hash. */
+                hash160 = bchjs.Address.toHash160(params[i])
+
+                /* Set script. */
+                // FIXME Use `OP` and TypedArray.
+                script = `76a914${hash160}88ac`
+
+                /* Conver to hex. */
+                script = hexToBin(script)
+
+                /* Generate script hash. */
+                scriptHash = sha256(script)
+
+                scriptHash = scriptHash.reverse()
+
+                /* Set (new) params. */
+                newParams = binToHex(scriptHash)
+
+                response = await electrum.request(method, newParams)
+
+                /* Add (a reference to) address. */
+                response = {
+                    address,
+                    txs: response,
+                }
+
+                // collection.push(response)
+                collection = [ ...collection, response ]
+            }
+        } else {
+            // Request the full transaction hex for the transaction ID.
+            response = await electrum.request(method, params)
+
+            // collection.push(response)
+            collection = [ ...response ]
+        }
+
+        return collection
+    default:
+        setResponseStatus(event, 401)
+        return 'Oops! Invalid request!'
+    }
+})

+ 0 - 12
web/server/api/fusions.get.ts

@@ -1,12 +0,0 @@
-export default defineEventHandler(async (event) => {
-    /* Set database. */
-    const Db = event.context.Db
-
-    /* Validate fusions. */
-    if (typeof Db.fusions === 'undefined' || !Db.fusions) {
-        return 'Profiles fail!'
-    }
-
-    /* Return (database) fusions. */
-    return Db.fusions
-})

+ 2 - 1
web/server/api/wallet.get.ts

@@ -6,7 +6,8 @@ import { binToHex } from '@nexajs/utils'
 const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
 
 const runtimeConfig = useRuntimeConfig()
-const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
+// const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
+const jwtAuthToken = runtimeConfig.PSF_JWT_AUTH_TOKEN
 // console.log('jwtAuthToken', jwtAuthToken)
 
 // Instantiate bch-js based on the network.

+ 84 - 2
web/server/middleware/03-wallet.ts

@@ -1,9 +1,31 @@
 /* Import modules. */
+import Bitcoin from '@psf/bitcoincashjs-lib'
+import BCHJS from '@psf/bch-js'
 import { Wallet } from '@nexajs/wallet'
 
-/* Initialize Wallet (instance) */
+// REST API servers.
+const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
+
+const runtimeConfig = useRuntimeConfig()
+// const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
+const jwtAuthToken = runtimeConfig.PSF_JWT_AUTH_TOKEN
+// console.log('jwtAuthToken', jwtAuthToken)
+
+// Instantiate bch-js based on the network.
+// FIXME https://github.com/Permissionless-Software-Foundation/jwt-bch-demo/blob/master/lib/fullstack-jwt.js
+const bchjs = new BCHJS({
+    restURL: BCHN_MAINNET,
+    apiToken: jwtAuthToken,
+})
+// console.log('bchjs', bchjs)
+
+/* Initialize globals. */
+let fusionsDb
 let wallet
 
+/* Set constants. */
+const CHECK_FUSIONS_INTERVAL = 5000
+
 /**
  * Initialize (Wallet)
  *
@@ -13,13 +35,73 @@ const init = async () => {
     wallet = await Wallet.init()
         .catch(err => console.error(err))
     // console.log('WALLET INIT', wallet)
+return // FIXME ONLY CLIENTS CAN BROADCAST FUSIONS
+    let lastUpdate = 0
+    let lastSnapshot
+
+    /* Manage Fusions */
+    setInterval(() => {
+        // console.log('Looking for DB(fusions) changes...', fusionsDb)
+
+        /* Set Fusion snapshot. */
+        const snapshot = JSON.stringify(fusionsDb)
+        // console.log('SNAPSHOT', snapshot)
+
+        /* Validate Fusion snapshot. */
+        if (fusionsDb && snapshot !== lastSnapshot) {
+            lastSnapshot = snapshot
+
+// console.log('FOUND CHANGES??', snapshot)
+// txObj.ins[1].script = txObj2.ins[1].script
+// txObj.ins[2].script = txObj3.ins[2].script
+
+            /* Initialize recents. */
+            const recents = []
+
+            /* Handle fusions. */
+            Object.keys(fusionsDb).forEach(_fusionid => {
+                /* Set fusion. */
+                const fusion = fusionsDb[_fusionid]
+
+                /* Validate last update. */
+                if (typeof fusion.completedAt === 'undefined' && fusion.updatedAt > lastUpdate) {
+                    /* Add fusion. */
+                    recents.push(fusion)
+                }
+            })
+
+            console.log('RECENTS', lastUpdate, recents)
+
+            /* Handle recents. */
+            // NOTE: Update last update handler.
+            recents.forEach(_recent => {
+                if (_recent.updatedAt > lastUpdate) {
+                    /* Set (new) last update. */
+                    lastUpdate = _recent.updatedAt
+                }
+
+                /* Validate fusion progress. */
+                if (_recent.progress === 100.0) {
+                    /* Build transaction. */
+                    // rawTx = ...
+
+                    /* Add to database. */
+                    // fusionsDb.rawTx
+                }
+            })
+            console.log('NEW LAST UPDATE', lastUpdate)
+        }
+    }, CHECK_FUSIONS_INTERVAL)
+
 }
 init()
 
 export default defineEventHandler((event) => {
     /* Set database. */
     const Db = event.context.Db
-    // console.log('DB', Db)
+
+    /* Set fusions database. */
+    fusionsDb = Db.fusions
 
     /* Inject wallet into server context. */
     event.context.Wallet = wallet

+ 77 - 0
web/server/middleware/04-fusions.ts

@@ -0,0 +1,77 @@
+/* Import modules. */
+
+/* Initialize globals. */
+let fusionsDb
+
+/* Set constants. */
+const CHECK_FUSIONS_INTERVAL = 5000
+
+/**
+ * Initialize (Fusions)
+ *
+ * Setup a Fusions (background) handler.
+ */
+const init = async () => {
+        /* Manage Fusions */
+    setInterval(() => {
+        // console.log('Looking for DB(fusions) changes...', fusionsDb)
+
+        /* Set Fusion snapshot. */
+        const snapshot = JSON.stringify(fusionsDb)
+        // console.log('SNAPSHOT', snapshot)
+
+        /* Validate Fusion snapshot. */
+        if (fusionsDb && snapshot !== lastSnapshot) {
+            lastSnapshot = snapshot
+
+            /* Initialize recents. */
+            const recents = []
+
+            /* Handle fusions. */
+            Object.keys(fusionsDb).forEach(_fusionid => {
+                /* Set fusion. */
+                const fusion = fusionsDb[_fusionid]
+
+                /* Validate last update. */
+                if (typeof fusion.completedAt === 'undefined' && fusion.updatedAt > lastUpdate) {
+                    /* Add fusion. */
+                    recents.push(fusion)
+                }
+            })
+
+            console.log('RECENTS', lastUpdate, recents)
+
+            /* Handle recents. */
+            // NOTE: Update last update handler.
+            recents.forEach(_recent => {
+                if (_recent.updatedAt > lastUpdate) {
+                    /* Set (new) last update. */
+                    lastUpdate = _recent.updatedAt
+                }
+
+                /* Validate fusion progress. */
+                if (_recent.progress === 100.0) {
+                    /* Build transaction. */
+                    // rawTx = ...
+
+// console.log('FOUND CHANGES??', snapshot)
+// txObj.ins[1].script = txObj2.ins[1].script
+// txObj.ins[2].script = txObj3.ins[2].script
+
+                    /* Save to database. */
+                    // fusionsDb[???].rawTx
+                }
+            })
+            console.log('NEW LAST UPDATE', lastUpdate)
+        }
+    }, CHECK_FUSIONS_INTERVAL)
+}
+// init()
+
+export default defineEventHandler((event) => {
+    /* Set database. */
+    const Db = event.context.Db
+
+    /* Set fusions database. */
+    fusionsDb = Db.fusions
+})

+ 119 - 30
web/server/routes/v1.post.ts

@@ -1,40 +1,68 @@
 /* Import modules. */
 import moment from 'moment'
+import {
+    decryptForPubkey,
+    sha256,
+ } from '@nexajs/crypto'
+import {
+    binToHex,
+    binToUtf8,
+} from '@nexajs/utils'
+import { v5 as uuidv5 } from 'uuid'
+
+/* Set Hush (UUIDv5) Namespace */
+// NOTE: Replace the 1st 4-bytes of the standard prefix with the
+//       Hush Protocol ID (0x48555348).
+// For reference:
+//   - uuidv5.DNS 6ba7b810-9dad-11d1-80b4-00c04fd430c8
+//   - uuidv5.URL 6ba7b811-9dad-11d1-80b4-00c04fd430c8
+const HUSH_NAMESPACE = '48555348-9dad-11d1-80b4-00c04fd430c8'
 
-// import { useProfileStore } from '@/stores/profile'
 
 export default defineEventHandler(async (event) => {
-    /* Initialize database store. */
-    // const Profile = useProfileStore()
+    /* Initialize locals. */
+    let componentid
+    let components
+    let params
+    let profile
+    let response
+    let session
+    let success
 
     /* Set database. */
     const Db = event.context.Db
-    // console.log('DB', Db)
+
+    /* Set wallet. */
+    const Wallet = event.context.Wallet
 
     /* Set (request) body. */
     const body = await readBody(event)
-    console.log('BODY', body)
+    // console.log('BODY', body)
 
     if (!body) {
+        setResponseStatus(event, 401)
+
         return `Authorization FAILED!`
     }
 
     /* Set profile parameters. */
     const authid = body.authid
-    console.log('AUTH ID', authid)
+    // console.log('AUTH ID', authid)
 
     const actionid = body.actionid
-    console.log('ACTION ID', actionid)
+    // console.log('ACTION ID', actionid)
 
-    const sessionid = body.sessionid
-    console.log('SESSION ID', sessionid)
+    // const sessionid = body.sessionid
+    // console.log('SESSION ID', sessionid)
 
-    /* Initialize locals. */
-    let params
-    let profile
-    let response
-    let session
-    let success
+    // const rawTx = body.rawTx
+    // console.log('RAW TRANSACTION', rawTx)
+
+    components = body.components
+    components = decryptForPubkey(binToHex(Wallet.privateKey), components)
+    components = binToUtf8(components)
+    components = JSON.parse(components)
+    console.log('COMPONENTS', components)
 
     /* Validate auth ID. */
     if (typeof authid === 'undefined' || authid === null) {
@@ -43,6 +71,10 @@ export default defineEventHandler(async (event) => {
         return 'Authorization failed!'
     }
 
+console.log('DEBUG::INSERTING A NEW FUSION')
+const fusion = Db.fusions['4e9654f9-3de9-4f9a-8169-3834f40847f5']
+console.log('FUSION', fusion)
+
     /* Request session. */
     profile = Db.profiles[authid]
     console.log('PROFILE', profile)
@@ -50,7 +82,7 @@ export default defineEventHandler(async (event) => {
     /* Validate profile id. */
     if (typeof profile === 'undefined' || profile === null) {
         /* Set status code. */
-        setResponseStatus(event, 404)
+        // setResponseStatus(event, 404)
 
         // return 'Oops! Something went wrong..'
 
@@ -63,31 +95,88 @@ export default defineEventHandler(async (event) => {
 
         /* Request session. */
         response = await Db.put('profiles', authid, profile)
-        console.log('SAVE NEW PROFILE', response)
+            .catch(err => console.error(err))
+        // console.log('SAVE NEW PROFILE', response)
     } else {
         /* Build profile package. */
         profile.updatedAt = moment().unix()
 
         /* Request session. */
         response = await Db.put('profiles', authid, profile)
-        console.log('UPDATE PROFILE', response)
+            .catch(err => console.error(err))
+        // console.log('UPDATE PROFILE', response)
+    }
 
-console.log('DEBUG::INSERTING A NEW FUSION')
-const fusion = {
-    tierid: 680000,
-    guests: 12,
-    inputs: 111,
-    outputs: 195,
-    createdAt: 1723245503,
-    updatedAt: 1723245503,
-}
-await Db.put('fusions', 'b2eedc48-52d1-4056-b441-c3c802c053a3', fusion)
+    if (profile) {
+        fusion.guests[authid] = {
+            createdAt: profile.createdAt,
+            updatedAt: profile.updatedAt,
+        }
+    } else {
+        setResponseStatus(event, 401)
+
+        return 'Oops! Authorization failed!'
     }
 
-    /* Return profile. */
-    return {
+    fusion.numGuests = Object.keys(fusion.guests).length
+
+    /* Update progress. */
+    fusion.progress = 12.5
+
+
+    components.forEach(_component => {
+        /* Validate inputs. */
+        if (_component.tx_hash) {
+            componentid = sha256(_component.tx_hash + ':' + _component.tx_pos)
+            fusion.inputs[componentid] = {
+                ..._component,
+                signature: null,
+            }
+        }
+
+        /* Validate outputs. */
+        if (!_component.tx_hash) {
+            componentid = sha256(_component.address + ':' + _component.value)
+            fusion.outputs[componentid] = _component
+        }
+    })
+
+    fusion.numInputs = Object.keys(fusion.inputs).length
+
+    fusion.numOutputs = Object.keys(fusion.outputs).length
+
+/* Calculate session contents. */
+const sessionContents = JSON.stringify({ ...fusion.inputs, ...fusion.outputs })
+// console.log('SESSION CONTENTS', sessionContents)
+
+/* Calculate (UUIDv5) session hash. */
+const sessionHash = sha256(sessionContents)
+// console.log('SESSION HASH', sessionHash)
+
+/* Calculate session ID. */
+const sessionid = uuidv5(sessionHash, HUSH_NAMESPACE)
+// console.log('SESSION (uuid v5)', sessionid)
+
+    /* Set session ID. */
+    fusion.sessionid = sessionid
+
+    /* Update progress. */
+    fusion.progress = 75.5
+
+    /* Set (new) updated at (timestamp). */
+    fusion.updatedAt = moment().unix()
+
+    await Db.put('fusions', '4e9654f9-3de9-4f9a-8169-3834f40847f5', fusion)
+
+    /* Build (response) package. */
+    const pkg = {
         id: profile._id,
+        components,
         createdAt: profile.createdAt,
         updatedAt: profile.updatedAt,
     }
+    // console.log('PKG', pkg)
+
+    /* Return pkg. */
+    return pkg
 })

+ 11 - 15
web/server/routes/v1/fusion/[fusionid].get.ts

@@ -1,25 +1,21 @@
 export default defineEventHandler(async (event) => {
-    /* Initialize locals. */
-    let session
+    /* Set database. */
+    const fusionid = event.context.params?.fusionid
 
     /* Set database. */
     const Db = event.context.Db
-    console.log('DB', Db)
-
-    /* Set session. */
-    const sessionid = event.context.params.sessionid
-    console.log('SESSION ID', sessionid)
 
-    /* Set session. */
-    session = Db.sessions[sessionid]
+    /* Validate fusions. */
+    if (typeof Db.fusions === 'undefined' || !Db.fusions) {
+        return 'Fusions fail!'
+    }
 
-    /* Validate session. */
-    if (typeof session === 'undefined' || session === null) {
+    /* Search for fusion. */
+    if (Db.fusions[fusionid]) {
+        return Db.fusions[fusionid]
+    } else {
         setResponseStatus(event, 404)
 
-        return `Sorry, session [ ${sessionid} ] COULD NOT be found.`
+        return 'Sorry, that fusion COULD NOT be found.'
     }
-
-    /* Return session. */
-    return session
 })

+ 0 - 13
web/server/routes/v1/tier/[tier].get.ts

@@ -1,13 +0,0 @@
-export default defineEventHandler(async (event) => {
-    /* Set tier. */
-    const tier = event.context.params.tier
-
-    /* Initialize status. */
-    const status = {}
-
-    /* Set (tier) ID. */
-    status.id = tier
-
-    /* Return status. */
-    return status
-})

+ 50 - 0
web/server/routes/v1/tier/[tierid].get.ts

@@ -0,0 +1,50 @@
+export default defineEventHandler(async (event) => {
+    /* Set tier id. */
+    const tierid = event.context.params.tierid
+    console.log('TIER ID', typeof tierid, tierid)
+
+    /* Set database. */
+    const fusionid = event.context.params?.fusionid
+
+    /* Set database. */
+    const Db = event.context.Db
+
+    /* Validate fusions. */
+    if (typeof Db.fusions === 'undefined' || !Db.fusions) {
+        return 'Fusions fail!'
+    }
+
+    /* Search for fusion. */
+    if (Db.fusions) {
+        const fusionid = Object.keys(Db.fusions).find(_fusionid => {
+            /* Set fusion. */
+            const fusion = Db.fusions[_fusionid]
+
+            /* Return match. */
+            return Number(fusion.tierid) === Number(tierid)
+        })
+
+        /* Validate fusion. */
+        if (typeof fusionid !== 'undefined') {
+            /* Set ALL tier fusions. */
+            // TODO All for more than ONE fusion per tier.
+            const tierFusions = []
+
+            tierFusions.push({
+                fusionid,
+                ...Db.fusions[fusionid],
+            })
+
+            /* Return tier fusions. */
+            return tierFusions
+        } else {
+            setResponseStatus(event, 404)
+
+            return 'Sorry, that fusion COULD NOT be found.'
+        }
+    } else {
+        setResponseStatus(event, 404)
+
+        return 'Sorry, that fusion COULD NOT be found.'
+    }
+})

+ 7 - 3
web/stores/profile.ts

@@ -9,7 +9,7 @@ export const useProfileStore = defineStore('profile', {
         /* Initialize session. */
         _session: null,
 
-        _apiKeys: {},
+        _profiles: null,
     }),
 
     getters: {
@@ -25,12 +25,16 @@ export const useProfileStore = defineStore('profile', {
             return _state._session?.challenge || null
         },
 
-        apiKey(_state) {
-            return (_exchangeid) => _state._apiKeys[_exchangeid] || null
+        profiles(_state) {
+            return _state._profiles || null
         },
     },
 
     actions: {
+        init () {
+            console.log('Initializing Profiles...')
+        },
+
         async initSession () {
             console.log('INIT SESSION (before):', this._session)
             /* Check for existing session. */

+ 296 - 56
web/stores/wallet.ts

@@ -1,65 +1,33 @@
 /* Import modules. */
 import { defineStore } from 'pinia'
+import moment from 'moment'
 
-import {
-    encodeAddress,
-    listUnspent,
-} from '@nexajs/address'
-
-import {
-    derivePublicKeyCompressed,
-    ripemd160,
-    sha256,
-} from '@nexajs/crypto'
-
-import {
-    encodePrivateKeyWif,
-    mnemonicToEntropy,
-} from '@nexajs/hdnode'
-
-import { getCoins } from '@nexajs/purse'
-
-import {
-    getOutpoint,
-    getTip,
-    getTokenInfo,
-    getTransaction,
-    subscribeAddress,
-} from '@nexajs/rostrum'
-
-import {
-    encodeDataPush,
-    encodeNullData,
-    OP,
-} from '@nexajs/script'
-
-import {
-    getTokens,
-    sendToken,
-} from '@nexajs/token'
-
-import {
-    binToHex,
-    hexToBin,
-} from '@nexajs/utils'
-
+import BCHJS from '@psf/bch-js'
+import { mnemonicToEntropy } from '@nexajs/hdnode'
 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 _startFusion from './wallet/startFusion.ts'
+
+/* Initialize constants. */
+const HUSH_PROTOCOL_ID = 0x48555348
+
+// REST API servers.
+const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
+
+// Instantiate bch-js based on the network.
+const bchjs = new BCHJS({
+    restURL: BCHN_MAINNET,
+})
 
-const TX_GAS_AMOUNT = 1000 // 10.00 NEXA
+/* Set constants. */
+// 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.
 
-/* Build (contract) script. */
-const STAKELINE_V1_SCRIPT = new Uint8Array([
-    OP.FROMALTSTACK,
-        OP.DROP,
-    OP.FROMALTSTACK,
-        OP.CHECKSEQUENCEVERIFY,
-        OP.DROP,
-    OP.FROMALTSTACK,
-        OP.CHECKSIGVERIFY,
-])
 
 /**
  * Wallet Store
@@ -102,6 +70,13 @@ export const useWalletStore = defineStore('wallet', {
          */
         _keychain: null,
 
+        /**
+         * Unspent Transaction Outputs (UTXOs)
+         *
+         * A dedicated handler for ALL network UTXOs.
+         */
+        _utxos: null,
+
         /**
          * Wallet
          *
@@ -138,6 +113,11 @@ export const useWalletStore = defineStore('wallet', {
             return _state.wallet?._balances
         },
 
+        /* Return keychain. */
+        keychain(_state) {
+            return _state._keychain
+        },
+
         /* Return mnemonic. */
         mnemonic(_state) {
             if (!_state._wallet) {
@@ -146,6 +126,62 @@ export const useWalletStore = defineStore('wallet', {
 
             return _state._wallet._mnemonic
         },
+
+        /* Return UTXOs. */
+        utxos(_state) {
+            if (!_state._wallet) {
+                return null
+            }
+
+            return _state._utxos
+        },
+
+        /* Return UTXOs. */
+        fusionInputs(_state) {
+            if (!_state._wallet) {
+                return null
+            }
+
+            const collection = _state._utxos
+            // console.log('COLLECTION (fusionInputs)', collection)
+
+            const mainList = []
+
+            collection[0]?.forEach(_account => {
+                // console.log('ACCOUNT (0)', _account)
+                _account.utxos.forEach(_utxo => {
+                    mainList.push({
+                        address: _account.address,
+                        ..._utxo,
+                    })
+                })
+            })
+
+            collection[HUSH_PROTOCOL_ID]?.forEach(_account => {
+                // console.log('ACCOUNT (1213551432)', _account)
+                _account.utxos.forEach(_utxo => {
+                    mainList.push({
+                        address: _account.address,
+                        ..._utxo,
+                    })
+                })
+            })
+
+            return mainList
+        },
+
+        fusionAddrs() {
+            return this._keychain[HUSH_PROTOCOL_ID]
+        },
+
+        /* Return wif. */
+        wif(_state) {
+            if (!_state._wallet) {
+                return null
+            }
+
+            return _state._wallet.wif
+        },
     },
 
     actions: {
@@ -166,6 +202,24 @@ export const useWalletStore = defineStore('wallet', {
                 return console.error('Missing wallet entropy.')
             }
 
+            /* Validate keychain. */
+            if (this._keychain === null) {
+                this._keychain = {
+                    0: {}, // NEXA chain
+                    0x48555348: {}, // HUSH chain (1_213_551_432)
+                }
+                console.log('Keychain initialized successfully!', this._keychain)
+
+                /* Set keychain. */
+                _setupKeychain.bind(this)()
+
+                /* Set Hush keychian. */
+                _setupHushKeychain.bind(this)()
+            }
+// FOR DEV PURPOSES ONLY
+_setupKeychain.bind(this)()
+_setupHushKeychain.bind(this)()
+
             /* Request a wallet instance (by mnemonic). */
             this._wallet = await Wallet.init(this._entropy, true)
             console.info('(Initialized) wallet', this.wallet)
@@ -183,6 +237,12 @@ export const useWalletStore = defineStore('wallet', {
 // FIXME Read ASSETS directly from library (getter).
                 this._assets = { ..._assets }
             })
+
+            // FIXME ADDED FOR BITCOIN CASH SUPPORT
+            // setInterval(this.updateUtxos, UPDATE_UTXOS_INTERVAL)
+
+            /* Update ALL chains. */
+            this.updateUtxos(true)
         },
 
         /**
@@ -209,6 +269,186 @@ export const useWalletStore = defineStore('wallet', {
             this.init()
         },
 
+        /**
+         * Get Bitcoin Cash Address
+         *
+         * Will return the "child" address of a master node,
+         * based on the account index, change flag and address index.
+         */
+        async getBchAddress(
+            _accountIdx = 0,
+            _isChange = 0, // NOTE: 0 = false, 1 = true
+            _addressIdx = 0,
+        ) {
+            /* Set root seed. */
+            const rootSeed = await bchjs.Mnemonic.toSeed(this.mnemonic)
+            // console.log('rootSeed', rootSeed)
+
+            /* Set HD master node. */
+            const masterHdnode = bchjs.HDNode.fromSeed(rootSeed)
+            // console.log('masterHdnode', masterHdnode);
+
+            /* Set child node. */
+            const childNode = masterHdnode
+                .derivePath(`m/44'/145'/${_accountIdx}'/${_isChange}/${_addressIdx}`)
+            // console.log('childNode', childNode)
+
+            /* Set Bitcoin Cash address. */
+            const cashAddress = bchjs.HDNode.toCashAddress(childNode)
+            // console.log('cashAddress', cashAddress)
+
+            return cashAddress
+        },
+
+        async updateUtxos(_allChains = false) {
+            /* Initialize locals. */
+            let coins
+            let data
+            let hushAddresses
+            let usedAddresses
+            let utxos
+            let walletAddresses
+
+            /* Validate UTXO handler. */
+            if (this._utxos === null) {
+                /* Initialize UTXO handler. */
+                this._utxos = {}
+            }
+
+            coins = this._keychain[HUSH_PROTOCOL_ID]
+            // console.log('WAITING FOR COINS', coins)
+
+            hushAddresses = Object.keys(coins).map(_coinid => {
+                const coin = coins[_coinid]
+                return coin.address
+            })
+            // console.log('HUSH ADDRESSES', hushAddresses)
+
+            /* Request UTXO data. */
+            data = await $fetch('/api/electrum', {
+                method: 'POST',
+                body: JSON.stringify({
+                    method: 'blockchain.scripthash.listunspent',
+                    params: hushAddresses.slice(0, 20)
+                }),
+            })
+            .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 $fetch('/api/electrum', {
+                method: 'POST',
+                body: JSON.stringify({
+                    method: 'blockchain.scripthash.get_history',
+                    params: hushAddresses.slice(0, 20)
+                }),
+            })
+            .catch(err => console.error(err))
+            console.log('HUSH TX HISTORY', data)
+
+            usedAddresses = data?.filter(_tx => {
+                if (_tx.txs.length > 0) {
+                    return _tx.address
+                }
+            })
+            usedAddresses = usedAddresses.map(_tx => {
+                return _tx.address
+            })
+            // console.log('USED ADDRESSES', usedAddresses)
+
+            Object.keys(coins).forEach(_coinid => {
+                /* Set coin. */
+                const coin = coins[_coinid]
+
+                /* Verify address exists. */
+                if (usedAddresses.includes(coin.address)) {
+                    /* Set (used) flag. */
+                    coin.isUsed = true
+                    // console.log('COIN (is used)', coin)
+                }
+            })
+
+            /* Validate chain handler flag. */
+            if (_allChains) {
+                let bchAddress1
+                let bchAddress2
+                let bchAddress3
+
+                bchAddress1 = await this.getBchAddress(0, 0, 0)
+                    .catch(err => console.error(err))
+                // console.log('BCH ADDRESS-1', bchAddress1)
+
+                bchAddress2 = await this.getBchAddress(0, 0, 1)
+                    .catch(err => console.error(err))
+                // console.log('BCH ADDRESS-2', bchAddress2)
+
+                bchAddress3 = await this.getBchAddress(0, 0, 2)
+                    .catch(err => console.error(err))
+                // console.log('BCH ADDRESS-3', bchAddress3)
+
+                data = await $fetch('/api/electrum', {
+                    method: 'POST',
+                    body: JSON.stringify({
+                        method: 'blockchain.scripthash.listunspent',
+                        params: [
+                            bchAddress1,
+                            bchAddress2,
+                            bchAddress3,
+                        ],
+                    })
+                })
+                console.log('MAIN WALLET DATA', data)
+
+                // FIXME Update the delta ONLY!
+                this._utxos[0] = data?.utxos
+            }
+
+            return true
+        },
+
+        /**
+         * Get Fusion Address
+         *
+         * Will retrieve the next available fusion address and lock it.
+         */
+        getFusionAddress() {
+            if (!this.fusionAddrs) {
+                return ''
+            }
+
+            const addressIdx = Object.keys(this.fusionAddrs).find(_addressIdx => {
+                const fusionAddress = this.fusionAddrs[_addressIdx]
+                // console.log('fusionAddress', fusionAddress)
+
+                return fusionAddress.isUsed === false && fusionAddress.isLocked === false
+            })
+            // console.log('ADDRESS IDX', addressIdx)
+
+            if (typeof addressIdx !== 'undefined') {
+                /* Set locked flag. */
+                this._keychain[HUSH_PROTOCOL_ID][addressIdx].isLocked = true
+                this._keychain[HUSH_PROTOCOL_ID][addressIdx].updatedAt = moment().unix()
+
+                /* Return address. */
+                return this.fusionAddrs[addressIdx].address
+            } else {
+                return ''
+            }
+        },
+
+        async startFusion() {
+            /* Start fusions. */
+            return _startFusion.bind(this)()
+        },
+
+        async completeFusion() {
+            /* Start fusions. */
+            return _completeFusion.bind(this)()
+        },
+
         async transfer(_receiver, _satoshis) {
             /* Validate transaction type. */
             if (this.asset.group === '0') {
@@ -220,9 +460,9 @@ export const useWalletStore = defineStore('wallet', {
             }
         },
 
-        broadcast(_receivers) {
-            /* Broadcast to receivers. */
-            return _broadcast.bind(this)(_receivers)
+        broadcast(_network, _rawTx) {
+            /* Broadcast to raw (hex) transaction to mainnet. */
+            return _broadcast.bind(this)(_network, _rawTx)
         },
 
         setEntropy(_entropy) {

+ 145 - 0
web/stores/wallet/authFusion.ts

@@ -0,0 +1,145 @@
+/* Import modules. */
+import { encryptForPubkey } from '@nexajs/crypto'
+import { randomOutputsForTier } from '@nexajs/privacy'
+import { binToHex } from '@nexajs/utils'
+import BCHJS from '@psf/bch-js'
+
+/* Initialize BCHJS. */
+const bchjs = new BCHJS()
+
+
+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
+
+    /* Clone fusion inputs. */
+    fusionInputs = [ ...this.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
+
+    /* Initialize components. */
+    components = []
+
+    /* Add best tiers to components. */
+    // NOTE: Best tiers index is derived from inputs index.
+    Object.keys(bestTiers).forEach(_tierIdx => {
+        /* Add (matching) input to components. */
+        components.push(fusionInputs[_tierIdx])
+
+        /* Set tier. */
+        const tier = bestTiers[_tierIdx]
+
+        /* Add (output) components. */
+        components.push(tier)
+    })
+
+    /* Validate components. */
+    if (components.length === 0) {
+        throw new Error('Oops! You MUST provide components to the Club.')
+    }
+
+    /* 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),
+            actionid: 'submit-components',
+            components: blindComponents,
+        },
+    })
+    .catch(err => console.error(err))
+    console.log('RESPONSE', response)
+}

+ 11 - 101
web/stores/wallet/broadcast.ts

@@ -1,106 +1,16 @@
-/* Import modules. */
-// import { sha256 } from '@nexajs/crypto'
-// import { encodePrivateKeyWif } from '@nexajs/hdnode'
+// REST API servers.
+const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
 
-import { encodeAddress } from '@nexajs/address'
+// bch-js-examples require code from the main bch-js repo
+import BCHJS from '@psf/bch-js'
 
-// import { sha256 } from '@nexajs/crypto'
+// Instantiate bch-js based on the network.
+const bchjs = new BCHJS({ restURL: BCHN_MAINNET })
 
-import {
-    getCoins,
-    sendCoins,
-} from '@nexajs/purse'
+export default async function (_network = 'NEXA', _rawTx) {
+    // FIXME Add Nexa broadcast functions.
 
-import {
-    encodeDataPush,
-    encodeNullData,
-    OP,
-} from '@nexajs/script'
-
-import {
-    getTokens,
-    sendToken,
-} from '@nexajs/token'
-
-// const TOKEN_ID_HEX = '57f46c1766dc0087b207acde1b3372e9f90b18c7e67242657344dcd2af660000' // AVAS
-const TOKEN_ID_HEX = '9732745682001b06e332b6a4a0dd0fffc4837c707567f8cbfe0f6a9b12080000' // STUDIO
-
-export default async function (_receivers) {
-    /* Initialize locals. */
-    let body
-    let coins
-    let nexaAddress
-    let nullData
-    // let publicKey
-    let publicKeyHash
-    let receivers
-    let response
-    let scriptPubKey
-    // let scriptPushPubKey
-    let tokens
-    let txResult
-    let userData
-    // let wif
-
-    console.info('\n  Nexa address:', this.address)
-
-    coins = await getCoins(this.wif)
-        .catch(err => console.error(err))
-    console.log('\n  Coins:', coins)
-
-    tokens = await getTokens(this.wif)
-        .catch(err => console.error(err))
-    console.log('\n  Tokens:', tokens)
-
-    /* Filter tokens. */
-    // NOTE: Currently limited to a "single" Id.
-    tokens = tokens.filter(_token => {
-        return _token.tokenidHex === TOKEN_ID_HEX
-    })
-    console.log('\n  Tokens (filtered):', tokens)
-
-    userData = [
-        'RAIN',
-        `$STUDIO Telegram Airdrop`,
-    ]
-
-    /* Initialize hex data. */
-    nullData = encodeNullData(userData)
-
-    receivers = [
-        {
-            data: nullData,
-        },
-    ]
-
-    _receivers.forEach(_receiver => {
-        receivers.push(        {
-            address: _receiver.address,
-            tokenid: TOKEN_ID_HEX, // TODO Allow auto-format conversion.
-            tokens: BigInt(_receiver.tokens),
-        },
-)
-    })
-
-    receivers.push({
-        address: this.address,
-    })
-    console.log('\n  Receivers:', receivers)
-// return
-    /* Send UTXO request. */
-    response = await sendToken(coins, tokens, receivers)
-    console.log('Send UTXO (response):', response)
-
-    try {
-        txResult = JSON.parse(response)
-        console.log('TX RESULT', txResult)
-
-        if (txResult.error) {
-            console.error(txResult.error)
-        }
-    } catch (err) {
-        console.error(err)
-    }
-
-    return txResult
+    /* Send raw transaction. */
+    const txid = await bchjs.RawTransactions.sendRawTransaction([ _rawTx ])
+    console.log(`Transaction ID: ${txid}`)
 }

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

@@ -0,0 +1,87 @@
+/* Import modules. */
+import signSharedTx from '../../handlers/signSharedTx.ts'
+
+const DUST_VAL = 546
+
+export default async function () {
+    /* Initialize locals. */
+    let inputs
+    let keys
+    let outputs
+    let rawTx
+    let response
+    let session
+    let sessionid
+
+    // FIXME Where do we get the session ID from??
+    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, this.mnemonic, sortedInputs, sortedOutputs)
+    console.log('RAW TX', rawTx)
+
+    response = await this.broadcast('BCH', rawTx)
+        .catch(err => console.error(err))
+    console.log('BROADCAST (response)', response)
+}

+ 0 - 0
web/stores/wallet/bchSend.ts → web/stores/wallet/sendBch.ts


+ 35 - 0
web/stores/wallet/setupHushKeychain.ts

@@ -0,0 +1,35 @@
+/* Import modules. */
+import moment from 'moment'
+
+/* Set constants. */
+const ADDRESS_POOL_SIZE = 40 // NOTE: Recommended default is 100 addresses.
+const CHANGE_IDX = 0
+const HUSH_PROTOCOL_ID = 0x48555348
+
+export default async function () {
+    console.time('Hush keychain initialization')
+
+    /* Initialize locals. */
+    let address
+    let addressIdx
+    let pkg
+
+    for (let i = 0; i < ADDRESS_POOL_SIZE; i++) {
+        /* Set address index. */
+        addressIdx = i
+
+        address = await this.getBchAddress(HUSH_PROTOCOL_ID, CHANGE_IDX, addressIdx)
+
+        pkg = {
+            address,
+            isUsed: false,
+            isLocked: false,
+            updatedAt: moment().unix(),
+        }
+        // console.log('PKG', addressIdx, pkg)
+
+        this._keychain[HUSH_PROTOCOL_ID][addressIdx] = pkg
+    }
+
+    console.timeEnd('Hush keychain initialization')
+}

+ 35 - 0
web/stores/wallet/setupKeychain.ts

@@ -0,0 +1,35 @@
+/* Import modules. */
+import moment from 'moment'
+
+/* Set constants. */
+const ADDRESS_POOL_SIZE = 20 // NOTE: Recommended default is 100 addresses.
+const CHANGE_IDX = 0
+const PROTOCOL_ID = 0
+
+export default async function () {
+    console.time('Keychain initialization')
+
+    /* Initialize locals. */
+    let address
+    let addressIdx
+    let pkg
+
+    for (let i = 0; i < ADDRESS_POOL_SIZE; i++) {
+        /* Set address index. */
+        addressIdx = i
+
+        address = await this.getBchAddress(PROTOCOL_ID, CHANGE_IDX, addressIdx)
+
+        pkg = {
+            address,
+            isUsed: false,
+            isLocked: false,
+            updatedAt: moment().unix(),
+        }
+        // console.log('PKG', addressIdx, pkg)
+
+        this._keychain[PROTOCOL_ID][addressIdx] = pkg
+    }
+
+    console.timeEnd('Keychain initialization')
+}

+ 162 - 0
web/stores/wallet/startFusion.ts

@@ -0,0 +1,162 @@
+/* Import modules. */
+import { encryptForPubkey } from '@nexajs/crypto'
+import { randomOutputsForTier } from '@nexajs/privacy'
+import { binToHex } from '@nexajs/utils'
+import BCHJS from '@psf/bch-js'
+
+/* Initialize BCHJS. */
+const bchjs = new BCHJS()
+
+
+export default async function () {
+    console.log('Starting fusion...')
+
+    /* Initialize locals. */
+    let bestTiers
+    let blindComponents
+    let components
+    let cipherTokens
+    let clubWallet
+    let fusionInputs
+    let inputAmount
+    let publicKey
+    let rawTx
+    let inputs
+    let outputs
+    let response
+    let tierid
+    let tierScale
+
+    const feeOffset = 1034//10034
+    const maxOutputCount = 17
+
+    /* Clone fusion inputs. */
+    fusionInputs = [ ...this.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,
+    ]
+
+
+
+    // for (let i = 0; i < fusionInputs.length; i++) {
+    // inputAmount =
+
+    /* Set input amount. */
+    inputAmount = fusionInputs.reduce(
+        (acc, input) => acc + input.value, 0
+    )
+    console.log('INPUT AMOUNT', fusionInputs.length, 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, response)
+
+                /* Test for the best tiers. */
+                if (typeof bestTiers === 'undefined' || response.length > bestTiers?.outputs.length) {
+                    const numOutputs = response.length
+                    console.log('NUM OUTPUTS', numOutputs)
+
+                    // FIXME THE RESULTING BYTE COUNTS ARE WRONG!!
+                    //       THIS FUNCTION MAY BE OUTDATED!!
+                    const fee = bchjs.BitcoinCash
+                        .getByteCount({ P2PKH: fusionInputs.length }, { P2PKH: numOutputs })
+                    // const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 })
+                    console.log('FEE', fee)
+
+                    const safeFee = (fee * 2) + (fee * Math.random())
+                    console.log('SAFE FEE', safeFee)
+
+                    const feeOffset = Math.ceil(safeFee / numOutputs)
+                    console.log('FEE OFFSET', feeOffset)
+
+                    const outputs = response.map(_outputValue => {
+                        return {
+                            address: this.getFusionAddress(),
+                            value: (_outputValue - feeOffset),
+                            // value: (_outputValue - fee),
+                        }
+                    })
+
+                    bestTiers = {
+                        tierid: _tierScale,
+                        outputs,
+                    }
+                }
+            }
+        } catch (err) {
+            // console.error(err)
+        }
+    })
+    console.log('BEST TIERS', bestTiers)
+// return
+
+    /* Initialize components. */
+    // NOTE: Automatically add ALL fusion inputs.
+    components = [ ...fusionInputs ]
+
+    /* Set tier ID. */
+    tierid = bestTiers.tierid
+
+    /* Add best tiers to components. */
+    // NOTE: Best tiers index is derived from inputs index.
+    for (let i = 0; i < bestTiers.outputs.length; i++) {
+        /* Set tier. */
+        const tier = bestTiers.outputs[i]
+        console.log('TIER', tier)
+
+        /* Add (output) components. */
+        components.push(tier)
+    }
+
+    /* Validate components. */
+    if (components.length === 0) {
+        throw new Error('Oops! You MUST provide components to the Club.')
+    }
+
+    /* 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),
+            actionid: 'submit-components',
+            tierid,
+            components: blindComponents,
+        },
+    })
+    .catch(err => console.error(err))
+    console.log('RESPONSE', response)
+}

+ 237 - 153
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"
@@ -1205,6 +1210,22 @@
     scrypt-js "3.0.1"
     secure-ls "1.2.6"
 
+"@nexajs/[email protected]":
+  version "24.8.16"
+  resolved "https://registry.yarnpkg.com/@nexajs/crypto/-/crypto-24.8.16.tgz#1ec6b94b87a4b2a7a1d50e5c21f5e413f87ebffe"
+  integrity sha512-+Yraj8wLndReZWIWxW8e++yx5hrsOtknyMMqtpQTYGOOyJs/joVOQzSJJwp0KKtyutDIyCUHazI/nMG/PGtyiQ==
+  dependencies:
+    "@ethersproject/random" "5.7.0"
+    "@nexajs/script" "24.7.15"
+    "@nexajs/utils" "24.7.15"
+    bn.js "5.2.1"
+    bs58 "6.0.0"
+    crypto-js "4.2.0"
+    elliptic "6.5.5"
+    lodash "4.17.21"
+    scrypt-js "3.0.1"
+    secure-ls "1.2.6"
+
 "@nexajs/[email protected]":
   version "24.7.20"
   resolved "https://registry.yarnpkg.com/@nexajs/hdnode/-/hdnode-24.7.20.tgz#a0bd11d4b10a842da7c642d289a6ab33c7e57c96"
@@ -1356,11 +1377,6 @@
     events "3.3.0"
     numeral "2.0.6"
 
-"@noble/hashes@^1.2.0":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
-  integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==
-
 "@nodelib/[email protected]":
   version "2.1.5"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -1387,19 +1403,19 @@
   resolved "https://registry.yarnpkg.com/@nuxt/devalue/-/devalue-2.0.2.tgz#5749f04df13bda4c863338d8dabaf370f45ef7c7"
   integrity sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==
 
-"@nuxt/[email protected].9":
-  version "1.3.9"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-1.3.9.tgz#ad2dc18a76e2508913f1693105185051b45a1bd3"
-  integrity sha512-tgr/F+4BbI53/JxgaXl3cuV9dMuCXMsd4GEXN+JqtCdAkDbH3wL79GGWx0/6I9acGzRsB6UZ1H6U96nfgcIrAw==
+"@nuxt/[email protected].14":
+  version "1.3.14"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-1.3.14.tgz#b4d7d98e314e22f3f8388943c5819cd71d5f4d19"
+  integrity sha512-mLPuCf5nFYLm/1JD0twt8qfFGwoVhTRA4Zx9CPiyWCQNf7XJXb3TfhCm89vHpcPP+9T6ulZxRJp+JZETjXY8+A==
   dependencies:
-    "@nuxt/kit" "^3.12.2"
-    "@nuxt/schema" "^3.12.3"
+    "@nuxt/kit" "^3.12.4"
+    "@nuxt/schema" "^3.12.4"
     execa "^7.2.0"
 
-"@nuxt/[email protected].9":
-  version "1.3.9"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-wizard/-/devtools-wizard-1.3.9.tgz#a6737acbba52db3e0d253d3e42c2d702e5bee8ca"
-  integrity sha512-WMgwWWuyng+Y6k7sfBI95wYnec8TPFkuYbHHOlYQgqE9dAewPisSbEm3WkB7p/W9UqxpN8mvKN5qUg4sTmEpgQ==
+"@nuxt/[email protected].14":
+  version "1.3.14"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-wizard/-/devtools-wizard-1.3.14.tgz#15b574c2952edaa847844747556fe8a6abe7c75f"
+  integrity sha512-5kLB53/7YUME6Y8byrOxRhl0hXWm05jPStJd1CJHKDcGrp+hjxYZaSgEwYtEIQ0A1GF04rfL4bJ+qIL+7e0+9Q==
   dependencies:
     consola "^3.2.3"
     diff "^5.2.0"
@@ -1407,55 +1423,55 @@
     global-directory "^4.0.1"
     magicast "^0.3.4"
     pathe "^1.1.2"
-    pkg-types "^1.1.2"
+    pkg-types "^1.1.3"
     prompts "^2.4.2"
     rc9 "^2.1.2"
-    semver "^7.6.2"
+    semver "^7.6.3"
 
 "@nuxt/devtools@^1.3.9":
-  version "1.3.9"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools/-/devtools-1.3.9.tgz#9ceb62e3a1dc4d9aad141ab7c4aa76884258131e"
-  integrity sha512-tFKlbUPgSXw4tyD8xpztQtJeVn3egdKbFCV0xc92FbfGbclAyaa3XhKA2tMWXEGZQpykAWMRNrGWN24FtXFA6Q==
+  version "1.3.14"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools/-/devtools-1.3.14.tgz#5c2144797d2e9cdbab6198fd5907d3f2f61e7ca4"
+  integrity sha512-ebeVWBisXbhJ7begAZTgSDF8cPbExHv4RPDb9fWTMI1YoVVxX+elqUPw0K6T5Yi4atdGhyxRtGMqjikl7QKp9w==
   dependencies:
     "@antfu/utils" "^0.7.10"
-    "@nuxt/devtools-kit" "1.3.9"
-    "@nuxt/devtools-wizard" "1.3.9"
-    "@nuxt/kit" "^3.12.2"
+    "@nuxt/devtools-kit" "1.3.14"
+    "@nuxt/devtools-wizard" "1.3.14"
+    "@nuxt/kit" "^3.12.4"
     "@vue/devtools-core" "7.3.3"
     "@vue/devtools-kit" "7.3.3"
     birpc "^0.2.17"
     consola "^3.2.3"
     cronstrue "^2.50.0"
     destr "^2.0.3"
-    error-stack-parser-es "^0.1.4"
+    error-stack-parser-es "^0.1.5"
     execa "^7.2.0"
     fast-glob "^3.3.2"
-    fast-npm-meta "^0.1.1"
+    fast-npm-meta "^0.2.2"
     flatted "^3.3.1"
     get-port-please "^3.1.2"
     hookable "^5.5.3"
-    image-meta "^0.2.0"
+    image-meta "^0.2.1"
     is-installed-globally "^1.0.0"
-    launch-editor "^2.8.0"
+    launch-editor "^2.8.1"
     local-pkg "^0.5.0"
     magicast "^0.3.4"
     nypm "^0.3.9"
     ohash "^1.1.3"
     pathe "^1.1.2"
     perfect-debounce "^1.0.0"
-    pkg-types "^1.1.2"
+    pkg-types "^1.1.3"
     rc9 "^2.1.2"
     scule "^1.3.0"
-    semver "^7.6.2"
+    semver "^7.6.3"
     simple-git "^3.25.0"
     sirv "^2.0.4"
-    unimport "^3.7.2"
-    vite-plugin-inspect "^0.8.4"
-    vite-plugin-vue-inspector "^5.1.2"
+    unimport "^3.10.1"
+    vite-plugin-inspect "^0.8.6"
+    vite-plugin-vue-inspector "^5.1.3"
     which "^3.0.1"
-    ws "^8.17.1"
+    ws "^8.18.0"
 
-"@nuxt/[email protected]", "@nuxt/kit@^3.0.0", "@nuxt/kit@^3.11.1", "@nuxt/kit@^3.11.2", "@nuxt/kit@^3.12.2", "@nuxt/kit@^3.2.0", "@nuxt/kit@^3.6.5":
+"@nuxt/[email protected]", "@nuxt/kit@^3.0.0", "@nuxt/kit@^3.11.1", "@nuxt/kit@^3.11.2", "@nuxt/kit@^3.12.4", "@nuxt/kit@^3.2.0", "@nuxt/kit@^3.6.5":
   version "3.12.4"
   resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.12.4.tgz#b7073611d533ac32b504d95664074be3587046b3"
   integrity sha512-aNRD1ylzijY0oYolldNcZJXVyxdGzNTl+Xd0UYyFQCu9f4wqUZqQ9l+b7arCEzchr96pMK0xdpvLcS3xo1wDcw==
@@ -1495,7 +1511,7 @@
     postcss-url "^10.1.1"
     semver "^7.3.4"
 
-"@nuxt/[email protected]", "@nuxt/schema@^3.12.3":
+"@nuxt/[email protected]", "@nuxt/schema@^3.12.4":
   version "3.12.4"
   resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.12.4.tgz#295873c5e8bfbda0c9312bd16272373c936e6a71"
   integrity sha512-H7FwBV4ChssMaeiLyPdVLOLUa0326ebp3pNbJfGgFt7rSoKh1MmgjorecA8JMxOQZziy3w6EELf4+5cgLh/F1w==
@@ -2008,12 +2024,17 @@
   integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
 
 "@types/node@*":
-  version "22.4.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.4.1.tgz#9b595d292c65b94c20923159e2ce947731b6fdce"
-  integrity sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==
+  version "22.5.0"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.0.tgz#10f01fe9465166b4cab72e75f60d8b99d019f958"
+  integrity sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==
   dependencies:
     undici-types "~6.19.2"
 
+"@types/[email protected]":
+  version "10.12.18"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
+  integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
+
 "@types/[email protected]":
   version "11.11.6"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
@@ -2034,46 +2055,53 @@
   resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
   integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
 
-"@unhead/[email protected]", "@unhead/dom@^1.9.16":
-  version "1.9.16"
-  resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.9.16.tgz#2cafa10d213526e6c76e44333f1c9b6148141080"
-  integrity sha512-aZIAnnc89Csi1vV4mtlHYI765B7m1yuaXUuQiYHwr6glE9FLyy2X87CzEci4yPH/YbkKm0bGQRfcxXq6Eq0W7g==
+"@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:
-    "@unhead/schema" "1.9.16"
-    "@unhead/shared" "1.9.16"
+    "@types/node" "*"
 
-"@unhead/[email protected]":
-  version "1.9.16"
-  resolved "https://registry.yarnpkg.com/@unhead/schema/-/schema-1.9.16.tgz#cd650cc3a48e501f676bd8b0077acd5bac6776a5"
-  integrity sha512-V2BshX+I6D2wN4ys5so8RQDUgsggsxW9FVBiuQi4h8oPWtHclogxzDiHa5BH2TgvNIoUxLnLYNAShMGipmVuUw==
+"@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"
+  integrity sha512-LdgtOlyMHOyuQNsUKM+1d8ViiiY4LxjCPJlgUU/5CwgqeRYf4LWFu8oRMQfSQVTusbPwwvr3MolM9iTUu2I4BQ==
+  dependencies:
+    "@unhead/schema" "1.10.0"
+    "@unhead/shared" "1.10.0"
+
+"@unhead/[email protected]":
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/@unhead/schema/-/schema-1.10.0.tgz#6d0f0dd4de21ab6e7c16d2471691131f63b7cb24"
+  integrity sha512-hmgkFdLzm/VPLAXBF89Iry4Wz/6FpHMfMKCnAdihAt1Ublsi04RrA0hQuAiuGG2CZiKL4VCxtmV++UXj/kyakA==
   dependencies:
     hookable "^5.5.3"
     zhead "^2.2.4"
 
-"@unhead/[email protected]":
-  version "1.9.16"
-  resolved "https://registry.yarnpkg.com/@unhead/shared/-/shared-1.9.16.tgz#86ce5161cfff9d52ca06642852835910063bf1d0"
-  integrity sha512-pfJnArULCY+GBr7OtYyyxihRiQLkT31TpyK6nUKIwyax4oNOGyhNfk0RFzNq16BwLg60d1lrc5bd5mZGbfClMA==
+"@unhead/shared@1.10.0":
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/@unhead/shared/-/shared-1.10.0.tgz#5883996f1aa52a1267c65d75feec2801172ee199"
+  integrity sha512-Lv7pP0AoWJy+YaiWd4kGD+TK78ahPUwnIRx6YCC6FjPmE0KCqooeDS4HbInYaklLlEMQZislXyIwLczK2DTWiw==
   dependencies:
-    "@unhead/schema" "1.9.16"
+    "@unhead/schema" "1.10.0"
 
 "@unhead/ssr@^1.9.16":
-  version "1.9.16"
-  resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.9.16.tgz#f84192a9350288cc407582df852b6f0e58b0ebd5"
-  integrity sha512-8R1qt4VAemX4Iun/l7DnUBJqmxA/KaUSc2+/hRYPJYOopXdCWkoaxC1K1ROX2vbRF7qmjdU5ik/a27kSPN94gg==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.10.0.tgz#b3d976c5e9e0d603ace558b71a1536422ff5cf9a"
+  integrity sha512-L2XqGUQ05+a/zBAJk4mseLpsDoHMsuEsZNWp5f7E/Kx8P1oBAAs6J/963nvVFdec41HuClNHtJZ5swz77dmb1Q==
   dependencies:
-    "@unhead/schema" "1.9.16"
-    "@unhead/shared" "1.9.16"
+    "@unhead/schema" "1.10.0"
+    "@unhead/shared" "1.10.0"
 
 "@unhead/vue@^1.9.16":
-  version "1.9.16"
-  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-1.9.16.tgz#94b2db11fd0658f0ae26d9e4edaa7047081c7cc4"
-  integrity sha512-kpMWWwm8cOwo4gw4An43pz30l2CqNtmJpX5Xsu79rwf6Viq8jHAjk6BGqyKy220M2bpa0Va4fnR532SgGO1YgQ==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-1.10.0.tgz#6ee1feb131a92bbc4d6657dc5e68dc38fbcfb4a9"
+  integrity sha512-Cv9BViaOwCBdXy3bsTvJ10Rs808FSSq/ZfeBXzOjOxt08sbubf6Mr5opBdOlv/i1bzyFVIAqe5ABmrhC9mB80w==
   dependencies:
-    "@unhead/schema" "1.9.16"
-    "@unhead/shared" "1.9.16"
+    "@unhead/schema" "1.10.0"
+    "@unhead/shared" "1.10.0"
     hookable "^5.5.3"
-    unhead "1.9.16"
+    unhead "1.10.0"
 
 "@vercel/nft@^0.26.5":
   version "0.26.5"
@@ -2516,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"
@@ -2591,11 +2633,6 @@ base-x@^3.0.2:
   dependencies:
     safe-buffer "^5.0.1"
 
-base-x@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a"
-  integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==
-
 base-x@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b"
@@ -2624,11 +2661,6 @@ bech32@^1.1.2:
   resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
   integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
 
-bech32@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355"
-  integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==
-
 [email protected]:
   version "1.6.36"
   resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36"
@@ -2674,7 +2706,7 @@ binary-extensions@^2.0.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
   integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
 
-bindings@^1.4.0, bindings@^1.5.0:
+bindings@^1.3.0, bindings@^1.4.0, bindings@^1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
   integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
@@ -2691,11 +2723,24 @@ [email protected]:
     random-bytes "^1.0.0"
     safe-buffer "^5.0.1"
 
-bip174@^2.1.1:
+bip174@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f"
   integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==
 
+bip32@^2.0.4:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134"
+  integrity sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==
+  dependencies:
+    "@types/node" "10.12.18"
+    bs58check "^2.1.1"
+    create-hash "^1.2.0"
+    create-hmac "^1.1.7"
+    tiny-secp256k1 "^1.1.3"
+    typeforce "^1.11.5"
+    wif "^2.0.6"
+
 [email protected]:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/bip38/-/bip38-2.0.2.tgz#6f7762bc90b0bdf63b489ff95349354aecf9baee"
@@ -2731,17 +2776,31 @@ birpc@^0.2.17:
   resolved "https://registry.yarnpkg.com/birpc/-/birpc-0.2.17.tgz#d0bdb90d4d063061156637f03b7b0adea1779734"
   integrity sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==
 
[email protected]:
-  version "6.1.6"
-  resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.6.tgz#f57c17c82511f860f11946d784c18da39f8618a8"
-  integrity sha512-Fk8+Vc+e2rMoDU5gXkW9tD+313rhkm5h6N9HfZxXvYU9LedttVvmXKTgd9k5rsQJjkSfsv6XRM8uhJv94SrvcA==
+bitcoin-ops@^1.3.0, bitcoin-ops@^1.4.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278"
+  integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==
+
[email protected]:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz#caf8b5efb04274ded1b67e0706960b93afb9d332"
+  integrity sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==
   dependencies:
-    "@noble/hashes" "^1.2.0"
-    bech32 "^2.0.0"
-    bip174 "^2.1.1"
-    bs58check "^3.0.1"
+    bech32 "^1.1.2"
+    bip174 "^2.0.1"
+    bip32 "^2.0.4"
+    bip66 "^1.1.0"
+    bitcoin-ops "^1.4.0"
+    bs58check "^2.0.0"
+    create-hash "^1.1.0"
+    create-hmac "^1.1.3"
+    merkle-lib "^2.0.10"
+    pushdata-bitcoin "^1.0.1"
+    randombytes "^2.0.1"
+    tiny-secp256k1 "^1.1.1"
     typeforce "^1.11.3"
-    varuint-bitcoin "^1.1.2"
+    varuint-bitcoin "^1.0.4"
+    wif "^2.0.1"
 
 [email protected]:
   version "2.0.0"
@@ -2898,14 +2957,7 @@ [email protected]:
   dependencies:
     base-x "^5.0.0"
 
-bs58@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279"
-  integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==
-  dependencies:
-    base-x "^4.0.0"
-
-bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.0.2, bs58check@^2.1.2:
+bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.0.2, bs58check@^2.1.1, bs58check@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
   integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==
@@ -2914,14 +2966,6 @@ bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.0.2, bs58check@^2.1.2:
     create-hash "^1.1.0"
     safe-buffer "^5.1.2"
 
-bs58check@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-3.0.1.tgz#2094d13720a28593de1cba1d8c4e48602fdd841c"
-  integrity sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==
-  dependencies:
-    "@noble/hashes" "^1.2.0"
-    bs58 "^5.0.0"
-
 buffer-crc32@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405"
@@ -3652,7 +3696,7 @@ [email protected]:
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5:
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6:
   version "4.3.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b"
   integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
@@ -3969,9 +4013,21 @@ [email protected]:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.4:
-  version "1.5.12"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz#ee31756eaa2e06f2aa606f170b7ad06dd402b4e4"
-  integrity sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA==
+  version "1.5.13"
+  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"
@@ -3999,7 +4055,7 @@ [email protected]:
     minimalistic-assert "^1.0.1"
     minimalistic-crypto-utils "^1.0.1"
 
-elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.5:
+elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.5:
   version "6.5.7"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
   integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
@@ -4059,7 +4115,7 @@ error-ex@^1.3.1:
   dependencies:
     is-arrayish "^0.2.1"
 
-error-stack-parser-es@^0.1.4:
+error-stack-parser-es@^0.1.5:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz#15b50b67bea4b6ed6596976ee07c7867ae25bb1c"
   integrity sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==
@@ -4427,10 +4483,10 @@ fast-json-stable-stringify@^2.0.0:
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
   integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
 
-fast-npm-meta@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/fast-npm-meta/-/fast-npm-meta-0.1.1.tgz#2fb1e111595aec787ec523c06901ccb1d44a9422"
-  integrity sha512-uS9DjGncI/9XZ6HJFrci0WzSi++N8Jskbb2uB7+9SQlrgA3VaLhXhV9Gl5HwIGESHkayYYZFGnVNhJwRDKCWIA==
+fast-npm-meta@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/fast-npm-meta/-/fast-npm-meta-0.2.2.tgz#619e4ab6b71f4ce19d9fad48bba6ffa8164b7361"
+  integrity sha512-E+fdxeaOQGo/CMWc9f4uHFfgUPJRAu7N3uB8GBvB3SDPAIWJK4GKyYhkAGFq+GYrcbKNfQIz5VVQyJnDuPPCrg==
 
 fast-safe-stringify@^2.1.1:
   version "2.1.1"
@@ -5041,7 +5097,7 @@ ignore@^5.2.4, ignore@^5.3.1:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
   integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
 
-image-meta@^0.2.0:
+image-meta@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/image-meta/-/image-meta-0.2.1.tgz#3a9eb9f0bfd2f767ca2b0720623c2e03742aa29f"
   integrity sha512-K6acvFaelNxx8wc2VjbIzXKDVB0Khs0QT35U6NkGfTdCmjLNcO2945m7RFNR9/RPVFm48hq7QPzK8uGH18HCGw==
@@ -5172,9 +5228,9 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
   integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
 
 is-core-module@^2.13.0:
-  version "2.15.0"
-  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea"
-  integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
+  integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==
   dependencies:
     hasown "^2.0.2"
 
@@ -5602,7 +5658,7 @@ kolorist@^1.8.0:
   resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
   integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
 
-launch-editor@^2.8.0:
+launch-editor@^2.8.1:
   version "2.8.1"
   resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.8.1.tgz#3bda72af213ec9b46b170e39661916ec66c2f463"
   integrity sha512-elBx2l/tp9z99X5H/qev8uyDywVh0VXAwEbjk8kJhnc5grOFkGh7aW6q55me9xnYbss261XtnUrysZ+XvGbhQA==
@@ -5712,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"
@@ -6030,7 +6091,7 @@ mz@^2.7.0:
     object-assign "^4.0.1"
     thenify-all "^1.0.0"
 
-nan@^2.14.0:
+nan@^2.13.2, nan@^2.14.0:
   version "2.20.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3"
   integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==
@@ -6158,9 +6219,9 @@ nitropack@^2.9.7:
     unwasm "^0.3.9"
 
 node-abi@^3.3.0:
-  version "3.65.0"
-  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.65.0.tgz#ca92d559388e1e9cab1680a18c1a18757cdac9d3"
-  integrity sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==
+  version "3.67.0"
+  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.67.0.tgz#1d159907f18d18e18809dbbb5df47ed2426a08df"
+  integrity sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==
   dependencies:
     semver "^7.3.5"
 
@@ -6735,7 +6796,7 @@ pkg-dir@^5.0.0:
   dependencies:
     find-up "^5.0.0"
 
-pkg-types@^1.0.3, pkg-types@^1.1.1, pkg-types@^1.1.2, pkg-types@^1.1.3:
+pkg-types@^1.0.3, pkg-types@^1.1.1, pkg-types@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.1.3.tgz#161bb1242b21daf7795036803f28e30222e476e3"
   integrity sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==
@@ -7168,6 +7229,13 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
   integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
 
+pushdata-bitcoin@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz#15931d3cd967ade52206f523aa7331aef7d43af7"
+  integrity sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==
+  dependencies:
+    bitcoin-ops "^1.3.0"
+
 [email protected]:
   version "1.4.2"
   resolved "https://registry.yarnpkg.com/qr-scanner/-/qr-scanner-1.4.2.tgz#bc4fb88022a8c9be95c49527a1c8fb8724b47dc4"
@@ -7427,7 +7495,7 @@ rollup-plugin-visualizer@^5.12.0:
     source-map "^0.7.4"
     yargs "^17.5.1"
 
-rollup@^4.13.0, rollup@^4.18.0:
+rollup@^4.18.0, rollup@^4.20.0:
   version "4.21.0"
   resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.0.tgz#28db5f5c556a5180361d35009979ccc749560b9d"
   integrity sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==
@@ -7847,9 +7915,9 @@ stream-http@^3.2.0:
     xtend "^4.0.2"
 
 streamx@^2.15.0:
-  version "2.18.0"
-  resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.18.0.tgz#5bc1a51eb412a667ebfdcd4e6cf6a6fc65721ac7"
-  integrity sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==
+  version "2.19.0"
+  resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.19.0.tgz#c66a43ad667539e81967d1bacc68c1575eb9fdde"
+  integrity sha512-5z6CNR4gtkPbwlxyEqoDGDmWIzoNJqCBt4Eac1ICP9YaIT08ct712cFj0u1rx4F8luAuL+3Qc+RFIdI4OX00kg==
   dependencies:
     fast-fifo "^1.3.2"
     queue-tick "^1.0.1"
@@ -8204,6 +8272,17 @@ tiny-invariant@^1.1.0:
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
   integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
 
+tiny-secp256k1@^1.1.1, tiny-secp256k1@^1.1.3:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c"
+  integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==
+  dependencies:
+    bindings "^1.3.0"
+    bn.js "^4.11.8"
+    create-hmac "^1.1.7"
+    elliptic "^6.4.0"
+    nan "^2.13.2"
+
 tinyrainbow@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5"
@@ -8241,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"
@@ -8325,7 +8409,7 @@ typed-array-length@^1.0.6:
     is-typed-array "^1.1.13"
     possible-typed-array-names "^1.0.0"
 
-typeforce@^1.11.3, typeforce@^1.18.0:
+typeforce@^1.11.3, typeforce@^1.11.5, typeforce@^1.18.0:
   version "1.18.0"
   resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
   integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
@@ -8388,14 +8472,14 @@ unenv@^1.10.0, unenv@^1.9.0:
     node-fetch-native "^1.6.4"
     pathe "^1.1.2"
 
-unhead@1.9.16:
-  version "1.9.16"
-  resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.9.16.tgz#1b231f3fc308b1707704923d4894b76d2373cb69"
-  integrity sha512-FOoXkuRNDwt7PUaNE0LXNCb6RCz4vTpkGymz4tJ8rcaG5uUJ0lxGK536hzCFwFw3Xkp3n+tkt2yCcbAZE/FOvA==
+unhead@1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.10.0.tgz#8e63cd60d5090534da927a13a651a60d1ff7aa6e"
+  integrity sha512-nv75Hvhu0asuD/rbP6b3tSRJUltxmThq/iZU5rLCGEkCqTkFk7ruQGNk+TRtx/RCYqL0R/IzIY9aqvhNOGe3mg==
   dependencies:
-    "@unhead/dom" "1.9.16"
-    "@unhead/schema" "1.9.16"
-    "@unhead/shared" "1.9.16"
+    "@unhead/dom" "1.10.0"
+    "@unhead/schema" "1.10.0"
+    "@unhead/shared" "1.10.0"
     hookable "^5.5.3"
 
 unicorn-magic@^0.1.0:
@@ -8403,10 +8487,10 @@ unicorn-magic@^0.1.0:
   resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4"
   integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==
 
-unimport@^3.7.2, unimport@^3.9.0:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/unimport/-/unimport-3.10.0.tgz#a2a442679db2332d1f703fe7bb6d902dc1a93683"
-  integrity sha512-/UvKRfWx3mNDWwWQhR62HsoM3wxHwYdTq8ellZzMOHnnw4Dp8tovgthyW7DjTrbjDL+i4idOp06voz2VKlvrLw==
+unimport@^3.10.1, unimport@^3.7.2, unimport@^3.9.0:
+  version "3.11.0"
+  resolved "https://registry.yarnpkg.com/unimport/-/unimport-3.11.0.tgz#2107bbb2d9b0499d46d05eb47bf8c6ebcf37d9e8"
+  integrity sha512-mPrvWwy+li8TLUeglC7CIREFAbeEMkJ8X2Bhxg4iLdh+HraxjFyxqWv8V+4lzekoGHChx9ofv1qGOfvHBJBl0A==
   dependencies:
     "@rollup/pluginutils" "^5.1.0"
     acorn "^8.12.1"
@@ -8420,7 +8504,7 @@ unimport@^3.7.2, unimport@^3.9.0:
     pkg-types "^1.1.3"
     scule "^1.3.0"
     strip-literal "^2.1.0"
-    unplugin "^1.12.0"
+    unplugin "^1.12.2"
 
 universalify@^2.0.0:
   version "2.0.1"
@@ -8447,7 +8531,7 @@ unplugin-vue-router@^0.10.0:
     unplugin "^1.12.1"
     yaml "^2.5.0"
 
-unplugin@^1.1.0, unplugin@^1.10.0, unplugin@^1.11.0, unplugin@^1.12.0, unplugin@^1.12.1, unplugin@^1.3.1, unplugin@^1.3.2:
+unplugin@^1.1.0, unplugin@^1.10.0, unplugin@^1.11.0, unplugin@^1.12.1, unplugin@^1.12.2, unplugin@^1.3.1, unplugin@^1.3.2:
   version "1.12.2"
   resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.12.2.tgz#cc85aef010614394898caccf5f17002af8a4cd6f"
   integrity sha512-bEqQxeC7rxtxPZ3M5V4Djcc4lQqKPgGe3mAWZvxcSmX5jhGxll19NliaRzQSQPrk4xJZSGniK3puLWpRuZN7VQ==
@@ -8566,7 +8650,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
   integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
 
-varuint-bitcoin@^1.0.1, varuint-bitcoin@^1.0.4, varuint-bitcoin@^1.1.2:
+varuint-bitcoin@^1.0.1, varuint-bitcoin@^1.0.4:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92"
   integrity sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==
@@ -8614,15 +8698,15 @@ vite-plugin-checker@^0.7.2:
     vscode-languageserver-textdocument "^1.0.1"
     vscode-uri "^3.0.2"
 
-vite-plugin-inspect@^0.8.4:
-  version "0.8.5"
-  resolved "https://registry.yarnpkg.com/vite-plugin-inspect/-/vite-plugin-inspect-0.8.5.tgz#9063a03c6868c3c78bdddd3a64aa57f7fa9cd3b7"
-  integrity sha512-JvTUqsP1JNDw0lMZ5Z/r5cSj81VK2B7884LO1DC3GMBhdcjcsAnJjdWq7bzQL01Xbh+v60d3lju3g+z7eAtNew==
+vite-plugin-inspect@^0.8.6:
+  version "0.8.6"
+  resolved "https://registry.yarnpkg.com/vite-plugin-inspect/-/vite-plugin-inspect-0.8.6.tgz#35389ca9877853a23bab6e7bb758d2e83bf52ea1"
+  integrity sha512-iM/smnFRSuDq9UMVAN06fqBbHAofGDtB5yBucsl0QnPCFqQ2TmPIbsSgSR3gUv13qJ8oPE/FFhXlm9g1xX9nzg==
   dependencies:
     "@antfu/utils" "^0.7.10"
     "@rollup/pluginutils" "^5.1.0"
-    debug "^4.3.5"
-    error-stack-parser-es "^0.1.4"
+    debug "^4.3.6"
+    error-stack-parser-es "^0.1.5"
     fs-extra "^11.2.0"
     open "^10.1.0"
     perfect-debounce "^1.0.0"
@@ -8637,7 +8721,7 @@ [email protected]:
     "@rollup/plugin-inject" "^5.0.5"
     node-stdlib-browser "^1.2.0"
 
-vite-plugin-vue-inspector@^5.1.2:
+vite-plugin-vue-inspector@^5.1.3:
   version "5.1.3"
   resolved "https://registry.yarnpkg.com/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.1.3.tgz#b85c85c2a2d5fe5aa382039f3230068cc0837996"
   integrity sha512-pMrseXIDP1Gb38mOevY+BvtNGNqiqmqa2pKB99lnLsADQww9w9xMbAfT4GB6RUoaOkSPrtlXqpq2Fq+Dj2AgFg==
@@ -8653,13 +8737,13 @@ vite-plugin-vue-inspector@^5.1.2:
     magic-string "^0.30.4"
 
 vite@^5.0.0, vite@^5.3.4:
-  version "5.4.1"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.1.tgz#2aa72370de824d23f53658affd807e4c9905b058"
-  integrity sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==
+  version "5.4.2"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.2.tgz#8acb6ec4bfab823cdfc1cb2d6c53ed311bc4e47e"
+  integrity sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==
   dependencies:
     esbuild "^0.21.3"
     postcss "^8.4.41"
-    rollup "^4.13.0"
+    rollup "^4.20.0"
   optionalDependencies:
     fsevents "~2.3.3"
 
@@ -8859,7 +8943,7 @@ wide-align@^1.1.2:
   dependencies:
     string-width "^1.0.2 || 2 || 3 || 4"
 
[email protected], wif@^2.0.1:
[email protected], wif@^2.0.1, wif@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704"
   integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==
@@ -8908,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.17.1:
+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==