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