Kaynağa Gözat

Fix signature bug (in NEXA.js lib) to prevent bad UX.

Shomari 2 hafta önce
ebeveyn
işleme
1fd826be04

+ 45 - 48
web/components/menu/Wallet.vue

@@ -1,14 +1,12 @@
 <script setup>
 /* Import modules. */
 import numeral from 'numeral'
-import { reverseHex } from '@nexajs/utils'
 
 /* Initialize stores. */
-import { useProfileStore } from '@/stores/profile'
 import { useWalletStore } from '@/stores/wallet'
-const Profile = useProfileStore()
 const Wallet = useWalletStore()
 
+const cashoutAddr = ref(null)
 const isShowingDeposit = ref(false)
 const isShowingHistory = ref(false)
 const isShowingCashout = ref(false)
@@ -25,10 +23,16 @@ const displayBalance = computed(() => {
     if (Wallet.asset.group === '0') {
         decimalValue = Wallet.asset.satoshis * BigInt(1e4)
     } else {
+        /* Validate amount type. */
+        if (typeof Wallet.asset.amount !== 'bigint') {
+            decimalValue = BigInt(0)
+        } else {
+            decimalValue = Wallet.asset.amount * BigInt(1e4)
+        }
         decimalValue = Wallet.asset.amount * BigInt(1e4)
     }
 
-    if (Wallet.asset.decimal_places > 0) {
+    if (Wallet.asset?.decimal_places > 0) {
         bigIntValue = decimalValue / BigInt(10**Wallet.asset.decimal_places)
     } else {
         bigIntValue = decimalValue
@@ -40,7 +44,7 @@ const displayBalance = computed(() => {
 const displayBalanceUsd = computed(() => {
     /* Validate asset. */
     if (!Wallet.asset || !Wallet.asset.fiat || !Wallet.asset.fiat.USD) {
-        return '0.00'
+        return '$0.00'
     }
 
     /* Initialize locals. */
@@ -53,41 +57,6 @@ const displayBalanceUsd = computed(() => {
     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'
-//     }
-//
-//     const satoshis = Wallet.coins.reduce(
-//         (totalSatoshis, coin) => (totalSatoshis + coin.satoshis), BigInt(0)
-//     )
-//
-//     /* Calculate (NEX) total. */
-//     const mex = (parseInt(satoshis) / 10**8)
-//
-//     const mexUsd = mex * System.usd
-//
-//     /* Return formatted value. */
-//     return numeral(mexUsd).format('$0,0.00')
-// })
-
-
 /**
  * Set Tab
  */
@@ -110,6 +79,17 @@ const setTab = (_tab) => {
     }
 }
 
+const createWallet = () => {
+    if (!cashoutAddr.value) {
+        alert('Please provide a Cashout address to continue your NEW Wallet setup.')
+    } else {
+        /* Set cashout address. */
+        Wallet.setCashoutAddr(cashoutAddr.value)
+    }
+
+    /* Create new wallet. */
+    Wallet.createWallet()
+}
 
 
 onMounted(() => {
@@ -119,32 +99,49 @@ onMounted(() => {
 // onBeforeUnmount(() => {
 //     console.log('Before Unmount!')
 // })
-
 </script>
 
 <template>
     <main v-if="!Wallet.isReady" 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-amber-100 text-base font-medium border-2 border-yellow-200 rounded-lg shadow-md">
             Welcome to your Wally Dice game wallet.
             Click the button below to create a new wallet and begin playing.
         </p>
 
-        <button @click="Wallet.createWallet" class="px-3 py-2 text-2xl text-blue-100 font-medium bg-blue-500 border-2 border-blue-700 rounded-lg shadow hover:bg-blue-400">
+        <input
+            class="w-full px-3 py-1 text-xl sm:text-2xl bg-amber-200 border-2 border-yellow-400 rounded-md shadow"
+            type="text"
+            v-model="cashoutAddr"
+            placeholder="Enter a Cashout address"
+        />
+
+        <button @click="createWallet" class="px-3 py-2 text-2xl text-blue-100 font-medium bg-blue-500 border-2 border-blue-700 rounded-lg shadow hover:bg-blue-400">
             Create New Wallet
         </button>
     </main>
 
     <main v-else class="">
 
-        <section class="px-5 py-3 bg-yellow-200 border-2 border-yellow-400 rounded-lg shadow">
-            <h3 class="text-sm text-yellow-700 font-medium uppercase">
+        <section class="px-5 py-3 bg-amber-100 border-2 border-yellow-400 rounded-lg shadow">
+            <h3 class="text-sm text-yellow-700 font-medium tracking-widest uppercase">
                 Available Balance
             </h3>
 
-            <h2 class="text-3xl font-medium">
-                {{displayBalance}}
-                {{displayBalanceUsd}}
+            <h2 class="flex justify-between items-end text-3xl text-amber-900 font-medium">
+                <span>{{displayBalance}}</span>
+
+                <span class="text-2xl">{{displayBalanceUsd}}</span>
             </h2>
+
+            <div class="mt-1 pt-1 text-xs text-amber-900 border-t border-amber-400">
+                <span class="tracking-wide font-medium uppercase">
+                    My Cashout Address
+                </span>
+
+                <NuxtLink :to="'https://explorer.nexa.org/address/' + Wallet.cashoutAddr" target="_blank" class="block tracking-tight hover:underline">
+                    {{Wallet.cashoutAddr}}
+                </NuxtLink>
+            </div>
         </section>
 
         <div class="mt-10">

+ 25 - 16
web/components/menu/wallet/Cashout.vue

@@ -3,16 +3,11 @@ import numeral from 'numeral'
 import QrScanner from 'qr-scanner'
 
 /* Initialize stores. */
-import { useSystemStore } from '@/stores/system'
 import { useWalletStore } from '@/stores/wallet'
-const System = useSystemStore()
 const Wallet = useWalletStore()
 
 /* Initialize handlers. */
 const receiver = ref(null)
-const currency = ref(null)
-const satoshis = ref(null)
-
 const video = ref(null)
 const scanner = ref(null)
 const cameraError = ref(null)
@@ -118,8 +113,9 @@ const cashout = async () => {
         return alert('Enter a destination address.')
     }
 
-    // const response = await Wallet.transfer(receiver.value, BigInt(satoshis.value))
+    /* Request cashout. */
     const response = await Wallet.cashout(receiver.value)
+        .catch(err => console.error(err))
     console.log('RESPONSE', response)
 
     /* Validate transaction idem. */
@@ -133,6 +129,11 @@ const cashout = async () => {
     }
 }
 
+const enterCashoutAddr = () => {
+    /* Set receiver address. */
+    receiver.value = Wallet.cashoutAddr
+}
+
 // onMounted(() => {
 //     console.log('Mounted!')
 //     // Now it's safe to perform setup operations.
@@ -145,13 +146,19 @@ const cashout = async () => {
 </script>
 
 <template>
-    <section class="mt-5 flex flex-row gap-1">
-        <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="text"
-            v-model="receiver"
-            placeholder="Enter a Crypto address"
-        />
+    <section class="mt-5 flex flex-row gap-1 items-start">
+        <div>
+            <input
+                class="w-full px-3 py-1 text-xl sm:text-2xl bg-amber-100 border-2 border-amber-300 rounded-md shadow"
+                type="text"
+                v-model="receiver"
+                placeholder="Enter a Crypto address"
+            />
+
+            <button @click="enterCashoutAddr" class="pt-2 pl-3 text-sm text-indigo-500 font-medium tracking-wide hover:underline">
+                Use My Cashout Address?
+            </button>
+        </div>
 
         <button @click="openScanner">
             <svg class="w-12 h-12 hover:text-red-500 hover:cursor-pointer" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
@@ -178,14 +185,16 @@ const cashout = async () => {
         playsinline
     />
 
-    <p class="py-5 px-3 text-sm font-medium text-rose-500 italic">
+    <p class="py-5 px-3 text-base font-medium text-gray-600 italic leading-7">
         Withdraw your FULL balance from your in-game Bank by entering your destination address and clicking the "Cashout" button below.
     </p>
 
     <button
         @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"
+        class="my-5 block px-8 py-3 bg-green-600 border-2 border-green-800 rounded-xl shadow hover:bg-green-500"
     >
-        Cashout
+        <span class="text-lime-100 text-3xl font-medium uppercase">
+            Cashout
+        </span>
     </button>
 </template>

+ 142 - 117
web/components/menu/wallet/Deposit.vue

@@ -1,12 +1,18 @@
 <script setup>
+import { copyToClipboard } from '@nexajs/app'
 import QRCode from 'qrcode'
 
+/* Define properties. */
+const props = defineProps({
+    isFullScreen: Boolean,
+})
+
 /* Initialize stores. */
-import { useSystemStore } from '@/stores/system'
 import { useWalletStore } from '@/stores/wallet'
-const System = useSystemStore()
 const Wallet = useWalletStore()
 
+const ADDRESS_POLLING_DELAY = 100
+
 const dataUrl = ref(null)
 const depositAmount = ref(null)
 
@@ -18,147 +24,166 @@ const isShowingCurrencyOptions = ref(false)
  * Uses BIP-21 to encode a data URI.
  */
  const updateQrCode = async () => {
+    if (!Wallet.address) {
+        return setTimeout(() => {
+            updateQrCode()
+        }, ADDRESS_POLLING_DELAY)
+    }
+
     let bip21Url
 
     /* Handle (user-defined) amount. */
     if (Wallet.nex > 0) {
-        bip21Url = `${Wallet.address}?amount=${Wallet.nex}&label=WallyDice`
+        bip21Url = `${Wallet.address}?amount=${Wallet.nex}`
     } else {
         bip21Url = Wallet.address
     }
+    // console.log('Wallet.address', Wallet.address)
+    // console.log('bip21Url', bip21Url)
 
     /* Set data URL. */
     dataUrl.value = await QRCode.toDataURL(bip21Url)
 }
 
+const clipboardHandler = () => {
+    /* Copy address to clipboard. */
+    if (copyToClipboard(Wallet.address)) {
+        alert(`[ ${Wallet.address} ] has been copied to the clipboard.`)
+    } else {
+        alert(`Oops! Unfortunately, something went wrong.`)
+    }
+}
 
 onMounted(() => {
     /* Update the QR code. */
     updateQrCode()
+
+    // console.log('PROPS', typeof props.isFullScreen, props.isFullScreen)
 })
 
 // onBeforeUnmount(() => {
 //     console.log('Before Unmount!')
 //     // Now is the time to perform all cleanup operations.
 // })
-
 </script>
 
 <template>
-    <NuxtLink  :to="Wallet.address">
-        <section class="px-3 py-2 my-5 bg-amber-500 border-2 border-amber-700 rounded-lg shadow">
-            <h2 class="text-lg sm:text-xl text-amber-700 font-medium text-center uppercase">
-                Your Deposit Address
-            </h2>
-
-            <h3 :to="Wallet.address"
-                class="flex justify-center text-lg text-amber-900 font-medium truncate"
-            >
-                {{Wallet.abbr}}
-            </h3>
-
-            <div class="flex justify-center">
-                <img
-                    :src="dataUrl"
-                    class="my-5 w-full h-auto border-2 border-amber-900 rounded-lg shadow-md"
-                />
+    <main class="" :class="[ props.isFullScreen === true ? 'grid lg:grid-cols-2 gap-8' : '' ]">
+        <NuxtLink :to="Wallet.address">
+            <section class="w-full px-3 py-2 my-5 bg-amber-500 border-2 border-amber-700 rounded-lg shadow">
+                <h2 class="text-lg sm:text-xl text-amber-700 font-medium text-center uppercase">
+                    Your Deposit Address
+                </h2>
+
+                <h3 :to="Wallet.address"
+                    class="flex justify-center text-lg text-amber-900 font-medium truncate"
+                >
+                    {{Wallet.abbr}}
+                </h3>
+
+                <div class="flex justify-center">
+                    <img
+                        :src="dataUrl"
+                        class="my-5 w-full h-auto border-2 border-amber-900 rounded-lg shadow-md"
+                    />
+                </div>
+
+                <p class="px-0 sm:px-5 text-sm text-amber-900 text-center">
+                    Scan the QR code shown above or click the image to open your preferred wallet.
+                </p>
+            </section>
+        </NuxtLink>
+
+        <section>
+            <label for="combobox" class="block text-lg font-medium leading-6 text-gray-900">
+                Choose a deposit currency:
+            </label>
+
+            <div class="relative mt-2">
+                <input
+                    id="combobox"
+                    type="text"
+                    class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-2xl sm:leading-6"
+                    role="combobox"
+                    aria-controls="options"
+                    aria-expanded="false"
+                    value="Nexa">
+
+                <button type="button" class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
+                    <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                        <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" />
+                    </svg>
+                </button>
+
+                <ul v-if="isShowingCurrencyOptions" class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" id="options" role="listbox">
+                    <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
+                        <span class="block truncate font-semobold">Nexa</span>
+
+                        <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600">
+                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                                <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
+                            </svg>
+                        </span>
+                    </li>
+
+                    <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
+                        <div class="flex items-center">
+                            <img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" class="h-6 w-6 flex-shrink-0 rounded-full">
+                            <!-- Selected: "font-semibold" -->
+                            <span class="ml-3 truncate">Tether - USDT</span>
+                        </div>
+
+                        <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
+                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                                <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
+                            </svg>
+                        </span>
+                    </li>
+
+                    <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
+                        <span class="block truncate">Bitcoin</span>
+
+                        <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
+                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                                <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
+                            </svg>
+                        </span>
+                    </li>
+
+                    <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
+                        <span class="block truncate">Bitcoin Cash</span>
+
+                        <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
+                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                                <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
+                            </svg>
+                        </span>
+                    </li>
+
+                </ul>
             </div>
 
-            <p class="px-0 sm:px-5 text-sm text-amber-900 text-center">
-                Scan the QR code shown above or click the image to open your preferred wallet.
-            </p>
-        </section>
-    </NuxtLink>
-
-    <section>
-        <label for="combobox" class="block text-lg font-medium leading-6 text-gray-900">
-            Choose a deposit currency:
-        </label>
-
-        <div class="relative mt-2">
             <input
-                id="combobox"
-                type="text"
-                class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-2xl sm:leading-6"
-                role="combobox"
-                aria-controls="options"
-                aria-expanded="false"
-                value="Nexa">
-
-            <button type="button" class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
-                <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                    <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" />
-                </svg>
-            </button>
-
-            <ul v-if="isShowingCurrencyOptions" class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" id="options" role="listbox">
-                <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
-                    <span class="block truncate font-semobold">Nexa</span>
-
-                    <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600">
-                        <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                            <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
-                        </svg>
-                    </span>
-                </li>
-
-                <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
-                    <div class="flex items-center">
-                        <img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" class="h-6 w-6 flex-shrink-0 rounded-full">
-                        <!-- Selected: "font-semibold" -->
-                        <span class="ml-3 truncate">Tether - USDT</span>
-                    </div>
-
-                    <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
-                        <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                            <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
-                        </svg>
-                    </span>
-                </li>
-
-                <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
-                    <span class="block truncate">Bitcoin</span>
-
-                    <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
-                        <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                            <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
-                        </svg>
-                    </span>
-                </li>
-
-                <li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
-                    <span class="block truncate">Bitcoin Cash</span>
-
-                    <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
-                        <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                            <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
-                        </svg>
-                    </span>
-                </li>
-
-            </ul>
-        </div>
-    </section>
-
-    <input
-        class="w-full my-3 px-3 py-1 text-xl sm:text-2xl bg-yellow-200 border-2 border-yellow-400 rounded-md shadow"
-        type="number"
-        v-model="depositAmount"
-        placeholder="Enter a (USD) amount"
-    />
-
-    <div class="mb-5 flex flex-row gap-3">
-        <button
-            class="w-full block px-3 py-1 text-2xl font-medium bg-blue-200 border-2 border-blue-400 rounded-md shadow hover:bg-blue-300"
-        >
-            Copy
-        </button>
-
-        <button
-            class="w-full block px-3 py-1 text-2xl font-medium bg-blue-200 border-2 border-blue-400 rounded-md shadow hover:bg-blue-300"
-        >
-            Share
-        </button>
-    </div>
-
+                class="w-full my-3 px-3 py-1 text-xl sm:text-2xl bg-yellow-200 border-2 border-yellow-400 rounded-md shadow"
+                type="number"
+                v-model="depositAmount"
+                placeholder="Enter a (USD) amount"
+            />
+
+            <div class="mb-5 flex flex-row gap-3">
+                <button
+                    @click="clipboardHandler"
+                    class="w-full block px-3 py-1 text-2xl font-medium bg-blue-200 border-2 border-blue-400 rounded-md shadow hover:bg-blue-300"
+                >
+                    Copy
+                </button>
+
+                <button
+                    class="w-full block px-3 py-1 text-2xl font-medium bg-blue-200 border-2 border-blue-400 rounded-md shadow hover:bg-blue-300"
+                >
+                    Share
+                </button>
+            </div>
+     </section>
+    </main>
 </template>

+ 215 - 18
web/components/menu/wallet/History.vue

@@ -1,37 +1,234 @@
 <script setup>
+/* Import modules. */
+import moment from 'moment'
 import numeral from 'numeral'
+import { getSender } from '@nexajs/address'
+import {
+    getAddressHistory,
+    getTransaction,
+} from '@nexajs/rostrum'
 
 /* Initialize stores. */
-import { useSystemStore } from '@/stores/system'
-const System = useSystemStore()
+import { useWalletStore } from '@/stores/wallet'
+const Wallet = useWalletStore()
+
+/* Set constants. */
+const MAX_RESULTS_PER_PAGE = 20
+
+/* Set responsive. */
+const txs = ref(null)
+
+/**
+ * Initialization
+ *
+ * Setup the wallet history.
+ */
+const init = async () => {
+    /* Initialize locals. */
+    let history
+    let txids
+
+    // console.log('ADDRESS', Wallet.address)
+
+    /* Request address history. */
+    history = await getAddressHistory(Wallet.address)
+        .catch(err => console.error(err))
+    // console.log('HISTORY', history)
+
+    /* Handle history. */
+    txids = history
+        .reverse()
+        .slice(0, MAX_RESULTS_PER_PAGE)
+        .map(_tx => _tx.tx_hash)
+
+    /* Initialize array. */
+    txs.value = []
+
+    txids.forEach(async _txid => {
+        // console.log('TXID', _txid)
+
+        /* Initialize locals. */
+        let details
+
+        /* Request transaction details. */
+        details = await getTransaction(_txid)
+            .catch(err => console.error(err))
+        // console.log('DETAILS', details)
+
+        /* Add transaction details. */
+        txs.value.push(details)
+    })
+}
+
+const displayInputs = (_inputs) => {
+    const inputs = []
+
+    _inputs.forEach(_input => {
+        inputs.push({
+            outpoint: _input.outpoint,
+            address: getSender(_input),
+            satoshis: _input.value_satoshi,
+        })
+    })
+
+    return inputs
+}
+
+const displayOutputs = (_outputs) => {
+    const outputs = []
+
+    _outputs.forEach(_output => {
+        outputs.push({
+            outpoint: _output.outpoint_hash,
+            address: _output.scriptPubKey.addresses[0],
+            satoshis: _output.value_satoshi,
+            script: {
+                hash: _output.scriptPubKey.scriptHash,
+                args: _output.scriptPubKey.argsHash,
+            },
+            group: _output.scriptPubKey.group,
+            groupAuthority: _output.scriptPubKey.groupAuthority,
+            groupQuantity: _output.scriptPubKey.groupQuantity,
+            hex: _output.scriptPubKey.hex,
+        })
+    })
+
+    return outputs
+}
+
+const displayTime = (_time) => {
+    return moment.unix(_time).format('lll')
+}
+
+const displayTimeAgo = (_time) => {
+    return moment.unix(_time).fromNow()
+}
 
-// onMounted(() => {
-//     console.log('Mounted!')
-//     // Now it's safe to perform setup operations.
-// })
+
+onMounted(() => {
+    init()
+})
 
 // onBeforeUnmount(() => {
 //     console.log('Before Unmount!')
 //     // Now is the time to perform all cleanup operations.
 // })
-
 </script>
 
 <template>
     <main class="flex flex-col gap-4">
-        <p class="px-3 py-2 bg-rose-100 text-sm border border-rose-200 rounded-lg shadow-md">
-            Thanks for visiting.
-            This area is still under development and will be updated soon.
-        </p>
-
-        <h2 class="text-2xl font-medium">
-            What's Coming?
+        <h2 class="text-xl font-medium">
+            Recent Transactions
         </h2>
 
-        <ol class="pl-10 list-disc">
-            <li>Transaction history</li>
-            <li>Data exporting</li>
-        </ol>
+        <NuxtLink
+            :to="'https://nexa.sh/tx/' + tx.txidem"
+            target="_blank"
+            v-for="tx of txs"
+            :key="tx.txidem"
+            class="px-2 p-1 bg-amber-50 border border-amber-300 rounded-md shadow hover:bg-amber-100"
+        >
+            <h3 class="text-xs font-medium truncate">
+                ID {{tx.txidem}}
+            </h3>
+
+            <h3>
+                {{displayTime(tx.time)}}
+                <small>({{displayTimeAgo(tx.time)}})</small>
+            </h3>
+
+            <h3 class="text-xs text-amber-600">
+                fee: {{tx.fee}}
+            </h3>
+
+            <h3 class="text-xs text-amber-600">
+                size: {{tx.size}}
+            </h3>
+
+            <section class="my-3 px-2 py-1 flex flex-col gap-3 bg-gray-200 border border-gray-400 rounded">
+                <h2 class="text-xs text-amber-600 uppercase">
+                    Inputs
+                </h2>
+
+                <div v-for="input of displayInputs(tx.vin)" :key="input.outpoint" class="flex flex-col text-xs divide-amber-700">
+                    <h3 class="text-xs text-amber-800 truncate">
+                        Outpoint:
+                        <span class="font-medium">{{input.outpoint}}</span>
+                    </h3>
+
+                    <NuxtLink
+                        :to="'https://explorer.nexa.org/address/' + input.address"
+                        target="_blank"
+                        class="text-xs text-amber-600 truncate hover:text-amber-500"
+                    >
+                        Address:
+                        <span class="font-medium">{{input.address}}</span>
+                    </NuxtLink>
+
+                    <h3 v-if="input.satoshis" class="text-xs text-amber-800 truncate">
+                        Satoshis:
+                        <span class="font-medium">{{numeral(Number(input.satoshis)).format('0,0')}}</span>
+                    </h3>
+                    <!-- {{input}} -->
+                </div>
+                <!-- <pre v-for="input of displayInputs(tx.vin)" :key="input.outpoint" class="text-xs">{{input}}</pre> -->
+            </section>
+
+            <section class="my-3 px-2 py-1 flex flex-col gap-3 bg-gray-700 border border-gray-900 rounded">
+                <h2 class="text-xs text-gray-50 uppercase">
+                    Outputs
+                </h2>
+
+                <div v-for="output of displayOutputs(tx.vout)" :key="output.outpoint" class="flex flex-col text-xs divide-amber-700">
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Outpoint:
+                        <span class="font-medium">{{output.outpoint}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Address:
+                        <span class="font-medium">{{output.address}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Satoshis:
+                        <span class="font-medium">{{output.satoshis}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Script (hash):
+                        <span class="font-medium">{{output.script.hash}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Script (args):
+                        <span class="font-medium">{{output.script.args}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Group:
+                        <span class="font-medium">{{output.group}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Authority:
+                        <span class="font-medium">{{output.groupAuthority}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Quantity:
+                        <span class="font-medium">{{output.groupQuantity}}</span>
+                    </h3>
+
+                    <h3 class="text-xs text-gray-50 truncate">
+                        Hex:
+                        <span class="font-medium">{{output.hex}}</span>
+                    </h3>
+                    <!-- {{input}} -->
+                </div>
+                <!-- <pre v-for="output of displayOutputs(tx.vout)" :key="output.outpoint" class="text-xs">{{output}}</pre> -->
+            </section>
+        </NuxtLink>
 
     </main>
 </template>

+ 2 - 2
web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "wally-dice-web",
-  "version": "24.9.10",
+  "version": "24.9.11",
   "license": "MIT",
   "scripts": {
     "build": "nuxt build",
@@ -13,7 +13,7 @@
     "@nuxtjs/plausible": "0.2.1",
     "@pinia/nuxt": "0.5.4",
     "moment": "2.29.4",
-    "nexajs": "24.9.5",
+    "nexajs": "24.9.11",
     "numeral": "2.0.6",
     "nuxt": "3.12.4",
     "pinia": "2.2.2",

+ 57 - 0
web/static/TimeToCashout.json

@@ -0,0 +1,57 @@
+{
+  "contracts": [
+    {
+      "contractName": "TimeToCashout",
+      "constructorInputs": [
+        {
+          "name": "owner",
+          "type": "pubkey",
+          "visible": false,
+          "unused": false
+        },
+        {
+          "name": "recoveryPkh",
+          "type": "bytes20",
+          "visible": true,
+          "unused": false
+        },
+        {
+          "name": "gratitude",
+          "type": "int",
+          "visible": true,
+          "unused": false
+        },
+        {
+          "name": "timeout",
+          "type": "int",
+          "visible": true,
+          "unused": false
+        }
+      ],
+      "abi": [
+        {
+          "name": "transfer",
+          "inputs": [
+            {
+              "name": "signature",
+              "type": "sig"
+            }
+          ]
+        },
+        {
+          "name": "cashout",
+          "inputs": []
+        }
+      ],
+      "dependencies": [],
+      "bytecode": "OP_FROMALTSTACK OP_FROMALTSTACK OP_FROMALTSTACK OP_FROMALTSTACK OP_4 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_5 OP_ROLL OP_SWAP OP_CHECKSIGVERIFY OP_2DROP OP_2DROP OP_ELSE OP_4 OP_ROLL OP_1 OP_NUMEQUALVERIFY OP_3 OP_ROLL OP_CHECKSEQUENCEVERIFY OP_DROP 005114 OP_ROT OP_CAT OP_0 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_OUTPUTVALUE OP_5 OP_PUSH_TX_STATE OP_3 OP_ROLL OP_SUB OP_GREATERTHANOREQUAL OP_VERIFY OP_DROP OP_ENDIF",
+      "contracts": []
+    }
+  ],
+  "source": "pragma nexscript >= 0.2.0;\n\ncontract TimeToCashout(\n  pubkey owner,\n  bytes20 visible recoveryPkh,\n  int visible gratitude,\n  int visible timeout,\n) {\n  function transfer(sig signature) {\n    require(checkSig(signature, owner));\n  }\n\n  function cashout() {\n    require(tx.age >= timeout);\n\n    bytes23 recoveryBytecode = new LockingBytecodeP2PKT(recoveryPkh);\n\n    require(tx.outputs[0].lockingBytecode == recoveryBytecode);\n    require(tx.outputs[0].value >= tx.amountIn - gratitude);\n  }\n}\n",
+  "compiler": {
+    "name": "nexc",
+    "version": "0.10.0"
+  },
+  "updatedAt": "2024-09-11T09:15:53.813Z"
+}

+ 21 - 0
web/static/TimeToCashout.nex

@@ -0,0 +1,21 @@
+pragma nexscript >= 0.2.0;
+
+contract TimeToCashout(
+  pubkey owner,
+  bytes20 visible recoveryPkh,
+  int visible gratitude,
+  int visible timeout,
+) {
+  function transfer(sig signature) {
+    require(checkSig(signature, owner));
+  }
+
+  function cashout() {
+    require(tx.age >= timeout);
+
+    bytes23 recoveryBytecode = new LockingBytecodeP2PKT(recoveryPkh);
+
+    require(tx.outputs[0].lockingBytecode == recoveryBytecode);
+    require(tx.outputs[0].value >= tx.amountIn - gratitude);
+  }
+}

+ 3 - 4
web/stores/game/new.ts

@@ -64,8 +64,7 @@ export default async function () {
     const response = await $fetch(ENDPOINT + '/v1/play', {
         method,
         body,
-    })
-    .catch(err => console.error(err))
+    }).catch(err => console.error(err))
     console.info('Gameplay details:', response)
 
     /* Set play id. */
@@ -76,9 +75,9 @@ export default async function () {
 
     /* Build authorization value. */
     const authValue = `${this.playid}:${this.rtp}:${this.payout}:${this.position}:${this.seed}`
-    console.log('AUTH VALUE', authValue)
+    // console.log('AUTH VALUE', authValue)
 
     /* Double-hash authorization value. */
     this.setAuthHash(binToHex(ripemd160(sha256(authValue), 'binary')))
-    console.log('AUTH HASH', this.authHash)
+    // console.log('AUTH HASH', this.authHash)
 }

+ 5 - 16
web/stores/game/rollDice.ts

@@ -1,11 +1,12 @@
 /* Import modules. */
 import { sendCoins } from '@nexajs/purse'
-
 import { encodeNullData } from '@nexajs/script'
 
+/* Import (local) modules. */
 import { useWalletStore } from '@/stores/wallet'
 
 export default async function () {
+    /* Initialize wallet store. */
     const Wallet = useWalletStore()
 
     /* Validate play address. */
@@ -26,7 +27,7 @@ export default async function () {
         'NEXA.games',
         this.authHash,
     ]
-    console.log('AUTH HASH', this.authHash)
+    // console.log('AUTH HASH', this.authHash)
 
     /* Initialize hex data. */
     const nullData = encodeNullData(userData)
@@ -44,19 +45,7 @@ export default async function () {
         })
     }
 
-    console.log('\n  Coins:', Wallet.wallet.coins)
-
-    // /* Calculate the total balance of the unspent outputs. */
-    // const unspentSatoshis = coins
-    //     .reduce(
-    //         (totalValue, unspentOutput) => (totalValue + unspentOutput.satoshis), 0
-    //     )
-    // console.log('UNSPENT SATOSHIS', unspentSatoshis)
-    //
-    // // NOTE: 150b (per input), 35b (per output), 10b (misc)
-    // // NOTE: Double the estimate (for safety).
-    // const feeEstimate = ((coins.length * 150) + (35 * 2) + 10) * 2
-    // console.log('FEE ESTIMATE', feeEstimate)
+    // console.log('\n  Coins:', Wallet.coins)
 
     /* Add value output. */
     receivers.push({
@@ -71,7 +60,7 @@ export default async function () {
     console.log('\n  Receivers:', receivers)
 
     /* Send UTXO request. */
-    const response = await sendCoins(Wallet.wallet.coins, receivers)
+    const response = await sendCoins(Wallet.coins, receivers)
     console.log('Send UTXO (response):', response)
 
     try {

+ 0 - 28
web/stores/system/setClipboard.js

@@ -1,28 +0,0 @@
-/**
- * Set Clipboard
- */
-export default (_content) => {
-    try {
-        const textArea = document.createElement('textarea')
-        textArea.value = _content
-        document.body.appendChild(textArea)
-
-        if (navigator.userAgent.match(/ipad|iphone/i)) {
-            const range = document.createRange()
-            range.selectNodeContents(textArea)
-
-            const selection = window.getSelection()
-            selection.removeAllRanges()
-            selection.addRange(range)
-
-            textArea.setSelectionRange(0, 999999)
-        } else {
-            textArea.select()
-        }
-
-        document.execCommand('copy')
-        document.body.removeChild(textArea)
-    } catch (err) {
-        console.error(err)
-    }
-}

+ 320 - 61
web/stores/wallet.ts

@@ -1,25 +1,67 @@
 /* Import modules. */
 import { defineStore } from 'pinia'
-import moment from 'moment'
-
+import {
+    decodeAddress,
+    encodeAddress,
+} from '@nexajs/address'
+import {
+    ripemd160,
+    sha256,
+ } from '@nexajs/crypto'
 import { mnemonicToEntropy } from '@nexajs/hdnode'
+import { broadcast } from '@nexajs/provider'
+import {
+    buildCoins,
+    getCoins,
+    sendCoins,
+} from '@nexajs/purse'
+import { getAddressUnspent } from '@nexajs/rostrum'
+import {
+    encodeDataPush,
+    OP,
+} from '@nexajs/script'
+import {
+    binToHex,
+    hexToBin,
+    utf8ToBin,
+} from '@nexajs/utils'
 import {
     Wallet,
     WalletStatus,
 } from '@nexajs/wallet'
 
+import { useSystemStore } from '@/stores/system'
 import _setEntropy from './wallet/setEntropy.ts'
 
 const CASHOUT_FEE = BigInt(1000) // FIXME We MUST calculate this (same as change).
+const TIMETOCASHOUT_HEX = '6c6c6c6c5479009c63557a7cad6d6d67547a519d537ab275030051147b7e00cd8800cc55ea537a94a2697568'
+const UPDATE_BALANCE_INTERVAL = 30000
 
 /**
  * Wallet Store
  */
 export const useWalletStore = defineStore('wallet', {
     state: () => ({
+        /**
+         * Assets
+         *
+         * Will hold ALL assets that the wallet manages.
+         */
         _assets: null,
 
-        // _forceUI: null,
+        /**
+         * Cashout Address
+         *
+         * Use to automatically cashout a wallet (after a specified timeout).
+         */
+        _cashoutAddr: null,
+
+        /**
+         * Coins
+         *
+         * Manage unspent coins.
+         */
+        _coins: null,
 
         /**
          * Entropy
@@ -32,31 +74,6 @@ export const useWalletStore = defineStore('wallet', {
          */
         _entropy: 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
          *
@@ -67,29 +84,109 @@ export const useWalletStore = defineStore('wallet', {
 
     getters: {
         /* Return (abbreviated) wallet status. */
+        // abbr(_state) {
+        //     if (!_state._wallet) {
+        //         return null
+        //     }
+
+        //     return _state._wallet.abbr
+        // },
         abbr(_state) {
-            if (!_state._wallet) {
+            if (!this.address) {
                 return null
             }
 
-            return _state._wallet.abbr
+            return this.address.slice(5, 17) + '...' + this.address.slice(-12)
         },
 
         /* Return wallet status. */
-        address(_state) {
-            if (!_state._wallet) {
-                return null
-            }
+        // address(_state) {
+        //     if (!_state._wallet) {
+        //         return null
+        //     }
 
-            return _state._wallet.address
+        //     return _state._wallet.address
+        // },
+        address(_state) {
+            /* Initialize locals. */
+            let contractAddress
+            let prefix
+
+            /* Set prefix. */
+            prefix = 'nexa'
+
+            /* Encode the public key hash into a P2PKH nexa address. */
+            contractAddress = encodeAddress(
+                prefix,
+                'TEMPLATE',
+                this.scriptPubkey,
+            )
+            // console.info('(CONTRACT) ADDRESS', contractAddress)
+
+            return contractAddress
         },
 
+        // asset(_state) {
+        //     if (!this.assets || !this.wallet) {
+        //         return null
+        //     }
+
+        //     return this.assets[this.wallet.assetid]
+        // },
         asset(_state) {
             if (!this.assets || !this.wallet) {
                 return null
             }
 
-            return this.assets[this.wallet.assetid]
+            /* Initialize locals. */
+            let amount
+            let decimal_places
+            let fiat
+            let satoshis
+            let USD
+
+            /* Set decimal places. */
+            decimal_places = 2
+
+            /* Calculate satoshis. */
+            satoshis = this._assets.reduce(
+                (totalSats, coin) => (totalSats + BigInt(coin.value)), BigInt(0)
+            )
+
+            /* Calculate amount. */
+            amount = satoshis
+
+            /* Initialize System. */
+            const System = useSystemStore()
+
+            /* Initialize USD value. */
+            USD = 0
+
+            /* Validate system ticker. */
+            if (System.ticker) {
+                /* Set quote. */
+                const quote = System.ticker.quote
+
+                /* Validate quote. */
+                if (quote) {
+                    /* Set (USD) price. */
+                    USD = quote?.USD?.price * Number(satoshis)
+                }
+            }
+
+            /* Set fiat. */
+            fiat = { USD }
+
+            /* Build asset. */
+            const asset = {
+                amount,
+                satoshis,
+                decimal_places,
+                fiat,
+            }
+
+            /* Return asset. */
+            return asset
         },
 
         assets(_state) {
@@ -104,6 +201,24 @@ export const useWalletStore = defineStore('wallet', {
             return _state._wallet.assets
         },
 
+        /* Return cashout address. */
+        cashoutAddr(_state) {
+            if (!_state._cashoutAddr) {
+                return null
+            }
+
+            return _state._cashoutAddr
+        },
+
+        /* Return unspent coins. */
+        coins(_state) {
+            if (!_state._coins) {
+                return []
+            }
+
+            return _state._coins
+        },
+
         /* Return wallet status. */
         isLoading(_state) {
             if (!_state._wallet) {
@@ -126,6 +241,76 @@ export const useWalletStore = defineStore('wallet', {
             return _state._wallet.isReady
         },
 
+        scriptPubkey(_state) {
+            /* Initialize locals. */
+            let constraintData
+            let constraintHash
+            let decoded
+            let gratitude
+            let lockingScript
+            let prefix
+            let publicKey
+            let recoveryPkh
+            let scriptHash
+            let scriptPubkey
+            let timeout
+
+            /* Set prefix. */
+            prefix = 'nexa'
+
+            /* Set locking script. */
+            lockingScript = hexToBin(TIMETOCASHOUT_HEX)
+            // console.info('CONTRACT TEMPLATE', binToHex(lockingScript))
+
+            scriptHash = ripemd160(sha256(lockingScript))
+            // console.log('TEMPLATE HASH', binToHex(scriptHash))
+
+            /* Set public key. */
+            publicKey = this._wallet.publicKey
+            // console.log('PUBLIC KEY', binToHex(publicKey))
+
+            /* Hash the public key hash according to the P2PKH/P2PKT scheme. */
+            constraintData = encodeDataPush(publicKey)
+
+            /* Set the constraint hash. */
+            constraintHash = ripemd160(sha256(constraintData))
+
+            /* Decode (cashout) address. */
+            decoded = decodeAddress(this._cashoutAddr)
+            // console.log('DECODED (cashout addr)', decoded)
+
+            recoveryPkh = decoded.hash.slice(3) // NOTE: Strip out public key hash.
+            // console.log('RECOVERY PKH', binToHex(recoveryPkh))
+
+            gratitude = hexToBin('2710') // 10,000 (100.00 NEXA)
+            // gratitude = hexToBin('03e8') // 1,000 (10.00 NEXA)
+            gratitude.reverse()
+            gratitude = encodeDataPush(gratitude)
+
+            // timeout = hexToBin('4013c7') // (5,063) approx 30 days
+            // timeout = hexToBin('4000a9') // (169) approx 24 hours
+            // timeout = hexToBin('400007') // (7) approx 1 hour
+            timeout = hexToBin('400001') // (1) approx 8 1/2 minutes
+            timeout.reverse()
+            timeout = encodeDataPush(timeout)
+
+            /* Build script public key. */
+            scriptPubkey = new Uint8Array([
+                OP.ZERO, // groupid or empty stack item
+                ...encodeDataPush(scriptHash), // script hash
+                // OP.ZERO, // arguments hash or empty stack item
+                ...encodeDataPush(constraintHash),  // arguments hash
+                // ...encodeDataPush(publicKey), // Owners public key.
+                ...encodeDataPush(recoveryPkh), // A recovery address (specified by the Owner) used to cashout the balance, after a timeout period.
+                ...gratitude, // The rate of exchange, charged by the Provider. (measured in <satoshis> per <asset>)
+                ...timeout, // The rate of exchange, charged by the Provider. (measured in <satoshis> per <asset>)
+            ])
+            // console.info('\nSCRIPT PUBLIC KEY', binToHex(scriptPubkey))
+
+            /* Return script public key. */
+            return scriptPubkey
+        },
+
         /* Return NEXA.js wallet instance. */
         wallet(_state) {
             return _state._wallet
@@ -134,6 +319,10 @@ export const useWalletStore = defineStore('wallet', {
         WalletStatus() {
             return WalletStatus
         },
+
+        wif(_state) {
+            return _state._wallet.wif
+        }
     },
 
     actions: {
@@ -158,19 +347,9 @@ export const useWalletStore = defineStore('wallet', {
             this._wallet = await Wallet.init(this._entropy, true)
             console.info('(Initialized) wallet', this.wallet)
 
-            // this._assets = { ...this.wallet.assets } // cloned assets
-
-            /* Set (default) asset. */
-            this.wallet.setAsset('0')
-
-            /* 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 }
-            })
+            /* Update balance. */
+            setInterval(this.updateBalance, UPDATE_BALANCE_INTERVAL)
+            this.updateBalance()
         },
 
         /**
@@ -198,28 +377,108 @@ export const useWalletStore = defineStore('wallet', {
         },
 
         async cashout(_receiver) {
-console.log('ASSET', this.asset)
-console.log('ASSETS', this.assets)
+            /* Calculate remaining satoshis. */
+            // FIXME This needs to be ZERO balance.
+            const satoshis = (this.asset.satoshis - CASHOUT_FEE)
 
-            /* Send coins. */
-            return await this.wallet.send(_receiver, this.asset.satoshis - CASHOUT_FEE)
+            /* Transfer assets to receiver. */
+            this.transfer(_receiver, satoshis)
         },
 
         async transfer(_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)
-            }
+// console.log('ASSET', this.asset)
+// console.log('ASSETS', this.assets)
+// console.log('COINS-1', this.coins)
+// console.log('PUBLIC KEY', binToHex(this._wallet.publicKey))
+            /* Initialize locals. */
+            let coins
+            let lockingScript
+            let receivers
+            let response
+            let scriptPubkey
+            let unlockingScript
+
+            /* Set script public key. */
+            scriptPubkey = encodeDataPush(this._wallet.publicKey)
+
+            /* Set locking script. */
+            lockingScript = hexToBin(TIMETOCASHOUT_HEX)
+            // console.info('\nCONTRACT TEMPLATE', binToHex(lockingScript))
+
+            /* Set unlocking script. */
+            // NOTE: Index of (executed) contract method.
+            unlockingScript = new Uint8Array([
+                ...encodeDataPush(scriptPubkey),
+                ...utf8ToBin('{{SIGNATURE}}'), // placeholder for signature
+                OP.ZERO, // contract function index
+            ])
+
+            /* Handle coin locks. */
+            coins = this.coins.map(_coin => {
+                return {
+                    ..._coin,
+                    locking: lockingScript,
+                    unlocking: unlockingScript,
+                }
+            })
+            // console.log('COINS-2', coins)
+
+            /* Initialize receivers. */
+            receivers = []
+
+            /* Add value output. */
+            receivers.push({
+                address: _receiver,
+                satoshis: _satoshis,
+            })
+
+            /* Add change output. */
+            receivers.push({
+                address: _receiver,
+            })
+            // console.log('RECEIVERS', receivers)
+
+            /* Send UTXO request. */
+            response = await buildCoins(coins, receivers)
+                .catch(err => console.error(err))
+            // console.log('BUILD TRANSACTION', response.raw)
+
+            response = await broadcast(response.raw)
+                .catch(err => console.error(err))
+            console.log('SEND TRANSACTION', response)
+        },
+
+        async updateBalance() {
+            /* Initialize locals. */
+            let assets
+            let unspent
+
+            /* Request (unspent) coins. */
+            this._coins = await getCoins(this.wif, this.scriptPubkey)
+                .catch(err => console.error(err))
+
+            /* Request (unspent) coins. */
+            unspent = await getAddressUnspent(this.address)
+                .catch(err => console.error(err))
+            // console.log('UNSPENT', unspent)
+
+            /* Filter coins ONLY. */
+            assets = unspent.filter(_unspent => {
+                return _unspent.has_token === false
+            })
+
+            /* Set assets. */
+            this._assets = assets
         },
 
         setEntropy(_entropy) {
             this._entropy = _entropy
         },
 
+        setCashoutAddr(_cashoutAddr) {
+            this._cashoutAddr = _cashoutAddr
+        },
+
         setMnemonic(_mnemonic) {
             let entropy
             let error

+ 243 - 238
web/yarn.lock

@@ -1107,10 +1107,10 @@
     graphql-ws "5.12.0"
     ws "8.13.0"
 
-"@nexajs/[email protected]7":
-  version "24.8.17"
-  resolved "https://registry.yarnpkg.com/@nexajs/app/-/app-24.8.17.tgz#03523b9aaccb810f0d1a5fc3cb8509c8e1281488"
-  integrity sha512-1dmmNDWf4GjTBQuEyhki8ZWjZCFatgCqhy+3IGh5If4CDxh3xF+yyG1Tr2WV6RRlLEqsOyJDw91hXfJ4kvRFKA==
+"@nexajs/[email protected]9":
+  version "24.8.19"
+  resolved "https://registry.yarnpkg.com/@nexajs/app/-/app-24.8.19.tgz#e68d012e077d314fc1ca0376f00fdf0711ceb995"
+  integrity sha512-dhRVDSdWFCiliq4pT09fTRtPYBcKM+ZoeV1UrrbQIVsYdcFYZXkrpld9M/ZCZq1jDtSn0sB+UlGy01ofXtrb1Q==
   dependencies:
     events "3.3.0"
 
@@ -1151,13 +1151,13 @@
     uuid "9.0.0"
     zxcvbn "4.4.2"
 
-"@nexajs/[email protected].5":
-  version "24.9.5"
-  resolved "https://registry.yarnpkg.com/@nexajs/market/-/market-24.9.5.tgz#180f95d5eddb13e80256ee8b4f482fc9f91a877f"
-  integrity sha512-Ued7Ehc1xCSeKI/NHwV9EVOBVIcnWFsuieTsrzr8QfmurWCM1brr21zT5J66lOjVdqX5fq0E8G0lgElVgkvTbA==
+"@nexajs/[email protected].12":
+  version "24.9.12"
+  resolved "https://registry.yarnpkg.com/@nexajs/market/-/market-24.9.12.tgz#77b57f8d96c29095d600d8b7e5da68a949ec3aa3"
+  integrity sha512-D5TZ6kQVAwfUvR44XjSAYiUV7tIHmDiI1BHkIW8X55Kihw6FVZriXPszVt46XxquZ6wW6EazTbY8SNi+ogctew==
   dependencies:
     "@nexajs/provider" "23.12.25"
-    "@nexajs/transaction" "24.9.5"
+    "@nexajs/transaction" "24.9.11"
     events "3.3.0"
     moment "2.29.4"
     numeral "2.0.6"
@@ -1190,15 +1190,15 @@
     isomorphic-ws "5.0.0"
     ws "8.13.0"
 
-"@nexajs/[email protected].5":
-  version "24.9.5"
-  resolved "https://registry.yarnpkg.com/@nexajs/purse/-/purse-24.9.5.tgz#3325eab328d5ff9c7be241c50c78f2a256bf678e"
-  integrity sha512-BJP2n2jY6hLfvyLOb/3aanSTdSaoisOue9FlUIWpgsLJW+N/QijpQdp5sfAV1hyDhgayhkOpip3ZDgmF111Wmg==
+"@nexajs/[email protected].11":
+  version "24.9.11"
+  resolved "https://registry.yarnpkg.com/@nexajs/purse/-/purse-24.9.11.tgz#c2b533a38ea902baddc457caad6b5df80900a269"
+  integrity sha512-OKNqg5eJ7iyBLeB+amR3R8aLGPtRIDavvG7gtMR6CKkMpKpcb+D6VvRjpUE9tLTO0DckNeW5XDaCsb/QN7N9Jw==
   dependencies:
     "@nexajs/address" "24.9.5"
     "@nexajs/hdnode" "24.9.5"
     "@nexajs/provider" "23.12.25"
-    "@nexajs/transaction" "24.9.5"
+    "@nexajs/transaction" "24.9.11"
     events "3.3.0"
 
 "@nexajs/[email protected]":
@@ -1236,23 +1236,23 @@
   dependencies:
     "@nexajs/utils" "24.7.15"
 
-"@nexajs/[email protected].5":
-  version "24.9.5"
-  resolved "https://registry.yarnpkg.com/@nexajs/token/-/token-24.9.5.tgz#ef547f85136d73e5f1807799b8fcc729f29c36f9"
-  integrity sha512-I+M/L9guDN4KFUgkSaHZflUEG9E8VwjdLdhnXgFeuS1eOO9UhX9vquu8C+m/Bd3XdKcjeIDIqPiwgbpf+cawWw==
+"@nexajs/[email protected].11":
+  version "24.9.11"
+  resolved "https://registry.yarnpkg.com/@nexajs/token/-/token-24.9.11.tgz#614da51ff3bd508ff2f45d1c176f00ef13fa3dff"
+  integrity sha512-QZIYts68U++CHSm+KZ88Zi/uPnBcy1WAKNiPPLxsbiRb23+BPltQHWNZRJktM2HAiUyC7/xBzGj5vRE5iQIcAQ==
   dependencies:
     "@nexajs/address" "24.9.5"
     "@nexajs/crypto" "24.9.5"
     "@nexajs/hdnode" "24.9.5"
     "@nexajs/provider" "23.12.25"
     "@nexajs/script" "24.7.15"
-    "@nexajs/transaction" "24.9.5"
+    "@nexajs/transaction" "24.9.11"
     events "3.3.0"
 
-"@nexajs/[email protected].5":
-  version "24.9.5"
-  resolved "https://registry.yarnpkg.com/@nexajs/transaction/-/transaction-24.9.5.tgz#a74e66e68d3583a8de96de9db68aba8b4f782a3b"
-  integrity sha512-0eTLqWojrs2iG+4G2fpxUpDROouVXWa07NA48Bgt2caQZwi1qG4fjW6P/abdpnEA3IGdlxIpqfo7i1jta6n5Cg==
+"@nexajs/[email protected].11":
+  version "24.9.11"
+  resolved "https://registry.yarnpkg.com/@nexajs/transaction/-/transaction-24.9.11.tgz#7fcc59e5cc5a4f9fe517d57604165e1f69d7e84d"
+  integrity sha512-77SSbyJn3IsTPG3xnGCVtasHpb5L2XyvhoQAhGQPAPlvcYYSKpHLmB6IcF8JhFbUlC6uQ9gp5u9lx/lO24mx5Q==
   dependencies:
     "@nexajs/address" "24.9.5"
     "@nexajs/crypto" "24.9.5"
@@ -1265,18 +1265,18 @@
   resolved "https://registry.yarnpkg.com/@nexajs/utils/-/utils-24.7.15.tgz#499bf20e1643e7c7bad91529a3f30c80512b6c5a"
   integrity sha512-kI2GSorfBmylBjmnuwqeIeJWqlaIONX2MLPZ1FyGSviXFfd9H91Yr5KrHyDTh+BIV2dIfhPDqu/Lk5caG6XiiA==
 
-"@nexajs/[email protected].5":
-  version "24.9.5"
-  resolved "https://registry.yarnpkg.com/@nexajs/wallet/-/wallet-24.9.5.tgz#313c9ba96412ccb9f9b7636c462c6b7ecf9fc983"
-  integrity sha512-XGAUJYxZAhdAn5i+lxMPf3ksmDiNrjMRSsPwuWX7rm3KWFIMDs3RgJoB6dBYPtjh/rv7h9cp7IAkRpKtTSuBZw==
+"@nexajs/[email protected].11":
+  version "24.9.11"
+  resolved "https://registry.yarnpkg.com/@nexajs/wallet/-/wallet-24.9.11.tgz#d56d557fc30e4d32f980c9400c43c480320f5924"
+  integrity sha512-8U97fzAtdr4x3QZ2SRFGCdjK41CEH9EEGQ4SyMToH10/5l1Y4//Zk0NVdCIhWoq0hoMosfXx/aOARxXfBsvrTw==
   dependencies:
     "@nexajs/address" "24.9.5"
     "@nexajs/crypto" "24.9.5"
     "@nexajs/hdnode" "24.9.5"
-    "@nexajs/purse" "24.9.5"
+    "@nexajs/purse" "24.9.11"
     "@nexajs/rostrum" "24.6.23"
     "@nexajs/script" "24.7.15"
-    "@nexajs/token" "24.9.5"
+    "@nexajs/token" "24.9.11"
     cross-fetch "4.0.0"
     events "3.3.0"
     numeral "2.0.6"
@@ -1316,25 +1316,25 @@
   resolved "https://registry.yarnpkg.com/@nuxt/devalue/-/devalue-2.0.2.tgz#5749f04df13bda4c863338d8dabaf370f45ef7c7"
   integrity sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==
 
-"@nuxt/[email protected].1":
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-1.4.1.tgz#8a0fd642a131af2c6d8b7e3928988d28bc0d545f"
-  integrity sha512-6h7T9B0tSZVap13/hf7prEAgIzraj/kyux6/Iif455Trew96jHIFCCboBApUMastYEuCo3l17tgZKe0HW+jrtA==
+"@nuxt/[email protected].2":
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-1.4.2.tgz#f49efe5a22d2ff5a2812cb64e17cb718d57442f6"
+  integrity sha512-8a5PhVnC7E94318/sHbNSe9mI2MlsQ8+pJLGs2Hh1OJyidB9SWe6hoFc8q4K9VOtXak9uCFVb5V2JGXS1q+1aA==
   dependencies:
-    "@nuxt/kit" "^3.13.0"
-    "@nuxt/schema" "^3.13.0"
+    "@nuxt/kit" "^3.13.1"
+    "@nuxt/schema" "^3.13.1"
     execa "^7.2.0"
 
-"@nuxt/[email protected].1":
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-wizard/-/devtools-wizard-1.4.1.tgz#9263397c529b1e02f0181331d46c3226e8f53c59"
-  integrity sha512-X9uTh5rgt0pw3UjXcHyl8ZFYmCgw8ITRe9Nr2VLCtNROfKz9yol/ESEhYMwTFiFlqSyfJP6/qtogJBjUt6dzTw==
+"@nuxt/[email protected].2":
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-wizard/-/devtools-wizard-1.4.2.tgz#07b1e1913b736f885dcd365bdd54e9ae225aca49"
+  integrity sha512-TyhmPBg/xJKPOdnwR3DAh8KMUt6/0dUNABCxGVeY7PYbIiXt4msIGVJkBc4y+WwIJHOYPrSRClmZVsXQfRlB4A==
   dependencies:
     consola "^3.2.3"
-    diff "^5.2.0"
+    diff "^7.0.0"
     execa "^7.2.0"
     global-directory "^4.0.1"
-    magicast "^0.3.4"
+    magicast "^0.3.5"
     pathe "^1.1.2"
     pkg-types "^1.2.0"
     prompts "^2.4.2"
@@ -1342,16 +1342,16 @@
     semver "^7.6.3"
 
 "@nuxt/devtools@^1.3.9":
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools/-/devtools-1.4.1.tgz#48c63cec97806d7dcecfabece5abcb2250497b36"
-  integrity sha512-BtmGRAr/pjSE3dBrM7iceNT6OZAQ/MHxq1brkHJDs2VdyZPnqqGS4n3/98saASoRdj0dddsuIElsqC/zIABhgg==
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools/-/devtools-1.4.2.tgz#2ef2ddd75e2e3219c9cae24c69184112afac5a1c"
+  integrity sha512-Ok3g2P7iwKyK8LiwozbYVAZTo8t91iXSmlJj2ozeo1okKQ2Qi1AtwB6nYgIlkUHZmo155ZjG/LCHYI5uhQ/sGw==
   dependencies:
     "@antfu/utils" "^0.7.10"
-    "@nuxt/devtools-kit" "1.4.1"
-    "@nuxt/devtools-wizard" "1.4.1"
-    "@nuxt/kit" "^3.13.0"
-    "@vue/devtools-core" "7.3.3"
-    "@vue/devtools-kit" "7.3.3"
+    "@nuxt/devtools-kit" "1.4.2"
+    "@nuxt/devtools-wizard" "1.4.2"
+    "@nuxt/kit" "^3.13.1"
+    "@vue/devtools-core" "7.4.4"
+    "@vue/devtools-kit" "7.4.4"
     birpc "^0.2.17"
     consola "^3.2.3"
     cronstrue "^2.50.0"
@@ -1364,9 +1364,9 @@
     hookable "^5.5.3"
     image-meta "^0.2.1"
     is-installed-globally "^1.0.0"
-    launch-editor "^2.8.1"
+    launch-editor "^2.9.1"
     local-pkg "^0.5.0"
-    magicast "^0.3.4"
+    magicast "^0.3.5"
     nypm "^0.3.11"
     ohash "^1.1.3"
     pathe "^1.1.2"
@@ -1375,12 +1375,12 @@
     rc9 "^2.1.2"
     scule "^1.3.0"
     semver "^7.6.3"
-    simple-git "^3.25.0"
+    simple-git "^3.26.0"
     sirv "^2.0.4"
-    tinyglobby "^0.2.5"
+    tinyglobby "^0.2.6"
     unimport "^3.11.1"
     vite-plugin-inspect "^0.8.7"
-    vite-plugin-vue-inspector "^5.1.3"
+    vite-plugin-vue-inspector "^5.2.0"
     which "^3.0.1"
     ws "^8.18.0"
 
@@ -1410,7 +1410,7 @@
     unimport "^3.9.0"
     untyped "^1.4.2"
 
-"@nuxt/kit@^3.10.3", "@nuxt/kit@^3.12.3", "@nuxt/kit@^3.13.0", "@nuxt/kit@^3.13.1", "@nuxt/kit@^3.4.3", "@nuxt/kit@^3.9.0":
+"@nuxt/kit@^3.10.3", "@nuxt/kit@^3.12.3", "@nuxt/kit@^3.13.1", "@nuxt/kit@^3.4.3", "@nuxt/kit@^3.9.0":
   version "3.13.1"
   resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.13.1.tgz#69501efc661ba861f45ed947918efb3e84838365"
   integrity sha512-FkUL349lp/3nVfTIyws4UDJ3d2jyv5Pk1DC1HQUCOkSloYYMdbRcQAUcb4fe2TCLNWvHM+FhU8jnzGTzjALZYA==
@@ -1454,7 +1454,7 @@
     unimport "^3.9.0"
     untyped "^1.4.2"
 
-"@nuxt/[email protected]", "@nuxt/schema@^3.13.0":
+"@nuxt/[email protected]", "@nuxt/schema@^3.13.1":
   version "3.13.1"
   resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.13.1.tgz#a60ccb53457dec085b4aa69b6b5a6683618af328"
   integrity sha512-ishbhzVGspjshG9AG0hYnKYY6LWXzCtua7OXV7C/DQ2yA7rRcy1xHpzKZUDbIRyxCHHCAcBd8jfHEUmEuhEPrA==
@@ -1791,85 +1791,85 @@
     estree-walker "^2.0.2"
     picomatch "^2.3.1"
 
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz#0412834dc423d1ff7be4cb1fc13a86a0cd262c11"
-  integrity sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz#baf1a014b13654f3b9e835388df9caf8c35389cb"
-  integrity sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz#0a2c364e775acdf1172fe3327662eec7c46e55b1"
-  integrity sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz#a972db75890dfab8df0da228c28993220a468c42"
-  integrity sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz#1609d0630ef61109dd19a278353e5176d92e30a1"
-  integrity sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz#3c1dca5f160aa2e79e4b20ff6395eab21804f266"
-  integrity sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz#c2fe376e8b04eafb52a286668a8df7c761470ac7"
-  integrity sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz#e62a4235f01e0f66dbba587c087ca6db8008ec80"
-  integrity sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz#24b3457e75ee9ae5b1c198bd39eea53222a74e54"
-  integrity sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz#38edfba9620fe2ca8116c97e02bd9f2d606bde09"
-  integrity sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz#a3bfb8bc5f1e802f8c76cff4a4be2e9f9ac36a18"
-  integrity sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz#0dadf34be9199fcdda44b5985a086326344f30ad"
-  integrity sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz#7b7deddce240400eb87f2406a445061b4fed99a8"
-  integrity sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz#a0ca0c5149c2cfb26fab32e6ba3f16996fbdb504"
-  integrity sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz#aae2886beec3024203dbb5569db3a137bc385f8e"
-  integrity sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==
-
-"@rollup/[email protected].2":
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz#e4291e3c1bc637083f87936c333cdbcad22af63b"
-  integrity sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz#155c7d82c1b36c3ad84d9adf9b3cd520cba81a0f"
+  integrity sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz#b94b6fa002bd94a9cbd8f9e47e23b25e5bd113ba"
+  integrity sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz#0934126cf9cbeadfe0eb7471ab5d1517e8cd8dcc"
+  integrity sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz#0ce8e1e0f349778938c7c90e4bdc730640e0a13e"
+  integrity sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz#5669d34775ad5d71e4f29ade99d0ff4df523afb6"
+  integrity sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz#f6d1a0e1da4061370cb2f4244fbdd727c806dd88"
+  integrity sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz#ed96a05e99743dee4d23cc4913fc6e01a0089c88"
+  integrity sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz#057ea26eaa7e537a06ded617d23d57eab3cecb58"
+  integrity sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz#6e6e1f9404c9bf3fbd7d51cd11cd288a9a2843aa"
+  integrity sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz#eef1536a53f6e6658a2a778130e6b1a4a41cb439"
+  integrity sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz#2b28fb89ca084efaf8086f435025d96b4a966957"
+  integrity sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz#5226cde6c6b495b04a3392c1d2c572844e42f06b"
+  integrity sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz#2c2412982e6c2a00a2ecac6d548ebb02f0aa6ca4"
+  integrity sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz#fbb6ef5379199e2ec0103ef32877b0985c773a55"
+  integrity sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz#d50e2082e147e24d87fe34abbf6246525ec3845a"
+  integrity sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==
+
+"@rollup/[email protected].3":
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz#4115233aa1bd5a2060214f96d8511f6247093212"
+  integrity sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==
 
 "@sindresorhus/merge-streams@^2.1.0":
   version "2.3.0"
@@ -1996,15 +1996,15 @@
     local-pkg "^0.5.0"
     magic-string-ast "^0.6.2"
 
-"@vue/[email protected].4":
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.4.tgz#0008c23fb721841f91e7b0a3baa6c40cef7f2423"
-  integrity sha512-3L9zXWRN2jvmLjtSyw9vtcO5KTSCfKhCD5rEZM+024bc+4dKSzTjIABl/5b+uZ5nXe5y31uUMxxLo1PdXkYaig==
+"@vue/[email protected].5":
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz#b9e195b92bfa8d15d5aa9581ca01cb702dbcc19d"
+  integrity sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==
 
 "@vue/babel-plugin-jsx@^1.1.5", "@vue/babel-plugin-jsx@^1.2.2":
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.4.tgz#94a190e9bc12b7ad88a1ecc02e45e7ea9143611d"
-  integrity sha512-jwAVtHUaDfOGGT1EmVKBi0anXOtPvsuKbImcdnHXluaJQ6GEJzshf1JMTtMRx2fPiG7BZjNmyMv+NdZY2OyZEA==
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.5.tgz#77f4f9f189d00c24ebd587ab84ae615dfa1c3abb"
+  integrity sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==
   dependencies:
     "@babel/helper-module-imports" "^7.24.7"
     "@babel/helper-plugin-utils" "^7.24.8"
@@ -2012,15 +2012,15 @@
     "@babel/template" "^7.25.0"
     "@babel/traverse" "^7.25.6"
     "@babel/types" "^7.25.6"
-    "@vue/babel-helper-vue-transform-on" "1.2.4"
-    "@vue/babel-plugin-resolve-type" "1.2.4"
+    "@vue/babel-helper-vue-transform-on" "1.2.5"
+    "@vue/babel-plugin-resolve-type" "1.2.5"
     html-tags "^3.3.1"
     svg-tags "^1.0.0"
 
-"@vue/[email protected].4":
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.4.tgz#4fd8f37ceb746c1ada8791cd3967a5d4e1a2603f"
-  integrity sha512-jWcJAmfKvc/xT2XBC4JAmy2eezNjU3CLfeDecl2Ge3tSjJCTmKJWkEhHdzXyx9Nr6PbIcQrFKhCaEDobhSrPqw==
+"@vue/[email protected].5":
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.5.tgz#f6ed0d39987fe0158370659b73156c55e80d17b5"
+  integrity sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==
   dependencies:
     "@babel/code-frame" "^7.24.7"
     "@babel/helper-module-imports" "^7.24.7"
@@ -2070,29 +2070,29 @@
     "@vue/compiler-dom" "3.5.4"
     "@vue/shared" "3.5.4"
 
-"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.3":
+"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.3", "@vue/devtools-api@^6.6.4":
   version "6.6.4"
   resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
   integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
 
-"@vue/devtools-core@7.3.3":
-  version "7.3.3"
-  resolved "https://registry.yarnpkg.com/@vue/devtools-core/-/devtools-core-7.3.3.tgz#cadd65806ed2ddd80ae1dc6378abc48820abf850"
-  integrity sha512-i6Bwkx4OwfY0QVHjAdsivhlzZ2HMj7fbNRYJsWspQ+dkA1f3nTzycPqZmVUsm2TGkbQlhTMhCAdDoP97JKoc+g==
+"@vue/devtools-core@7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@vue/devtools-core/-/devtools-core-7.4.4.tgz#20fdef8e846fce25ed98008011942505cf23bcd5"
+  integrity sha512-DLxgA3DfeADkRzhAfm3G2Rw/cWxub64SdP5b+s5dwL30+whOGj+QNhmyFpwZ8ZTrHDFRIPj0RqNzJ8IRR1pz7w==
   dependencies:
-    "@vue/devtools-kit" "^7.3.3"
-    "@vue/devtools-shared" "^7.3.3"
+    "@vue/devtools-kit" "^7.4.4"
+    "@vue/devtools-shared" "^7.4.4"
     mitt "^3.0.1"
     nanoid "^3.3.4"
     pathe "^1.1.2"
     vite-hot-client "^0.2.3"
 
-"@vue/devtools-kit@7.3.3":
-  version "7.3.3"
-  resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.3.3.tgz#7e1549fb9685fc033e560e1f69a2245c79406f84"
-  integrity sha512-m+dFI57BrzKYPKq73mt4CJ5GWld5OLBseLHPHGVP7CaILNY9o1gWVJWAJeF8XtQ9LTiMxZSaK6NcBsFuxAhD0g==
+"@vue/devtools-kit@7.4.4":
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.4.4.tgz#f05e775e6e80636362a25c955c7c1291f990e456"
+  integrity sha512-awK/4NfsUG0nQ7qnTM37m7ZkEUMREyPh8taFCX+uQYps/MTFEum0AD05VeGDRMXwWvMmGIcWX9xp8ZiBddY0jw==
   dependencies:
-    "@vue/devtools-shared" "^7.3.3"
+    "@vue/devtools-shared" "^7.4.4"
     birpc "^0.2.17"
     hookable "^5.5.3"
     mitt "^3.0.1"
@@ -2100,12 +2100,12 @@
     speakingurl "^14.0.1"
     superjson "^2.2.1"
 
-"@vue/devtools-kit@^7.3.3":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.4.4.tgz#f05e775e6e80636362a25c955c7c1291f990e456"
-  integrity sha512-awK/4NfsUG0nQ7qnTM37m7ZkEUMREyPh8taFCX+uQYps/MTFEum0AD05VeGDRMXwWvMmGIcWX9xp8ZiBddY0jw==
+"@vue/devtools-kit@^7.4.4":
+  version "7.4.5"
+  resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.4.5.tgz#e80a551d4bce4c40c38a7ef6b080e408d541d346"
+  integrity sha512-Uuki4Z6Bc/ExvtlPkeDNGSAe4580R+HPcVABfTE9TF7BTz3Nntk7vxIRUyWblZkUEcB/x+wn2uofyt5i2LaUew==
   dependencies:
-    "@vue/devtools-shared" "^7.4.4"
+    "@vue/devtools-shared" "^7.4.5"
     birpc "^0.2.17"
     hookable "^5.5.3"
     mitt "^3.0.1"
@@ -2113,10 +2113,10 @@
     speakingurl "^14.0.1"
     superjson "^2.2.1"
 
-"@vue/devtools-shared@^7.3.3", "@vue/devtools-shared@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.4.4.tgz#f841a99ed30ac3120f878de77e4721c964984976"
-  integrity sha512-yeJULXFHOKIm8yL2JFO050a9ztTVqOCKTqN9JHFxGTJN0b+gjtfn6zC+FfyHUgjwCwf6E3hfKrlohtthcqoYqw==
+"@vue/devtools-shared@^7.4.4", "@vue/devtools-shared@^7.4.5":
+  version "7.4.5"
+  resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.4.5.tgz#f8f5ef73451c05922e8d18aa86d39a05ab883e94"
+  integrity sha512-2XgUOkL/7QDmyYI9J7cm+rz/qBhcGv+W5+i1fhwdQ0HQ1RowhdK66F0QBuJSz/5k12opJY8eN6m03/XZMs7imQ==
   dependencies:
     rfdc "^1.4.1"
 
@@ -3191,10 +3191,10 @@ didyoumean@^1.2.2:
   resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
   integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
 
-diff@^5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531"
-  integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==
+diff@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a"
+  integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==
 
 dijkstrajs@^1.0.1:
   version "1.0.3"
@@ -3264,9 +3264,9 @@ [email protected]:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.4:
-  version "1.5.18"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.18.tgz#5fe62b9d21efbcfa26571066502d94f3ed97e495"
-  integrity sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ==
+  version "1.5.20"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.20.tgz#2914e42cfc5cc992cbee5538b500ddaf7c2c7091"
+  integrity sha512-74mdl6Fs1HHzK9SUX4CKFxAtAe3nUns48y79TskHNAG6fGOlLfyKA4j855x+0b5u8rWJIrlaG9tcTPstMlwjIw==
 
 [email protected]:
   version "6.5.4"
@@ -3314,6 +3314,11 @@ encodeurl@^1.0.2, encodeurl@~1.0.2:
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
 
+encodeurl@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+  integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
 end-of-stream@^1.1.0, end-of-stream@^1.4.1:
   version "1.4.4"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -4478,7 +4483,7 @@ kolorist@^1.8.0:
   resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
   integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
 
-launch-editor@^2.8.1:
+launch-editor@^2.9.1:
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.9.1.tgz#253f173bd441e342d4344b4dae58291abb425047"
   integrity sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==
@@ -4603,7 +4608,7 @@ magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string
   dependencies:
     "@jridgewell/sourcemap-codec" "^1.5.0"
 
-magicast@^0.3.4:
+magicast@^0.3.5:
   version "0.3.5"
   resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739"
   integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==
@@ -4856,29 +4861,29 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
   integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
 
[email protected].5:
-  version "24.9.5"
-  resolved "https://registry.yarnpkg.com/nexajs/-/nexajs-24.9.5.tgz#025ccb43961501976c080b5362847fc2fc27d437"
-  integrity sha512-bDZbFyReFr8WC85Y5BiYeVQM2LRHbPXVJZgQmxIDLZzQAETNX8O/fYsRUyMpZ8Lop2rJJUGEiKN+DzxymaPXKw==
[email protected].11:
+  version "24.9.11"
+  resolved "https://registry.yarnpkg.com/nexajs/-/nexajs-24.9.11.tgz#6f789e19837921e069789298c9691fbcd7c67ac6"
+  integrity sha512-ndnlmsDOD1gsx2HSHMZBC7PX5F2nwudIm0L+Xrw9bHvn7NLBnaAiNJkqtjENV/p5NkrkshJeAUWq1+y6Q+yj/g==
   dependencies:
     "@nexajs/address" "24.9.5"
-    "@nexajs/app" "24.8.17"
+    "@nexajs/app" "24.8.19"
     "@nexajs/crypto" "24.9.5"
     "@nexajs/hdnode" "24.9.5"
     "@nexajs/id" "23.5.15"
-    "@nexajs/market" "24.9.5"
+    "@nexajs/market" "24.9.12"
     "@nexajs/message" "24.9.5"
     "@nexajs/privacy" "24.8.15"
     "@nexajs/provider" "23.12.25"
-    "@nexajs/purse" "24.9.5"
+    "@nexajs/purse" "24.9.11"
     "@nexajs/request" "23.5.15"
     "@nexajs/rostrum" "24.6.23"
     "@nexajs/rpc" "23.5.15"
     "@nexajs/script" "24.7.15"
-    "@nexajs/token" "24.9.5"
-    "@nexajs/transaction" "24.9.5"
+    "@nexajs/token" "24.9.11"
+    "@nexajs/transaction" "24.9.11"
     "@nexajs/utils" "24.7.15"
-    "@nexajs/wallet" "24.9.5"
+    "@nexajs/wallet" "24.9.11"
     "@nexajs/zk" "24.9.5"
     events "3.3.0"
 
@@ -5333,9 +5338,9 @@ path-scurry@^1.11.1:
     minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
 
 path-to-regexp@^6.2.1:
-  version "6.2.2"
-  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36"
-  integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4"
+  integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==
 
 path-type@^5.0.0:
   version "5.0.0"
@@ -5721,9 +5726,9 @@ protocols@^2.0.0, protocols@^2.0.1:
   integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==
 
 pump@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
-  integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8"
+  integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==
   dependencies:
     end-of-stream "^1.1.0"
     once "^1.3.1"
@@ -5932,28 +5937,28 @@ rollup-plugin-visualizer@^5.12.0:
     yargs "^17.5.1"
 
 rollup@^4.18.0, rollup@^4.20.0:
-  version "4.21.2"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.2.tgz#f41f277a448d6264e923dd1ea179f0a926aaf9b7"
-  integrity sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==
+  version "4.21.3"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.3.tgz#c64ba119e6aeb913798a6f7eef2780a0df5a0821"
+  integrity sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==
   dependencies:
     "@types/estree" "1.0.5"
   optionalDependencies:
-    "@rollup/rollup-android-arm-eabi" "4.21.2"
-    "@rollup/rollup-android-arm64" "4.21.2"
-    "@rollup/rollup-darwin-arm64" "4.21.2"
-    "@rollup/rollup-darwin-x64" "4.21.2"
-    "@rollup/rollup-linux-arm-gnueabihf" "4.21.2"
-    "@rollup/rollup-linux-arm-musleabihf" "4.21.2"
-    "@rollup/rollup-linux-arm64-gnu" "4.21.2"
-    "@rollup/rollup-linux-arm64-musl" "4.21.2"
-    "@rollup/rollup-linux-powerpc64le-gnu" "4.21.2"
-    "@rollup/rollup-linux-riscv64-gnu" "4.21.2"
-    "@rollup/rollup-linux-s390x-gnu" "4.21.2"
-    "@rollup/rollup-linux-x64-gnu" "4.21.2"
-    "@rollup/rollup-linux-x64-musl" "4.21.2"
-    "@rollup/rollup-win32-arm64-msvc" "4.21.2"
-    "@rollup/rollup-win32-ia32-msvc" "4.21.2"
-    "@rollup/rollup-win32-x64-msvc" "4.21.2"
+    "@rollup/rollup-android-arm-eabi" "4.21.3"
+    "@rollup/rollup-android-arm64" "4.21.3"
+    "@rollup/rollup-darwin-arm64" "4.21.3"
+    "@rollup/rollup-darwin-x64" "4.21.3"
+    "@rollup/rollup-linux-arm-gnueabihf" "4.21.3"
+    "@rollup/rollup-linux-arm-musleabihf" "4.21.3"
+    "@rollup/rollup-linux-arm64-gnu" "4.21.3"
+    "@rollup/rollup-linux-arm64-musl" "4.21.3"
+    "@rollup/rollup-linux-powerpc64le-gnu" "4.21.3"
+    "@rollup/rollup-linux-riscv64-gnu" "4.21.3"
+    "@rollup/rollup-linux-s390x-gnu" "4.21.3"
+    "@rollup/rollup-linux-x64-gnu" "4.21.3"
+    "@rollup/rollup-linux-x64-musl" "4.21.3"
+    "@rollup/rollup-win32-arm64-msvc" "4.21.3"
+    "@rollup/rollup-win32-ia32-msvc" "4.21.3"
+    "@rollup/rollup-win32-x64-msvc" "4.21.3"
     fsevents "~2.3.2"
 
 run-applescript@^7.0.0:
@@ -6006,10 +6011,10 @@ semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.6.2, semver@^7.6.3:
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
   integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
 
[email protected]8.0:
-  version "0.18.0"
-  resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
-  integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
[email protected]9.0:
+  version "0.19.0"
+  resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
+  integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
   dependencies:
     debug "2.6.9"
     depd "2.0.0"
@@ -6040,14 +6045,14 @@ serve-placeholder@^2.0.2:
     defu "^6.1.4"
 
 serve-static@^1.15.0:
-  version "1.16.0"
-  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
-  integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
+  version "1.16.2"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
+  integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
   dependencies:
-    encodeurl "~1.0.2"
+    encodeurl "~2.0.0"
     escape-html "~1.0.3"
     parseurl "~1.3.3"
-    send "0.18.0"
+    send "0.19.0"
 
 set-blocking@^2.0.0:
   version "2.0.0"
@@ -6141,7 +6146,7 @@ simple-get@^4.0.0, simple-get@^4.0.1:
     once "^1.3.1"
     simple-concat "^1.0.0"
 
-simple-git@^3.25.0:
+simple-git@^3.26.0:
   version "3.26.0"
   resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.26.0.tgz#9ee91de402206911dcb752c65db83f5177e18121"
   integrity sha512-5tbkCSzuskR6uA7uA23yjasmA0RzugVo8QM2bpsnxkrgP13eisFT7TMS4a+xKEJvbmr4qf+l0WT3eKa9IxxUyw==
@@ -6414,9 +6419,9 @@ tailwind-config-viewer@^2.0.4:
     replace-in-file "^6.1.0"
 
 tailwindcss@~3.4.4:
-  version "3.4.10"
-  resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.10.tgz#70442d9aeb78758d1f911af29af8255ecdb8ffef"
-  integrity sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==
+  version "3.4.11"
+  resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.11.tgz#4d6df41acc05a1d0291b1319490db8df375ab709"
+  integrity sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==
   dependencies:
     "@alloc/quick-lru" "^5.2.0"
     arg "^5.0.2"
@@ -6524,7 +6529,7 @@ tiny-invariant@^1.1.0:
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
   integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
 
-tinyglobby@^0.2.5:
+tinyglobby@^0.2.6:
   version "0.2.6"
   resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.6.tgz#950baf1462d0c0b443bc3d754d0d39c2e589aaae"
   integrity sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==
@@ -6717,9 +6722,9 @@ unplugin-vue-router@^0.10.0:
     yaml "^2.5.0"
 
 unplugin@^1.1.0, unplugin@^1.10.0, unplugin@^1.10.1, unplugin@^1.11.0, unplugin@^1.12.2, unplugin@^1.3.1:
-  version "1.14.0"
-  resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.14.0.tgz#4455bdff958a0f29fbbb82bc143fd61688ff40d9"
-  integrity sha512-cfkZeALGyW7tKYjZbi0G+pn0XnUFa0QvLIeLJEUUlnU0R8YYsBQnt5+h9Eu1B7AB7KETld+UBFI5lOeBL+msoQ==
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.14.1.tgz#c76d6155a661e43e6a897bce6b767a1ecc344c1a"
+  integrity sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==
   dependencies:
     acorn "^8.12.1"
     webpack-virtual-modules "^0.6.2"
@@ -6858,7 +6863,7 @@ vite-plugin-inspect@^0.8.7:
     picocolors "^1.0.1"
     sirv "^2.0.4"
 
-vite-plugin-vue-inspector@^5.1.3:
+vite-plugin-vue-inspector@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.2.0.tgz#4c29926aa86e23492a99ac24401f4f9cd4f4c171"
   integrity sha512-wWxyb9XAtaIvV/Lr7cqB1HIzmHZFVUJsTNm3yAxkS87dgh/Ky4qr2wDEWNxF23fdhVa3jQ8MZREpr4XyiuaRqA==
@@ -6874,9 +6879,9 @@ vite-plugin-vue-inspector@^5.1.3:
     magic-string "^0.30.4"
 
 vite@^5.0.0, vite@^5.3.4:
-  version "5.4.3"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.3.tgz#771c470e808cb6732f204e1ee96c2ed65b97a0eb"
-  integrity sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==
+  version "5.4.4"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.4.tgz#3da90314b617047366459443320ea78f39111008"
+  integrity sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==
   dependencies:
     esbuild "^0.21.3"
     postcss "^8.4.43"
@@ -6955,11 +6960,11 @@ vue-i18n@^9.9.0:
     "@vue/devtools-api" "^6.5.0"
 
 vue-router@^4.2.5, vue-router@^4.4.0:
-  version "4.4.3"
-  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.4.3.tgz#58a39dc804632bfb6d26f052aa8f6718bd130299"
-  integrity sha512-sv6wmNKx2j3aqJQDMxLFzs/u/mjA9Z5LCgy6BE0f7yFWMjrPLnS/sPNn8ARY/FXw6byV18EFutn5lTO6+UsV5A==
+  version "4.4.4"
+  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.4.4.tgz#4678d309c19c7513a05ef920ad14bdcd4db34fef"
+  integrity sha512-3MlnDqwRwZwCQVbtVfpsU+nrNymNjnXSsQtXName5925NVC1+326VVfYH9vSrA0N13teGEo8z5x7gbRnGjCDiQ==
   dependencies:
-    "@vue/devtools-api" "^6.6.3"
+    "@vue/devtools-api" "^6.6.4"
 
 vue@^3.4.32:
   version "3.5.4"