const fs = require('fs') const path = require('path') const _ = require('lodash/fp') const jsonRpc = require('../../common/json-rpc') const { COINS, utils } = require('@lamassu/coins') const BN = require('../../../bn') const E = require('../../../error') const { logger } = require('../../../blockchain/common') const options = require('../../../options') const blockchainDir = options.blockchainDir const cryptoRec = utils.getCryptoCurrency(COINS.XMR) const configPath = utils.configPath(cryptoRec, blockchainDir) const walletDir = path.resolve(utils.cryptoDir(cryptoRec, blockchainDir), 'wallets') function rpcConfig () { try { const config = jsonRpc.parseConf(configPath) return { username: config['rpc-login'].split(':')[0], password: config['rpc-login'].split(':')[1], port: cryptoRec.walletPort || cryptoRec.defaultPort } } catch (err) { throw new Error('wallet is currently not installed') } } function fetch (method, params) { return jsonRpc.createDigestRequest(rpcConfig(), method, params) } function handleError (error, method) { switch(error.code) { case -13: { if ( fs.existsSync(path.resolve(walletDir, 'Wallet')) && fs.existsSync(path.resolve(walletDir, 'Wallet.keys')) ) { logger.debug('Found wallet! Opening wallet...') return openWallet() } logger.debug('Couldn\'t find wallet! Creating...') return createWallet() } case -21: throw new Error('Wallet already exists!') case -17: throw new E.InsufficientFundsError() case -37: throw new E.InsufficientFundsError() default: throw new Error( _.join(' ', [ `json-rpc::${method} error:`, JSON.stringify(_.get('message', error, '')), JSON.stringify(_.get('response.data.error', error, '')) ]) ) } } function openWallet () { return fetch('open_wallet', { filename: 'Wallet', password: rpcConfig().password }) } function createWallet () { return fetch('create_wallet', { filename: 'Wallet', password: rpcConfig().password, language: 'English' }) .then(() => new Promise(() => setTimeout(() => openWallet(), 3000))) .then(() => fetch('auto_refresh')) } function checkCryptoCode (cryptoCode) { if (cryptoCode !== 'XMR') return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode)) return Promise.resolve() } function refreshWallet () { return fetch('refresh') } function accountBalance (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => refreshWallet()) .then(() => fetch('get_balance', { account_index: 0, address_indices: [0] })) .then(res => { return BN(res.unlocked_balance).decimalPlaces(0) }) .catch(err => handleError(err, 'accountBalance')) } function balance (account, cryptoCode, settings, operatorId) { return accountBalance(cryptoCode) } function sendCoins (account, tx, settings, operatorId, feeMultiplier) { const { toAddress, cryptoAtoms, cryptoCode } = tx return checkCryptoCode(cryptoCode) .then(() => refreshWallet()) .then(() => fetch('transfer_split', { destinations: [{ amount: cryptoAtoms, address: toAddress }], account_index: 0, subaddr_indices: [], priority: 0, mixin: 6, ring_size: 7, unlock_time: 0, get_tx_hex: false, new_algorithm: false, get_tx_metadata: false })) .then(res => ({ fee: BN(res.fee_list[0]).abs(), txid: res.tx_hash_list[0] })) .catch(err => handleError(err, 'sendCoins')) } function newAddress (account, info, tx, settings, operatorId) { return checkCryptoCode(info.cryptoCode) .then(() => fetch('create_address', { account_index: 0 })) .then(res => res.address) .catch(err => handleError(err, 'newAddress')) } function getStatus (account, tx, requested, settings, operatorId) { const { toAddress, cryptoCode } = tx return checkCryptoCode(cryptoCode) .then(() => refreshWallet()) .then(() => fetch('get_address_index', { address: toAddress })) .then(addressRes => fetch('get_transfers', { in: true, pool: true, account_index: addressRes.index.major, address_indices: [addressRes.index.minor] })) .then(transferRes => { const confirmedToAddress = _.filter(it => it.address === toAddress, transferRes.in ?? []) const pendingToAddress = _.filter(it => it.address === toAddress, transferRes.pool ?? []) const confirmed = _.reduce((acc, value) => acc.plus(value.amount), BN(0), confirmedToAddress) const pending = _.reduce((acc, value) => acc.plus(value.amount), BN(0), pendingToAddress) if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' } if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } return { receivedCryptoAtoms: pending, status: 'notSeen' } }) .catch(err => handleError(err, 'getStatus')) } function newFunding (account, cryptoCode, settings, operatorId) { return checkCryptoCode(cryptoCode) .then(() => refreshWallet()) .then(() => Promise.all([ fetch('get_balance', { account_index: 0, address_indices: [0] }), fetch('create_address', { account_index: 0 }), fetch('get_transfers', { pool: true, account_index: 0 }) ])) .then(([balanceRes, addressRes, transferRes]) => { const memPoolBalance = _.reduce((acc, value) => acc.plus(value.amount), BN(0), transferRes.pool) return { fundingPendingBalance: BN(balanceRes.balance).minus(balanceRes.unlocked_balance).plus(memPoolBalance), fundingConfirmedBalance: BN(balanceRes.unlocked_balance), fundingAddress: addressRes.address } }) .catch(err => handleError(err, 'newFunding')) } function cryptoNetwork (account, cryptoCode, settings, operatorId) { return checkCryptoCode(cryptoCode) .then(() => { switch(parseInt(rpcConfig().port, 10)) { case 18082: return 'main' case 28082: return 'test' case 38083: return 'stage' default: return '' } }) } function checkBlockchainStatus (cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => { try { const config = jsonRpc.parseConf(configPath) // Daemon uses a different connection of the wallet const rpcConfig = { username: config['rpc-login'].split(':')[0], password: config['rpc-login'].split(':')[1], port: cryptoRec.defaultPort } return jsonRpc.fetchDigest(rpcConfig, 'get_info') .then(res => { console.log('res XMR', res) return !!res.synchronized ? 'ready' : 'syncing' }) } catch (err) { throw new Error('XMR daemon is currently not installed') } }) } module.exports = { balance, sendCoins, newAddress, getStatus, newFunding, cryptoNetwork, checkBlockchainStatus }