Merge branch 'release-8.1' into chore/update-ciphertrace-logs
This commit is contained in:
commit
5e1d706ca2
115 changed files with 1416 additions and 580 deletions
|
|
@ -11,7 +11,7 @@ const bitpay = require('../ticker/bitpay')
|
|||
const binance = require('../exchange/binance')
|
||||
const logger = require('../../logger')
|
||||
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT } = COINS
|
||||
|
||||
const ALL = {
|
||||
cex: cex,
|
||||
|
|
@ -22,7 +22,7 @@ const ALL = {
|
|||
itbit: itbit,
|
||||
bitpay: bitpay,
|
||||
coinbase: {
|
||||
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH],
|
||||
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH, USDT],
|
||||
FIAT: 'ALL_CURRENCIES'
|
||||
},
|
||||
binance: binance
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const _ = require('lodash/fp')
|
|||
const { ORDER_TYPES } = require('./consts')
|
||||
|
||||
const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH]
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT]
|
||||
const FIAT = ['USD']
|
||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const _ = require('lodash/fp')
|
|||
const { ORDER_TYPES } = require('./consts')
|
||||
|
||||
const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||
const { BTC, ETH, LTC, BCH } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, BCH]
|
||||
const { BTC, ETH, LTC, BCH, USDT } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, BCH, USDT]
|
||||
const FIAT = ['USD', 'EUR']
|
||||
const AMOUNT_PRECISION = 8
|
||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId']
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const _ = require('lodash/fp')
|
|||
const { ORDER_TYPES } = require('./consts')
|
||||
|
||||
const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||
const { BTC, BCH, DASH, ETH, LTC } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, DASH, BCH]
|
||||
const { BTC, BCH, DASH, ETH, LTC, USDT } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, DASH, BCH, USDT]
|
||||
const FIAT = ['USD', 'EUR']
|
||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const _ = require('lodash/fp')
|
|||
const { ORDER_TYPES } = require('./consts')
|
||||
|
||||
const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||
const { BTC, BCH, ETH, LTC } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, BCH]
|
||||
const { BTC, BCH, ETH, LTC, USDT } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, BCH, USDT]
|
||||
const FIAT = ['USD']
|
||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const { ORDER_TYPES } = require('./consts')
|
|||
const { COINS } = require('@lamassu/coins')
|
||||
|
||||
const ORDER_TYPE = ORDER_TYPES.LIMIT
|
||||
const { BTC, ETH } = COINS
|
||||
const CRYPTO = [BTC, ETH]
|
||||
const { BTC, ETH, USDT } = COINS
|
||||
const CRYPTO = [BTC, ETH, USDT]
|
||||
const FIAT = ['USD']
|
||||
const AMOUNT_PRECISION = 4
|
||||
const REQUIRED_CONFIG_FIELDS = ['clientKey', 'clientSecret', 'userId', 'walletId']
|
||||
|
|
|
|||
|
|
@ -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, XMR } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR]
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, XMR, USDT } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR, USDT]
|
||||
const FIAT = ['USD', 'EUR']
|
||||
const AMOUNT_PRECISION = 6
|
||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
||||
|
|
|
|||
|
|
@ -52,44 +52,62 @@ function isValidWalletScore (account, score) {
|
|||
return _.isNil(account) ? Promise.resolve(true) : Promise.resolve(score < threshold)
|
||||
}
|
||||
|
||||
function getTransactionHash (account, cryptoCode, receivingAddress) {
|
||||
const client = getClient(account)
|
||||
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||
|
||||
function getAddressTransactionsHashes (receivingAddress, cryptoCode, client, wallet) {
|
||||
const { apiVersion, authHeader } = client
|
||||
|
||||
logger.info(`** DEBUG ** getTransactionHash ENDPOINT: https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`)
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, 2000)
|
||||
|
||||
return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`, {
|
||||
headers: authHeader
|
||||
})
|
||||
.then(axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`, {
|
||||
headers: authHeader
|
||||
}))
|
||||
.then(res => {
|
||||
const data = res.data
|
||||
if (_.size(data.txHistory) > 1) {
|
||||
logger.warn('An address generated by this wallet was used in more than one transaction')
|
||||
}
|
||||
logger.info(`** DEBUG ** getTransactionHash RETURN: ${_.join(', ', _.map(it => it.txHash, data.txHistory))}`)
|
||||
return _.join(', ', _.map(it => it.txHash, data.txHistory))
|
||||
})
|
||||
.then(_.flow(
|
||||
_.get(['data', 'txHistory']),
|
||||
_.map(_.get(['txHash']))
|
||||
))
|
||||
.catch(err => {
|
||||
logger.error(`** DEBUG ** getTransactionHash ERROR: ${err}`)
|
||||
logger.error(`** DEBUG ** Fetching transactions hashes via wallet node...`)
|
||||
return wallet.getTxHashesByAddress(cryptoCode, receivingAddress)
|
||||
})
|
||||
}
|
||||
|
||||
function getTransactionHash (account, cryptoCode, receivingAddress, wallet) {
|
||||
const client = getClient(account)
|
||||
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||
return getAddressTransactionsHashes(receivingAddress, cryptoCode, client, wallet)
|
||||
.then(txHashes => {
|
||||
if (_.size(txHashes) > 1) {
|
||||
logger.warn('An address generated by this wallet was used in more than one transaction')
|
||||
}
|
||||
logger.info('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes))
|
||||
return txHashes
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('** DEBUG ** getTransactionHash from wallet node ERROR: ', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function getInputAddresses (account, cryptoCode, txHashes) {
|
||||
const client = getClient(account)
|
||||
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||
if (_.isEmpty(txHashes) || !_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client))
|
||||
return Promise.resolve([])
|
||||
|
||||
/* NOTE: The API accepts at most 10 hashes, and for us here more than 1 is already an exception, not the norm. */
|
||||
if (_.size(txHashes) > 10)
|
||||
return Promise.reject(new Error("Too many tx hashes -- shouldn't happen!"))
|
||||
|
||||
const { apiVersion, authHeader } = client
|
||||
|
||||
logger.info(`** DEBUG ** getInputAddresses ENDPOINT: https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}tx?txhashes=${txHashes}`)
|
||||
cryptoCode = _.toLower(cryptoCode)
|
||||
const lastPathComp = cryptoCode !== 'btc' ? cryptoCode + '_tx' : 'tx'
|
||||
|
||||
return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}tx?txhashes=${txHashes}`, {
|
||||
headers: authHeader
|
||||
})
|
||||
txHashes = _(txHashes).take(10).join(',')
|
||||
|
||||
const url = `https://rest.ciphertrace.com/api/${apiVersion}/${lastPathComp}?txhashes=${txHashes}`
|
||||
console.log('** DEBUG ** getInputAddresses ENDPOINT: ', url)
|
||||
|
||||
return axios.get(url, { headers: authHeader })
|
||||
.then(res => {
|
||||
const data = res.data
|
||||
if (_.size(data.transactions) > 1) {
|
||||
|
|
@ -109,10 +127,19 @@ function getInputAddresses (account, cryptoCode, txHashes) {
|
|||
})
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (account, cryptoCode) {
|
||||
if (!SUPPORTED_COINS.includes(cryptoCode)) {
|
||||
return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode))
|
||||
}
|
||||
|
||||
return Promise.resolve(!_.isNil(account) && account.enabled)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,20 @@ function getInputAddresses (account, cryptoCode, txHashes) {
|
|||
})
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (account, cryptoCode) {
|
||||
return new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
return resolve(true)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,13 @@ function checkBlockchainStatus (cryptoCode) {
|
|||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('listreceivedbyaddress', [0, true, true, address]))
|
||||
.then(txsByAddress => Promise.all(_.map(id => fetch('getrawtransaction', [id]), _.flatMap(it => it.txids, txsByAddress))))
|
||||
.then(_.map(({ hash }) => hash))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
balance,
|
||||
sendCoins,
|
||||
|
|
@ -136,5 +143,6 @@ module.exports = {
|
|||
getStatus,
|
||||
newFunding,
|
||||
cryptoNetwork,
|
||||
checkBlockchainStatus
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,6 +191,13 @@ function checkBlockchainStatus (cryptoCode) {
|
|||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('listreceivedbyaddress', [0, true, true, address]))
|
||||
.then(txsByAddress => Promise.all(_.map(id => fetch('getrawtransaction', [id]), _.flatMap(it => it.txids, txsByAddress))))
|
||||
.then(_.map(({ hash }) => hash))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
balance,
|
||||
sendCoins,
|
||||
|
|
@ -202,5 +209,6 @@ module.exports = {
|
|||
estimateFee,
|
||||
sendCoinsBatch,
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress,
|
||||
SUPPORTS_BATCHING
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,11 +125,19 @@ function checkBlockchainStatus (cryptoCode) {
|
|||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('listreceivedbyaddress', [0, true, true, true, address]))
|
||||
.then(txsByAddress => Promise.all(_.map(id => fetch('getrawtransaction', [id]), _.flatMap(it => it.txids, txsByAddress))))
|
||||
.then(_.map(({ hash }) => hash))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
balance,
|
||||
sendCoins,
|
||||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
checkBlockchainStatus
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@ const web3 = new Web3()
|
|||
const hdkey = require('ethereumjs-wallet/hdkey')
|
||||
const { FeeMarketEIP1559Transaction } = require('@ethereumjs/tx')
|
||||
const { default: Common, Chain, Hardfork } = require('@ethereumjs/common')
|
||||
const Tx = require('ethereumjs-tx')
|
||||
const { default: PQueue } = require('p-queue')
|
||||
const util = require('ethereumjs-util')
|
||||
const coins = require('@lamassu/coins')
|
||||
const pify = require('pify')
|
||||
|
||||
const _pify = require('pify')
|
||||
const BN = require('../../../bn')
|
||||
const ABI = require('../../tokens')
|
||||
const logger = require('../../../logger')
|
||||
|
||||
exports.SUPPORTED_MODULES = ['wallet']
|
||||
|
||||
|
|
@ -30,7 +34,27 @@ module.exports = {
|
|||
privateKey,
|
||||
isStrictAddress,
|
||||
connect,
|
||||
checkBlockchainStatus
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress,
|
||||
_balance
|
||||
}
|
||||
|
||||
const SWEEP_QUEUE = new PQueue({
|
||||
concurrency: 3,
|
||||
interval: 250,
|
||||
})
|
||||
|
||||
const infuraCalls = {}
|
||||
|
||||
const pify = _function => {
|
||||
if (_.isString(_function.call)) logInfuraCall(_function.call)
|
||||
return _pify(_function)
|
||||
}
|
||||
|
||||
const logInfuraCall = call => {
|
||||
if (!_.includes('infura', web3.currentProvider.host)) return
|
||||
_.isNil(infuraCalls[call]) ? infuraCalls[call] = 1 : infuraCalls[call]++
|
||||
logger.info(`Calling web3 method ${call} via Infura. Current count for this session: ${JSON.stringify(infuraCalls)}`)
|
||||
}
|
||||
|
||||
function connect (url) {
|
||||
|
|
@ -44,12 +68,19 @@ function privateKey (account) {
|
|||
}
|
||||
|
||||
function isStrictAddress (cryptoCode, toAddress, settings, operatorId) {
|
||||
return cryptoCode === 'ETH' && util.isValidChecksumAddress(toAddress)
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => util.isValidChecksumAddress(toAddress))
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
throw new Error(`Transactions hash retrieval is not implemented for this coin!`)
|
||||
}
|
||||
|
||||
function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
|
||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false, cryptoCode)
|
||||
const isErc20Token = coins.utils.isErc20Token(cryptoCode)
|
||||
|
||||
return (isErc20Token ? generateErc20Tx : generateTx)(toAddress, defaultWallet(account), cryptoAtoms, false, cryptoCode)
|
||||
.then(pify(web3.eth.sendSignedTransaction))
|
||||
.then(txid => {
|
||||
return pify(web3.eth.getTransaction)(txid)
|
||||
|
|
@ -77,14 +108,16 @@ function balance (account, cryptoCode, settings, operatorId) {
|
|||
|
||||
const pendingBalance = (address, cryptoCode) => {
|
||||
const promises = [_balance(true, address, cryptoCode), _balance(false, address, cryptoCode)]
|
||||
return Promise.all(promises).then(([pending, confirmed]) => pending.minus(confirmed))
|
||||
return Promise.all(promises).then(([pending, confirmed]) => BN(pending).minus(confirmed))
|
||||
}
|
||||
const confirmedBalance = (address, cryptoCode) => _balance(false, address, cryptoCode)
|
||||
|
||||
function _balance (includePending, address, cryptoCode) {
|
||||
if (coins.utils.isErc20Token(cryptoCode)) {
|
||||
const contract = web3.eth.contract(ABI.ERC20).at(coins.utils.getErc20Token(cryptoCode).contractAddress)
|
||||
return contract.balanceOf(address.toLowerCase())
|
||||
const contract = new web3.eth.Contract(ABI.ERC20, coins.utils.getErc20Token(cryptoCode).contractAddress)
|
||||
return contract.methods.balanceOf(address.toLowerCase()).call((_, balance) => {
|
||||
return contract.methods.decimals().call((_, decimals) => BN(balance).div(10 ** decimals))
|
||||
})
|
||||
}
|
||||
const block = includePending ? 'pending' : undefined
|
||||
return pify(web3.eth.getBalance)(address.toLowerCase(), block)
|
||||
|
|
@ -92,27 +125,78 @@ function _balance (includePending, address, cryptoCode) {
|
|||
.then(balance => balance ? BN(balance) : BN(0))
|
||||
}
|
||||
|
||||
function generateTx (_toAddress, wallet, amount, includesFee, cryptoCode) {
|
||||
function generateErc20Tx (_toAddress, wallet, amount, includesFee, cryptoCode) {
|
||||
const fromAddress = '0x' + wallet.getAddress().toString('hex')
|
||||
|
||||
const isErc20Token = coins.utils.isErc20Token(cryptoCode)
|
||||
const toAddress = isErc20Token ? coins.utils.getErc20Token(cryptoCode).contractAddress : _toAddress.toLowerCase()
|
||||
const toAddress = coins.utils.getErc20Token(cryptoCode).contractAddress
|
||||
|
||||
let contract, contractData
|
||||
if (isErc20Token) {
|
||||
contract = web3.eth.contract(ABI.ERC20).at(toAddress)
|
||||
contractData = isErc20Token && contract.transfer.getData(_toAddress.toLowerCase(), hex(toSend))
|
||||
const contract = new web3.eth.Contract(ABI.ERC20, toAddress)
|
||||
const contractData = contract.methods.transfer(_toAddress.toLowerCase(), hex(amount))
|
||||
|
||||
const txTemplate = {
|
||||
from: fromAddress,
|
||||
to: toAddress,
|
||||
value: hex(BN(0)),
|
||||
data: contractData.encodeABI()
|
||||
}
|
||||
|
||||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.London })
|
||||
|
||||
const promises = [
|
||||
pify(contractData.estimateGas)(txTemplate),
|
||||
pify(web3.eth.getTransactionCount)(fromAddress),
|
||||
pify(web3.eth.getBlock)('pending')
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(([gas, txCount, { baseFeePerGas }]) => [
|
||||
BN(gas),
|
||||
_.max([0, txCount, lastUsedNonces[fromAddress] + 1]),
|
||||
BN(baseFeePerGas)
|
||||
])
|
||||
.then(([gas, txCount, baseFeePerGas]) => {
|
||||
lastUsedNonces[fromAddress] = txCount
|
||||
|
||||
const maxPriorityFeePerGas = new BN(web3.utils.toWei('2.5', 'gwei')) // web3 default value
|
||||
const maxFeePerGas = new BN(2).times(baseFeePerGas).plus(maxPriorityFeePerGas)
|
||||
|
||||
if (includesFee && (toSend.isNegative() || toSend.isZero())) {
|
||||
throw new Error(`Trying to send a nil or negative amount (Transaction ID: ${txId} | Value provided: ${toSend.toNumber()}). This is probably caused due to the estimated fee being higher than the address' balance.`)
|
||||
}
|
||||
|
||||
const rawTx = {
|
||||
chainId: 1,
|
||||
nonce: txCount,
|
||||
maxPriorityFeePerGas: web3.utils.toHex(maxPriorityFeePerGas),
|
||||
maxFeePerGas: web3.utils.toHex(maxFeePerGas),
|
||||
gasLimit: hex(gas),
|
||||
to: toAddress,
|
||||
from: fromAddress,
|
||||
value: hex(BN(0)),
|
||||
data: contractData.encodeABI()
|
||||
}
|
||||
|
||||
const tx = FeeMarketEIP1559Transaction.fromTxData(rawTx, { common })
|
||||
const privateKey = wallet.getPrivateKey()
|
||||
|
||||
const signedTx = tx.sign(privateKey)
|
||||
|
||||
return '0x' + signedTx.serialize().toString('hex')
|
||||
})
|
||||
}
|
||||
|
||||
function generateTx (_toAddress, wallet, amount, includesFee, cryptoCode, txId) {
|
||||
const fromAddress = '0x' + wallet.getAddress().toString('hex')
|
||||
|
||||
const toAddress = _toAddress.toLowerCase()
|
||||
|
||||
const txTemplate = {
|
||||
from: fromAddress,
|
||||
to: toAddress,
|
||||
value: amount.toString()
|
||||
}
|
||||
|
||||
if (isErc20Token) txTemplate.data = contractData
|
||||
|
||||
const common = new Common({ chain: Chain.Ropsten, hardfork: Hardfork.London })
|
||||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.London })
|
||||
|
||||
const promises = [
|
||||
pify(web3.eth.estimateGas)(txTemplate),
|
||||
|
|
@ -122,34 +206,33 @@ function generateTx (_toAddress, wallet, amount, includesFee, cryptoCode) {
|
|||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(([gas, gasPrice, txCount]) => [
|
||||
.then(([gas, gasPrice, txCount, { baseFeePerGas }]) => [
|
||||
BN(gas),
|
||||
BN(gasPrice),
|
||||
_.max([0, txCount, lastUsedNonces[fromAddress] + 1])
|
||||
_.max([0, txCount, lastUsedNonces[fromAddress] + 1]),
|
||||
BN(baseFeePerGas)
|
||||
])
|
||||
.then(([gas, gasPrice, txCount, baseFeePerGas]) => {
|
||||
lastUsedNonces[fromAddress] = txCount
|
||||
|
||||
const toSend = includesFee
|
||||
? amount.minus(gasPrice.times(gas))
|
||||
: amount
|
||||
const maxPriorityFeePerGas = new BN(web3.utils.toWei('2.5', 'gwei')) // web3 default value
|
||||
const neededPriority = new BN(web3.utils.toWei('2.0', 'gwei'))
|
||||
const maxFeePerGas = baseFeePerGas.plus(neededPriority)
|
||||
const newGasPrice = BN.minimum(maxFeePerGas, baseFeePerGas.plus(maxPriorityFeePerGas))
|
||||
|
||||
const maxPriorityFeePerGas = new BN(2.5) // web3 default value
|
||||
const maxFeePerGas = new BN(2).times(baseFeePerGas).plus(maxPriorityFeePerGas)
|
||||
const toSend = includesFee
|
||||
? new BN(amount).minus(newGasPrice.times(gas))
|
||||
: amount
|
||||
|
||||
const rawTx = {
|
||||
chainId: 1,
|
||||
nonce: txCount,
|
||||
maxPriorityFeePerGas: web3.utils.toHex(web3.utils.toWei(maxPriorityFeePerGas.toString(), 'gwei')),
|
||||
maxFeePerGas: web3.utils.toHex(web3.utils.toWei(maxFeePerGas.toString(), 'gwei')),
|
||||
maxPriorityFeePerGas: web3.utils.toHex(maxPriorityFeePerGas),
|
||||
maxFeePerGas: web3.utils.toHex(maxFeePerGas),
|
||||
gasLimit: hex(gas),
|
||||
to: toAddress,
|
||||
from: fromAddress,
|
||||
value: isErc20Token ? hex(BN(0)) : hex(toSend)
|
||||
}
|
||||
|
||||
if (isErc20Token) {
|
||||
rawTx.data = contractData
|
||||
value: hex(toSend)
|
||||
}
|
||||
|
||||
const tx = FeeMarketEIP1559Transaction.fromTxData(rawTx, { common })
|
||||
|
|
@ -169,17 +252,18 @@ function defaultAddress (account) {
|
|||
return defaultWallet(account).getChecksumAddressString()
|
||||
}
|
||||
|
||||
function sweep (account, cryptoCode, hdIndex, settings, operatorId) {
|
||||
function sweep (account, txId, cryptoCode, hdIndex, settings, operatorId) {
|
||||
const wallet = paymentHdNode(account).deriveChild(hdIndex).getWallet()
|
||||
const fromAddress = wallet.getChecksumAddressString()
|
||||
|
||||
return confirmedBalance(fromAddress, cryptoCode)
|
||||
return SWEEP_QUEUE.add(() => confirmedBalance(fromAddress, cryptoCode)
|
||||
.then(r => {
|
||||
if (r.eq(0)) return
|
||||
|
||||
return generateTx(defaultAddress(account), wallet, r, true, cryptoCode)
|
||||
return generateTx(defaultAddress(account), wallet, r, true, cryptoCode, txId)
|
||||
.then(signedTx => pify(web3.eth.sendSignedTransaction)(signedTx))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function newAddress (account, info, tx, settings, operatorId) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
const _ = require('lodash/fp')
|
||||
const NodeCache = require('node-cache')
|
||||
const base = require('../geth/base')
|
||||
const T = require('../../../time')
|
||||
const { BALANCE_FETCH_SPEED_MULTIPLIER } = require('../../../constants')
|
||||
|
||||
const REGULAR_TX_POLLING = 5 * T.seconds
|
||||
|
||||
const NAME = 'infura'
|
||||
|
||||
|
|
@ -12,4 +17,54 @@ function run (account) {
|
|||
base.connect(endpoint)
|
||||
}
|
||||
|
||||
module.exports = _.merge(base, { NAME, run })
|
||||
const txsCache = new NodeCache({
|
||||
stdTTL: T.hour / 1000,
|
||||
checkperiod: T.minute / 1000,
|
||||
deleteOnExpire: true
|
||||
})
|
||||
|
||||
function shouldGetStatus (tx) {
|
||||
const timePassedSinceTx = Date.now() - new Date(tx.created)
|
||||
const timePassedSinceReq = Date.now() - new Date(txsCache.get(tx.id).lastReqTime)
|
||||
|
||||
// Allow for infura to gradually lower the amount of requests based on the time passed since the transaction
|
||||
// Until first 5 minutes - 1/2 regular polling speed
|
||||
// Until first 10 minutes - 1/4 regular polling speed
|
||||
// Until first hour - 1/8 polling speed
|
||||
// Until first two hours - 1/12 polling speed
|
||||
// Until first four hours - 1/16 polling speed
|
||||
// Until first day - 1/24 polling speed
|
||||
// After first day - 1/32 polling speed
|
||||
if (timePassedSinceTx < 5 * T.minutes) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 2 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 10 * T.minutes) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 4 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 1 * T.hour) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 8 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 2 * T.hours) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 12 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 4 * T.hours) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 16 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 1 * T.day) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 24 * REGULAR_TX_POLLING
|
||||
return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 32 * REGULAR_TX_POLLING
|
||||
}
|
||||
|
||||
// Override geth's getStatus function to allow for different polling timing
|
||||
function getStatus (account, tx, requested, settings, operatorId) {
|
||||
if (_.isNil(txsCache.get(tx.id))) {
|
||||
txsCache.set(tx.id, { lastReqTime: Date.now() })
|
||||
}
|
||||
|
||||
// return last available response
|
||||
if (!shouldGetStatus(tx)) {
|
||||
return Promise.resolve(txsCache.get(tx.id).res)
|
||||
}
|
||||
|
||||
return base.getStatus(account, tx, requested, settings, operatorId)
|
||||
.then(res => {
|
||||
if (res.status === 'confirmed') {
|
||||
txsCache.del(tx.id) // Transaction reached final status, can trim it from the caching obj
|
||||
} else {
|
||||
txsCache.set(tx.id, { lastReqTime: Date.now(), res })
|
||||
txsCache.ttl(tx.id, T.hour / 1000)
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = _.merge(base, { NAME, run, getStatus, fetchSpeed: BALANCE_FETCH_SPEED_MULTIPLIER.SLOW })
|
||||
|
|
|
|||
|
|
@ -125,11 +125,16 @@ function checkBlockchainStatus (cryptoCode) {
|
|||
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
throw new Error(`Transactions hash retrieval not implemented for this coin!`)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
balance,
|
||||
sendCoins,
|
||||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
checkBlockchainStatus
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,14 @@ const SECONDS = 1000
|
|||
const PUBLISH_TIME = 3 * SECONDS
|
||||
const AUTHORIZE_TIME = PUBLISH_TIME + 5 * SECONDS
|
||||
const CONFIRM_TIME = AUTHORIZE_TIME + 10 * SECONDS
|
||||
const SUPPORTED_COINS = coinUtils.cryptoCurrencies()
|
||||
|
||||
let t0
|
||||
|
||||
const checkCryptoCode = (cryptoCode) => !_.includes(cryptoCode, SUPPORTED_COINS)
|
||||
? Promise.reject(new Error('Unsupported crypto: ' + cryptoCode))
|
||||
: Promise.resolve()
|
||||
|
||||
function _balance (cryptoCode) {
|
||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||
const unitScale = cryptoRec.unitScale
|
||||
|
|
@ -107,7 +112,15 @@ function getStatus (account, tx, requested, settings, operatorId) {
|
|||
|
||||
console.log('[%s] DEBUG: Mock wallet has confirmed transaction [%s]', cryptoCode, toAddress.slice(0, 5))
|
||||
|
||||
return Promise.resolve({status: 'confirmed'})
|
||||
return Promise.resolve({ status: 'confirmed' })
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
return resolve([]) // TODO: should return something other than empty list?
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
function checkBlockchainStatus (cryptoCode) {
|
||||
|
|
@ -123,5 +136,6 @@ module.exports = {
|
|||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
checkBlockchainStatus
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ function handleError (error, method) {
|
|||
|
||||
function openWallet () {
|
||||
return fetch('open_wallet', { filename: 'Wallet' })
|
||||
.catch(err => handleError(err, 'openWallet'))
|
||||
.catch(() => openWalletWithPassword())
|
||||
}
|
||||
|
||||
function openWalletWithPassword () {
|
||||
|
|
@ -164,7 +164,7 @@ function getStatus (account, tx, requested, settings, operatorId) {
|
|||
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(addressRes => fetch('get_transfers', { in: true, pool: true, account_index: addressRes.index.major, subaddr_indices: [addressRes.index.minor] }))
|
||||
.then(transferRes => {
|
||||
const confirmedToAddress = _.filter(it => it.address === toAddress, transferRes.in ?? [])
|
||||
const pendingToAddress = _.filter(it => it.address === toAddress, transferRes.pool ?? [])
|
||||
|
|
@ -235,6 +235,14 @@ function checkBlockchainStatus (cryptoCode) {
|
|||
})
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
checkCryptoCode(cryptoCode)
|
||||
.then(() => refreshWallet())
|
||||
.then(() => fetch('get_address_index', { address: address }))
|
||||
.then(addressRes => fetch('get_transfers', { in: true, pool: true, pending: true, account_index: addressRes.index.major, subaddr_indices: [addressRes.index.minor] }))
|
||||
.then(_.map(({ txid }) => txid))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
balance,
|
||||
sendCoins,
|
||||
|
|
@ -242,5 +250,6 @@ module.exports = {
|
|||
getStatus,
|
||||
newFunding,
|
||||
cryptoNetwork,
|
||||
checkBlockchainStatus
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ function newFunding (account, cryptoCode, settings, operatorId) {
|
|||
throw new E.NotImplementedError()
|
||||
}
|
||||
|
||||
function sweep (account, cryptoCode, hdIndex, settings, operatorId) {
|
||||
function sweep (account, txId, cryptoCode, hdIndex, settings, operatorId) {
|
||||
throw new E.NotImplementedError()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ function sendCoins (account, tx, settings, operatorId) {
|
|||
const checker = opid => pRetry(() => checkSendStatus(opid), { retries: 20, minTimeout: 300, factor: 1.05 })
|
||||
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('z_sendmany', ['ANY_TADDR', [{ address: toAddress, amount: coins }]]))
|
||||
.then(() => fetch('z_sendmany', ['ANY_TADDR', [{ address: toAddress, amount: coins }], null, null, 'NoPrivacy']))
|
||||
.then(checker)
|
||||
.then((res) => {
|
||||
return {
|
||||
|
|
@ -151,11 +151,17 @@ function checkBlockchainStatus (cryptoCode) {
|
|||
.then(res => !!res['initial_block_download_complete'] ? 'ready' : 'syncing')
|
||||
}
|
||||
|
||||
function getTxHashesByAddress (cryptoCode, address) {
|
||||
checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('getaddresstxids', [address]))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
balance,
|
||||
sendCoins,
|
||||
newAddress,
|
||||
getStatus,
|
||||
newFunding,
|
||||
checkBlockchainStatus
|
||||
checkBlockchainStatus,
|
||||
getTxHashesByAddress
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue