Browse Source

Add wallet middleware.

Shomari 1 month ago
parent
commit
fb897203e1

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

@@ -21,7 +21,7 @@ const createWallet = () => {
 const importWallet = () => {
 const importWallet = () => {
     // NOTE: This confirmation is NOT REQUIRED for single-application
     // NOTE: This confirmation is NOT REQUIRED for single-application
     //       wallet integration(s), and can be SAFELY removed.
     //       wallet integration(s), and can be SAFELY removed.
-    if (confirm('Before you continue, please close ALL other Studio browser windows. Failure to do so may result in LOSS OF ASSETS!\n\nWould you like to continue importing an existing wallet?')) {
+    if (confirm('Before you continue, please close ALL other Hush Your Money browser windows. Failure to do so may result in LOSS OF ASSETS!\n\nWould you like to continue importing an existing wallet?')) {
         /* Set/save mnemonic. */
         /* Set/save mnemonic. */
         // NOTE: Will save `entropy` to the local storage.
         // NOTE: Will save `entropy` to the local storage.
         Wallet.setMnemonic(mnemonic.value)
         Wallet.setMnemonic(mnemonic.value)
@@ -42,7 +42,7 @@ const importWallet = () => {
 <template>
 <template>
     <section class="flex flex-col gap-5">
     <section class="flex flex-col gap-5">
         <p class="px-3 py-2 bg-yellow-100 text-base font-medium border-2 border-yellow-200 rounded-lg shadow-md">
         <p class="px-3 py-2 bg-yellow-100 text-base font-medium border-2 border-yellow-200 rounded-lg shadow-md">
-            Welcome to your Studio wallet.
+            Welcome to your Hush Your Money wallet.
             Click the button below to create a new wallet and begin trading.
             Click the button below to create a new wallet and begin trading.
         </p>
         </p>
 
 
@@ -53,7 +53,7 @@ const importWallet = () => {
         <hr />
         <hr />
 
 
         <p class="px-3 py-2 bg-yellow-100 text-base font-medium border-2 border-yellow-200 rounded-lg shadow-md">
         <p class="px-3 py-2 bg-yellow-100 text-base font-medium border-2 border-yellow-200 rounded-lg shadow-md">
-            Import your existing wallet into Studio.
+            Import your existing wallet into Hush Your Money.
         </p>
         </p>
 
 
         <textarea
         <textarea

+ 115 - 14
web/components/Admin/ChooseWallet.vue → web/components/Wallet/Welcome.vue

@@ -1,23 +1,120 @@
 <script setup lang="ts">
 <script setup lang="ts">
+/* Import modules. */
+import BCHJS from '@psf/bch-js'
+import { binToHex } from '@nexajs/utils'
+
 /* Define properties. */
 /* Define properties. */
 // https://vuejs.org/guide/components/props.html#props-declaration
 // https://vuejs.org/guide/components/props.html#props-declaration
 const props = defineProps({
 const props = defineProps({
     balances: Object,
     balances: Object,
     cashAddress: String,
     cashAddress: String,
+    utxos: Object,
+})
+
+/* Initialize stores. */
+import { useWalletStore } from '@/stores/wallet'
+const Wallet = useWalletStore()
+
+// REST API servers.
+const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
+
+const runtimeConfig = useRuntimeConfig()
+const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
+
+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 clearWallet = () => {
 
 
+const calculateFee = () => {
+  // Calculate miner fees.
+  // Get byte count (x inputs, pay + remainder = 2x outputs)
+  return bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 2 })
 }
 }
 
 
-const start = () => {
+// 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
+    }
 }
 }
 
 
-// onMounted(() => {
-//     console.log('Mounted!')
-//     // Now it's safe to perform setup operations.
-// })
+
+const cashout = () => {
+    alert('WIP?? sorry...')
+}
+
+const start = async () => {
+    console.log('Starting...')
+
+    console.log('FEE', calculateFee())
+
+    buildUnsignedTx()
+
+    const readyToFuse = props.utxos
+    console.log('READY TO FUSE', readyToFuse)
+
+    // TODO Handle any filtering required BEFORE submitting for fusion.
+
+    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.
+})
 
 
 // onBeforeUnmount(() => {
 // onBeforeUnmount(() => {
 //     console.log('Before Unmount!')
 //     console.log('Before Unmount!')
@@ -46,19 +143,23 @@ const start = () => {
             </h3>
             </h3>
 
 
             <h3>
             <h3>
-                Confirmed: {{balances?.confirmed}}
+                Confirmed: {{props.balances?.confirmed}}
             </h3>
             </h3>
             <h3>
             <h3>
-                Unconfirmed: {{balances?.unconfirmed}}
+                Unconfirmed: {{props.balances?.unconfirmed}}
             </h3>
             </h3>
 
 
-            <button @click="clearWallet">
-                Clear wallet
-            </button>
+            <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>
+            </div>
 
 
-            <button @click="start">
-                Start
-            </button>
+            <pre class="text-xs">{{props.utxos}}</pre>
 
 
         </section>
         </section>
 
 

+ 5 - 19
web/handlers/initFusions.ts

@@ -1,7 +1,10 @@
 /* Initialize globals. */
 /* Initialize globals. */
 let fusionsDb
 let fusionsDb
 
 
-const setupGlobalDb = async (_fusionsDb) => {
+/**
+ * Initialize
+ */
+const init = async (_fusionsDb) => {
     fusionsDb = _fusionsDb
     fusionsDb = _fusionsDb
 
 
     fusionsDb['4e9654f9-3de9-4f9a-8169-3834f40847f5'] = {
     fusionsDb['4e9654f9-3de9-4f9a-8169-3834f40847f5'] = {
@@ -36,28 +39,11 @@ const setupGlobalDb = async (_fusionsDb) => {
     }
     }
 }
 }
 
 
-const init = async () => {
-    setTimeout(() => {
-        console.log('ADD ONE MORE', fusionsDb)
-        fusionsDb['af371828-7199-4e4e-baca-bcd11d01edda'] = {
-            tierid: 5600000,
-            guests: 2,
-            inputs: 0,
-            outputs: 0,
-            createdAt: 1723245503,
-            updatedAt: 1723245503,
-        }
-
-    }, 10000)
-}
 
 
 /**
 /**
  * Initialize Fusions
  * Initialize Fusions
  */
  */
 export default async (_fusionsDb) => {
 export default async (_fusionsDb) => {
-    /* Setup global database. */
-    setupGlobalDb(_fusionsDb)
-
     /* Initialize. */
     /* Initialize. */
-    init()
+    init(_fusionsDb)
 }
 }

+ 40 - 0
web/pages/admin/index.vue

@@ -21,6 +21,9 @@ const profiles = ref(null)
 const status = ref(null)
 const status = ref(null)
 const system = ref(null)
 const system = ref(null)
 
 
+const clubAddress = ref(null)
+const clubPubkey = ref(null)
+
 const displayCreatedAt = computed(() => {
 const displayCreatedAt = computed(() => {
     if (!status.value || !status.value.createdAt) {
     if (!status.value || !status.value.createdAt) {
         return 'loading...'
         return 'loading...'
@@ -98,6 +101,14 @@ const init = async () => {
     profiles.value = await $fetch('/api/profiles')
     profiles.value = await $fetch('/api/profiles')
         .catch(err => console.error(err))
         .catch(err => console.error(err))
     // console.log('PROFILES', profiles.value)
     // console.log('PROFILES', profiles.value)
+
+    /* Request wallet. */
+    const response = await $fetch('/api/wallet')
+        .catch(err => console.error(err))
+    console.log('WALLET', response)
+
+    clubAddress.value = response.address
+    clubPubkey.value = response.publicKey
 }
 }
 
 
 onMounted(() => {
 onMounted(() => {
@@ -176,6 +187,35 @@ onMounted(() => {
                     {{displayTimeAgo}}
                     {{displayTimeAgo}}
                 </h3>
                 </h3>
             </section>
             </section>
+
+            <section class="w-full px-5 py-3 flex flex-col gap-0 bg-amber-100 border-2 border-amber-300 rounded-2xl shadow">
+                <h2 class="text-amber-500 text-base font-bold tracking-widest uppercase">
+                    Club Wallet
+                </h2>
+
+                <NuxtLink :to="'https://explorer.nexa.org/address/' + clubAddress" target="_blank" class="text-amber-700 text-base font-medium tracking-tight hover:text-amber-600 hover:underline">
+                    {{clubAddress}}
+                </NuxtLink>
+
+                <h3 class="text-amber-700 text-base text-right font-medium italic">
+                    Current signer of ALL Club messages
+                </h3>
+            </section>
+
+            <section class="w-full px-5 py-3 flex flex-col gap-0 bg-amber-100 border-2 border-amber-300 rounded-2xl shadow">
+                <h2 class="text-amber-500 text-base font-bold tracking-widest uppercase">
+                    Club Public Key
+                </h2>
+
+                <h3 class="text-amber-700 text-xs font-medium tracking-tight">
+                    {{clubPubkey}}
+                </h3>
+
+                <h3 class="text-amber-700 text-base text-right font-medium italic">
+                    Use this key to sign encrypted messages
+                </h3>
+            </section>
+
         </div>
         </div>
 
 
         <pre class="text-xs">{{system}}</pre>
         <pre class="text-xs">{{system}}</pre>

+ 10 - 10
web/pages/admin/liquidity.vue

@@ -7,13 +7,12 @@ const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
 
 
 const runtimeConfig = useRuntimeConfig()
 const runtimeConfig = useRuntimeConfig()
 const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
 const jwtAuthToken = runtimeConfig.public.PSF_JWT_AUTH_TOKEN
-// console.log('jwtAuthToken', jwtAuthToken)
 
 
 const balances = ref(null)
 const balances = ref(null)
 const cashAddress = ref(null)
 const cashAddress = ref(null)
+const utxos = ref(null)
 
 
 // Instantiate bch-js based on the network.
 // 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({
 const bchjs = new BCHJS({
     restURL: BCHN_MAINNET,
     restURL: BCHN_MAINNET,
     apiToken: jwtAuthToken,
     apiToken: jwtAuthToken,
@@ -45,6 +44,8 @@ const init = async () => {
     let response
     let response
     let rootSeed
     let rootSeed
 
 
+    utxos.value = []
+
     /* Set root seed. */
     /* Set root seed. */
     rootSeed = await bchjs.Mnemonic.toSeed(Wallet.mnemonic)
     rootSeed = await bchjs.Mnemonic.toSeed(Wallet.mnemonic)
     // console.log('rootSeed', rootSeed)
     // console.log('rootSeed', rootSeed)
@@ -62,11 +63,11 @@ const init = async () => {
     // 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)
+    // console.log('RESPONSE', response)
 
 
     /* Set UTXOs. */
     /* Set UTXOs. */
-    const utxos = response.utxos
-    console.log('UTXOS', JSON.stringify(utxos, null, 2))
+    utxos.value = response.utxos
+    console.log('UTXOS', JSON.stringify(utxos.value, null, 2))
 
 
     /* Request balances. */
     /* Request balances. */
     response = await bchjs.Electrumx.balance(cashAddress.value)
     response = await bchjs.Electrumx.balance(cashAddress.value)
@@ -100,17 +101,16 @@ onMounted(() => {
             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.
             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>
         </p>
 
 
-        <section class="py-10 flex justify-center">
+        <section class="w-full w-3/4 py-10 flex justify-center">
             <Loading v-if="Wallet.isLoading" />
             <Loading v-if="Wallet.isLoading" />
 
 
-            <WalletSetup v-else-if="!Wallet.isReady" class="w-3/4" />
+            <WalletSetup v-else-if="!Wallet.isReady" />
 
 
-            <AdminChooseWallet v-else
-                class="w-3/4"
+            <WalletWelcome v-else
                 :balances="balances"
                 :balances="balances"
                 :cashAddress="cashAddress"
                 :cashAddress="cashAddress"
+                :utxos="utxos"
             />
             />
-
         </section>
         </section>
     </main>
     </main>
 </template>
 </template>

+ 13 - 29
web/server/routes/_wallet.get.ts → web/server/api/wallet.get.ts

@@ -1,6 +1,6 @@
 /* Import modules. */
 /* Import modules. */
-import { Wallet } from '@nexajs/wallet'
 import BCHJS from '@psf/bch-js'
 import BCHJS from '@psf/bch-js'
+import { binToHex } from '@nexajs/utils'
 
 
 // REST API servers.
 // REST API servers.
 const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
 const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
@@ -17,9 +17,6 @@ const bchjs = new BCHJS({
 })
 })
 // console.log('bchjs', bchjs)
 // console.log('bchjs', bchjs)
 
 
-/* Initialize Wallet (instance) */
-let wallet
-
 const lang = 'english' // Set the language of the wallet.
 const lang = 'english' // Set the language of the wallet.
 
 
 const aliceObj = {}
 const aliceObj = {}
@@ -101,39 +98,26 @@ async function createWallets () {
   }
   }
 }
 }
 
 
-/**
- * Initialize (Wallet)
- *
- * Setup an "ephemeral" wallet for the use of this Club Session.
- */
-const init = async () => {
-    wallet = await Wallet.init()
-        .catch(err => console.error(err))
-    // console.log('WALLET', wallet)
-}
-
-init()
-// createWallets()
-
 export default defineEventHandler((event) => {
 export default defineEventHandler((event) => {
-    /* Set project mnemonic. */
-    // const mnemonic = process.env.PROJECT_MNEMONIC
+    /* Set database. */
+    const Db = event.context.Db
+    // console.log('DB', Db)
 
 
-    /* Build wallet. */
-    // const wallet = {
-    //     mnemonic,
-    // }
+    /* Set database. */
+    const Wallet = event.context.Wallet
+    // console.log('WALLET', Wallet)
 
 
     const walletPkg = {
     const walletPkg = {
-        address: wallet.address,
-        assets: JSON.stringify(wallet.assets, (key, value) =>
+        address: Wallet.address,
+        publicKey: binToHex(Wallet.publicKey),
+        assets: JSON.stringify(Wallet.assets, (key, value) =>
             typeof value === 'bigint' ? value.toString() + 'n' : value
             typeof value === 'bigint' ? value.toString() + 'n' : value
         ),
         ),
-        coins: JSON.stringify(wallet.coins, (key, value) =>
+        coins: JSON.stringify(Wallet.coins, (key, value) =>
             typeof value === 'bigint' ? value.toString() + 'n' : value
             typeof value === 'bigint' ? value.toString() + 'n' : value
         ),
         ),
-        mnemonic: wallet.mnemonic,
-        tokens: JSON.stringify(wallet.tokens, (key, value) =>
+        mnemonic: Wallet.mnemonic,
+        tokens: JSON.stringify(Wallet.tokens, (key, value) =>
             typeof value === 'bigint' ? value.toString() + 'n' : value
             typeof value === 'bigint' ? value.toString() + 'n' : value
         ),
         ),
         aliceObj,
         aliceObj,

+ 0 - 0
web/server/middleware/cors.ts → web/server/middleware/01-cors.ts


+ 3 - 7
web/server/middleware/db.ts → web/server/middleware/02-db.ts

@@ -6,13 +6,11 @@ import initFusions from '../../handlers/initFusions.ts'
 console.info('Initializing Ephemeral Database Manager...')
 console.info('Initializing Ephemeral Database Manager...')
 
 
 /* Initialize locals. */
 /* Initialize locals. */
-let response
 let status
 let status
 
 
 /* Initialize DB handlers. */
 /* Initialize DB handlers. */
 let fusionsDb
 let fusionsDb
 let profilesDb
 let profilesDb
-// let sessionsDb
 let systemDb
 let systemDb
 
 
 /**
 /**
@@ -82,9 +80,8 @@ const init = async () => {
             updatedAt: moment().unix(),
             updatedAt: moment().unix(),
         },
         },
 
 
-        response = await put('system', 'status', status)
+        put('system', 'status', status)
             .catch(err => console.error(err))
             .catch(err => console.error(err))
-        // console.log('RESPONSE (system)', response)
     } else {
     } else {
         /* Set status. */
         /* Set status. */
         status = systemDb.status
         status = systemDb.status
@@ -93,9 +90,8 @@ const init = async () => {
         status.updatedAt = moment().unix()
         status.updatedAt = moment().unix()
 
 
         /* Save data to store. */
         /* Save data to store. */
-        response = await put('system', 'status', status)
+        put('system', 'status', status)
             .catch(err => console.error(err))
             .catch(err => console.error(err))
-        // console.log('RESPONSE (status)', response)
     }
     }
 
 
     // console.log('SYSTEM (DB) STATUS', systemDb.status)
     // console.log('SYSTEM (DB) STATUS', systemDb.status)
@@ -107,11 +103,11 @@ initFusions(fusionsDb)
 export default defineEventHandler((event) => {
 export default defineEventHandler((event) => {
     // console.log('Ephemeral Db Request: ' + getRequestURL(event))
     // console.log('Ephemeral Db Request: ' + getRequestURL(event))
 
 
+    /* Inject database into server context. */
     event.context.Db = {
     event.context.Db = {
         /* Getters */
         /* Getters */
         fusions: fusionsDb,
         fusions: fusionsDb,
         profiles: profilesDb,
         profiles: profilesDb,
-        // sessions: sessionsDb,
         system: systemDb,
         system: systemDb,
 
 
         /* Setters */
         /* Setters */

+ 26 - 0
web/server/middleware/03-wallet.ts

@@ -0,0 +1,26 @@
+/* Import modules. */
+import { Wallet } from '@nexajs/wallet'
+
+/* Initialize Wallet (instance) */
+let wallet
+
+/**
+ * Initialize (Wallet)
+ *
+ * Setup an "ephemeral" wallet for the use of this Club Session.
+ */
+const init = async () => {
+    wallet = await Wallet.init()
+        .catch(err => console.error(err))
+    // console.log('WALLET INIT', wallet)
+}
+init()
+
+export default defineEventHandler((event) => {
+    /* Set database. */
+    const Db = event.context.Db
+    // console.log('DB', Db)
+
+    /* Inject wallet into server context. */
+    event.context.Wallet = wallet
+})

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

@@ -13,7 +13,7 @@ export default defineEventHandler(async (event) => {
 
 
     /* Set (request) body. */
     /* Set (request) body. */
     const body = await readBody(event)
     const body = await readBody(event)
-    // console.log('BODY', body)
+    console.log('BODY', body)
 
 
     if (!body) {
     if (!body) {
         return `Authorization FAILED!`
         return `Authorization FAILED!`
@@ -22,15 +22,13 @@ export default defineEventHandler(async (event) => {
     /* Set profile parameters. */
     /* Set profile parameters. */
     const authid = body.authid
     const authid = body.authid
     console.log('AUTH ID', authid)
     console.log('AUTH ID', authid)
+
     const actionid = body.actionid
     const actionid = body.actionid
     console.log('ACTION ID', actionid)
     console.log('ACTION ID', actionid)
+
     const sessionid = body.sessionid
     const sessionid = body.sessionid
     console.log('SESSION ID', sessionid)
     console.log('SESSION ID', sessionid)
 
 
-    console.log({
-        authid,
-    })
-
     /* Initialize locals. */
     /* Initialize locals. */
     let params
     let params
     let profile
     let profile
@@ -38,7 +36,12 @@ export default defineEventHandler(async (event) => {
     let session
     let session
     let success
     let success
 
 
-    // FIXME Validate authid
+    /* Validate auth ID. */
+    if (typeof authid === 'undefined' || authid === null) {
+        setResponseStatus(event, 401)
+
+        return 'Authorization failed!'
+    }
 
 
     /* Request session. */
     /* Request session. */
     profile = Db.profiles[authid]
     profile = Db.profiles[authid]