123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- /* Import modules. */
- import moment from 'moment'
- import PouchDB from 'pouchdb'
- import { listUnspent } from '@nexajs/address'
- import { sha256 } from '@nexajs/crypto'
- import { encodePrivateKeyWif } from '@nexajs/hdnode'
- import { sendCoin } from '@nexajs/purse'
- import { encodeNullData } from '@nexajs/script'
- import { hexToBin } from '@nexajs/utils'
- import { Wallet } from '@nexajs/wallet'
- /* Initialize databases. */
- const playsDb = new PouchDB(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@127.0.0.1:5984/plays`)
- const walletDb = new PouchDB(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@127.0.0.1:5984/wallet`)
- /* Set constants. */
- const DUST_LIMIT = BigInt(546)
- const ESTIMATED_NUM_OUTPUTS = 5
- const VAULT_MNEMONIC = process.env.MNEMONIC
- /* Initialize wallet. */
- const vaultWallet = new Wallet(VAULT_MNEMONIC)
- /* Initialize spent coins. */
- const spentCoins = []
- /**
- * Handle (Wallet) Queue
- *
- * Process the pending queue of open transactions.
- *
- * NOTE: We handle payment processing in a SINGLE thread,
- * to mitigate the possibility of a "double send".
- */
- export default async (_queue, _pending) => {
- let latestDb
- let response
- let txResult
- let updatedDb
- let unspent
- let wif
- for (let i = 0; i < _pending.length; i++) {
- const payment = _queue[_pending[i]]
- console.log('PAYMENT (pending):', payment)
- console.log('PAYMENT (pending) receivers:', payment.receivers)
- /* Remove (payment) from queue. */
- delete _queue[_pending[i]]
- const paymentSatoshis = payment.receivers
- .reduce(
- (totalValue, receiver) => (totalValue + receiver.satoshis), BigInt(0)
- )
- // console.log('PAYMENT SATOSHIS', paymentSatoshis)
- const wallet = new Wallet(process.env.MNEMONIC)
- // console.log('WALLET', wallet)
- const address = wallet.address
- // console.log('TREASURY ADDRESS', address)
- /* Initialize receivers. */
- const receivers = []
- unspent = await listUnspent(address)
- // console.log('UNSPENT', unspent)
- /* Encode Private Key WIF. */
- wif = encodePrivateKeyWif(wallet.privateKey, 'mainnet')
- // console.log('PRIVATE KEY (WIF):', wif)
- /* Filter out ANY tokens. */
- // FIXME We should probably do something better than this, lol.
- // unspent = unspent.filter(_unspent => {
- // return _unspent.satoshis > DUST_LIMIT
- // })
- /* Filter out ANY tokens & spent. */
- // FIXME We should probably do something better than this, lol.
- // unspent = unspent.filter(_unspent => {
- // /* Initialize flag. */
- // let isValid = true
- //
- // if (_unspent.satoshis <= DUST_LIMIT) {
- // /* Set flag. */
- // isValid = false
- // }
- //
- // if (spentCoins.includes(_unspent.outpoint)) {
- // /* Set flag. */
- // isValid = false
- // }
- //
- // /* Return flag. */
- // return isValid
- // })
- /* Validate unspent outputs. */
- if (unspent.length === 0) {
- return console.error('There are NO unspent outputs available.')
- }
- /* Build parameters. */
- const coins = unspent.map(_unspent => {
- const outpoint = _unspent.outpoint
- const satoshis = _unspent.satoshis
- return {
- outpoint,
- satoshis,
- wif,
- }
- })
- // console.log('\n Coins-1:', coins)
- const wallet2 = new Wallet(payment.entropy)
- // console.log('WALLET-2', wallet2)
- const address2 = wallet2.address
- // console.log('TREASURY ADDRESS-2', address2)
- const pk2 = wallet2.privateKey
- // console.log('PRIVATE KEY-2', pk2)
- const playerCoin = {
- outpoint: payment.unspent.outpoint,
- satoshis: payment.unspent.satoshis,
- wif: encodePrivateKeyWif(pk2, 'mainnet'),
- }
- coins.unshift(playerCoin)
- // console.log('\n Coins-2:', coins)
- /* Calculate the total balance of the unspent outputs. */
- const unspentSatoshis = coins
- .reduce(
- (totalValue, coin) => (totalValue + coin.satoshis), BigInt(0)
- )
- // console.log('UNSPENT SATOSHIS', unspentSatoshis)
- const userData = `NEXA.games~${payment.id}`
- // console.log('BLOCKCHAIN DATA', chainData)
- /* Initialize hex data. */
- const nullData = encodeNullData(userData)
- // console.log('HEX DATA', nullData)
- // NOTE: 150b (per input), 35b (per output), 10b (misc)
- // NOTE: Double the estimate (for safety).
- // const feeEstimate = ((coins.length * 150) + (35 * ESTIMATED_NUM_OUTPUTS) + 10 + (chainData.length / 2)) * 2
- // console.log('FEE ESTIMATE', feeEstimate)
- // TODO Validate data length is less than OP_RETURN max (220).
- /* Add OP_RETURN data. */
- receivers.push({
- data: nullData,
- })
- /* Add value output. */
- for (let j = 0; j < payment.receivers.length; j++) {
- if (payment.receivers[j].satoshis > DUST_LIMIT) {
- receivers.push({
- address: payment.receivers[j].address,
- satoshis: payment.receivers[j].satoshis,
- })
- }
- }
- /* Set change receiver. */
- receivers.push({
- address: vaultWallet.address
- })
- console.log('\n Receivers:', receivers)
- /* Send UTXO request. */
- response = await sendCoin(coins, receivers)
- console.log('Send UTXO (response):', response)
- try {
- txResult = JSON.parse(response)
- console.log('TX RESULT', txResult)
- /* Validate transaction result. */
- if (txResult?.result) {
- /* Manage coins. */
- // coins.forEach(_coin => {
- // /* Add hash to spent. */
- // spentCoins.push(_coin.outpoint)
- //
- // // TODO Add check for MAX spent (eg. 100).
- // })
- latestDb = await walletDb
- .get(payment.id)
- .catch(err => console.err(err))
- // console.log('LATEST (wallet)', latestDb)
- updatedDb = {
- ...latestDb,
- txidem: txResult?.result,
- updatedAt: moment().valueOf(),
- }
- // console.log('UPDATED (wallet)', updatedDb)
- response = await walletDb
- .put(updatedDb)
- .catch(err => console.error(err))
- // console.log('UPDATE (wallet) RESPONSE', response)
- latestDb = await playsDb
- .get(payment.id)
- .catch(err => console.err(err))
- // console.log('LATEST (plays)', latestDb)
- updatedDb = {
- ...latestDb,
- txidem: txResult?.result,
- updatedAt: moment().valueOf(),
- updatedBy: 'WALLET',
- }
- // console.log('UPDATED (plays)', updatedDb)
- response = await playsDb
- .put(updatedDb)
- .catch(err => console.error(err))
- // console.log('UPDATE (plays) RESPONSE', response)
- }
- // TODO SEND EMAIL ERROR
- // txResult?.error
- // txResult?.result
- } catch (err) {
- console.error(err)
- }
- }
- }
|