Forráskód Böngészése

Update all to latest.

Shomari 2 hónapja
szülő
commit
b390283e83

+ 0 - 0
web/components/Blank.vue → web/components/_Blank.vue


+ 56 - 16
web/components/menu/Wallet.vue

@@ -11,24 +11,64 @@ const Wallet = useWalletStore()
 
 const isShowingDeposit = ref(false)
 const isShowingHistory = ref(false)
-const isShowingWithdraw = ref(false)
+const isShowingCashout = ref(false)
 
 const displayBalance = computed(() => {
-    if (!Wallet.coins) {
+    /* Validate asset. */
+    if (!Wallet.asset || !Wallet.asset.amount) {
         return '0.00'
     }
 
-    const satoshis = Wallet.coins.reduce(
-        (totalSatoshis, coin) => (totalSatoshis + coin.satoshis), BigInt(0)
-    )
+    let decimalValue
+    let bigIntValue
 
-    /* Calculate (NEX) total. */
-    const nex = (parseInt(satoshis) / 100.0)
+    if (Wallet.asset.group === '0') {
+        decimalValue = Wallet.asset.satoshis * BigInt(1e4)
+    } else {
+        decimalValue = Wallet.asset.amount * BigInt(1e4)
+    }
+
+    if (Wallet.asset.decimal_places > 0) {
+        bigIntValue = decimalValue / BigInt(10**Wallet.asset.decimal_places)
+    } else {
+        bigIntValue = decimalValue
+    }
+
+    return numeral(parseFloat(bigIntValue) / 1e4).format('0,0[.]00[0000]')
+})
+
+const displayBalanceUsd = computed(() => {
+    /* Validate asset. */
+    if (!Wallet.asset || !Wallet.asset.fiat || !Wallet.asset.fiat.USD) {
+        return '0.00'
+    }
+
+    /* Initialize locals. */
+    let balanceUsd
+
+    /* Set balance. */
+    balanceUsd = Wallet.asset.fiat.USD || 0.00
 
     /* Return formatted value. */
-    return numeral(nex).format('0,0.00')
+    return numeral(balanceUsd).format('$0,0.00[0000]')
 })
 
+// const displayBalance = computed(() => {
+//     if (!Wallet.coins) {
+//         return '0.00'
+//     }
+//
+//     const satoshis = Wallet.coins.reduce(
+//         (totalSatoshis, coin) => (totalSatoshis + coin.satoshis), BigInt(0)
+//     )
+//
+//     /* Calculate (NEX) total. */
+//     const nex = (parseInt(satoshis) / 100.0)
+//
+//     /* Return formatted value. */
+//     return numeral(nex).format('0,0.00')
+// })
+
 // const displayBalanceUsd = computed(() => {
 //     if (!Wallet.coins) {
 //         return '0.00'
@@ -55,7 +95,7 @@ const setTab = (_tab) => {
     /* Clear all tabs. */
     isShowingDeposit.value = false
     isShowingHistory.value = false
-    isShowingWithdraw.value = false
+    isShowingCashout.value = false
 
     if (_tab === 'deposit') {
         isShowingDeposit.value = true
@@ -66,7 +106,7 @@ const setTab = (_tab) => {
     }
 
     if (_tab === 'withdraw') {
-        isShowingWithdraw.value = true
+        isShowingCashout.value = true
     }
 }
 
@@ -103,7 +143,7 @@ onMounted(() => {
 
             <h2 class="text-3xl font-medium">
                 {{displayBalance}}
-                <!-- {{displayBalanceUsd}} -->
+                {{displayBalanceUsd}}
             </h2>
         </section>
 
@@ -111,17 +151,17 @@ onMounted(() => {
             <div class="block">
                 <nav class="isolate flex divide-x divide-gray-200 rounded-lg shadow" aria-label="Tabs">
                     <!-- Current: "text-gray-900", Default: "text-gray-500 hover:text-gray-700" -->
-                    <button @click="setTab('deposit')" class="bg-gray-700 text-gray-100 rounded-l-lg group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 hover:text-gray-600 focus:z-10" aria-current="page">
+                    <button @click="setTab('deposit')" class="bg-gray-700 text-gray-100 rounded-l-lg group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-lg font-medium hover:bg-gray-50 hover:text-gray-600 focus:z-10" aria-current="page">
                         <span>Deposit</span>
                         <span aria-hidden="true" class="bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
                     </button>
 
-                    <button @click="setTab('withdraw')" class="bg-gray-700 text-gray-400 hover:text-gray-700 group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 hover:text-gray-600 focus:z-10">
-                        <span>Withdraw</span>
+                    <button @click="setTab('withdraw')" class="bg-gray-700 text-gray-400 hover:text-gray-700 group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-lg font-medium hover:bg-gray-50 hover:text-gray-600 focus:z-10">
+                        <span>Cashout</span>
                         <span aria-hidden="true" class="bg-transparent absolute inset-x-0 bottom-0 h-0.5"></span>
                     </button>
 
-                    <button @click="setTab('history')" class="bg-gray-700 text-gray-400 hover:text-gray-700 rounded-r-lg group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 hover:text-gray-600 focus:z-10">
+                    <button @click="setTab('history')" class="bg-gray-700 text-gray-400 hover:text-gray-700 rounded-r-lg group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-lg font-medium hover:bg-gray-50 hover:text-gray-600 focus:z-10">
                         <span>History</span>
                         <span aria-hidden="true" class="bg-transparent absolute inset-x-0 bottom-0 h-0.5"></span>
                     </button>
@@ -132,7 +172,7 @@ onMounted(() => {
         <div class="my-5">
             <MenuWalletDeposit v-if="isShowingDeposit" />
             <MenuWalletHistory v-if="isShowingHistory" />
-            <MenuWalletWithdraw v-if="isShowingWithdraw" />
+            <MenuWalletCashout v-if="isShowingCashout" />
         </div>
 
     </main>

+ 24 - 37
web/components/menu/wallet/Withdraw.vue → web/components/menu/wallet/Cashout.vue

@@ -8,8 +8,7 @@ import { useWalletStore } from '@/stores/wallet'
 const System = useSystemStore()
 const Wallet = useWalletStore()
 
-
-const amount = ref(null)
+/* Initialize handlers. */
 const receiver = ref(null)
 const currency = ref(null)
 const satoshis = ref(null)
@@ -20,28 +19,25 @@ const cameraError = ref(null)
 
 const isShowingVideoPreview = ref('hidden')
 
-
-watch(() => amount.value, (_amount) => {
-    // console.log('AMOUNT CHANGED', _amount)
-
-    /* Convert to satoshis. */
-    satoshis.value = parseInt(_amount / System._ticker.quote?.USD?.price * 100)
-})
-
+/**
+ * Open Scanner
+ *
+ * Opens the QR code scanner window.
+ */
 const openScanner = () => {
     /* Start scanner. */
     startScanner()
 }
 
+/**
+ * Set Receiver
+ *
+ * Set the "cashout" address to receive balance.
+ */
 const setReceiver = (_result) => {
-    // console.log('SET RECEIVER', _result)
-
     /* Set (local) receiver. */
     receiver.value = _result
 
-    /* Set (Wallet) receiver. */
-    // Wallet.setReceiver(_result)
-
     /* Hide video preview. */
     isShowingVideoPreview.value = 'hidden'
 
@@ -112,16 +108,18 @@ const startScanner = async () => {
     }
 }
 
-const send = async () => {
+/**
+ * Cashout
+ *
+ * Send coin balance to an address.
+ */
+const cashout = async () => {
     if (!receiver.value) {
         return alert('Enter a destination address.')
     }
 
-    if (!satoshis.value) {
-        return alert('Enter an amount to send.')
-    }
-
-    const response = await Wallet.transfer(receiver.value, BigInt(satoshis.value))
+    // const response = await Wallet.transfer(receiver.value, BigInt(satoshis.value))
+    const response = await Wallet.cashout(receiver.value)
     console.log('RESPONSE', response)
 
     /* Validate transaction idem. */
@@ -144,7 +142,6 @@ const send = async () => {
 //     console.log('Before Unmount!')
 //     // Now is the time to perform all cleanup operations.
 // })
-
 </script>
 
 <template>
@@ -181,24 +178,14 @@ const send = async () => {
         playsinline
     />
 
-    <section class="my-5 flex flex-col">
-        <input
-            class="w-full px-3 py-1 text-xl sm:text-2xl bg-yellow-200 border-2 border-yellow-400 rounded-md shadow"
-            type="number"
-            v-model="amount"
-            placeholder="Enter a (USD) amount"
-        />
-
-        <h4 v-if="satoshis > 0" class="mt-1 ml-3 text-sm text-gray-500 font-medium">
-            = {{numeral(satoshis / 100).format('0,0')}} NEXA
-        </h4>
-    </section>
+    <p class="py-5 px-3 text-sm font-medium text-rose-500 italic">
+        Withdraw your FULL balance from your in-game Bank by entering your destination address and clicking the "Cashout" button below.
+    </p>
 
     <button
-        @click="send"
+        @click="cashout"
         class="my-5 block px-3 py-1 text-2xl font-medium bg-blue-200 border-2 border-blue-400 rounded-md shadow hover:bg-blue-300"
     >
-        Send NEXA
+        Cashout
     </button>
-
 </template>

+ 0 - 0
web/pages/blank.vue → web/pages/_blank.vue


+ 2 - 2
web/pages/index.vue

@@ -214,14 +214,14 @@ const cleanup = watch(() => displayMinUsd.value, (_usd) => {
     cleanup()
 })
 
-onMounted(() => {
+onMounted(async () => {
     console.info('Game is starting...')
 
     /* Initialize the (Application's) System. */
     System.init()
 
     /* Initialize the Web wallet. */
-    Wallet.init()
+    await Wallet.init()
 
     /* Initialize the Game. */
     Game.init()

+ 0 - 3
web/server/api/[...].ts

@@ -1,3 +0,0 @@
-export default defineEventHandler(() => {
-    return 'ok'
-})

+ 0 - 1
web/stores/game/new.ts

@@ -17,7 +17,6 @@ import { useProfileStore } from '@/stores/profile'
 export default async function () {
     /* Set Nexa Games endpoint. */
     const ENDPOINT = 'https://nexa.games'
-    // const ENDPOINT = 'http://localhost:9690'
 
     const Profile = useProfileStore()
 

+ 170 - 143
web/stores/wallet.ts

@@ -1,229 +1,256 @@
 /* Import modules. */
 import { defineStore } from 'pinia'
-import { encodePrivateKeyWif } from '@nexajs/hdnode'
-import { entropyToMnemonic } from '@nexajs/hdnode'
-import { getAddressBalance } from '@nexajs/rostrum'
-import { listUnspent } from '@nexajs/address'
-import { sha256 } from '@nexajs/crypto'
-import { subscribeAddress } from '@nexajs/rostrum'
-import { Wallet } from '@nexajs/wallet'
+import moment from 'moment'
 
-import _createWallet from './wallet/create.ts'
+import { mnemonicToEntropy } from '@nexajs/hdnode'
+import {
+    Wallet,
+    WalletStatus,
+} from '@nexajs/wallet'
 
+import _setEntropy from './wallet/setEntropy.ts'
+
+const CASHOUT_FEE = BigInt(1000) // FIXME We MUST calculate this (same as change).
 
 /**
  * Wallet Store
  */
 export const useWalletStore = defineStore('wallet', {
     state: () => ({
-        MAX_OPRETURN_DATA_BYTES: 220,
-        DUST_LIMIT: 546,
-
-        /* Initialize entropy (used for HD wallet). */
-        // NOTE: This is a cryptographically-secure "random" 32-byte (256-bit) value. */
-        _entropy: null,
-
-        _wallet: null,
+        _assets: null,
 
-        _wif: null,
+        // _forceUI: null,
 
-        _coins: null,
-
-        _spentCoins: null,
+        /**
+         * Entropy
+         * (DEPRECATED -- MUST REMAIN SUPPORTED INDEFINITELY)
+         *
+         * Initialize entropy (used for HD wallet).
+         *
+         * NOTE: This is a cryptographically-secure "random"
+         * 32-byte (256-bit) value.
+         */
+        _entropy: null,
 
-        _satoshis: null,
+        /**
+         * Keychain
+         *
+         * Manages a collection of BIP-32 wallets.
+         *
+         * [
+         *   {
+         *     id        : '5be2e5c3-9d27-4b0f-bb3c-8b2ef6fdaafd',
+         *     type      : 'studio',
+         *     title     : `My Studio Wallet`,
+         *     entropy   : 0x0000000000000000000000000000000000000000000000000000000000000000,
+         *     createdAt : 0123456789,
+         *     updatedAt : 1234567890,
+         *   },
+         *   {
+         *     id        : 'f2457985-4b92-4025-be8d-5f11a5fc4077',
+         *     type      : 'ledger',
+         *     title     : `My Ledger Wallet`,
+         *     createdAt : 0123456789,
+         *     updatedAt : 1234567890,
+         *   },
+         * ]
+         */
+        _keychain: null,
 
+        /**
+         * Wallet
+         *
+         * Currently active wallet object.
+         */
+        _wallet: null,
     }),
 
     getters: {
-        isReady(_state) {
-            return _state._entropy ? true : false
+        /* Return (abbreviated) wallet status. */
+        abbr(_state) {
+            if (!_state._wallet) {
+                return null
+            }
+
+            return _state._wallet.abbr
         },
 
+        /* Return wallet status. */
         address(_state) {
-            if (!_state._wallet) return null
+            if (!_state._wallet) {
+                return null
+            }
 
             return _state._wallet.address
         },
 
-        abbr(_state) {
-            if (!_state._wallet) return null
+        asset(_state) {
+            if (!this.assets || !this.wallet) {
+                return null
+            }
 
-            return _state._wallet.address.slice(0, 19) + '...' + _state._wallet.address.slice(-6)
+            return this.assets[this.wallet.assetid]
         },
 
-        mnemonic(_state) {
-            if (!_state._entropy) return null
+        assets(_state) {
+            if (_state._assets) {
+                return _state._assets
+            }
 
-            return entropyToMnemonic(_state._entropy)
-        },
+            if (!_state._wallet) {
+                return null
+            }
 
-        wallet(_state) {
-            return _state._wallet
+            return _state._wallet.assets
         },
 
-        wif(_state) {
-            return _state._wif
-        },
+        /* Return wallet status. */
+        isLoading(_state) {
+            if (!_state._wallet) {
+                return true
+            }
 
-        coins(_state) {
-            return _state._coins
+            return _state._wallet.isLoading
         },
 
-        spentCoins(_state) {
-            return _state._spentCoins
-        },
+        /* Return wallet status. */
+        isReady(_state) {
+            if (!_state._wallet) {
+                return false
+            }
 
-        balance(_state) {
-            return _state._balance
-        },
+            if (_state._wallet._entropy) {
+                return true
+            }
 
-        satoshis(_state) {
-            return _state._satoshis
+            return _state._wallet.isReady
         },
 
-        nex(_state) {
-            return _state._satoshis / 100.0
+        /* Return NEXA.js wallet instance. */
+        wallet(_state) {
+            return _state._wallet
         },
 
-        mex(_state) {
-            return _state._satoshis / 100000000.0
+        WalletStatus() {
+            return WalletStatus
         },
-
     },
 
     actions: {
+        /**
+         * Initialize
+         *
+         * Setup the wallet store.
+         *   1. Retrieve the saved entropy.
+         *   2. Initialize a Wallet instance.
+         *   3. Load assets.
+         */
         async init() {
             console.info('Initializing wallet...')
 
             if (this._entropy === null) {
-                throw new Error('Missing wallet entropy.')
+                this._wallet = 'NEW' // FIXME TEMP NEW WALLET FLAG
+                // throw new Error('Missing wallet entropy.')
+                return console.error('Missing wallet entropy.')
             }
 
-            if (!this.mnemonic) {
-                throw new Error('Missing mnemonic (seed) phrase.')
-            }
+            /* Request a wallet instance (by mnemonic). */
+            this._wallet = await Wallet.init(this._entropy, true)
+            console.info('(Initialized) wallet', this.wallet)
+
+            // this._assets = { ...this.wallet.assets } // cloned assets
 
-            this._wallet = new Wallet(this.mnemonic)
-            // console.log('RE-CREATED WALLET', this._wallet)
+            /* Set (default) asset. */
+            this.wallet.setAsset('0')
 
-            // FIXME Workaround to solve race condition.
-            setTimeout(this.loadCoins, 100)
+            /* Handle balance updates. */
+            this.wallet.on('balances', async (_assets) => {
+                // console.log('Wallet Balances (onChanges):', _assets)
+
+                /* Close asset locally. */
+// FIXME Read ASSETS directly from library (getter).
+                this._assets = { ..._assets }
+            })
         },
 
+        /**
+         * Create Wallet
+         *
+         * Create a fresh wallet.
+         *
+         * @param _entropy A 32-byte (hex-encoded) random value.
+         */
         createWallet(_entropy) {
             /* Validate entropy. */
             // NOTE: Expect HEX value to be 32 or 64 characters.
-            if (_entropy.length !== 32 && _entropy.length !== 64) {
+            if (_entropy?.length !== 32 && _entropy?.length !== 64) {
                 console.error(_entropy, 'is NOT valid entropy.')
 
+                /* Clear (invalid) entropy. */
                 _entropy = null
             }
 
-            _createWallet.bind(this)(_entropy)
+            /* Set entropy. */
+            _setEntropy.bind(this)(_entropy)
 
             /* Initialize wallet. */
             this.init()
         },
 
-        /**
-         * Load Coins
-         *
-         * Retrieves all spendable UTXOs.
-         */
-        async loadCoins(_isReloading = false) {
-            console.info('Wallet address:', this.address)
-            // console.info('Wallet address (1):', this.getAddress(1))
-            // console.info('Wallet address (2):', this.getAddress(2))
-            // console.info('Wallet address (3):', this.getAddress(3))
-
-            /* Initialize locals. */
-            // let satoshis
-            let unspent
-
-            /* Validate coin re-loading. */
-            // FIXME: What happens if we re-subscribe??
-            if (_isReloading === false) {
-                /* Start monitoring address. */
-                await subscribeAddress(
-                    this.address,
-                    () => this.loadCoins.bind(this)(true),
-                )
-            }
-
-            /* Encode Private Key WIF. */
-            this._wif = encodePrivateKeyWif({ hash: sha256 }, this._wallet.privateKey, 'mainnet')
-
-            // Fetch all unspent transaction outputs for the temporary in-browser wallet.
-            unspent = await listUnspent(this.address)
-                .catch(err => console.error(err))
-            console.log('UNSPENT', unspent)
-
-            /* Validate unspent outputs. */
-            if (unspent.length === 0) {
-                /* Clear (saved) coins. */
-                this._coins = []
-
-                /* Clear (saved) tokens. */
-                this._tokens = []
-
-                return console.error('There are NO unspent outputs available.')
-            }
+        async cashout(_receiver) {
+console.log('ASSET', this.asset)
+console.log('ASSETS', this.assets)
 
-            /* Retrieve coins. */
-            this._coins = unspent
-                .filter(_unspent => _unspent.hasToken === false)
-                .map(_unspent => {
-                    const outpoint = _unspent.outpoint
-                    const satoshis = _unspent.satoshis
-
-                    return {
-                        outpoint,
-                        satoshis,
-                        wif: this._wif,
-                    }
-                })
-            console.log('\n  Coins:', this.coins)
-
-            /* Retrieve tokens. */
-            this._tokens = unspent
-                .filter(_unspent => _unspent.hasToken === true)
-                .map(_unspent => {
-                    const outpoint = _unspent.outpoint
-                    const satoshis = _unspent.satoshis
-                    const tokenid = _unspent.tokenid
-                    const tokens = _unspent.tokens
-
-                    return {
-                        outpoint,
-                        satoshis,
-                        tokenid,
-                        tokens,
-                        wif: this._wif,
-                    }
-                })
-            console.log('\n  Tokens:', this.tokens)
+            /* Send coins. */
+            return await this.wallet.send(_receiver, this.asset.satoshis - CASHOUT_FEE)
         },
 
         async transfer(_receiver, _satoshis) {
-            return await this.wallet.send(_receiver, _satoshis)
+            /* Validate transaction type. */
+            if (this.asset.group === '0') {
+                /* Send coins. */
+                return await this.wallet.send(_receiver, _satoshis)
+            } else {
+                /* Send tokens. */
+                return await this.wallet.send(this.asset.token_id_hex, _receiver, _satoshis)
+            }
         },
 
         setEntropy(_entropy) {
             this._entropy = _entropy
         },
 
-        setSatoshis(_satoshis) {
-            this._satoshis = _satoshis
+        setMnemonic(_mnemonic) {
+            let entropy
+            let error
+
+            try {
+                /* Derive entropy. */
+                entropy = mnemonicToEntropy(_mnemonic)
+            } catch (err) {
+                /* Set error message. */
+                error = err.message
+            }
+
+            /* Validate error. */
+            if (error) {
+                return error
+            }
+
+            /* Set entropy. */
+            this._entropy = entropy
+
+            /* Create wallet. */
+            this.createWallet(entropy)
+
+            /* Return entropy. */
+            return this.wallet
         },
 
         destroy() {
             /* Reset wallet. */
             this._entropy = null
             this._wallet = null
-            this._wif = null
-            this._coins = null
-            this._tokens = null
 
             console.info('Wallet destroyed successfully!')
         },

+ 16 - 5
web/stores/wallet/create.ts → web/stores/wallet/setEntropy.ts

@@ -1,8 +1,13 @@
 /* Import modules. */
-import { binToHex } from '@nexajs/utils'
-import { hexToBin } from '@nexajs/utils'
-import { randomBytes } from '@nexajs/crypto'
-import { sha256 } from '@nexajs/crypto'
+import {
+    randomBytes,
+    sha256,
+} from '@nexajs/crypto'
+
+import {
+    binToHex,
+    hexToBin,
+} from '@nexajs/utils'
 
 /* Set constants. */
 const ENTROPY_BYTES_LENGTH = 32
@@ -13,9 +18,15 @@ const ENTROPY_BYTES_LENGTH = 32
  * Generates 128-bits of random entropy and saves it to the
  * local browser.
  */
-export default async function () {
+export default function (_entropy) {
     let entropy
 
+    if (_entropy) {
+        this.setEntropy(_entropy)
+
+        return _entropy
+    }
+
     /* Return random bytes (as hex string). */
     const localBytes = binToHex(randomBytes(ENTROPY_BYTES_LENGTH))
 

+ 3 - 3
web/yarn.lock

@@ -2372,9 +2372,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001640:
-  version "1.0.30001642"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz#6aa6610eb24067c246d30c57f055a9d0a7f8d05f"
-  integrity sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==
+  version "1.0.30001643"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd"
+  integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==
 
 chalk@^2.4.2:
   version "2.4.2"