Skip to content

Commit

Permalink
feat(rpc): expose reserved balance for GetBalance (#1925)
Browse files Browse the repository at this point in the history
This adds a field to the `GetBalance` response to return the balance
that is reserved for open orders.
  • Loading branch information
sangaman authored Oct 13, 2020
1 parent 3a0da01 commit 8b18dd7
Show file tree
Hide file tree
Showing 16 changed files with 585 additions and 274 deletions.
1 change: 1 addition & 0 deletions docs/api.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 22 additions & 16 deletions lib/cli/commands/getbalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,46 @@ import { satsToCoinsStr } from '../utils';
const HEADERS = [
colors.blue('Currency'),
colors.blue('Total Balance'),
colors.blue('Channel Balance (Tradable)'),
colors.blue('Wallet Balance (Not Tradable)'),
colors.blue('Channel Balance (Tradable)'),
colors.blue('In Orders'),
];

const formatBalances = (balances: GetBalanceResponse.AsObject) => {
const formatted: any[] = [];
balances.balancesMap.forEach((balance) => {
balances.balancesMap.forEach((balanceElement) => {
const currency = balanceElement[0];
const balance = balanceElement[1];
const element = [];
element.push(
balance[0],
`${satsToCoinsStr(balance[1].totalBalance)}`,
formatBalance(balance[1].channelBalance, balance[1].pendingChannelBalance, balance[1].inactiveChannelBalance),
formatBalance(balance[1].walletBalance, balance[1].unconfirmedWalletBalance),
currency,
satsToCoinsStr(balance.totalBalance),
formatBalance(balance.channelBalance, balance.pendingChannelBalance, balance.inactiveChannelBalance),
formatBalance(balance.walletBalance, balance.unconfirmedWalletBalance),
satsToCoinsStr(balance.reservedBalance),
);
formatted.push(element);
});
return formatted;
};

const formatBalance = (confirmedBalance: number, unconfirmedBalance: number, inactiveBalance = 0) => {
const confirmedBalanceStr = satsToCoinsStr(confirmedBalance);
const unconfirmedBalanceStr = unconfirmedBalance > 0 ? `${satsToCoinsStr(unconfirmedBalance)} pending` : undefined;
const formatBalance = (availableBalance: number, pendingBalance: number, inactiveBalance = 0) => {
const availableBalanceStr = satsToCoinsStr(availableBalance);
const unconfirmedBalanceStr = pendingBalance > 0 ? `${satsToCoinsStr(pendingBalance)} pending` : undefined;
const inactiveBalanceStr = inactiveBalance > 0 ? `${satsToCoinsStr(inactiveBalance)} inactive` : undefined;
if (unconfirmedBalanceStr || inactiveBalanceStr) {
let str = `${confirmedBalanceStr} (`;
let str = availableBalanceStr;
let paranthetical = '';
if (unconfirmedBalanceStr) {
str += inactiveBalanceStr ? `${inactiveBalanceStr} | ${unconfirmedBalanceStr}` : unconfirmedBalanceStr;
} else {
str += inactiveBalanceStr;
paranthetical += paranthetical ? ` | ${unconfirmedBalanceStr}` : unconfirmedBalanceStr;
}
if (inactiveBalanceStr) {
paranthetical += paranthetical ? ` | ${inactiveBalanceStr}` : inactiveBalanceStr;
}
str += ')';
str += ` (${paranthetical})`;
return str;
}
return confirmedBalanceStr;
return availableBalanceStr;
};

const createTable = () => {
Expand All @@ -51,7 +57,7 @@ const createTable = () => {
return table;
};

const displayBalances = (balances: GetBalanceResponse.AsObject) => {
export const displayBalances = (balances: GetBalanceResponse.AsObject) => {
const table = createTable();
const formatted = formatBalances(balances);
formatted.forEach(balance => table.push(balance));
Expand Down
3 changes: 3 additions & 0 deletions lib/grpc/GrpcService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ class GrpcService {
balance.setInactiveChannelBalance(balanceObj.inactiveChannelBalance);
balance.setWalletBalance(balanceObj.walletBalance);
balance.setUnconfirmedWalletBalance(balanceObj.unconfirmedWalletBalance);
if (balanceObj.reservedBalance) {
balance.setReservedBalance(balanceObj.reservedBalance);
}
balancesMap.set(currency, balance);
});
callback(null, response);
Expand Down
5 changes: 5 additions & 0 deletions lib/proto/xudrpc.swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lib/proto/xudrpc_pb.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 28 additions & 1 deletion lib/proto/xudrpc_pb.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions lib/service/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import OrderBook from '../orderbook/OrderBook';
import { Currency, isOwnOrder, Order, OrderPortion, OwnLimitOrder, OwnMarketOrder, OwnOrder, PeerOrder, PlaceOrderEvent } from '../orderbook/types';
import Pool from '../p2p/Pool';
import swapsErrors from '../swaps/errors';
import { TradingLimits } from '../swaps/SwapClient';
import { ChannelBalance, TradingLimits } from '../swaps/SwapClient';
import SwapClientManager from '../swaps/SwapClientManager';
import Swaps from '../swaps/Swaps';
import { ResolveRequest, SwapDeal, SwapFailure, SwapSuccess, SwapAccepted } from '../swaps/types';
import { ResolveRequest, SwapAccepted, SwapDeal, SwapFailure, SwapSuccess } from '../swaps/types';
import { isNodePubKey } from '../utils/aliasUtils';
import { parseUri, toUri, UriParts } from '../utils/uriUtils';
import { checkDecimalPlaces, sortOrders, toEip55Address } from '../utils/utils';
Expand Down Expand Up @@ -113,7 +113,7 @@ class Service {
/** Gets the total balance for one or all currencies. */
public getBalance = async (args: { currency: string }) => {
const { currency } = args;
const channelBalances = new Map<string, { balance: number, pendingOpenBalance: number, inactiveBalance: number }>();
const channelBalances = new Map<string, ChannelBalance>();
const walletBalances = new Map<string, { confirmedBalance: number, unconfirmedBalance: number }>();

if (currency) {
Expand All @@ -125,6 +125,7 @@ class Service {
await swapClient.channelBalance(currency),
await swapClient.walletBalance(currency),
]);
channelBalance.reservedBalance = this.swapClientManager.getOutboundReservedAmount(currency);
channelBalances.set(currency, channelBalance);
walletBalances.set(currency, walletBalance);
} else {
Expand All @@ -135,6 +136,7 @@ class Service {
this.swapClientManager.swapClients.forEach((swapClient, currency) => {
if (swapClient.isConnected()) {
balancePromises.push(swapClient.channelBalance(currency).then((channelBalance) => {
channelBalance.reservedBalance = this.swapClientManager.getOutboundReservedAmount(currency);
channelBalances.set(currency, channelBalance);
}).catch(this.logger.error));
balancePromises.push(swapClient.walletBalance(currency).then((walletBalance) => {
Expand All @@ -147,7 +149,7 @@ class Service {
const balances = new Map<string, {
channelBalance: number, pendingChannelBalance: number, inactiveChannelBalance: number,
walletBalance: number, unconfirmedWalletBalance: number,
totalBalance: number,
totalBalance: number, reservedBalance?: number,
}>();
channelBalances.forEach((channelBalance, currency) => {
const walletBalance = walletBalances.get(currency);
Expand All @@ -162,6 +164,7 @@ class Service {
channelBalance: channelBalance.balance,
pendingChannelBalance: channelBalance.pendingOpenBalance,
inactiveChannelBalance: channelBalance.inactiveBalance,
reservedBalance: channelBalance.reservedBalance,
walletBalance: walletBalance.confirmedBalance,
unconfirmedWalletBalance: walletBalance.unconfirmedBalance,
});
Expand Down
2 changes: 2 additions & 0 deletions lib/swaps/SwapClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type ChannelBalance = {
pendingOpenBalance: number,
/** The cumulative balance of inactive channels denominated in satoshis. */
inactiveBalance: number,
/** The balance that is reserved for open orders denominated in satoshis. */
reservedBalance?: number,
};

type WalletBalance = {
Expand Down
12 changes: 10 additions & 2 deletions lib/swaps/SwapClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ class SwapClientManager extends EventEmitter {
}
}

public getOutboundReservedAmount = (currency: string) => {
return this.outboundReservedAmounts.get(currency);
}

public getInboundReservedAmount = (currency: string) => {
return this.inboundReservedAmounts.get(currency);
}

public addOutboundReservedAmount = (currency: string, amount: number) => {
const outboundReservedAmount = this.outboundReservedAmounts.get(currency);
const newOutboundReservedAmount = (outboundReservedAmount ?? 0) + amount;
Expand All @@ -180,13 +188,13 @@ class SwapClientManager extends EventEmitter {
public subtractOutboundReservedAmount = (currency: string, amount: number) => {
const outboundReservedAmount = this.outboundReservedAmounts.get(currency);
assert(outboundReservedAmount && outboundReservedAmount >= amount);
this.outboundReservedAmounts.set(currency, (outboundReservedAmount ?? 0) - amount);
this.outboundReservedAmounts.set(currency, outboundReservedAmount - amount);
}

public subtractInboundReservedAmount = (currency: string, amount: number) => {
const inboundReservedAmount = this.inboundReservedAmounts.get(currency);
assert(inboundReservedAmount && inboundReservedAmount >= amount);
this.inboundReservedAmounts.set(currency, (inboundReservedAmount ?? 0) - amount);
this.inboundReservedAmounts.set(currency, inboundReservedAmount - amount);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions proto/xudrpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ message Balance {
uint64 wallet_balance = 5 [json_name = "wallet_balance"];
// Unconfirmed wallet balance in satoshis.
uint64 unconfirmed_wallet_balance = 6 [json_name = "unconfirmed_wallet_balance"];
// The balance that's reserved for open orders.
uint64 reserved_balance = 7 [json_name = "reserved_balance"];
}

message BanRequest {
Expand Down
56 changes: 56 additions & 0 deletions test/jest/Service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jest.mock('../../lib/swaps/SwapClientManager', () => {
return jest.fn().mockImplementation(() => {
return {
getType: () => SwapClientType.Lnd,
getOutboundReservedAmount: () => 0,
};
});
});
Expand Down Expand Up @@ -189,6 +190,61 @@ describe('Service', () => {
expect(btcBalance.totalBalance).toEqual(289008);
});

test('returns balance with reserved amounts', async () => {
setup();
const reservedBalance = 10000;
service['swapClientManager'].getOutboundReservedAmount = jest.fn().mockReturnValue(reservedBalance);
const result = await service.getBalance({ currency: 'BTC' });
expect(result.size).toEqual(1);

const btcBalance = result.get('BTC')!;
expect(btcBalance).toBeTruthy();
expect(btcBalance.channelBalance).toEqual(70000);
expect(btcBalance.pendingChannelBalance).toEqual(190191);
expect(btcBalance.inactiveChannelBalance).toEqual(18817);
expect(btcBalance.walletBalance).toEqual(10000);
expect(btcBalance.unconfirmedWalletBalance).toEqual(0);
expect(btcBalance.totalBalance).toEqual(289008);
expect(btcBalance.reservedBalance).toEqual(reservedBalance);
});

test('returns balance with reserved amounts for multiple currencies', async () => {
setup();
const btcReservedBalance = 10000;
const ltcReservedBalance = 2345;
service['swapClientManager'].getOutboundReservedAmount = jest.fn().mockImplementation((currency) => {
if (currency === 'BTC') {
return btcReservedBalance;
}
if (currency === 'LTC') {
return ltcReservedBalance;
}
return undefined;
});
const result = await service.getBalance({ currency: '' });
expect(result.size).toEqual(2);

const btcBalance = result.get('BTC')!;
expect(btcBalance).toBeTruthy();
expect(btcBalance.channelBalance).toEqual(70000);
expect(btcBalance.pendingChannelBalance).toEqual(190191);
expect(btcBalance.inactiveChannelBalance).toEqual(18817);
expect(btcBalance.walletBalance).toEqual(10000);
expect(btcBalance.unconfirmedWalletBalance).toEqual(0);
expect(btcBalance.totalBalance).toEqual(289008);
expect(btcBalance.reservedBalance).toEqual(btcReservedBalance);

const ltcBalance = result.get('LTC')!;
expect(ltcBalance).toBeTruthy();
expect(ltcBalance.channelBalance).toEqual(0);
expect(ltcBalance.pendingChannelBalance).toEqual(0);
expect(ltcBalance.inactiveChannelBalance).toEqual(12345);
expect(ltcBalance.walletBalance).toEqual(1500);
expect(ltcBalance.unconfirmedWalletBalance).toEqual(500);
expect(ltcBalance.totalBalance).toEqual(14345);
expect(ltcBalance.reservedBalance).toEqual(ltcReservedBalance);
});

test('throws in case of invalid currency', async () => {
setup();
await expect(service.getBalance({ currency: 'A' })).rejects.toMatchSnapshot();
Expand Down
12 changes: 6 additions & 6 deletions test/jest/SwapClientManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,19 +185,19 @@ describe('Swaps.SwapClientManager', () => {
});

test('it adds outbound reserved amounts', () => {
expect(swapClientManager['outboundReservedAmounts'].get(currency)).toBeUndefined();
expect(swapClientManager.getOutboundReservedAmount(currency)).toBeUndefined();
swapClientManager.addOutboundReservedAmount(currency, amount);
expect(swapClientManager['outboundReservedAmounts'].get(currency)).toEqual(amount);
expect(swapClientManager.getOutboundReservedAmount(currency)).toEqual(amount);
swapClientManager.addOutboundReservedAmount(currency, amount);
expect(swapClientManager['outboundReservedAmounts'].get(currency)).toEqual(amount * 2);
expect(swapClientManager.getOutboundReservedAmount(currency)).toEqual(amount * 2);
});

test('it subtracts outbound reserved amounts', () => {
expect(swapClientManager['outboundReservedAmounts'].get(currency)).toBeUndefined();
expect(swapClientManager.getOutboundReservedAmount(currency)).toBeUndefined();
swapClientManager.addOutboundReservedAmount(currency, amount);
expect(swapClientManager['outboundReservedAmounts'].get(currency)).toEqual(amount);
expect(swapClientManager.getOutboundReservedAmount(currency)).toEqual(amount);
swapClientManager.subtractOutboundReservedAmount(currency, amount);
expect(swapClientManager['outboundReservedAmounts'].get(currency)).toEqual(0);
expect(swapClientManager.getOutboundReservedAmount(currency)).toEqual(0);
});

test('it adds inbound reserved amounts and sets amount on swap client', () => {
Expand Down
Loading

0 comments on commit 8b18dd7

Please sign in to comment.