From 198dbce4225204868da196ae018841de52c38f79 Mon Sep 17 00:00:00 2001 From: Jack Bonaguro Date: Sun, 3 Jan 2021 23:44:36 -0500 Subject: [PATCH] Added balance at block xrp method --- packages/bitcore-client/bin/wallet-balance | 22 +++-- packages/bitcore-client/src/client.ts | 20 ++++ packages/bitcore-client/src/wallet.ts | 14 +++ .../src/modules/ripple/api/csp.ts | 94 ++++++++++++++----- .../src/modules/ripple/api/xrp-routes.ts | 17 ++++ .../types/namespaces/ChainStateProvider.ts | 6 ++ 6 files changed, 141 insertions(+), 32 deletions(-) diff --git a/packages/bitcore-client/bin/wallet-balance b/packages/bitcore-client/bin/wallet-balance index 59a14260225..e5a1fda0a48 100755 --- a/packages/bitcore-client/bin/wallet-balance +++ b/packages/bitcore-client/bin/wallet-balance @@ -8,6 +8,7 @@ try { .version(require('../package.json').version) .option('--name ', 'REQUIRED - Wallet name') .option('--time [time]', 'optional - Get balance at specific time') + .option('--block [block]', 'optional - Get balance at specific block height') .option('--path [path]', 'optional - Custom wallet storage path') .option('--storageType [storageType]', 'optional - name of the database to use (default level)') .option('--token [token]', 'optional - Get balance of an ERC20 token') @@ -19,12 +20,21 @@ try { async function main() { const { name, path, time, storageType, token } = program; - const wallet = await Wallet.loadWallet({ name, path, storageType }); - const balance = await wallet.getBalance(time, token); - return Object.assign(balance, { - currency: token || wallet.chain, - network: wallet.network - }); + if (program.block) { + const wallet = await Wallet.loadWallet({ name, path, storageType }); + const balance = await wallet.getBalanceAtBlock(program.block, token); + return Object.assign(balance, { + currency: token || wallet.chain, + network: wallet.network + }); + } else { + const wallet = await Wallet.loadWallet({ name, path, storageType }); + const balance = await wallet.getBalance(time, token); + return Object.assign(balance, { + currency: token || wallet.chain, + network: wallet.network + }); + } } main() diff --git a/packages/bitcore-client/src/client.ts b/packages/bitcore-client/src/client.ts index d0f45a2cc2b..0845385e7c6 100644 --- a/packages/bitcore-client/src/client.ts +++ b/packages/bitcore-client/src/client.ts @@ -55,6 +55,26 @@ export class Client { url += `?tokenAddress=${payload.tokenContractAddress}`; } const signature = this.sign({ method: 'GET', url }); + console.log(url); + console.log(signature); + return request.get(url, { + headers: { 'x-signature': signature }, + json: true + }); + } + + async getBalanceAtBlock(params: { payload?: any; pubKey: string; block?: string }) { + const { payload, pubKey, block } = params; + let url = `${this.apiUrl}/wallet/${pubKey}/balanceAtBlock`; + if (block) { + url += `/${block}`; + } + if (payload && payload.tokenContractAddress) { + url += `?tokenAddress=${payload.tokenContractAddress}`; + } + const signature = this.sign({ method: 'GET', url }); + console.log(url); + console.log(signature); return request.get(url, { headers: { 'x-signature': signature }, json: true diff --git a/packages/bitcore-client/src/wallet.ts b/packages/bitcore-client/src/wallet.ts index 9549b4139d7..e68b6d285be 100644 --- a/packages/bitcore-client/src/wallet.ts +++ b/packages/bitcore-client/src/wallet.ts @@ -251,6 +251,20 @@ export class Wallet { return this.client.getBalance({ payload, pubKey: this.authPubKey, time }); } + getBalanceAtBlock(block?: string, token?: string) { + let payload; + if (token) { + let tokenContractAddress; + const tokenObj = this.tokens.find(tok => tok.symbol === token); + if (!tokenObj) { + throw new Error(`${token} not found on wallet ${this.name}`); + } + tokenContractAddress = tokenObj.address; + payload = { tokenContractAddress }; + } + return this.client.getBalanceAtBlock({ payload, pubKey: this.authPubKey, block }); + } + getNetworkFee(params: { target?: number } = {}) { const target = params.target || 2; return this.client.getFee({ target }); diff --git a/packages/bitcore-node/src/modules/ripple/api/csp.ts b/packages/bitcore-node/src/modules/ripple/api/csp.ts index 0457548d70f..04171803a36 100644 --- a/packages/bitcore-node/src/modules/ripple/api/csp.ts +++ b/packages/bitcore-node/src/modules/ripple/api/csp.ts @@ -17,6 +17,7 @@ import { GetBalanceForAddressParams, GetBlockBeforeTimeParams, GetEstimateSmartFeeParams, + GetWalletBalanceAtBlockParams, GetWalletBalanceParams, IChainStateService, StreamAddressUtxosParams, @@ -71,33 +72,57 @@ export class RippleStateProvider extends InternalStateProvider implements IChain async getBalanceForAddress(params: GetBalanceForAddressParams) { const { chain, network, address } = params; - const lowerAddress = address.toLowerCase(); - const cacheKey = `getBalanceForAddress-${chain}-${network}-${lowerAddress}`; - return CacheStorage.getGlobalOrRefresh( - cacheKey, - async () => { - const client = await this.getClient(network); - try { - const info = await client.getAccountInfo(address); - const confirmed = Math.round(Number(info.xrpBalance) * 1e6); - const balance = confirmed; - const unconfirmed = 0; - return { confirmed, unconfirmed, balance }; - } catch (e) { - if (e && e.data && e.data.error_code === 19) { - // Error code for when we have derived an address, - // but the account has not yet been funded - return { - confirmed: 0, - unconfirmed: 0, - balance: 0 - }; - } - throw e; + if (params.args && params.args.block) { + const block = params.args.block; + const client = await this.getClient(network); + try { + const blockNum = parseInt(block); + const info = await client.getAccountInfo(address, { ledgerVersion: blockNum }); + const confirmed = Math.round(Number(info.xrpBalance) * 1e6); + const balance = confirmed; + const unconfirmed = 0; + return { confirmed, unconfirmed, balance }; + } catch (e) { + if (e && e.data && e.data.error_code === 19) { + // Error code for when we have derived an address, + // but the account has not yet been funded + return { + confirmed: 0, + unconfirmed: 0, + balance: 0 + }; } - }, - CacheStorage.Times.Hour / 2 - ); + throw e; + } + } else { + const lowerAddress = address.toLowerCase(); + const cacheKey = `getBalanceForAddress-${chain}-${network}-${lowerAddress}`; + return CacheStorage.getGlobalOrRefresh( + cacheKey, + async () => { + const client = await this.getClient(network); + try { + const info = await client.getAccountInfo(address); + const confirmed = Math.round(Number(info.xrpBalance) * 1e6); + const balance = confirmed; + const unconfirmed = 0; + return { confirmed, unconfirmed, balance }; + } catch (e) { + if (e && e.data && e.data.error_code === 19) { + // Error code for when we have derived an address, + // but the account has not yet been funded + return { + confirmed: 0, + unconfirmed: 0, + balance: 0 + }; + } + throw e; + } + }, + CacheStorage.Times.Hour / 2 + ); + } } async getBlock(params: GetBlockParams) { @@ -175,6 +200,23 @@ export class RippleStateProvider extends InternalStateProvider implements IChain ); } + async getWalletBalanceAtBlock(params: GetWalletBalanceAtBlockParams) { + const { chain, block, network } = params; + const addresses = await this.getWalletAddresses(params.wallet._id!); + const balances = await Promise.all( + addresses.map(a => this.getBalanceForAddress({ address: a.address, chain, network, args: { block } })) + ); + return balances.reduce( + (total, current) => { + total.balance += current.balance; + total.confirmed += current.confirmed; + total.unconfirmed += current.unconfirmed; + return total; + }, + { confirmed: 0, unconfirmed: 0, balance: 0 } + ); + } + streamTxs(txs: Array, stream: Readable) { for (let tx of txs) { stream.push(tx); diff --git a/packages/bitcore-node/src/modules/ripple/api/xrp-routes.ts b/packages/bitcore-node/src/modules/ripple/api/xrp-routes.ts index e0aa7a2dcd8..d6c92c30deb 100644 --- a/packages/bitcore-node/src/modules/ripple/api/xrp-routes.ts +++ b/packages/bitcore-node/src/modules/ripple/api/xrp-routes.ts @@ -1,5 +1,6 @@ import { Router } from 'express'; import { XRP } from './csp'; +import { Auth, AuthenticatedRequest } from '../../../utils/auth'; export const XrpRoutes = Router(); XrpRoutes.get('/api/XRP/:network/address/:address/txs/count', async (req, res) => { @@ -11,3 +12,19 @@ XrpRoutes.get('/api/XRP/:network/address/:address/txs/count', async (req, res) = res.status(500).send(err); } }); + +XrpRoutes.get('/api/:chain/:network/wallet/:pubKey/balanceAtBlock/:block', Auth.authenticateMiddleware, async (req: AuthenticatedRequest, res) => { + let { network, block } = req.params; + try { + const result = await XRP.getWalletBalanceAtBlock({ + chain: 'XRP', + network, + wallet: req.wallet!, + block, + args: req.query + }); + return res.send(result || { confirmed: 0, unconfirmed: 0, balance: 0 }); + } catch (err) { + return res.status(500).json(err); + } +}); diff --git a/packages/bitcore-node/src/types/namespaces/ChainStateProvider.ts b/packages/bitcore-node/src/types/namespaces/ChainStateProvider.ts index 47f609a0391..77ceec1b03b 100644 --- a/packages/bitcore-node/src/types/namespaces/ChainStateProvider.ts +++ b/packages/bitcore-node/src/types/namespaces/ChainStateProvider.ts @@ -78,6 +78,12 @@ export type GetWalletBalanceAtTimeParams = ChainNetwork & { args: any; }; +export type GetWalletBalanceAtBlockParams = ChainNetwork & { + wallet: MongoBound; + block: string + args: any; +}; + export type StreamAddressUtxosParams = ChainNetwork & { address: string; req?: Request;