7 コミット b048526b32 ... 557507b137

作者 SHA1 メッセージ 日付
  Shomari 557507b137 Fix "main" chain handling. 1 ヶ月 前
  Shomari a5b6cf149e Add (initial) sponsor addresses. 1 ヶ月 前
  Shomari 951632a25e Add $HUSH rewards to sponsors page. 1 ヶ月 前
  Shomari dc0d10861d Add WIF to fusion inputs. 1 ヶ月 前
  Shomari 808ce42fa5 Fix UTXOs update. 1 ヶ月 前
  Shomari 1b135d9ef9 Enable "manual" building + broadcast from shared details. 1 ヶ月 前
  Shomari 240622d9b8 Fix wallet creation issues? 1 ヶ月 前

+ 29 - 8
web/components/Wallet/Welcome.vue

@@ -27,7 +27,13 @@ const balance = computed(() => {
         return 0
     }
 
-    const totalValue = Wallet.fusionInputs.reduce(
+    const inputs = []
+
+    Object.keys(Wallet.fusionInputs).forEach(_outpoint => {
+        inputs.push(Wallet.fusionInputs[_outpoint])
+    })
+
+    const totalValue = inputs.reduce(
         (acc, utxo) => acc + utxo.value, 0
     )
     // console.log('TOTAL VALUE', totalValue)
@@ -43,8 +49,12 @@ const cashout = async () => {
     console.log('COMPLETE FUSION', response)
 }
 
-const consolidate = () => {
-    alert('WIP?? sorry...')
+const consolidate = async () => {
+    // alert('WIP?? sorry...')
+
+    const response = await Wallet.signFusion()
+        .catch(err => console.error(err))
+    console.log('SIGN FUSION', response)
 }
 
 const startFusion = () => {
@@ -55,22 +65,33 @@ const init = () => {
     /* Initialize locals. */
     let address
 
+    if (typeof props.cashAddress === 'undefined' || !props.cashAddress || props.cashAddress === '') {
+        console.log('RELOAD PAGE FOR CASH ADDRESS!!')
+        const router = useRouter()
+        // router.push({ path: "/admin/liquidity?setup-complete" })
+        router.push({ path: "/admin" })
+        return
+    }
+
     /* Initialize #? Hush addresses. */
     hushAddresses.value = []
 
     for (let i = 0; i < 20; i++) {
         // HUSH == 0x48555348 == 1,213,551,432
-        // address = await Wallet.getBchAddress(HUSH_PROTOCOL_ID, 0, i)
+        // address = 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)
     }
 
+
 }
 
 onMounted(() => {
     // console.log('KEYCHAIN', Wallet.keychain)
     // init()
+
+    // FIXME Race conditions with loading time and reloading...
     setTimeout(init, 1000)
 })
 
@@ -97,7 +118,7 @@ onMounted(() => {
                         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">
+                    <NuxtLink v-if="props.cashAddress" :to="'https://explorer.melroy.org/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>
 
@@ -105,7 +126,7 @@ onMounted(() => {
                         Current Balance
                     </h3>
 
-                    <h3 class="text-green-900 text-xl font-medium">
+                    <h3 v-if="balance" class="text-green-900 text-xl font-medium">
                         {{balance}}
                         <small class="text-sm">sats</small>
                     </h3>
@@ -114,7 +135,7 @@ onMounted(() => {
                 <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>
+                        <NuxtLink :to="'https://explorer.melroy.org/address/' + address.slice(12)" target="_blank" class="col-span-3 text-blue-500 hover:underline">{{address}}</NuxtLink>
                     </div>
                 </div>
             </div>
@@ -141,7 +162,7 @@ onMounted(() => {
                 <!-- <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>
+                        <NuxtLink :to="'https://explorer.melroy.org/address/' + address.slice(12)" target="_blank" class="col-span-3 text-blue-500 hover:underline">{{address}}</NuxtLink>
                     </div>
                 </div> -->
             </div>

+ 76 - 89
web/handlers/buildSharedTx.ts

@@ -7,115 +7,102 @@ import { utf8ToBin } from '@nexajs/utils'
 
 const bchjs = new BCHJS()
 
+/* Initialize constants. */
+const HUSH_PROTOCOL_ID = 0x48555348
+
+
 /**
  * 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) {
+export default function (_sessionid, _inputs, _outputs) {
+console.log('BUILD SHARED TX', _sessionid, _inputs, _outputs)
     /* Initialize locals. */
+    let accountIdx
+    let addressIdx
+    let changeIdx
+    let childNode
+    let data
+    let ecPair
+    // let ownedInputs
+    let protocolId
+    let msg
     let rawTx
-    let safeBalance
-    let utxo
+    let redeemScript
+    let script
+    let wif
 
+    /* Initialize transaction builde.r */
     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)
+console.log('DO WE HAVE FUSION INPUTS??', this.fusionInputs)
 
+    /* Initialize our addresses. */
+    const ourAddresses = []
 
+    /* Handle fusion inputs. */
+    Object.keys(this.fusionInputs).forEach(_outpoint => {
+        const ourInput = this.fusionInputs[_outpoint]
+        console.log('OUR INPUT', ourInput)
 
-        // 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)
+        ourAddresses.push(ourInput.address)
     })
+    console.log('OUR ADDRESSES', ourAddresses)
 
-    /* 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)
+    /* Initialize address index. */
+    // addressIdx = 0
 
-/* Set account index. */
-const accountIdx = 0
-/* Set change index. */
-const changeIdx = 0
-/* Set address index. */
-const addressIdx = 0
+    /* Initialize owned inputs. */
+    // ownedInputs = []
 
-    /* Generate child node. */
-    const chidleNode = masterNode
-        .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
+    /* Handle inputs. */
+    _inputs.forEach(_input => {
+        /* Add input. */
+        transactionBuilder.addInput(_input.tx_hash, _input.tx_pos)
 
-    /* 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,
-    )
+// console.log('VALIDATING (SELF) INPUT', _input)
+//         /* Validate (our) address. */
+//         if (ourAddresses.includes(_input.address)) {
+//             ownedInputs.push(addressIdx)
+//         }
+//         addressIdx++
+    })
+    // console.log('OUR INPUTS INDEX', ownedInputs)
+
+    /* Set protocol ID. */
+    protocolId = '1337'
+
+    /* Set protocol message. */
+    msg = 'FINAL!'
+
+    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)
+    })
 
     /* Generate (incomplete) transaction. */
-    const tx = transactionBuilder.transaction.buildIncomplete()
-    // console.log('TRANSACTION', tx)
-
-    /* Convert to (raw) hex. */
-    rawTx = tx.toHex()
+    const transaction = transactionBuilder.transaction.buildIncomplete()
+    console.log('TRANSACTION', transaction)
 
     /* Return raw (hex) transaction. */
-    return rawTx
+    // return transaction
+    return transactionBuilder
 }

+ 67 - 36
web/handlers/signSharedTx.ts

@@ -5,6 +5,7 @@ import { mnemonicToSeed } from '@nexajs/hdnode'
 import { encodeNullData } from '@nexajs/script'
 import { utf8ToBin } from '@nexajs/utils'
 
+/* Initialize BCHJS. */
 const bchjs = new BCHJS()
 
 /* Initialize constants. */
@@ -16,20 +17,19 @@ const HUSH_PROTOCOL_ID = 0x48555348
  *
  * Combine all participating inputs and outputs into one (signed) transaction.
  */
-export default function (_sessionid, _mnemonic, _inputs, _outputs) {
+export default function (_sessionid, _inputs, _outputs) {
 console.log('SIGN SHARED TX', _sessionid, _inputs, _outputs)
     /* Initialize locals. */
-    let accountIdx
-    let addressIdx
-    let changeIdx
-    let childNode
+    // let accountIdx
+    // let addressIdx
+    // let changeIdx
+    // let childNode
     let data
     let ecPair
-    let hushIndexes
-    let ownedInputs
+    // let ownedInputs
     let protocolId
     let msg
-    let rawTx
+    // let rawTx
     let redeemScript
     let script
     let wif
@@ -37,17 +37,45 @@ console.log('SIGN SHARED TX', _sessionid, _inputs, _outputs)
     /* Initialize transaction builde.r */
     const transactionBuilder = new bchjs.TransactionBuilder()
 
+console.log('DO WE HAVE FUSION INPUTS??', this.fusionInputs)
+
+    /* Initialize our addresses. */
+    const ourAddresses = []
+
+    /* Handle fusion inputs. */
+    Object.keys(this.fusionInputs).forEach(_outpoint => {
+        const ourInput = this.fusionInputs[_outpoint]
+        console.log('OUR INPUT', ourInput)
+
+        ourAddresses.push(ourInput.address)
+    })
+    console.log('OUR ADDRESSES', ourAddresses)
+
+    /* Initialize address index. */
+    // addressIdx = 0
+
+    /* Initialize owned inputs. */
+    // ownedInputs = []
+
     /* Handle inputs. */
     _inputs.forEach(_input => {
         /* Add input. */
         transactionBuilder.addInput(_input.tx_hash, _input.tx_pos)
+
+// console.log('VALIDATING (SELF) INPUT', _input)
+//         /* Validate (our) address. */
+//         if (ourAddresses.includes(_input.address)) {
+//             ownedInputs.push(addressIdx)
+//         }
+//         addressIdx++
     })
+    // console.log('OUR INPUTS INDEX', ownedInputs)
 
     /* Set protocol ID. */
     protocolId = '1337'
 
     /* Set protocol message. */
-    msg = 'finalization...'
+    msg = 'FINAL!'
 
     script = [
         utf8ToBin(protocolId),
@@ -71,69 +99,72 @@ console.log('SIGN SHARED TX', _sessionid, _inputs, _outputs)
         transactionBuilder.addOutput(_output.address, _output.value)
     })
 
-
-
     /* Convert mnemonic to seed. */
-    const seed = mnemonicToSeed(_mnemonic)
+    // const seed = mnemonicToSeed(_mnemonic)
 
     /* Conver to seed buffer. */
     // FIXME Migrate to TypedArrays.
-    const seedBuffer = Buffer.from(seed, 'hex')
+    // const seedBuffer = Buffer.from(seed, 'hex')
 
     /* Generate master node. */
-    const masterNode = bchjs.HDNode.fromSeed(seedBuffer)
+    // const masterNode = bchjs.HDNode.fromSeed(seedBuffer)
 
 // FIXME Identify our owned inputs.
-ownedInputs = [ 0, 1, 2, 3, 4, 5, 6 ]
-// hushIndexes = [ 0, 1, 2, 3, 4, 5, 6 ]
-// 0 - L58m5XW3cYG84ZN5Cbqqap7Mvx1TihNNMPSq7Vw9W7t3oHeuS36q
-// 1 - L2qsjy4xprtF2hocseepEtKM3V6y5hHjvkX5K7zedeBjmd6forbb
-// 2 - L4FmiWogy4THbosEUHbfLgxgTQpZ1e4dFDrAoik2fvoQ9TNKCsRe
+// ownedInputs = [ 0, 1, 2, 3, 4, 5, 6 ]
+
     for (let i = 0; i < _inputs.length; i++) {
+        const input = _inputs[i]
+        console.log('AN INPUT---', input)
+
+        const address = input.address
+        console.log('INPUT ADDR', address)
         /* Verify input ownership. */
-        if (!ownedInputs.includes(i)) {
+        // if (!ownedInputs.includes(i)) {
+        // if (typeof input.wif === 'undefined' || input.wif === null) {
+        if (!ourAddresses.includes(address)) {
             continue
         }
 
         /* Set account index. */
         // accountIdx = 0
-        accountIdx = HUSH_PROTOCOL_ID
+        // accountIdx = HUSH_PROTOCOL_ID
         /* Set change index. */
-        changeIdx = 0
+        // changeIdx = 0
         /* Set address index. */
-        // addressIdx = 0
-        // addressIdx = hushIndexes[i]
-        addressIdx = _inputs[i].address_idx
-        console.log('ADDRESS IDX')
+        // addressIdx = _inputs[i].address_idx
+        // console.log('ADDRESS IDX')
 
         /* Generate child node. */
-        childNode = masterNode
-            .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
+        // childNode = masterNode
+        //     .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
 
         /* Generate wallet import format (WIF). */
-        wif = bchjs.HDNode.toWIF(childNode)
-        // console.log('BCH WIF', i, wif)
+        // wif = bchjs.HDNode.toWIF(childNode)
+
+        /* Set WIF. */
+        wif = this.getWifForAddress(address)
+        console.log('BCH WIF', wif)
 
         /* Generate elliptic pair. */
         ecPair = bchjs.ECPair.fromWIF(wif)
-console.log('PARAMS', i, redeemScript, _inputs[i].value)
+console.log('SIGNING!', i, redeemScript, input.value)
         /* Sign (our own) input. */
         transactionBuilder.sign(
             i,
             ecPair,
             redeemScript,
             Transaction.SIGHASH_ALL,
-            _inputs[i].value,
+            input.value,
         )
     }
 
     /* Generate (incomplete) transaction. */
-    const tx = transactionBuilder.transaction.buildIncomplete()
-    // console.log('TRANSACTION', tx)
+    const transaction = transactionBuilder.transaction.buildIncomplete()
+    // console.log('TRANSACTION', transaction)
 
     /* Convert to (raw) hex. */
-    rawTx = tx.toHex()
+    // rawTx = tx.toHex()
 
     /* Return raw (hex) transaction. */
-    return rawTx
+    return transaction
 }

+ 170 - 17
web/pages/sponsors/[...pageid].vue

@@ -19,10 +19,115 @@ console.log('NETWORK ID', networkid)
 const campaignid = route?.params?.pageid[1]
 console.log('CAMPAIGN ID', campaignid)
 
-// onMounted(() => {
-//     console.log('Mounted!')
-//     // Now it's safe to perform setup operations.
-// })
+const campaigns = ref(null)
+const campaignTitle = ref(null)
+const donationAddress = ref(null)
+const networkTitle = ref(null)
+
+const init = () => {
+    /* Handle campaign id. */
+    switch(campaignid) {
+    case 'seekers':
+        campaignTitle.value = 'Privacy Seekers'
+        break
+    case 'masters':
+        campaignTitle.value = 'Privacy Masters'
+        break
+    case 'gurus':
+        campaignTitle.value = 'Privacy Gurus'
+        break
+    }
+
+    /* Handle network id. */
+    switch(networkid) {
+    case 'btc':
+        networkTitle.value = 'Bitcoin'
+        break
+    case 'bch':
+        networkTitle.value = 'Bitcoin Cash'
+        break
+    case 'nexa':
+        networkTitle.value = 'Nexa'
+        break
+    }
+
+    /* Initialize ALL campaigns handler. */
+    campaigns.value = {}
+
+    /* Initialize Seekers handler. */
+    campaigns.value['seekers'] = {}
+
+    /* Initialize Masters handler. */
+    campaigns.value['masters'] = {}
+
+    /* Initialize Gurus handler. */
+    campaigns.value['gurus'] = {}
+
+    /* Initialize Seekers (Bitcoin) handler. */
+    campaigns.value['seekers']['btc'] = {
+        address: 'bitcoin:3Gwb2zrg64REcnDFdGHeAUMuibfpk932Kr',
+        round: 1,
+        goal: 0,
+        donated: 0,
+        hasAirdrop: true,
+        createdAt: 0,
+        updatedAt: 0,
+    }
+
+    /* Initialize Seekers (Bitcoin Cash) handler. */
+    campaigns.value['seekers']['bch'] = {
+        address: 'bitcoincash:qrqdff68n8wm757kvfumylxs47tud3fuvqz3h6depv',
+        round: 1,
+        goal: 0,
+        donated: 0,
+        hasAirdrop: true,
+        createdAt: 0,
+        updatedAt: 0,
+    }
+
+    /* Initialize Seekers (Nexa) handler. */
+    campaigns.value['seekers']['nexa'] = {
+        address: 'nexa:nqtsq5g5fxezfwrhc323dm9npzy77lay7p26x903hkk4u8zu',
+        round: 1,
+        goal: 0,
+        donated: 0,
+        hasAirdrop: true,
+        createdAt: 0,
+        updatedAt: 0,
+    }
+
+    campaigns.value['masters']['btc'] = {
+        address: 'bitcoin:3EQELPmJREqt3gVyELGYFnzLxV6Lnthudk',
+        round: 1,
+        goal: 0,
+        donated: 0,
+        hasAirdrop: true,
+        createdAt: 0,
+        updatedAt: 0,
+    }
+    campaigns.value['masters']['bch'] = {}
+    campaigns.value['masters']['nexa'] = {}
+
+    campaigns.value['gurus']['btc'] = {
+        address: 'bitcoin:34XzM3e3Tsf4wLGGcFCPRyoTP1NF6HF53z',
+        round: 1,
+        goal: 0,
+        donated: 0,
+        hasAirdrop: true,
+        createdAt: 0,
+        updatedAt: 0,
+    }
+    campaigns.value['gurus']['bch'] = {}
+    campaigns.value['gurus']['nexa'] = {}
+
+    /* Set donation address. */
+    donationAddress.value = campaigns.value[campaignid][networkid].address
+
+}
+
+onMounted(() => {
+    init()
+})
 
 // onBeforeUnmount(() => {
 //     console.log('Before Unmount!')
@@ -33,9 +138,13 @@ console.log('CAMPAIGN ID', campaignid)
 <template>
     <main class="max-w-5xl mx-auto py-5 flex flex-col gap-4">
         <h1 class="text-5xl font-medium">
-            Sponsors — {{ networkid }} — {{ campaignid }}
+            Sponsors for {{networkTitle}}
         </h1>
 
+        <h2 class="text-5xl font-light italic">
+            {{campaignTitle}}
+        </h2>
+
         <section class="flex flex-col gap-4">
             <h2 class="text-rose-500 text-2xl font-bold uppercase">
                 Do Not Send From ANY Exchange
@@ -45,6 +154,10 @@ console.log('CAMPAIGN ID', campaignid)
                 Rewards tokens, ie $HUSH, will be sent directly to Sponsors point of delivery.
                 In order to GUARANTEE receipt of ALL rewards, please send from a Wallet/Address that you HOLD THE KEYS.
             </p>
+
+            <div>
+                0 of 27 campaigns completed for $HUSH airdrop!
+            </div>
         </section>
 
         <section>
@@ -63,33 +176,73 @@ console.log('CAMPAIGN ID', campaignid)
             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>
+        <NuxtLink :to="donationAddress" v-if="campaigns" class="p-5 flex flex-col gap-3 bg-sky-100 border-4 border-sky-300 rounded-3xl shadow">
+            <h3 class="text-gray-500 text-3xl font-light tracking-widest">
+                {{networkTitle}}
+            </h3>
+
+            <h3 class="font-medium tracking-tight">
+                {{donationAddress}}
+            </h3>
+        </NuxtLink>
+
+        <section v-if="campaigns">
             <h2>
-                Bitcoin Seekers Round 1
+                Privacy Masters
             </h2>
 
             <h3>
-                3FDveRn9A2cdqJqy7LDQu48crEfYLJhYDV
+                Bitcoin
             </h3>
-        </section>
 
-        <section>
-            <h2>
-                Bitcoin Cash Seekers Round 1
-            </h2>
+            <h3>
+                {{campaigns.masters.btc.address}}
+            </h3>
+
+            <h3>
+                Bitcoin Cash
+            </h3>
+
+            <h3>
+                bitcoincash:qzl4kq5wapcpae9zc7sql5hz5xmj8fqrqs5c7kgywy
+            </h3>
 
             <h3>
-                qzdyw35wslfrk6qjrcva2ujzkwv5a6ufl5uhg5xjkg
+                Nexa
+            </h3>
+
+            <h3>
+                nexa:nqtsq5g5egqz48van0zjx2xtxl38ec3czapdtryewwe5he0z
             </h3>
         </section>
 
-        <section>
+        <section v-if="campaigns">
             <h2>
-                Nexa Seekers Round 1
+                Privacy Gurus
             </h2>
 
             <h3>
-                nexa:XXX
+                Bitcoin
+            </h3>
+
+            <h3>
+                {{campaigns.gurus.btc.address}}
+            </h3>
+
+            <h3>
+                Bitcoin Cash
+            </h3>
+
+            <h3>
+                bitcoincash:qqfk0ag6tnzgxevfzp9ma7qw0npd9vwe65v40e22a9
+            </h3>
+
+            <h3>
+                Nexa
+            </h3>
+
+            <h3>
+                nexa:nqtsq5g5ya3jv0tv36je7d8ld8vsds0vfqmt0g9lryhqnv6m
             </h3>
         </section>
     </main>

+ 106 - 20
web/pages/sponsors/index.vue

@@ -47,12 +47,16 @@ const System = useSystemStore()
         <SponsorHeading />
 
         <div>
-            <h2 class="text-4xl font-medium">
-                Privacy Seekers - Unlock Higher Tiers
+            <h3 class="pl-3 text-sky-300 text-2xl font-medium uppercase">
+                For Privacy Seekers
+            </h3>
+
+            <h2 class="text-sky-700 text-7xl font-light italic">
+                Unlock Higher Tiers
             </h2>
 
-            <p class="py-5">
-                Will introduce higher blending tiers for large bag holders.
+            <p class="py-5 text-gray-600 text-lg leading-7">
+                Will introduce higher CoinJoin tiers for large bag holders.
             </p>
 
             <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
@@ -62,13 +66,17 @@ const System = useSystemStore()
                     </h2>
 
                     <h3 class="py-2 text-sm">
-                        Unlock the 0.1 BTC tier for blender pools
+                        Unlock the 0.1 BTC tier for CoinJoin pools
                     </h3>
 
                     <h3 class="px-3 py-1 bg-amber-200 text-center text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow group-hover:bg-sky-700 group-hover:text-sky-100 group-hover:border-sky-300">
                         <span class="text-2xl font-bold">0.100000 BTC</span>
                         <span class="block text-sm">remaining to unlock the next tier</span>
                     </h3>
+
+                    <p class="mt-3 text-xs text-gray-700 text-center italic">
+                        + qualified for $HUSH BRC-20 token airdrop
+                    </p>
                 </NuxtLink>
 
                 <NuxtLink to="/sponsors/bch/seekers" class="group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 hover:bg-sky-100">
@@ -77,13 +85,17 @@ const System = useSystemStore()
                     </h2>
 
                     <h3 class="py-2 text-sm">
-                        Unlock the 10 BCH tier for blender pools
+                        Unlock the 10 BCH tier for CoinJoin pools
                     </h3>
 
                     <h3 class="px-3 py-1 bg-amber-200 text-center text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow group-hover:bg-sky-700 group-hover:text-sky-100 group-hover:border-sky-300">
                         <span class="text-2xl font-bold">10.000000 BCH</span>
                         <span class="block text-sm">remaining to unlock the next tier</span>
                     </h3>
+
+                    <p class="mt-3 text-xs text-gray-700 text-center italic">
+                        + qualified for $HUSH cash token airdrop
+                    </p>
                 </NuxtLink>
 
                 <NuxtLink to="/sponsors/nexa/seekers" class="group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 hover:bg-sky-100">
@@ -92,12 +104,16 @@ const System = useSystemStore()
                     </h2>
 
                     <h3 class="py-2 text-sm">
-                        Adding 1B NEXA, 10B NEXA and 100B NEXA blender pools
+                        Adding 1B NEXA, 10B NEXA and 100B NEXA CoinJoin pools
                     </h3>
 
                     <h3 class="px-3 py-1 bg-amber-200 text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow group-hover:bg-sky-700 group-hover:text-sky-100 group-hover:border-sky-300">
                         <span class="text-2xl font-bold">1.00B NEXA</span> to unlock next tier
                     </h3>
+
+                    <p class="mt-3 text-xs text-gray-700 text-center italic">
+                        + qualified for $HUSH native token airdrop
+                    </p>
                 </NuxtLink>
 
                 <div class="cursor-not-allowed group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 opacity-30">
@@ -106,7 +122,7 @@ const System = useSystemStore()
                     </h2>
 
                     <h3 class="py-2 text-sm">
-                        Adding 50 LTC, 500 LTC and 5K LTC blender pools
+                        Adding 50 LTC, 500 LTC and 5K LTC CoinJoin pools
                     </h3>
 
                     <h3 class="px-3 py-1 bg-amber-200 text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow">
@@ -120,7 +136,7 @@ const System = useSystemStore()
                     </h2>
 
                     <h3 class="py-2 text-sm">
-                        Adding 5K FLUX, 50K FLUX and 500K FLUX blender pools
+                        Adding 5K FLUX, 50K FLUX and 500K FLUX CoinJoin pools
                     </h3>
 
                     <h3 class="px-3 py-1 bg-amber-200 text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow">
@@ -134,7 +150,7 @@ const System = useSystemStore()
                     </h2>
 
                     <h3 class="py-2 text-sm">
-                        Adding 5K XEC, 50K XEC and 500K XEC blender pools
+                        Adding 5K XEC, 50K XEC and 500K XEC CoinJoin pools
                     </h3>
 
                     <h3 class="px-3 py-1 bg-amber-200 text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow">
@@ -142,19 +158,64 @@ const System = useSystemStore()
                     </h3>
                 </div>
 
-                <!-- <div class="cursor-not-allowed group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 opacity-30">
+                <div class="cursor-not-allowed group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 opacity-30">
+                    <h2 class="text-xl text-gray-700 font-medium tracking-widest uppercase">
+                        Doge
+                    </h2>
+
+                    <h3 class="py-2 text-sm">
+                        Adding 5K DOGE, 50K DOGE and 500K DOGE CoinJoin pools
+                    </h3>
+
+                    <h3 class="px-3 py-1 bg-amber-200 text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow">
+                        <span class="text-2xl font-bold">5K DOGE</span> to unlock next tier
+                    </h3>
+                </div>
+
+                <div class="cursor-not-allowed group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 opacity-30">
                     <h2 class="text-xl text-gray-700 font-medium tracking-widest uppercase">
                         Dash
                     </h2>
 
                     <h3 class="py-2 text-sm">
-                        Adding 5K DASH, 50K DASH and 500K DASH blender pools
+                        Adding 5K DASH, 50K DASH and 500K DASH CoinJoin pools
                     </h3>
 
                     <h3 class="px-3 py-1 bg-amber-200 text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow">
                         <span class="text-2xl font-bold">5K DASH</span> to unlock next tier
                     </h3>
-                </div> -->
+                </div>
+
+                <div class="cursor-not-allowed group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 opacity-30">
+                    <h2 class="text-xl text-gray-700 font-medium tracking-widest uppercase">
+                        Kaspa
+                    </h2>
+
+                    <h3 class="py-2 text-sm">
+                        Adding 50K KAS, 500K KAS and 5M KAS CoinJoin pools
+                    </h3>
+
+                    <h3 class="px-3 py-1 bg-amber-200 text-lg text-amber-900 font-medium border border-amber-400 rounded-lg shadow">
+                        <span class="text-2xl font-bold">50K KAS</span> to unlock next tier
+                    </h3>
+                </div>
+
+                <div class="cursor-help group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300">
+                    <h2 class="flex items-center gap-2 text-xl text-rose-700 font-medium tracking-widest uppercase">
+                        Sponsors Vote
+
+                        <!-- <svg class="h-8 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="m4.5 12.75 6 6 9-13.5"></path>
+                        </svg> -->
+                        <svg class="h-8 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="M9 12.75 11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 0 1-1.043 3.296 3.745 3.745 0 0 1-3.296 1.043A3.745 3.745 0 0 1 12 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 0 1-3.296-1.043 3.745 3.745 0 0 1-1.043-3.296A3.745 3.745 0 0 1 3 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 0 1 1.043-3.296 3.746 3.746 0 0 1 3.296-1.043A3.746 3.746 0 0 1 12 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 0 1 3.296 1.043 3.746 3.746 0 0 1 1.043 3.296A3.745 3.745 0 0 1 21 12Z"></path>
+                        </svg>
+                    </h2>
+
+                    <p class="py-2 text-sm italic leading-6">
+                        All Sponsors will be invited to vote for the <span class="font-bold">FINAL network integration</span> into the inaugural launch of the HYM cloud network, before <span class="font-bold">"locking in $HUSH rewards"</span> and moving on to Phase II (Zero-Knowledge + Incognito Cash).
+                    </p>
+                </div>
 
             </div>
 
@@ -164,11 +225,15 @@ const System = useSystemStore()
         <SponsorHeading />
 
         <div>
-            <h2 class="text-4xl font-medium">
-                Privacy Masters - Wallet Integrations + Dev Kits
+            <h3 class="pl-3 text-sky-300 text-2xl font-medium uppercase">
+                For Privacy Masters
+            </h3>
+
+            <h2 class="text-sky-700 text-7xl font-light italic">
+                Wallet Integrations + Dev Kits
             </h2>
 
-            <p class="py-5">
+            <p class="py-5 text-gray-600 text-lg leading-7">
                 Will offer native integration with the ecosystem's MOST popular wallets.
             </p>
 
@@ -209,6 +274,10 @@ const System = useSystemStore()
                             Unstoppable wallet
                         </li>
                     </ol>
+
+                    <p class="mt-3 text-xs text-gray-700 text-center italic">
+                        + qualified for $HUSH BRC-20 token airdrop
+                    </p>
                 </NuxtLink>
 
                 <section>
@@ -328,12 +397,17 @@ const System = useSystemStore()
         <SponsorHeading />
 
         <div>
-            <h2 class="text-4xl font-medium">
-                Privacy Gurus - Native Apps
+            <h3 class="pl-3 text-sky-300 text-2xl font-medium uppercase">
+                For Privacy Gurus
+            </h3>
+
+            <h2 class="text-sky-700 text-7xl font-light italic">
+                Incognito Cash Apps
             </h2>
 
-            <p class="py-5">
-                Will introduce native Android and iOS applications.
+            <p class="py-5 text-gray-600 text-lg leading-7">
+                We will introduce native Android and iOS applications to support the NEW Incognito network
+                — supporting ALL networks with $NITO native assets.
             </p>
 
             <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
@@ -359,6 +433,10 @@ const System = useSystemStore()
                         <li>Mac desktop app</li>
                         <li>Linux desktop app</li>
                     </ol>
+
+                    <p class="mt-3 text-xs text-gray-700 text-center italic">
+                        + qualified for $HUSH BRC-20 token airdrop
+                    </p>
                 </NuxtLink>
 
                 <div class="cursor-not-allowed group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 opacity-30">
@@ -383,6 +461,10 @@ const System = useSystemStore()
                         <li>Mac desktop app</li>
                         <li>Linux desktop app</li>
                     </ol>
+
+                    <p class="mt-3 text-xs text-gray-700 text-center italic">
+                        + qualified for $HUSH cash token airdrop
+                    </p>
                 </div>
 
                 <div class="cursor-not-allowed group px-5 py-7 bg-sky-200 rounded-xl border border-sky-300 opacity-30">
@@ -407,6 +489,10 @@ const System = useSystemStore()
                         <li>Mac desktop app</li>
                         <li>Linux desktop app</li>
                     </ol>
+
+                    <p class="mt-3 text-xs text-gray-700 text-center italic">
+                        + qualified for $HUSH native token airdrop
+                    </p>
                 </div>
 
             </div>

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

@@ -28,6 +28,7 @@ export default defineEventHandler(async (event) => {
     let response
     let session
     let success
+    let unlocked
 
     /* Set database. */
     const Db = event.context.Db
@@ -58,11 +59,23 @@ export default defineEventHandler(async (event) => {
     // 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 components. */
+    if (body.components) {
+        components = body.components
+        components = decryptForPubkey(binToHex(Wallet.privateKey), components)
+        components = binToUtf8(components)
+        components = JSON.parse(components)
+        console.log('COMPONENTS', components)
+    }
+
+    /* Validate unlocked. */
+    if (body.unlocked) {
+        unlocked = body.unlocked
+        unlocked = decryptForPubkey(binToHex(Wallet.privateKey), unlocked)
+        unlocked = binToUtf8(unlocked)
+        unlocked = JSON.parse(unlocked)
+        console.log('UNLOCKED', unlocked)
+    }
 
     /* Validate auth ID. */
     if (typeof authid === 'undefined' || authid === null) {
@@ -118,6 +131,42 @@ console.log('FUSION', fusion)
         return 'Oops! Authorization failed!'
     }
 
+
+
+    if (actionid === 'unlock-components') {
+        /* Update progress. */
+        // fusion.progress = 75.5
+
+        /* Set (new) updated at (timestamp). */
+        fusion.updatedAt = moment().unix()
+
+        console.log('***UNLOCK COMPONENT(S)***', unlocked)
+        // await Db.put('fusions', '4e9654f9-3de9-4f9a-8169-3834f40847f5', fusion)
+
+        const inputs = fusion.inputs
+        console.log('INPUTS', inputs)
+
+        /* Handle inputs. */
+        Object.keys(inputs).forEach(_outpoint => {
+console.log('OUTPOINT', _outpoint)
+            /* Handle unlocked (scripts). */
+            Object.keys(unlocked).forEach(_lockpoint => {
+console.log('LOCKPOINT', _lockpoint)
+                const unlock = unlocked[_lockpoint]
+console.log('UNLOCK', unlock)
+                /* Validate transaction hash. */
+                if (inputs[_outpoint].tx_hash === unlock.tx_hash) {
+console.log('FOUND A MATCH', unlock.tx_hash)
+                    /* Set unlocking script. */
+                    inputs[_outpoint].unlocking = unlock.unlocking
+                }
+            })
+        })
+console.log('UPDATED FUSION', fusion)
+        return 'almost there...'
+    }
+
+
     fusion.numGuests = Object.keys(fusion.guests).length
 
     /* Update progress. */
@@ -130,7 +179,7 @@ console.log('FUSION', fusion)
             componentid = sha256(_component.tx_hash + ':' + _component.tx_pos)
             fusion.inputs[componentid] = {
                 ..._component,
-                signature: null,
+                unlocking: null,
             }
         }
 

+ 83 - 34
web/stores/wallet.ts

@@ -3,14 +3,20 @@ import { defineStore } from 'pinia'
 import moment from 'moment'
 
 import BCHJS from '@psf/bch-js'
-import { mnemonicToEntropy } from '@nexajs/hdnode'
+import { sha256 } from '@nexajs/crypto'
+import {
+    mnemonicToEntropy,
+    mnemonicToSeed,
+} from '@nexajs/hdnode'
 import { Wallet } from '@nexajs/wallet'
 
 import _broadcast from './wallet/broadcast.ts'
 import _completeFusion from './wallet/completeFusion.ts'
+import _getWifForAddress from './wallet/getWifForAddress.ts'
 import _setEntropy from './wallet/setEntropy.ts'
 import _setupKeychain from './wallet/setupKeychain.ts'
 import _setupHushKeychain from './wallet/setupHushKeychain.ts'
+import _signFusion from './wallet/signFusion.ts'
 import _startFusion from './wallet/startFusion.ts'
 
 /* Initialize constants. */
@@ -142,32 +148,25 @@ export const useWalletStore = defineStore('wallet', {
                 return null
             }
 
-            const collection = _state._utxos
-            // console.log('COLLECTION (fusionInputs)', collection)
+            // const collection = _state._utxos
+            console.log('STATE (utxos)', _state._utxos)
 
-            const mainList = []
+            /* Initialize inputs (collection). */
+            let inputs = {}
 
-            collection[0]?.forEach(_account => {
-                // console.log('ACCOUNT (0)', _account)
-                _account.utxos.forEach(_utxo => {
-                    mainList.push({
-                        address: _account.address,
-                        ..._utxo,
-                    })
-                })
-            })
+            /* Verify Main chain. */
+            if (_state._utxos[0]) {
+                inputs = { ...inputs, ..._state._utxos[0] }
+            }
 
-            collection[HUSH_PROTOCOL_ID]?.forEach(_account => {
-                // console.log('ACCOUNT (1213551432)', _account)
-                _account.utxos.forEach(_utxo => {
-                    mainList.push({
-                        address: _account.address,
-                        ..._utxo,
-                    })
-                })
-            })
+            /* Verify Hush chain. */
+            if (_state._utxos[HUSH_PROTOCOL_ID]) {
+                inputs = { ...inputs, ..._state._utxos[HUSH_PROTOCOL_ID] }
+            }
+            console.log('FUSION (inputs)', inputs)
 
-            return mainList
+            /* Return inputs. */
+            return inputs
         },
 
         fusionAddrs() {
@@ -275,17 +274,17 @@ _setupHushKeychain.bind(this)()
          * Will return the "child" address of a master node,
          * based on the account index, change flag and address index.
          */
-        async getBchAddress(
+        getBchAddress(
             _accountIdx = 0,
             _isChange = 0, // NOTE: 0 = false, 1 = true
             _addressIdx = 0,
         ) {
             /* Set root seed. */
-            const rootSeed = await bchjs.Mnemonic.toSeed(this.mnemonic)
+            const rootSeed = mnemonicToSeed(this.mnemonic)
             // console.log('rootSeed', rootSeed)
 
             /* Set HD master node. */
-            const masterHdnode = bchjs.HDNode.fromSeed(rootSeed)
+            const masterHdnode = bchjs.HDNode.fromSeed(Buffer.from(rootSeed, 'hex') )
             // console.log('masterHdnode', masterHdnode);
 
             /* Set child node. */
@@ -336,7 +335,25 @@ _setupHushKeychain.bind(this)()
             console.log('HUSH UTXOS', data)
 
             // FIXME Update the deltas ONLY!
-            // this._utxos[HUSH_PROTOCOL_ID] = data?.utxos
+            this._utxos[HUSH_PROTOCOL_ID] = {}
+
+            /* Handle unspent outputs. */
+            data.forEach(_unspent => {
+                _unspent.utxos.forEach(_utxo => {
+                    // console.log('ADDING HUSH UTXO...', _utxo)
+
+                    /* Generate outpoint (hash). */
+                    const outpoint = sha256(_utxo.tx_hash + ':' + _utxo.tx_pos)
+
+                    /* Add to UTXOs. */
+                    this._utxos[HUSH_PROTOCOL_ID][outpoint] = {
+                        address: _unspent.address,
+                        ..._utxo,
+                        wif: _getWifForAddress.bind(this)(_unspent.address),
+                    }
+                })
+            })
+
 
             /* Request history data. */
             data = await $fetch('/api/electrum', {
@@ -377,16 +394,13 @@ _setupHushKeychain.bind(this)()
                 let bchAddress2
                 let bchAddress3
 
-                bchAddress1 = await this.getBchAddress(0, 0, 0)
-                    .catch(err => console.error(err))
+                bchAddress1 = this.getBchAddress(0, 0, 0)
                 // console.log('BCH ADDRESS-1', bchAddress1)
 
-                bchAddress2 = await this.getBchAddress(0, 0, 1)
-                    .catch(err => console.error(err))
+                bchAddress2 = this.getBchAddress(0, 0, 1)
                 // console.log('BCH ADDRESS-2', bchAddress2)
 
-                bchAddress3 = await this.getBchAddress(0, 0, 2)
-                    .catch(err => console.error(err))
+                bchAddress3 = this.getBchAddress(0, 0, 2)
                 // console.log('BCH ADDRESS-3', bchAddress3)
 
                 data = await $fetch('/api/electrum', {
@@ -402,8 +416,29 @@ _setupHushKeychain.bind(this)()
                 })
                 console.log('MAIN WALLET DATA', data)
 
+                // if (!this._utxos[0]) {
+                    this._utxos[0] = {}
+                // }
+
+                /* Handle unspent outputs. */
+                data.forEach(_unspent => {
+                    _unspent.utxos.forEach(_utxo => {
+                        console.log('ADDING UTXO...', _utxo)
+
+                        /* Generate outpoint (hash). */
+                        const outpoint = sha256(_utxo.tx_hash + ':' + _utxo.tx_pos)
+
+                        /* Add to UTXOs. */
+                        this._utxos[0][outpoint] = {
+                            address: _unspent.address,
+                            ..._utxo,
+                            wif: _getWifForAddress.bind(this)(_unspent.address),
+                        }
+                    })
+                })
+
                 // FIXME Update the delta ONLY!
-                this._utxos[0] = data?.utxos
+                // this._utxos[0] = data?.utxos
             }
 
             return true
@@ -439,11 +474,25 @@ _setupHushKeychain.bind(this)()
             }
         },
 
+        /**
+         * Get WIF for Address
+         *
+         * TBD..
+         */
+        getWifForAddress(_address) {
+            return _getWifForAddress.bind(this)(_address)
+        },
+
         async startFusion() {
             /* Start fusions. */
             return _startFusion.bind(this)()
         },
 
+        async signFusion() {
+            /* Start fusions. */
+            return _signFusion.bind(this)()
+        },
+
         async completeFusion() {
             /* Start fusions. */
             return _completeFusion.bind(this)()

+ 83 - 15
web/stores/wallet/completeFusion.ts

@@ -1,5 +1,8 @@
 /* Import modules. */
-import signSharedTx from '../../handlers/signSharedTx.ts'
+import { TransactionBuilder } from 'bitcoinjs-lib'
+import { hexToBin } from '@nexajs/utils'
+
+import buildSharedTx from '../../handlers/buildSharedTx.ts'
 
 const DUST_VAL = 546
 
@@ -23,14 +26,14 @@ export default async function () {
 
     /* Set inputs. */
     inputs = session.inputs
-    // console.log('INPUTS', inputs)
+    console.log('INPUTS', inputs)
 
     /* Initialize keys. */
     keys = []
 
     /* Handle (input) keys. */
-    Object.keys(inputs).forEach(_inputid => {
-        keys.push(_inputid)
+    Object.keys(inputs).forEach(_outpoint => {
+        keys.push(_outpoint)
     })
     // console.log('KEYS', keys)
 
@@ -48,13 +51,13 @@ export default async function () {
         console.log('HANDLE INPUT', input)
 
         /* Find address index for input. */
-        for (let i = 0; i < this.fusionInputs.length; i++) {
-            if (this.fusionInputs[i].address === input.address) {
-                input.address_idx = i
-                console.log('ADDRESS INDEX', i)
-                break
-            }
-        }
+        // for (let i = 0; i < this.fusionInputs.length; i++) {
+        //     if (this.fusionInputs[i].address === input.address) {
+        //         input.address_idx = i
+        //         console.log('ADDRESS INDEX', i)
+        //         break
+        //     }
+        // }
 
         /* Add input. */
         sortedInputs.push(input)
@@ -90,11 +93,76 @@ export default async function () {
     // console.log('OUTPUTS (sorted)', sortedOutputs)
 
     /* Sign shared transaction. */
-    rawTx = signSharedTx(
-        sessionid, this.mnemonic, sortedInputs, sortedOutputs)
-    console.log('RAW TX', rawTx)
+    const transactionBuilder = buildSharedTx.bind(this)(
+        sessionid, sortedInputs, sortedOutputs)
+// transactionBuilder.transaction.tx.ins[0].script = script
+    console.log('FINALIZED TRANSACTION', transactionBuilder)
+
+// console.log('UNLOCKING', inputs['185ad6a10ea70d977d943a910f54dc446163a16771017c6df35c7893c1db0c35'])
+
+
+    const transaction = transactionBuilder.transaction.buildIncomplete()
+    // const transaction = transactionBuilder.transaction
+    console.log('TRANSACTION', transaction)
+
+const scripts = []
+Object.keys(inputs).forEach(_outpoint => {
+    const input = inputs[_outpoint]
+    console.log('SCRIPT INPUT', input)
 
-    response = await this.broadcast('BCH', rawTx)
+    scripts.push({
+        id: _outpoint,
+        script: Buffer.from(input.unlocking, 'hex')
+    })
+})
+console.log('SCRIPTS', scripts)
+// const script = Buffer.from(hexToBin(inputs['185ad6a10ea70d977d943a910f54dc446163a16771017c6df35c7893c1db0c35'].unlocking))
+// console.log('SCRIPT', script)
+    // transaction.ins[0].script = script
+    transaction.ins[0].script = scripts[0].script
+    console.log('TRANSACTION (hex)', transaction.toHex())
+
+    // transaction.ins[0].script = Buffer.from(hexToBin(inputs['185ad6a10ea70d977d943a910f54dc446163a16771017c6df35c7893c1db0c35'].unlocking))
+
+    // const transaction2 = transactionBuilder.transaction.build()
+
+    // const transactionBuilder2 = TransactionBuilder.fromTransaction(
+    //     transaction,
+    //     'mainnet'
+    //   )
+    // console.log('TX BUILDER-2', transactionBuilder2)
+
+      // build tx
+    //   const tx = transactionBuilder2.build()
+    //   const tx = transactionBuilder2.transaction.buildIncomplete()
+    //   console.log('TX BUILDER 2', tx)
+    //   // output rawhex
+    //   const txHex = tx.toHex()
+    //   console.log('TX HEX', txHex)
+
+    /* Convert to (raw) hex. */
+    // rawTx = tx.toHex()
+
+    response = await this.broadcast('BCH', transaction.toHex())
         .catch(err => console.error(err))
     console.log('BROADCAST (response)', response)
 }
+
+
+    // // Overwrite the tx inputs of the first partially-signed TX with the signed
+    // // inputs from the other two transactions.
+    // txObj.ins[1].script = txObj2.ins[1].script
+    // txObj.ins[2].script = txObj3.ins[2].script
+
+    // // console.log(`Fully-signed txObj.ins: ${JSON.stringify(txObj.ins, null, 2)}`)
+
+    // // Port the transaction object into the TransactionBuilder.
+    // const transactionBuilder = Bitcoin.TransactionBuilder.fromTransaction(
+    //   txObj,
+    //   'mainnet'
+    // )
+
+    // // build tx
+    // const tx = transactionBuilder.build()
+    // // output rawhex
+    // const txHex = tx.toHex()

+ 81 - 0
web/stores/wallet/getWifForAddress.ts

@@ -0,0 +1,81 @@
+/* Import modules. */
+import BCHJS from '@psf/bch-js'
+import { mnemonicToSeed } from '@nexajs/hdnode'
+
+/* Initialize BCHJS. */
+const bchjs = new BCHJS()
+
+/* Initialize constants. */
+const HUSH_PROTOCOL_ID = 0x48555348
+
+
+/**
+ * Get WIF For An Address
+ *
+ * Will return the wallet import format (WIF) for a specific address.
+ */
+export default function (_address) {
+    /* Initialize locals. */
+    let accountIdx
+    let addressIdx
+    let changeIdx
+    let childNode
+    let mainAddress
+    let wif
+
+    /* Set main (wallet) address. */
+    mainAddress = this.getBchAddress(0, 0, 0)
+
+    if (mainAddress === _address) {
+        /* Set account index. */
+        accountIdx = 0 // NOTE: This is the Main chain
+
+        /* Set address index. */
+        addressIdx = 0
+    } else {
+        /* Set account index. */
+        accountIdx = HUSH_PROTOCOL_ID // NOTE: This is the Hush chain
+
+        /* Handle fusion addresses. */
+        Object.keys(this.fusionAddrs).forEach(_addressIdx => {
+            /* Set fusion address. */
+            const fusionAddress = this.fusionAddrs[_addressIdx]
+
+            /* Validate address. */
+            if (_address === fusionAddress.address) {
+                /* Set address index. */
+                addressIdx = _addressIdx
+            }
+        })
+    }
+    // console.log('ACCOUNT IDX', accountIdx)
+    // console.log('ADDRESS IDX', addressIdx)
+
+    if (typeof addressIdx === 'undefined' || addressIdx === null) {
+        throw new Error(`Oops! There is NO private key for [ ${_address} ]`)
+    }
+
+    /* Set change index. */
+    changeIdx = 0
+
+    /* 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)
+
+    /* Generate child node. */
+    childNode = masterNode
+        .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
+
+    /* Generate wallet import format (WIF). */
+    wif = bchjs.HDNode.toWIF(childNode)
+    // console.log('BCH WIF', i, wif)
+
+    /* Return WIF. */
+    return wif
+}

+ 1 - 1
web/stores/wallet/setupHushKeychain.ts

@@ -18,7 +18,7 @@ export default async function () {
         /* Set address index. */
         addressIdx = i
 
-        address = await this.getBchAddress(HUSH_PROTOCOL_ID, CHANGE_IDX, addressIdx)
+        address = this.getBchAddress(HUSH_PROTOCOL_ID, CHANGE_IDX, addressIdx)
 
         pkg = {
             address,

+ 1 - 1
web/stores/wallet/setupKeychain.ts

@@ -18,7 +18,7 @@ export default async function () {
         /* Set address index. */
         addressIdx = i
 
-        address = await this.getBchAddress(PROTOCOL_ID, CHANGE_IDX, addressIdx)
+        address = this.getBchAddress(PROTOCOL_ID, CHANGE_IDX, addressIdx)
 
         pkg = {
             address,

+ 160 - 0
web/stores/wallet/signFusion.ts

@@ -0,0 +1,160 @@
+/* Import modules. */
+import { encryptForPubkey } from '@nexajs/crypto'
+import { binToHex } from '@nexajs/utils'
+
+import signSharedTx from '../../handlers/signSharedTx.ts'
+
+const DUST_VAL = 546
+
+export default async function () {
+    /* Initialize locals. */
+    let blindComponents
+    let clubWallet
+    let inputs
+    let keys
+    let outputs
+    let publicKey
+    // let rawTx
+    let response
+    let session
+    let sessionid
+    let transaction
+
+    // 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 => {
+        /* Set input. */
+        const input = inputs[_keyid]
+        console.log('HANDLE INPUT', input)
+
+        // let addressIdx = 0
+
+        // /* Find address index for input. */
+        // Object.keys(this.fusionInputs).forEach(_outpoint => {
+        //     /* Set fusion input. */
+        //     const fusionInput = this.fusionInputs[_outpoint]
+        //     console.log('FUSION INPUT', fusionInput)
+
+        //     if (fusionInput.address === input.address) {
+        //         input.address_idx = addressIdx
+        //         console.log('ADDED ADDRESS INDEX', addressIdx)
+        //         // break
+        //     }
+
+        //     addressIdx++
+        // })
+
+        /* Add input. */
+        sortedInputs.push(input)
+    })
+    // 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. */
+    transaction = signSharedTx.bind(this)(
+        sessionid, sortedInputs, sortedOutputs)
+    console.log('(partially) SIGNED TRANSACTION', transaction)
+    // txObj.ins[1].script = txObj2.ins[1].script
+
+    /* Initailize unlocked. */
+    let unlocked = {}
+
+    transaction.ins.forEach(_input => {
+        /* Validate (unlocked) script. */
+        if (_input.script) {
+            /* Add input. */
+            unlocked[binToHex(_input.hash)] = {
+                tx_hash: binToHex(_input.hash.reverse()),
+                unlocking: binToHex(_input.script),
+            }
+        }
+    })
+    console.log('UNLOCKED', unlocked)
+
+    /* Prepare unlocked (inputs) for encryption. */
+    unlocked = JSON.stringify(unlocked)
+    // console.log('FUSION (unlocked)', unlocked)
+
+    // 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, unlocked)
+    // console.log('BLINDED COMPONENTS', blindComponents)
+
+    const body = {
+        authid: binToHex(this.wallet.publicKey),
+        actionid: 'unlock-components',
+        // tierid,
+        unlocked: blindComponents,
+    }
+    console.log('BODY', body)
+
+    response = await $fetch('/v1', {
+        method: 'POST',
+        body,
+    })
+    .catch(err => console.error(err))
+    console.log('RESPONSE', response)
+}

+ 23 - 8
web/stores/wallet/startFusion.ts

@@ -31,7 +31,11 @@ export default async function () {
     const maxOutputCount = 17
 
     /* Clone fusion inputs. */
-    fusionInputs = [ ...this.fusionInputs ]
+    fusionInputs = []
+
+    Object.keys(this.fusionInputs).forEach(_outpoint => {
+        fusionInputs.push(this.fusionInputs[_outpoint])
+    })
 
     const tierScales = [
         10000,      12000,      15000,      18000,      22000,      27000,      33000,      39000,      47000,      56000,      68000,      82000,
@@ -108,9 +112,17 @@ export default async function () {
 // return
 
     /* Initialize components. */
-    // NOTE: Automatically add ALL fusion inputs.
+    // NOTE: Automatically clone + add ALL fusion inputs.
     components = [ ...fusionInputs ]
 
+    /* Sanitize (cloned input) components. */
+    Object.keys(components).forEach(_outpoint => {
+        const component = components[_outpoint]
+
+        /* Delete WIF. */
+        delete component.wif
+    })
+
     /* Set tier ID. */
     tierid = bestTiers.tierid
 
@@ -148,14 +160,17 @@ export default async function () {
     blindComponents = encryptForPubkey(publicKey, components)
     // console.log('BLINDED COMPONENTS', blindComponents)
 
+    const body = {
+        authid: binToHex(this.wallet.publicKey),
+        actionid: 'submit-components',
+        tierid,
+        components: blindComponents,
+    }
+    // console.log('BODY', body)
+
     response = await $fetch('/v1', {
         method: 'POST',
-        body: {
-            authid: binToHex(this.wallet.publicKey),
-            actionid: 'submit-components',
-            tierid,
-            components: blindComponents,
-        },
+        body,
     })
     .catch(err => console.error(err))
     console.log('RESPONSE', response)