Bläddra i källkod

Migrate to "hosted" ElectrumX.

Shomari 1 månad sedan
förälder
incheckning
f5a43487fe

+ 3 - 3
web/components/Wallet/Welcome.vue

@@ -48,8 +48,8 @@ const consolidate = () => {
     alert('WIP?? sorry...')
 }
 
-const startFusions = () => {
-    Wallet.startFusions()
+const startFusion = () => {
+    Wallet.startFusion()
 }
 
 const init = () => {
@@ -151,7 +151,7 @@ onMounted(() => {
         <hr />
 
         <div class="my-3 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
-            <button @click="startFusions" 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">
+            <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>
 

+ 1 - 7
web/server/api/electrum.post.ts

@@ -43,7 +43,7 @@ export default defineEventHandler(async (event) => {
 
     /* Set (request) body. */
     body = await readBody(event)
-    console.log('BODY', body)
+    // console.log('BODY', body)
 
     if (!body || !body.method || !body.params) {
         return `Request FAILED!`
@@ -82,12 +82,10 @@ export default defineEventHandler(async (event) => {
 
                 /* Convert to script hash. */
                 hash160 = bchjs.Address.toHash160(params[i])
-                console.log('HASH160', hash160)
 
                 /* Set script. */
                 // FIXME Use `OP` and TypedArray.
                 script = `76a914${hash160}88ac`
-                // console.log('SCRIPT', script)
 
                 /* Conver to hex. */
                 script = hexToBin(script)
@@ -126,16 +124,12 @@ export default defineEventHandler(async (event) => {
                 /* Set address. */
                 address = params[i]
 
-                console.log('ADDRESS', address)
-
                 /* Convert to script hash. */
                 hash160 = bchjs.Address.toHash160(params[i])
-                console.log('HASH160', hash160)
 
                 /* Set script. */
                 // FIXME Use `OP` and TypedArray.
                 script = `76a914${hash160}88ac`
-                // console.log('SCRIPT', script)
 
                 /* Conver to hex. */
                 script = hexToBin(script)

+ 6 - 6
web/server/routes/v1.post.ts

@@ -51,7 +51,7 @@ export default defineEventHandler(async (event) => {
     components = decryptForPubkey(binToHex(Wallet.privateKey), components)
     components = binToUtf8(components)
     components = JSON.parse(components)
-    // console.log('COMPONENTS', components)
+    console.log('COMPONENTS', components)
 
     /* Validate auth ID. */
     if (typeof authid === 'undefined' || authid === null) {
@@ -99,6 +99,7 @@ console.log('FUSION', fusion)
     let componentid
 
     components.forEach(_component => {
+        /* Validate inputs. */
         if (_component.tx_hash) {
             componentid = sha256(_component.tx_hash + ':' + _component.tx_pos)
             fusion.inputs[componentid] = {
@@ -107,11 +108,10 @@ console.log('FUSION', fusion)
             }
         }
 
-        if (_component.tierid >= 10000) {
-            _component.outputs.forEach(_output => {
-                componentid = sha256(_output.address + ':' + _output.value)
-                fusion.outputs[componentid] = _output
-            })
+        /* Validate outputs. */
+        if (!_component.tx_hash) {
+            componentid = sha256(_component.address + ':' + _component.value)
+            fusion.outputs[componentid] = _component
         }
     })
 

+ 55 - 33
web/stores/wallet.ts

@@ -3,10 +3,7 @@ import { defineStore } from 'pinia'
 import moment from 'moment'
 
 import BCHJS from '@psf/bch-js'
-import { encryptForPubkey } from '@nexajs/crypto'
 import { mnemonicToEntropy } from '@nexajs/hdnode'
-import { randomOutputsForTier } from '@nexajs/privacy'
-import { binToHex } from '@nexajs/utils'
 import { Wallet } from '@nexajs/wallet'
 
 import _broadcast from './wallet/broadcast.ts'
@@ -14,7 +11,7 @@ import _completeFusion from './wallet/completeFusion.ts'
 import _setEntropy from './wallet/setEntropy.ts'
 import _setupKeychain from './wallet/setupKeychain.ts'
 import _setupHushKeychain from './wallet/setupHushKeychain.ts'
-import _startFusions from './wallet/startFusions.ts'
+import _startFusion from './wallet/startFusion.ts'
 
 /* Initialize constants. */
 const HUSH_PROTOCOL_ID = 0x48555348
@@ -146,11 +143,11 @@ export const useWalletStore = defineStore('wallet', {
             }
 
             const collection = _state._utxos
-            // console.log('COLLECTION', collection)
+            // console.log('COLLECTION (fusionInputs)', collection)
 
             const mainList = []
 
-            collection[0].forEach(_account => {
+            collection[0]?.forEach(_account => {
                 // console.log('ACCOUNT (0)', _account)
                 _account.utxos.forEach(_utxo => {
                     mainList.push({
@@ -160,7 +157,7 @@ export const useWalletStore = defineStore('wallet', {
                 })
             })
 
-            collection[HUSH_PROTOCOL_ID].forEach(_account => {
+            collection[HUSH_PROTOCOL_ID]?.forEach(_account => {
                 // console.log('ACCOUNT (1213551432)', _account)
                 _account.utxos.forEach(_utxo => {
                     mainList.push({
@@ -328,32 +325,51 @@ _setupHushKeychain.bind(this)()
             // console.log('HUSH ADDRESSES', hushAddresses)
 
             /* Request UTXO data. */
-            data = await bchjs.Electrumx.utxo(hushAddresses.slice(0, 20))
-                .catch(err => console.error(err))
+            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 bchjs.Electrumx.transactions(hushAddresses.slice(0, 5))
-            //     .catch(err => console.error(err))
-            // console.log('HUSH TX HISTORY', data)
-
-            // usedAddresses = data?.utxos.map(_utxo => {
-            //     if (_utxo.utxos.length > 0) {
-            //         return _utxo.address
-            //     }
-            // })
+            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 => {
-            //     const coin = coins[_coinid]
+            Object.keys(coins).forEach(_coinid => {
+                /* Set coin. */
+                const coin = coins[_coinid]
 
-            //     if (usedAddresses.includes(coin.address)) {
-            //         coin.isUsed = true
-            //     }
-            // })
+                /* 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) {
@@ -373,12 +389,18 @@ _setupHushKeychain.bind(this)()
                     .catch(err => console.error(err))
                 // console.log('BCH ADDRESS-3', bchAddress3)
 
-                data = await bchjs.Electrumx.utxo([
-                    bchAddress1,
-                    bchAddress2,
-                    bchAddress3,
-                ])
-                // console.log('WALLET DATA', data)
+                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
@@ -403,7 +425,7 @@ _setupHushKeychain.bind(this)()
 
                 return fusionAddress.isUsed === false && fusionAddress.isLocked === false
             })
-            console.log('ADDRESS IDX', addressIdx)
+            // console.log('ADDRESS IDX', addressIdx)
 
             if (typeof addressIdx !== 'undefined') {
                 /* Set locked flag. */
@@ -417,9 +439,9 @@ _setupHushKeychain.bind(this)()
             }
         },
 
-        async startFusions() {
+        async startFusion() {
             /* Start fusions. */
-            return _startFusions.bind(this)()
+            return _startFusion.bind(this)()
         },
 
         async completeFusion() {

+ 28 - 14
web/stores/wallet/startFusions.ts → web/stores/wallet/authFusion.ts

@@ -1,3 +1,12 @@
+/* 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...')
@@ -19,18 +28,9 @@ export default async function () {
     const feeOffset = 1034//10034
     const maxOutputCount = 17
 
-    /* Calculate input amount. */
-    // inputAmount = this.fusionInputs.reduce(
-    //     (acc, utxo) => (utxo.value > 10000) ? acc + utxo.value : 0, 0
-    // )
-    // console.log('INPUT AMOUNT', inputAmount)
-
     /* Clone fusion inputs. */
     fusionInputs = [ ...this.fusionInputs ]
 
-    /* Add inputs to components. */
-    components = [ ...fusionInputs ]
-
     const tierScales = [
         10000,      12000,      15000,      18000,      22000,      27000,      33000,      39000,      47000,      56000,      68000,      82000,
         100000,     120000,     150000,     180000,     220000,     270000,     330000,     390000,     470000,     560000,     680000,     820000,
@@ -65,11 +65,11 @@ export default async function () {
                     /* 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)
+                        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)
+                        console.log('FEE', fee)
 
                         const outputs = response.map(_outputValue => {
                             return {
@@ -93,14 +93,27 @@ export default async function () {
     console.log('BEST TIERS', bestTiers)
 // return
 
+    /* Initialize components. */
+    components = []
+
     /* Add best tiers to components. */
-    Object.keys(bestTiers).forEach(_tierid => {
-        const tier = bestTiers[_tierid]
+    // 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) 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)
@@ -123,6 +136,7 @@ export default async function () {
         method: 'POST',
         body: {
             authid: binToHex(this.wallet.publicKey),
+            actionid: 'submit-components',
             components: blindComponents,
         },
     })

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

@@ -0,0 +1,154 @@
+/* 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)
+
+                    const fee = bchjs.BitcoinCash
+                        .getByteCount({ P2PKH: fusionInputs.length }, { 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 = {
+                        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)
+}