Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/ Fix account balance not reflect correctly + Improve websocket connection #249

Merged
merged 4 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading