123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- /* Import modules. */
- import { defineStore } from 'pinia'
- import moment from 'moment'
- import BCHJS from '@psf/bch-js'
- import { sha256 } from '@nexajs/crypto'
- import {
- mnemonicToEntropy,
- mnemonicToSeed,
- } from '@nexajs/hdnode'
- import { Wallet } from '@nexajs/wallet'
- import _broadcast from './wallet/broadcast.ts'
- import _completeFusion from './wallet/completeFusion.ts'
- import _getWifForAddress from './wallet/getWifForAddress.ts'
- import _setEntropy from './wallet/setEntropy.ts'
- import _setupKeychain from './wallet/setupKeychain.ts'
- import _setupHushKeychain from './wallet/setupHushKeychain.ts'
- import _signFusion from './wallet/signFusion.ts'
- import _startFusion from './wallet/startFusion.ts'
- /* Initialize constants. */
- const HUSH_PROTOCOL_ID = 0x48555348
- // REST API servers.
- const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
- // Instantiate bch-js based on the network.
- const bchjs = new BCHJS({
- restURL: BCHN_MAINNET,
- })
- /* Set constants. */
- // const UPDATE_UTXOS_INTERVAL = 8000 // Allows for ~7 (REST) requests per minute.
- const UPDATE_UTXOS_INTERVAL = 15000 // Allows for ~8 (Double REST) requests per minute.
- /**
- * Wallet Store
- */
- export const useWalletStore = defineStore('wallet', {
- state: () => ({
- /**
- * 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,
- /**
- * 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,
- /**
- * Unspent Transaction Outputs (UTXOs)
- *
- * A dedicated handler for ALL network UTXOs.
- */
- _utxos: null,
- /**
- * Wallet
- *
- * Currently active wallet object.
- */
- _wallet: null,
- }),
- getters: {
- /* Return NexaJS wallet instance. */
- wallet(_state) {
- return _state._wallet
- },
- /* Return wallet status. */
- isReady(_state) {
- return _state.wallet?.isReady
- },
- /* Return wallet status. */
- address(_state) {
- return _state.wallet?.address
- },
- /* Return wallet status. */
- assets(_state) {
- return _state.wallet?.assets
- },
- /* Return wallet status. */
- balances(_state) {
- // FIXME: Update library to expose data OR
- // refactor to `markets`.
- return _state.wallet?._balances
- },
- /* Return keychain. */
- keychain(_state) {
- return _state._keychain
- },
- /* Return mnemonic. */
- mnemonic(_state) {
- if (!_state._wallet) {
- return null
- }
- return _state._wallet._mnemonic
- },
- /* Return UTXOs. */
- utxos(_state) {
- if (!_state._wallet) {
- return null
- }
- return _state._utxos
- },
- /* Return UTXOs. */
- fusionInputs(_state) {
- if (!_state._wallet) {
- return null
- }
- // const collection = _state._utxos
- console.log('STATE (utxos)', _state._utxos)
- /* Initialize inputs (collection). */
- let inputs = {}
- /* Verify Main chain. */
- if (_state._utxos[0]) {
- inputs = { ...inputs, ..._state._utxos[0] }
- }
- /* Verify Hush chain. */
- if (_state._utxos[HUSH_PROTOCOL_ID]) {
- inputs = { ...inputs, ..._state._utxos[HUSH_PROTOCOL_ID] }
- }
- console.log('FUSION (inputs)', inputs)
- /* Return inputs. */
- return inputs
- },
- fusionAddrs() {
- return this._keychain[HUSH_PROTOCOL_ID]
- },
- /* Return wif. */
- wif(_state) {
- if (!_state._wallet) {
- return null
- }
- return _state._wallet.wif
- },
- },
- 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) {
- this._wallet = 'NEW' // FIXME TEMP NEW WALLET FLAG
- // throw new Error('Missing wallet entropy.')
- return console.error('Missing wallet entropy.')
- }
- /* Validate keychain. */
- if (this._keychain === null) {
- this._keychain = {
- 0: {}, // NEXA chain
- 0x48555348: {}, // HUSH chain (1_213_551_432)
- }
- console.log('Keychain initialized successfully!', this._keychain)
- /* Set keychain. */
- _setupKeychain.bind(this)()
- /* Set Hush keychian. */
- _setupHushKeychain.bind(this)()
- }
- // FOR DEV PURPOSES ONLY
- _setupKeychain.bind(this)()
- _setupHushKeychain.bind(this)()
- /* 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
- /* 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 }
- })
- // FIXME ADDED FOR BITCOIN CASH SUPPORT
- // setInterval(this.updateUtxos, UPDATE_UTXOS_INTERVAL)
- /* Update ALL chains. */
- this.updateUtxos(true)
- },
- /**
- * 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) {
- console.error(_entropy, 'is NOT valid entropy.')
- /* Clear (invalid) entropy. */
- _entropy = null
- }
- /* Set entropy. */
- _setEntropy.bind(this)(_entropy)
- /* Initialize wallet. */
- this.init()
- },
- /**
- * Get Bitcoin Cash Address
- *
- * Will return the "child" address of a master node,
- * based on the account index, change flag and address index.
- */
- getBchAddress(
- _accountIdx = 0,
- _isChange = 0, // NOTE: 0 = false, 1 = true
- _addressIdx = 0,
- ) {
- /* Set root seed. */
- const rootSeed = mnemonicToSeed(this.mnemonic)
- // console.log('rootSeed', rootSeed)
- /* Set HD master node. */
- const masterHdnode = bchjs.HDNode.fromSeed(Buffer.from(rootSeed, 'hex') )
- // console.log('masterHdnode', masterHdnode);
- /* Set child node. */
- const childNode = masterHdnode
- .derivePath(`m/44'/145'/${_accountIdx}'/${_isChange}/${_addressIdx}`)
- // console.log('childNode', childNode)
- /* Set Bitcoin Cash address. */
- const cashAddress = bchjs.HDNode.toCashAddress(childNode)
- // console.log('cashAddress', cashAddress)
- return cashAddress
- },
- async updateUtxos(_allChains = false) {
- /* Initialize locals. */
- let coins
- let data
- let hushAddresses
- let usedAddresses
- let utxos
- let walletAddresses
- /* Validate UTXO handler. */
- if (this._utxos === null) {
- /* Initialize UTXO handler. */
- this._utxos = {}
- }
- coins = this._keychain[HUSH_PROTOCOL_ID]
- // console.log('WAITING FOR COINS', coins)
- hushAddresses = Object.keys(coins).map(_coinid => {
- const coin = coins[_coinid]
- return coin.address
- })
- // console.log('HUSH ADDRESSES', hushAddresses)
- /* Request UTXO data. */
- data = await $fetch('/api/electrum', {
- method: 'POST',
- body: JSON.stringify({
- method: 'blockchain.scripthash.listunspent',
- params: hushAddresses.slice(0, 20)
- }),
- })
- .catch(err => console.error(err))
- console.log('HUSH UTXOS', data)
- // FIXME Update the deltas ONLY!
- this._utxos[HUSH_PROTOCOL_ID] = {}
- /* Handle unspent outputs. */
- data.forEach(_unspent => {
- _unspent.utxos.forEach(_utxo => {
- // console.log('ADDING HUSH UTXO...', _utxo)
- /* Generate outpoint (hash). */
- const outpoint = sha256(_utxo.tx_hash + ':' + _utxo.tx_pos)
- /* Add to UTXOs. */
- this._utxos[HUSH_PROTOCOL_ID][outpoint] = {
- address: _unspent.address,
- ..._utxo,
- wif: _getWifForAddress.bind(this)(_unspent.address),
- }
- })
- })
- /* Request history data. */
- data = await $fetch('/api/electrum', {
- method: 'POST',
- body: JSON.stringify({
- method: 'blockchain.scripthash.get_history',
- params: hushAddresses.slice(0, 20)
- }),
- })
- .catch(err => console.error(err))
- console.log('HUSH TX HISTORY', data)
- usedAddresses = data?.filter(_tx => {
- if (_tx.txs.length > 0) {
- return _tx.address
- }
- })
- usedAddresses = usedAddresses.map(_tx => {
- return _tx.address
- })
- // console.log('USED ADDRESSES', usedAddresses)
- Object.keys(coins).forEach(_coinid => {
- /* Set coin. */
- const coin = coins[_coinid]
- /* Verify address exists. */
- if (usedAddresses.includes(coin.address)) {
- /* Set (used) flag. */
- coin.isUsed = true
- // console.log('COIN (is used)', coin)
- }
- })
- /* Validate chain handler flag. */
- if (_allChains) {
- let bchAddress1
- let bchAddress2
- let bchAddress3
- bchAddress1 = this.getBchAddress(0, 0, 0)
- // console.log('BCH ADDRESS-1', bchAddress1)
- bchAddress2 = this.getBchAddress(0, 0, 1)
- // console.log('BCH ADDRESS-2', bchAddress2)
- bchAddress3 = this.getBchAddress(0, 0, 2)
- // console.log('BCH ADDRESS-3', bchAddress3)
- data = await $fetch('/api/electrum', {
- method: 'POST',
- body: JSON.stringify({
- method: 'blockchain.scripthash.listunspent',
- params: [
- bchAddress1,
- bchAddress2,
- bchAddress3,
- ],
- })
- })
- console.log('MAIN WALLET DATA', data)
- // if (!this._utxos[0]) {
- this._utxos[0] = {}
- // }
- /* Handle unspent outputs. */
- data.forEach(_unspent => {
- _unspent.utxos.forEach(_utxo => {
- console.log('ADDING UTXO...', _utxo)
- /* Generate outpoint (hash). */
- const outpoint = sha256(_utxo.tx_hash + ':' + _utxo.tx_pos)
- /* Add to UTXOs. */
- this._utxos[0][outpoint] = {
- address: _unspent.address,
- ..._utxo,
- wif: _getWifForAddress.bind(this)(_unspent.address),
- }
- })
- })
- // FIXME Update the delta ONLY!
- // this._utxos[0] = data?.utxos
- }
- return true
- },
- /**
- * Get Fusion Address
- *
- * Will retrieve the next available fusion address and lock it.
- */
- getFusionAddress() {
- if (!this.fusionAddrs) {
- return ''
- }
- const addressIdx = Object.keys(this.fusionAddrs).find(_addressIdx => {
- const fusionAddress = this.fusionAddrs[_addressIdx]
- // console.log('fusionAddress', fusionAddress)
- return fusionAddress.isUsed === false && fusionAddress.isLocked === false
- })
- // console.log('ADDRESS IDX', addressIdx)
- if (typeof addressIdx !== 'undefined') {
- /* Set locked flag. */
- this._keychain[HUSH_PROTOCOL_ID][addressIdx].isLocked = true
- this._keychain[HUSH_PROTOCOL_ID][addressIdx].updatedAt = moment().unix()
- /* Return address. */
- return this.fusionAddrs[addressIdx].address
- } else {
- return ''
- }
- },
- /**
- * Get WIF for Address
- *
- * TBD..
- */
- getWifForAddress(_address) {
- return _getWifForAddress.bind(this)(_address)
- },
- async startFusion() {
- /* Start fusions. */
- return _startFusion.bind(this)()
- },
- async signFusion() {
- /* Start fusions. */
- return _signFusion.bind(this)()
- },
- async completeFusion() {
- /* Start fusions. */
- return _completeFusion.bind(this)()
- },
- 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)
- }
- },
- broadcast(_network, _rawTx) {
- /* Broadcast to raw (hex) transaction to mainnet. */
- return _broadcast.bind(this)(_network, _rawTx)
- },
- setEntropy(_entropy) {
- this._entropy = _entropy
- },
- 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
- console.info('Wallet destroyed successfully!')
- },
- },
- })
|