feat: start working on monero implementation

feat: monero files and config

feat: monero interface
feat: monero rpc digest request

feat: add monero to wallet wizard splash

fix: tarball unzipping

fix: monero files

fix: monero zipped folder path

fix: undefined variable

chore: xmr stagenet

fix: prune-blockchain flag

fix: monero wallet arguments

chore: rpc-bind-port on monero wallet

chore: monero wallet directory

fix: fetch digest request
fix: monero authentication
fix: monero address creation

fix: monero config port

fix: wallet creation
fix: wallet rpc daemon login

fix: get monero funding

fix: unauthorized issue with multiple requests on Promise.all
fix: generate funding addresses
fix: destination address balance for XMR cashout
fix: transaction creation

feat: transaction recommended mixin and ring size

fix: monero wallet error handling

fix: error handling

fix: monero wallet error
feat: guide to add new cryptos

chore: small code shortcuts

fix: crypto implementation guide

chore: add XMR to exchange files
This commit is contained in:
Sérgio Salgado 2021-05-06 17:11:24 +01:00
parent 9ec871e163
commit c0808e9bd7
11 changed files with 445 additions and 14 deletions

View file

@ -8,7 +8,7 @@ const binanceus = require('../exchange/binanceus')
const cex = require('../exchange/cex')
const ftx = require('../exchange/ftx')
const bitpay = require('../ticker/bitpay')
const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, XMR } = COINS
const ALL = {
cex: cex,
@ -19,7 +19,7 @@ const ALL = {
itbit: itbit,
bitpay: bitpay,
coinbase: {
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH],
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, XMR],
FIAT: 'ALL_CURRENCIES'
}
}

View file

@ -3,8 +3,9 @@ const axios = require('axios')
const uuid = require('uuid')
const fs = require('fs')
const _ = require('lodash/fp')
const request = require('request-promise')
module.exports = {fetch, parseConf}
module.exports = {fetch, fetchDigest, parseConf}
function fetch (account = {}, method, params) {
params = _.defaultTo([], params)
@ -41,6 +42,40 @@ function fetch (account = {}, method, params) {
})
}
function fetchDigest(account = {}, method, params = []) {
return Promise.resolve(true)
.then(() => {
if (_.isNil(account.port))
throw new Error('port attribute required for jsonRpc')
const headers = {
'Content-Type': 'application/json'
}
const dataString = `{"jsonrpc":"2.0","id":"${uuid.v4()}","method":"${method}","params":${JSON.stringify(params)}}`
const options = {
url: `http://localhost:${account.port}/json_rpc`,
method: 'POST',
headers,
body: dataString,
forever: true,
auth: {
user: account.username,
pass: account.password,
sendImmediately: false
}
}
return request(options)
})
.then((res) => {
const r = JSON.parse(res)
if (r.error) throw r.error
return r.result
})
}
function split (str) {
const i = str.indexOf('=')
if (i === -1) return []

View file

@ -4,8 +4,8 @@ const { ORDER_TYPES } = require('./consts')
const { COINS } = require('lamassu-coins')
const ORDER_TYPE = ORDER_TYPES.MARKET
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT } = COINS
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT]
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, XMR } = COINS
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, XMR]
const FIAT = ['USD', 'EUR']
const AMOUNT_PRECISION = 6
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']

View file

@ -0,0 +1,196 @@
const fs = require('fs')
const path = require('path')
const _ = require('lodash/fp')
const jsonRpc = require('../../common/json-rpc')
const { utils } = require('lamassu-coins')
const blockchainUtils = require('../../../coin-utils')
const BN = require('../../../bn')
const E = require('../../../error')
const { logger } = require('../../../blockchain/common')
const cryptoRec = utils.getCryptoCurrency('XMR')
const configPath = utils.configPath(cryptoRec, blockchainUtils.blockchainDir())
const walletDir = path.resolve(utils.cryptoDir(cryptoRec, blockchainUtils.blockchainDir()), 'wallets')
const unitScale = cryptoRec.unitScale
const config = jsonRpc.parseConf(configPath)
const rpcConfig = {
username: config['rpc-login'].split(':')[0],
password: config['rpc-login'].split(':')[1],
port: cryptoRec.walletPort || cryptoRec.defaultPort
}
function fetch (method, params) {
return jsonRpc.fetchDigest(rpcConfig, method, params)
}
function handleError (error) {
switch(error.code) {
case -13:
{
if (fs.existsSync(path.resolve(walletDir, 'Wallet')) && fs.existsSync(path.resolve(walletDir, 'Wallet.keys')) && fs.existsSync(path.resolve(walletDir, 'Wallet.address.txt'))) {
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::got 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).shift(unitScale).round()
})
.catch(err => handleError(err))
}
function balance (account, cryptoCode) {
return accountBalance(cryptoCode)
}
function sendCoins (account, address, cryptoAtoms, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => refreshWallet())
.then(() => fetch('transfer_split', {
destinations: [{ amount: cryptoAtoms, address }],
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))
}
function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode)
.then(() => fetch('create_address', { account_index: 0 }))
.then(res => res.address)
.catch(err => handleError(err))
}
function addressBalance (address, confirmations) {
return fetch('get_address_index', { address: address })
.then(addressRes => fetch('get_balance', { account_index: addressRes.index.major, address_indices: [addressRes.index.minor] }))
.then(res => BN(res.unlocked_balance))
.catch(err => handleError(err))
}
function confirmedBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => refreshWallet())
.then(() => addressBalance(address, 1))
.catch(err => handleError(err))
}
function pendingBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => refreshWallet())
.then(() => addressBalance(address, 0))
.catch(err => handleError(err))
}
function getStatus (account, toAddress, requested, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => refreshWallet())
.then(() => confirmedBalance(toAddress, cryptoCode))
.then(confirmed => {
if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' }
return pendingBalance(toAddress, cryptoCode)
.then(pending => {
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))
}
function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => refreshWallet())
.then(() => fetch('get_balance', { account_index: 0, address_indices: [0] }))
.then(balanceRes => Promise.all([
balanceRes,
fetch('create_address', { account_index: 0 })
]))
.then(([balanceRes, addressRes]) => ({
fundingPendingBalance: BN(balanceRes.balance).sub(balanceRes.unlocked_balance),
fundingConfirmedBalance: BN(balanceRes.unlocked_balance),
fundingAddress: addressRes.address
}))
.catch(err => handleError(err))
}
function cryptoNetwork (account, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => {
switch(parseInt(rpcConfig.port, 10)) {
case 18083:
return 'main'
case 28083:
return 'test'
case 38083:
return 'stage'
default:
return ''
}
})
}
module.exports = {
balance,
sendCoins,
newAddress,
getStatus,
newFunding,
cryptoNetwork
}