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:
parent
9ec871e163
commit
c0808e9bd7
11 changed files with 445 additions and 14 deletions
|
|
@ -48,6 +48,11 @@ const BINARIES = {
|
|||
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v23.1.0/bitcoin-cash-node-23.1.0-x86_64-linux-gnu.tar.gz',
|
||||
dir: 'bitcoin-cash-node-23.1.0/bin',
|
||||
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
||||
},
|
||||
XMR: {
|
||||
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.17.2.0.tar.bz2',
|
||||
dir: 'monero-x86_64-linux-gnu-v0.17.2.0',
|
||||
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,11 +76,24 @@ function es (cmd) {
|
|||
return res.toString()
|
||||
}
|
||||
|
||||
function writeSupervisorConfig (coinRec, cmd) {
|
||||
function writeSupervisorConfig (coinRec, cmd, walletCmd = '') {
|
||||
if (isInstalledSoftware(coinRec)) return
|
||||
|
||||
const blockchain = coinRec.code
|
||||
|
||||
if (!_.isNil(coinRec.wallet)) {
|
||||
const supervisorConfigWallet = `[program:${blockchain}-wallet]
|
||||
command=nice ${walletCmd}
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/supervisor/${blockchain}-wallet.err.log
|
||||
stdout_logfile=/var/log/supervisor/${blockchain}-wallet.out.log
|
||||
environment=HOME="/root"
|
||||
`
|
||||
|
||||
writeFile(`/etc/supervisor/conf.d/${coinRec.code}-wallet.conf`, supervisorConfigWallet)
|
||||
}
|
||||
|
||||
const supervisorConfig = `[program:${blockchain}]
|
||||
command=nice ${cmd}
|
||||
autostart=true
|
||||
|
|
@ -89,7 +107,10 @@ environment=HOME="/root"
|
|||
}
|
||||
|
||||
function isInstalledSoftware (coinRec) {
|
||||
return fs.existsSync(`/etc/supervisor/conf.d/${coinRec.code}.conf`)
|
||||
return !_.isNil(coinRec.wallet)
|
||||
? fs.existsSync(`/etc/supervisor/conf.d/${coinRec.code}.conf`)
|
||||
&& fs.existsSync(`/etc/supervisor/conf.d/${coinRec.code}-wallet.conf`)
|
||||
: fs.existsSync(`/etc/supervisor/conf.d/${coinRec.code}.conf`)
|
||||
}
|
||||
|
||||
function fetchAndInstall (coinRec) {
|
||||
|
|
@ -104,7 +125,9 @@ function fetchAndInstall (coinRec) {
|
|||
const binDir = requiresUpdate ? binaries.defaultDir : binaries.dir
|
||||
|
||||
es(`wget -q ${url}`)
|
||||
es(`tar -xzf ${downloadFile}`)
|
||||
coinRec.cryptoCode === 'XMR'
|
||||
? es(`tar -xf ${downloadFile}`)
|
||||
: es(`tar -xzf ${downloadFile}`)
|
||||
|
||||
if (_.isEmpty(binaries.files)) {
|
||||
es(`sudo cp ${binDir}/* /usr/local/bin`)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ const PLUGINS = {
|
|||
DASH: require('./dash.js'),
|
||||
ETH: require('./ethereum.js'),
|
||||
LTC: require('./litecoin.js'),
|
||||
ZEC: require('./zcash.js')
|
||||
ZEC: require('./zcash.js'),
|
||||
XMR: require('./monero.js')
|
||||
}
|
||||
|
||||
module.exports = {run}
|
||||
|
|
|
|||
30
lib/blockchain/monero.js
Normal file
30
lib/blockchain/monero.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
const path = require('path')
|
||||
|
||||
const { utils } = require('lamassu-coins')
|
||||
|
||||
const common = require('./common')
|
||||
|
||||
module.exports = {setup}
|
||||
|
||||
const coinRec = utils.getCryptoCurrency('XMR')
|
||||
|
||||
function setup (dataDir) {
|
||||
common.firewall([coinRec.defaultPort])
|
||||
const auth = `lamassuserver:${common.randomPass()}`
|
||||
const config = buildConfig(auth)
|
||||
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
|
||||
const cmd = `/usr/local/bin/${coinRec.daemon} --data-dir ${dataDir} --config-file ${dataDir}/${coinRec.configFile}`
|
||||
const walletCmd = `/usr/local/bin/${coinRec.wallet} --stagenet --rpc-login ${auth} --daemon-host 127.0.0.1 --daemon-port 38081 --trusted-daemon --daemon-login ${auth} --rpc-bind-port 38083 --wallet-dir ${dataDir}/wallets`
|
||||
common.writeSupervisorConfig(coinRec, cmd, walletCmd)
|
||||
}
|
||||
|
||||
function buildConfig (auth) {
|
||||
return `rpc-login=${auth}
|
||||
stagenet=1
|
||||
restricted-rpc=1
|
||||
db-sync-mode=safe
|
||||
out-peers=20
|
||||
in-peers=20
|
||||
prune-blockchain=1
|
||||
`
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ const _ = require('lodash/fp')
|
|||
|
||||
const { ALL } = require('../../plugins/common/ccxt')
|
||||
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT } = COINS
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, XMR } = COINS
|
||||
const { bitpay, coinbase, itbit, bitstamp, kraken, binanceus, cex, ftx } = ALL
|
||||
|
||||
const TICKER = 'ticker'
|
||||
|
|
@ -32,6 +32,7 @@ const ALL_ACCOUNTS = [
|
|||
{ code: 'zcashd', display: 'zcashd', class: WALLET, cryptos: [ZEC] },
|
||||
{ code: 'litecoind', display: 'litecoind', class: WALLET, cryptos: [LTC] },
|
||||
{ code: 'dashd', display: 'dashd', class: WALLET, cryptos: [DASH] },
|
||||
{ code: 'monerod', display: 'monerod', class: WALLET, cryptos: [XMR] },
|
||||
{ code: 'bitcoincashd', display: 'bitcoincashd', class: WALLET, cryptos: [BCH] },
|
||||
{ code: 'bitgo', display: 'BitGo', class: WALLET, cryptos: [BTC, ZEC, LTC, BCH, DASH] },
|
||||
{ code: 'bitstamp', display: 'Bitstamp', class: EXCHANGE, cryptos: bitstamp.CRYPTO },
|
||||
|
|
@ -47,7 +48,7 @@ const ALL_ACCOUNTS = [
|
|||
{ code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER, dev: true },
|
||||
{ code: 'twilio', display: 'Twilio', class: SMS },
|
||||
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
||||
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH] },
|
||||
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH, XMR] },
|
||||
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
||||
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
196
lib/plugins/wallet/monerod/monerod.js
Normal file
196
lib/plugins/wallet/monerod/monerod.js
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue