Skip to content

Commit

Permalink
Merge pull request hummingbot#249 from hummingbot/feat/xrpl-fix-dec-11
Browse files Browse the repository at this point in the history
fix/ Fix account balance not reflect correctly + Improve websocket connection
  • Loading branch information
rapcmia authored Dec 14, 2023
2 parents 6bbec30 + 9aea964 commit 343eead
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 23 deletions.
20 changes: 18 additions & 2 deletions src/chains/xrpl/xrpl.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
XRPLBalanceResponse,
XRPLPollRequest,
XRPLPollResponse,
BalanceRecord,
} from './xrpl.requests';

import {
Expand Down Expand Up @@ -48,9 +49,24 @@ export class XRPLController {

const xrplBalances = await xrplish.getAllBalance(wallet);

const balances: Record<string, string> = {};
const xrplSubtractedBalances = await xrplish.subtractBalancesWithOpenOffers(
xrplBalances,
wallet
);

const balances: Record<string, BalanceRecord> = {};
xrplBalances.forEach((balance) => {
balances[balance.currency] = balance.value;
balances[balance.currency] = {
total_balance: balance.value,
available_balance: balance.value,
};
});

xrplSubtractedBalances.forEach((balance) => {
balances[balance.currency] = {
...balances[balance.currency],
available_balance: balance.value,
};
});

return {
Expand Down
7 changes: 6 additions & 1 deletion src/chains/xrpl/xrpl.requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ export interface XRPLBalanceResponse {
timestamp: number;
latency: number;
address: string;
balances: Record<string, string>;
balances: Record<string, BalanceRecord>;
}

export interface BalanceRecord {
total_balance: string;
available_balance: string;
}

export type TokenBalance = {
Expand Down
117 changes: 114 additions & 3 deletions src/chains/xrpl/xrpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
PathFindStream,
TxResponse,
TransactionMetadata,
AccountOffersResponse,
dropsToXrp,
} from 'xrpl';
import axios from 'axios';
import { promises as fs } from 'fs';
Expand All @@ -25,6 +27,7 @@ import { XRPLOrderStorage } from './xrpl.order-storage';
import { OrderTracker } from './xrpl.order-tracker';
import { ReferenceCountingCloseable } from '../../services/refcounting-closeable';
import { XRPLController } from './xrpl.controllers';
import { convertHexToString } from '../../connectors/xrpl/xrpl.utils';

export type XRPTokenInfo = {
id: number;
Expand All @@ -39,7 +42,9 @@ export type MarketInfo = {
id: number;
marketId: string;
baseIssuer: string;
baseCode: string;
quoteIssuer: string;
quoteCode: string;
baseTokenID: number;
quoteTokenID: number;
};
Expand All @@ -58,7 +63,7 @@ export class XRPL implements XRPLish {

protected tokenList: XRPTokenInfo[] = [];
protected marketList: MarketInfo[] = [];
private _tokenMap: Record<string, XRPTokenInfo[]> = {};
private _tokenMap: Record<string, XRPTokenInfo[]> = {}; // TODO: tokenMap should be identified by code and issuer to prevent duplicate codes
private _marketMap: Record<string, MarketInfo[]> = {};

private _client: Client;
Expand All @@ -71,6 +76,8 @@ export class XRPL implements XRPLish {
private _marketListSource: string;
private _tokenListType: TokenListType;
private _marketListType: MarketListType;
private _reserveBaseXrp: number;
private _reserveIncrementXrp: number;

private _ready: boolean = false;
private initializing: boolean = false;
Expand All @@ -91,6 +98,8 @@ export class XRPL implements XRPLish {
this._tokenListType = <TokenListType>config.network.tokenListType;
this._marketListSource = config.network.marketListSource;
this._marketListType = <MarketListType>config.network.marketListType;
this._reserveBaseXrp = 0;
this._reserveIncrementXrp = 0;

this._client = new Client(this.rpcUrl, {
timeout: config.requestTimeout,
Expand Down Expand Up @@ -119,6 +128,10 @@ export class XRPL implements XRPLish {
);
this._orderStorage.declareOwnership(this._refCountingHandle);
this.controller = XRPLController;

this.onDisconnected(async (_code: number) => {
this.ensureConnection();
});
}

public static getInstance(network: string): XRPL {
Expand Down Expand Up @@ -189,6 +202,7 @@ export class XRPL implements XRPLish {
if (!this.ready() && !this.initializing) {
this.initializing = true;
await this.ensureConnection();
await this.getReserveInfo();
await this.loadTokens(this._tokenListSource, this._tokenListType);
await this.loadMarkets(this._marketListSource, this._marketListType);
await this.getFee();
Expand Down Expand Up @@ -270,7 +284,14 @@ export class XRPL implements XRPLish {
}

public getTokenForSymbol(code: string): XRPTokenInfo[] | undefined {
return this._tokenMap[code] ? this._tokenMap[code] : undefined;
let query = code;

// Special case for SOLO on mainnet
if (code === 'SOLO') {
query = '534F4C4F00000000000000000000000000000000';
}

return this._tokenMap[query] ? this._tokenMap[query] : undefined;
}

public getWalletFromSeed(seed: string): Wallet {
Expand Down Expand Up @@ -339,6 +360,24 @@ export class XRPL implements XRPLish {
return balance;
}

async getNativeAvailableBalance(wallet: Wallet): Promise<string> {
await this.ensureConnection();

const AccountInfoResponse = await this._client.request({
command: 'account_info',
account: wallet.address,
});

const ownerItem = AccountInfoResponse.result.account_data.OwnerCount;
const totalReserve =
this._reserveBaseXrp + ownerItem * this._reserveIncrementXrp;

const balance =
parseFloat(await this._client.getXrpBalance(wallet.address)) -
totalReserve;
return balance.toString();
}

async getAllBalance(wallet: Wallet): Promise<Array<TokenBalance>> {
await this.ensureConnection();
const balances: Array<TokenBalance> = [];
Expand All @@ -359,7 +398,7 @@ export class XRPL implements XRPLish {
}

balances.push({
currency: token.currency,
currency: convertHexToString(token.currency),
issuer: token.issuer,
value: token.value,
});
Expand All @@ -383,6 +422,17 @@ export class XRPL implements XRPLish {
}
}

async getReserveInfo() {
await this.ensureConnection();
const reserveInfoResp = await this._client.request({
command: 'server_info',
});
this._reserveBaseXrp =
reserveInfoResp.result.info.validated_ledger?.reserve_base_xrp ?? 0;
this._reserveIncrementXrp =
reserveInfoResp.result.info.validated_ledger?.reserve_inc_xrp ?? 0;
}

public get chain(): string {
return this._chain;
}
Expand Down Expand Up @@ -495,6 +545,67 @@ export class XRPL implements XRPLish {
public get orderStorage(): XRPLOrderStorage {
return this._orderStorage;
}

async subtractBalancesWithOpenOffers(
balances: TokenBalance[],
wallet: Wallet
) {
await this.ensureConnection();
const accountOffcersResp: AccountOffersResponse =
await this._client.request({
command: 'account_offers',
account: wallet.address,
});

const offers = accountOffcersResp.result.offers;

// create new balances array with deepcopy
const subtractedBalances: TokenBalance[] = JSON.parse(
JSON.stringify(balances)
);

// Subtract XRP balance with reverses
const xrpBalance = subtractedBalances.find(
(balance) => balance.currency === 'XRP'
);

const currentNativeBalance = await this.getNativeAvailableBalance(wallet);
if (xrpBalance) xrpBalance.value = currentNativeBalance;

// Subtract balances with open offers
if (offers !== undefined) {
offers.forEach((offer) => {
const takerGetsBalance = offer.taker_gets as TokenBalance | string;

if (typeof takerGetsBalance === 'string') {
// XRP open offer, find XRP in balances and subtract it
const xrpBalance = subtractedBalances.find(
(balance) => balance.currency === 'XRP'
);
if (xrpBalance) {
xrpBalance.value = (
parseFloat(xrpBalance.value) -
parseFloat(dropsToXrp(takerGetsBalance))
).toString();
}
} else {
// Token open offer, find token in balances and subtract it
const tokenBalance = subtractedBalances.find(
(balance) =>
balance.currency === convertHexToString(takerGetsBalance.currency)
);
if (tokenBalance) {
tokenBalance.value = (
parseFloat(tokenBalance.value) -
parseFloat(takerGetsBalance.value)
).toString();
}
}
});
}

return subtractedBalances;
}
}

export type XRPLish = XRPL;
Expand Down
45 changes: 29 additions & 16 deletions src/connectors/xrpl/xrpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getTakerPaysAmount,
getTakerGetsFundedAmount,
getTakerPaysFundedAmount,
convertHexToString,
} from './xrpl.utils';
import {
ClobMarketsRequest,
Expand All @@ -44,7 +45,7 @@ import { getXRPLConfig } from '../../chains/xrpl/xrpl.config';
import { isUndefined } from 'mathjs';

// const XRP_FACTOR = 1000000;
const ORDERBOOK_LIMIT = 100;
const ORDERBOOK_LIMIT = 50;
const TXN_SUBMIT_DELAY = 100;
export class XRPLCLOB implements CLOBish {
private static _instances: LRUCache<string, XRPLCLOB>;
Expand Down Expand Up @@ -153,11 +154,13 @@ export class XRPLCLOB implements CLOBish {
quoteTransferRate: number;
const zeroTransferRate = 1000000000;

const [baseCurrency, quoteCurrency] = market.marketId.split('-');
const baseCurrency = market.baseCode;
const quoteCurrency = market.quoteCode;
const baseIssuer = market.baseIssuer;
const quoteIssuer = market.quoteIssuer;

if (baseCurrency != 'XRP') {
await this._xrpl.ensureConnection();
const baseMarketResp: AccountInfoResponse = await this._client.request({
command: 'account_info',
ledger_index: 'validated',
Expand All @@ -179,6 +182,7 @@ export class XRPLCLOB implements CLOBish {
}

if (quoteCurrency != 'XRP') {
await this._xrpl.ensureConnection();
const quoteMarketResp: AccountInfoResponse = await this._client.request({
command: 'account_info',
ledger_index: 'validated',
Expand Down Expand Up @@ -220,26 +224,27 @@ export class XRPLCLOB implements CLOBish {
}

async getOrderBook(
market: MarketInfo,
market: Market,
limit: number = ORDERBOOK_LIMIT
): Promise<Orderbook> {
const [baseCurrency, quoteCurrency] = market.marketId.split('-');
const baseIssuer = market.baseIssuer;
const quoteIssuer = market.quoteIssuer;

const baseRequest: any = {
currency: baseCurrency,
currency: market.baseCurrency,
issuer: market.baseIssuer,
};

const quoteRequest: any = {
currency: quoteCurrency,
currency: market.quoteCurrency,
issuer: market.quoteIssuer,
};

if (baseIssuer) {
baseRequest['issuer'] = baseIssuer;
if (market.baseCurrency == 'XRP') {
// remove issuer
delete baseRequest['issuer'];
}
if (quoteIssuer) {
quoteRequest['issuer'] = quoteIssuer;

if (market.quoteCurrency == 'XRP') {
// remove issuer
delete quoteRequest['issuer'];
}

const { bids, asks } = await this.getOrderBookFromXRPL(
Expand Down Expand Up @@ -380,7 +385,8 @@ export class XRPLCLOB implements CLOBish {
req: ClobPostOrderRequest
): Promise<{ txHash: string }> {
const market = this.parsedMarkets[req.market] as Market;
const [baseCurrency, quoteCurrency] = market.marketId.split('-');
const baseCurrency = market.baseCurrency;
const quoteCurrency = market.quoteCurrency;
const baseIssuer = market.baseIssuer;
const quoteIssuer = market.quoteIssuer;

Expand Down Expand Up @@ -444,7 +450,10 @@ export class XRPLCLOB implements CLOBish {

const order: Order = {
hash: prepared.Sequence ? prepared.Sequence : 0,
marketId: baseCurrency + '-' + quoteCurrency,
marketId:
convertHexToString(baseCurrency) +
'-' +
convertHexToString(quoteCurrency),
price: req.price,
amount: req.amount,
filledAmount: '0',
Expand Down Expand Up @@ -524,6 +533,7 @@ export class XRPLCLOB implements CLOBish {
this._isSubmittingTxn = true;
const prepared = await this._client.autofill(offer);
const signed = wallet.sign(prepared);
await this._xrpl.ensureConnection();
await this._client.submit(signed.tx_blob);
this._isSubmittingTxn = false;
return { prepared, signed };
Expand All @@ -539,6 +549,7 @@ export class XRPLCLOB implements CLOBish {
quoteRequest: any,
limit: number
) {
await this._xrpl.ensureConnection();
const orderbook_resp_ask: BookOffersResponse = await this._client.request({
command: 'book_offers',
ledger_index: 'validated',
Expand All @@ -547,6 +558,7 @@ export class XRPLCLOB implements CLOBish {
limit,
});

await this._xrpl.ensureConnection();
const orderbook_resp_bid: BookOffersResponse = await this._client.request({
command: 'book_offers',
ledger_index: 'validated',
Expand All @@ -561,6 +573,7 @@ export class XRPLCLOB implements CLOBish {
}

private async getCurrentBlockNumber() {
await this._xrpl.ensureConnection();
return await this._client.getLedgerIndex();
}

Expand Down Expand Up @@ -595,7 +608,7 @@ export class XRPLCLOB implements CLOBish {
await orderTracker.addOrder(order);
}

private async getMidPriceForMarket(market: MarketInfo) {
private async getMidPriceForMarket(market: Market) {
const orderbook = await this.getOrderBook(market, 1);
try {
const bestAsk = orderbook.sells[0];
Expand Down
Loading

0 comments on commit 343eead

Please sign in to comment.