From bb9e7dfdcf46969ab1a82dbe6f865640c43870ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danilo=20Arau=CC=81jo=20Silva?= Date: Fri, 17 Feb 2023 14:43:40 +0000 Subject: [PATCH 01/39] Updating .gitignore. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8e4034621e..847acba67f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /dist /db /.idea +/*.iml package-lock.json /certs /coverage @@ -13,4 +14,4 @@ package-lock.json # VSCode .vscode/ -.history \ No newline at end of file +.history From df59dad2ef97e441426030329cae8fc7f08c423b Mon Sep 17 00:00:00 2001 From: La Hoang Date: Fri, 17 Feb 2023 18:16:47 +0700 Subject: [PATCH 02/39] Add XRPL & XRPLDEX connector (v1) --- docs/swagger/definitions.yml | 63 +- docs/swagger/wallet-routes.yml | 12 +- docs/swagger/xrpl-routes.yml | 65 + docs/swagger/xrpldex-routes.yml | 148 + package.json | 2 + src/app.ts | 4 +- src/chains/xrpl/xrpl-middlewares.ts | 24 + src/chains/xrpl/xrpl.config.ts | 48 + src/chains/xrpl/xrpl.constants.ts | 26 + src/chains/xrpl/xrpl.controllers.ts | 65 + src/chains/xrpl/xrpl.helpers.ts | 122 + src/chains/xrpl/xrpl.requests.ts | 47 + src/chains/xrpl/xrpl.routes.ts | 77 + src/chains/xrpl/xrpl.ts | 369 + src/chains/xrpl/xrpl.validators.ts | 52 + src/chains/xrpl/xrpl_tokens.json | 12370 ++++++++++++++++ src/chains/xrpl/xrpl_tokens_devnet.json | 1 + src/chains/xrpl/xrpl_tokens_small.json | 162 + src/chains/xrpl/xrpl_tokens_testnet.json | 1 + src/connectors/connectors.routes.ts | 13 +- src/connectors/xrpldex/xrpldex.config.ts | 45 + src/connectors/xrpldex/xrpldex.controllers.ts | 347 + src/connectors/xrpldex/xrpldex.middlewares.ts | 21 + src/connectors/xrpldex/xrpldex.requests.ts | 92 + src/connectors/xrpldex/xrpldex.routes.ts | 193 + src/connectors/xrpldex/xrpldex.ts | 841 ++ src/connectors/xrpldex/xrpldex.types.ts | 192 + src/connectors/xrpldex/xrpldex.validators.ts | 202 + src/network/network.controllers.ts | 6 + src/network/network.routes.ts | 123 + src/services/connection-manager.ts | 14 +- src/services/schema/xrpl-schema.json | 73 + src/services/wallet/wallet.controllers.ts | 21 +- src/services/wallet/wallet.validators.ts | 18 +- src/templates/root.yml | 4 + src/templates/xrpl.yml | 31 + yarn.lock | 171 +- 37 files changed, 15935 insertions(+), 130 deletions(-) create mode 100644 docs/swagger/xrpl-routes.yml create mode 100644 docs/swagger/xrpldex-routes.yml create mode 100644 src/chains/xrpl/xrpl-middlewares.ts create mode 100644 src/chains/xrpl/xrpl.config.ts create mode 100644 src/chains/xrpl/xrpl.constants.ts create mode 100644 src/chains/xrpl/xrpl.controllers.ts create mode 100644 src/chains/xrpl/xrpl.helpers.ts create mode 100644 src/chains/xrpl/xrpl.requests.ts create mode 100644 src/chains/xrpl/xrpl.routes.ts create mode 100644 src/chains/xrpl/xrpl.ts create mode 100644 src/chains/xrpl/xrpl.validators.ts create mode 100644 src/chains/xrpl/xrpl_tokens.json create mode 100644 src/chains/xrpl/xrpl_tokens_devnet.json create mode 100644 src/chains/xrpl/xrpl_tokens_small.json create mode 100644 src/chains/xrpl/xrpl_tokens_testnet.json create mode 100644 src/connectors/xrpldex/xrpldex.config.ts create mode 100644 src/connectors/xrpldex/xrpldex.controllers.ts create mode 100644 src/connectors/xrpldex/xrpldex.middlewares.ts create mode 100644 src/connectors/xrpldex/xrpldex.requests.ts create mode 100644 src/connectors/xrpldex/xrpldex.routes.ts create mode 100644 src/connectors/xrpldex/xrpldex.ts create mode 100644 src/connectors/xrpldex/xrpldex.types.ts create mode 100644 src/connectors/xrpldex/xrpldex.validators.ts create mode 100644 src/network/network.routes.ts create mode 100644 src/services/schema/xrpl-schema.json create mode 100644 src/templates/xrpl.yml diff --git a/docs/swagger/definitions.yml b/docs/swagger/definitions.yml index 7c231e9813..b48628b7e6 100644 --- a/docs/swagger/definitions.yml +++ b/docs/swagger/definitions.yml @@ -35,7 +35,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' NonceResponse: type: 'object' @@ -70,7 +70,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' AllowancesResponse: type: 'object' @@ -189,7 +189,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' ApproveResponse: type: 'object' @@ -243,7 +243,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' PollResponse: type: 'object' @@ -328,7 +328,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' PriceResponse: type: 'object' @@ -418,7 +418,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' connector: type: 'string' example: 'uniswap' @@ -496,7 +496,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' CancelResponse: type: 'object' @@ -518,32 +518,19 @@ definitions: type: 'string' example: '0xa321bbe8888c3bc88ecb1ad4f03f22a71e6f5715dfcb19e0a2dca9036c981b6d' # noqa: documentation - AddWalletRequest: + GetWalletRequest: type: 'object' required: - - 'chainName' - - 'privateKey' - properties: - chainName: - type: 'string' - example: 'ethereum' - privateKey: - type: 'string' - example: '6078d949c953351685fd2026646028f2a862e6148d25d504967ba63898d720c0' # noqa: documentation - - RemoveWalletRequest: - type: 'object' - required: - - 'chainName' - - 'address' + - 'chain' + - 'network' properties: - chainName: + chain: type: 'string' example: 'ethereum' - address: + network: type: 'string' - example: '0xd0A1E359811322d97991E03f863a0C30C2cF029C' - + example: 'testnet' + GetWalletResponse: type: 'object' required: @@ -639,7 +626,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' connector: type: 'string' example: 'uniswap' @@ -715,7 +702,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' connector: type: 'string' example: 'uniswapLP' @@ -735,7 +722,7 @@ definitions: properties: network: type: 'string' - example: 'goerli' + example: 'kovan' timestamp: type: 'integer' example: 1636368085740 @@ -781,7 +768,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' connector: type: 'string' example: 'uniswapLP' @@ -800,7 +787,7 @@ definitions: properties: network: type: 'string' - example: 'goerli' + example: 'kovan' timestamp: type: 'integer' example: 1636368085740 @@ -843,7 +830,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' connector: type: 'string' example: 'uniswapLP' @@ -862,7 +849,7 @@ definitions: properties: network: type: 'string' - example: 'goerli' + example: 'kovan' timestamp: type: 'integer' example: 1636368085740 @@ -897,7 +884,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' connector: type: 'string' example: 'uniswapLP' @@ -920,7 +907,7 @@ definitions: properties: network: type: 'string' - example: 'goerli' + example: 'kovan' timestamp: type: 'integer' example: 1636368085740 @@ -984,7 +971,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'goerli' + example: 'kovan' connector: type: 'string' example: 'uniswapLP' @@ -1002,7 +989,7 @@ definitions: properties: network: type: 'string' - example: 'goerli' + example: 'kovan' timestamp: type: 'integer' example: 1636368085740 diff --git a/docs/swagger/wallet-routes.yml b/docs/swagger/wallet-routes.yml index cfde507bca..4653f11c4a 100644 --- a/docs/swagger/wallet-routes.yml +++ b/docs/swagger/wallet-routes.yml @@ -7,6 +7,12 @@ paths: operationId: 'get' produces: - 'application/json' + parameters: + - in: 'body' + name: 'body' + required: true + schema: + $ref: '#/definitions/GetWalletRequest' responses: '200': description: 'Wallet list' @@ -27,7 +33,11 @@ paths: required: true schema: $ref: '#/definitions/AddWalletRequest' - responses: '200' + responses: + '200': + description: 'Added wallet address' + schema: + $ref: '#/definitions/AddWalletResponse' /wallet/remove: delete: diff --git a/docs/swagger/xrpl-routes.yml b/docs/swagger/xrpl-routes.yml new file mode 100644 index 0000000000..09222bb935 --- /dev/null +++ b/docs/swagger/xrpl-routes.yml @@ -0,0 +1,65 @@ +paths: + /xrpl: + get: + tags: + - 'xrpl' + summary: 'View the XRPL network and RPC URL that gateway is configured to use' + description: 'The user can change this by editing src/chains/xrpl/xrpl.config.ts' + operationId: 'root' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + required: true + schema: + $ref: '#/definitions/XRPLConfigRequest' + responses: + '200': + description: 'XRPL config' + schema: + $ref: '#/definitions/XRPLConfigResponse' + + /xrpl/balances: + get: + tags: + - 'xrpl' + summary: 'Get the balances of an XRPL secret key' + operationId: 'balances' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + required: true + schema: + $ref: '#/definitions/XRPLBalancesRequest' + responses: + '200': + description: 'XRPL wallet balances' + schema: + $ref: '#/definitions/XRPLBalancesResponse' + + /xrpl/poll: + get: + tags: + - 'xrpl' + summary: 'Get transaction details from transaction hash' + operationId: 'poll' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + required: true + schema: + $ref: '#/definitions/XRPLPollRequest' + responses: + '200': + description: 'XRPL transaction details' + schema: + $ref: '#/definitions/XRPLPollResponse' diff --git a/docs/swagger/xrpldex-routes.yml b/docs/swagger/xrpldex-routes.yml new file mode 100644 index 0000000000..97a5278a4d --- /dev/null +++ b/docs/swagger/xrpldex-routes.yml @@ -0,0 +1,148 @@ +paths: + /xrpldex/tickers: + get: + tags: + - 'xrpldex' + summary: 'Get middle price ticker for requested market' + operationId: 'tickers' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/XRPLDEXGetTickersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/XRPLDEXGetTickersResponse' + '404': + description: 'Not found response.' + + /xrpldex/orderBooks: + get: + tags: + - 'xrpldex' + summary: 'Get Order Book Details' + operationId: 'orderBooks' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/XRPLDEXGetOrderBooksRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/XRPLDEXGetOrderBooksResponse' + '404': + description: 'Not found response.' + + /xrpldex/orders: + get: + tags: + - 'xrpldex' + summary: 'Get status details of one or more orders' + operationId: 'getOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/XRPLDEXGetOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/XRPLDEXGetOrdersResponse' + '404': + description: 'Not found response.' + post: + tags: + - 'xrpldex' + summary: 'Create one or more orders' + description: 'Create one or more orders.' + operationId: 'createOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/XRPLDEXPostCreateOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/XRPLDEXPostCreateOrdersResponse' + '400': + description: 'Bad request response.' + delete: + tags: + - 'xrpldex' + summary: 'Cancel one or more orders' + description: 'Cancel one or more orders.' + operationId: 'cancelOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/XRPLDEXDeleteCancelOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/XRPLDEXDeleteCancelOrdersResponse' + '400': + description: 'Bad request response.' + + /xrpldex/orders/open: + get: + tags: + - 'xrpldex' + summary: 'Get open orders from a wallet' + operationId: 'getOpenOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/XRPLDEXGetOpenOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/XRPLDEXGetOpenOrdersResponse' + '404': + description: 'Not found response.' \ No newline at end of file diff --git a/package.json b/package.json index 6fd0a42954..9baa971ba5 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "express": "^4.17.1", "express-winston": "^4.1.0", "fs-extra": "^10.0.0", + "http-status-codes": "^2.2.0", "js-yaml": "^4.1.0", "level": "^8.0.0", "lodash": "^4.17.21", @@ -95,6 +96,7 @@ "winston": "^3.3.3", "winston-daily-rotate-file": "^4.5.5", "xsswap-sdk": "^1.0.1", + "xrpl": "^2.5.0", "yarn": "^1.22.17" }, "devDependencies": { diff --git a/src/app.ts b/src/app.ts index b9ef0e9da9..bb9815b929 100644 --- a/src/app.ts +++ b/src/app.ts @@ -112,14 +112,14 @@ export const startSwagger = async () => { export const startGateway = async () => { const port = ConfigManagerV2.getInstance().get('server.port'); - const gateway_version="dev-1.19.0"; // gateway version + const gateway_version = 'dev-1.19.0'; // gateway version if (!ConfigManagerV2.getInstance().get('server.id')) { ConfigManagerV2.getInstance().set( 'server.id', Math.random().toString(16).substr(2, 14) ); } - logger.info(`Gateway Version: ${gateway_version}`) // display gateway version + logger.info(`Gateway Version: ${gateway_version}`); // display gateway version logger.info(`⚡️ Starting Gateway API on port ${port}...`); if (ConfigManagerV2.getInstance().get('server.unsafeDevModeWithHTTP')) { logger.info('Running in UNSAFE HTTP! This could expose private keys.'); diff --git a/src/chains/xrpl/xrpl-middlewares.ts b/src/chains/xrpl/xrpl-middlewares.ts new file mode 100644 index 0000000000..a735999571 --- /dev/null +++ b/src/chains/xrpl/xrpl-middlewares.ts @@ -0,0 +1,24 @@ +import { HttpException } from '../../services/error-handler'; +import { XRPL } from './xrpl'; +import { NextFunction, Request, Response } from 'express'; + +export const verifyXRPLIsAvailable = async ( + req: Request, + _res: Response, + next: NextFunction +) => { + if (!req || !req.body || !req.body.network) { + throw new HttpException(404, 'No XRPL network informed.'); + } + + const xrpl = await XRPL.getInstance(req.body.network); + if (!xrpl.ready) { + await xrpl.init(); + } + + if (!xrpl.isConnected()) { + await xrpl.client.connect(); + } + + return next(); +}; diff --git a/src/chains/xrpl/xrpl.config.ts b/src/chains/xrpl/xrpl.config.ts new file mode 100644 index 0000000000..42c4f83ab9 --- /dev/null +++ b/src/chains/xrpl/xrpl.config.ts @@ -0,0 +1,48 @@ +import { ConfigManagerV2 } from '../../services/config-manager-v2'; + +export interface NetworkConfig { + name: string; + nodeUrl: string; // example: wss://xrplcluster.com/ + tokenListType: string; // default: FILE + tokenListSource: string; // default: src/chains/xrpl/xrpl_tokens.json + nativeCurrencySymbol: string; // XRP +} + +export interface Config { + // "mainnet" | "testnet" | "devnet" + network: NetworkConfig; + requestTimeout: number; // default: 20 + connectionTimeout: number; // default: 5 + feeCushion: number; // default: 1.2 + maxFeeXRP: string; // default: 2 +} + +// @todo: find out which configs are required +export function getXRPLConfig(chainName: string, networkName: string): Config { + const configManager = ConfigManagerV2.getInstance(); + return { + network: { + name: networkName, + nodeUrl: configManager.get( + chainName + '.networks.' + networkName + '.nodeURL' + ), + tokenListType: ConfigManagerV2.getInstance().get( + chainName + '.networks.' + networkName + '.tokenListType' + ), + tokenListSource: ConfigManagerV2.getInstance().get( + chainName + '.networks.' + networkName + '.tokenListSource' + ), + nativeCurrencySymbol: ConfigManagerV2.getInstance().get( + chainName + '.networks.' + networkName + '.nativeCurrencySymbol' + ), + }, + requestTimeout: ConfigManagerV2.getInstance().get( + chainName + '.requestTimeout' + ), + connectionTimeout: ConfigManagerV2.getInstance().get( + chainName + '.connectionTimeout' + ), + feeCushion: ConfigManagerV2.getInstance().get(chainName + '.feeCushion'), + maxFeeXRP: ConfigManagerV2.getInstance().get(chainName + '.maxFeeXRP'), + }; +} diff --git a/src/chains/xrpl/xrpl.constants.ts b/src/chains/xrpl/xrpl.constants.ts new file mode 100644 index 0000000000..197920242b --- /dev/null +++ b/src/chains/xrpl/xrpl.constants.ts @@ -0,0 +1,26 @@ +import { ConfigManagerV2 } from '../../services/config-manager-v2'; + +const configManager = ConfigManagerV2.getInstance(); + +export const constants = { + retry: { + all: { + maxNumberOfRetries: + configManager.get('xrpl.retry.all.maxNumberOfRetries') || 0, // 0 means no retries + delayBetweenRetries: + configManager.get('xrpl.retry.all.delayBetweenRetries') || 0, // 0 means no delay (milliseconds) + }, + }, + timeout: { + all: configManager.get('xrpl.timeout.all') || 0, // 0 means no timeout (milliseconds) + }, + parallel: { + all: { + batchSize: configManager.get('xrpl.parallel.all.batchSize') || 0, // 0 means no batching (group all) + delayBetweenBatches: + configManager.get('xrpl.parallel.all.delayBetweenBatches') || 0, // 0 means no delay (milliseconds) + }, + }, +}; + +export default constants; diff --git a/src/chains/xrpl/xrpl.controllers.ts b/src/chains/xrpl/xrpl.controllers.ts new file mode 100644 index 0000000000..7f33ad83ae --- /dev/null +++ b/src/chains/xrpl/xrpl.controllers.ts @@ -0,0 +1,65 @@ +import { Wallet, TxResponse } from 'xrpl'; +import { XRPLish } from './xrpl'; +import { latency } from '../../services/base'; +import { + HttpException, + LOAD_WALLET_ERROR_CODE, + LOAD_WALLET_ERROR_MESSAGE, +} from '../../services/error-handler'; +import { getNotNullOrThrowError } from '../../chains/xrpl/xrpl.helpers'; + +import { + XRPLBalanceRequest, + XRPLBalanceResponse, + XRPLPollRequest, + XRPLPollResponse, +} from './xrpl.requests'; + +export async function balances( + xrplish: XRPLish, + req: XRPLBalanceRequest +): Promise { + const initTime = Date.now(); + let wallet: Wallet; + + try { + wallet = await xrplish.getWallet(req.address); + } catch (err) { + throw new HttpException( + 500, + LOAD_WALLET_ERROR_MESSAGE + err, + LOAD_WALLET_ERROR_CODE + ); + } + + const balances = await xrplish.getAllBalance(wallet); + + return { + network: xrplish.network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + balances, + }; +} + +export async function poll( + xrplish: XRPLish, + req: XRPLPollRequest +): Promise { + const initTime = Date.now(); + const currentLedgerIndex = await xrplish.getCurrentLedgerIndex(); + const txData = getNotNullOrThrowError( + await xrplish.getTransaction(req.txHash) + ); + const txStatus = await xrplish.getTransactionStatusCode(txData); + + return { + network: xrplish.network, + timestamp: initTime, + currentLedgerIndex: currentLedgerIndex, + txHash: req.txHash, + txStatus: txStatus, + txLedgerIndex: txData.result.ledger_index, + txData: getNotNullOrThrowError(txData), + }; +} diff --git a/src/chains/xrpl/xrpl.helpers.ts b/src/chains/xrpl/xrpl.helpers.ts new file mode 100644 index 0000000000..72ce429591 --- /dev/null +++ b/src/chains/xrpl/xrpl.helpers.ts @@ -0,0 +1,122 @@ +import web3 from 'web3'; +import { default as constants } from './../../chains/xrpl/xrpl.constants'; + +/** + * + * @param value + * @param errorMessage + */ +export const getNotNullOrThrowError = ( + value?: any, + errorMessage: string = 'Value is null or undefined' +): R => { + if (value === undefined || value === null) throw new Error(errorMessage); + + return value as R; +}; + +/** + * + * @param milliseconds + */ +export const sleep = (milliseconds: number) => + new Promise((callback) => setTimeout(callback, milliseconds)); + +/** + * Same as Promise.all(items.map(item => task(item))), but it waits for + * the first {batchSize} promises to finish before starting the next batch. + * + * @template A + * @template B + * @param {function(A): B} task The task to run for each item. + * @param {A[]} items Arguments to pass to the task for each call. + * @param {int} batchSize The number of items to process at a time. + * @param {int} delayBetweenBatches Delay between each batch (milliseconds). + * @returns {B[]} + */ +export const promiseAllInBatches = async ( + task: (item: I) => O, + items: any[], + batchSize: number = constants.parallel.all.batchSize, + delayBetweenBatches: number = constants.parallel.all.delayBetweenBatches +): Promise => { + let position = 0; + let results: any[] = []; + + if (!batchSize) { + batchSize = items.length; + } + + while (position < items.length) { + const itemsForBatch = items.slice(position, position + batchSize); + results = [ + ...results, + ...(await Promise.all(itemsForBatch.map((item) => task(item)))), + ]; + position += batchSize; + + if (position < items.length) { + if (delayBetweenBatches > 0) { + await sleep(delayBetweenBatches); + } + } + } + + return results; +}; + +/** + * + */ +export const getRandonBN = () => { + return web3.utils.toBN(web3.utils.randomHex(32)); +}; + +/** + * @param targetObject + * @param targetFunction + * @param targetParameters + * @param maxNumberOfRetries 0 means no retries + * @param delayBetweenRetries 0 means no delay (milliseconds) + * @param timeout 0 means no timeout (milliseconds) + * @param timeoutMessage + */ +export const runWithRetryAndTimeout = async ( + targetObject: any, + targetFunction: (...args: any[]) => R, + targetParameters: any, + maxNumberOfRetries: number = constants.retry.all.maxNumberOfRetries, + delayBetweenRetries: number = constants.retry.all.delayBetweenRetries, + timeout: number = constants.timeout.all, + timeoutMessage: string = 'Timeout exceeded.' +): Promise => { + let retryCount = 0; + let timer: any; + + if (timeout > 0) { + timer = setTimeout(() => new Error(timeoutMessage), timeout); + } + + do { + try { + const result = await targetFunction.apply(targetObject, targetParameters); + + if (timeout > 0) { + clearTimeout(timer); + } + + return result as R; + } catch (error) { + retryCount++; + if (retryCount < maxNumberOfRetries) { + if (delayBetweenRetries > 0) { + await sleep(delayBetweenRetries); + } + } else { + throw error; + } + } + } while (retryCount < maxNumberOfRetries); + + throw Error('Unknown error.'); +}; diff --git a/src/chains/xrpl/xrpl.requests.ts b/src/chains/xrpl/xrpl.requests.ts new file mode 100644 index 0000000000..c69112f56e --- /dev/null +++ b/src/chains/xrpl/xrpl.requests.ts @@ -0,0 +1,47 @@ +import { NetworkSelectionRequest } from '../../services/common-interfaces'; +import { TxResponse } from 'xrpl'; + +export interface XRPLBalanceRequest extends NetworkSelectionRequest { + address: string; + tokenSymbols: string[]; +} + +export interface XRPLBalanceResponse { + network: string; + timestamp: number; + latency: number; + balances: Record; +} + +export interface XRPLTokenRequest extends NetworkSelectionRequest { + address: string; // the user's Solana address as Base58 + token: string; // the token symbol the spender will be approved for +} + +export interface XRPLTokenResponse { + network: string; + timestamp: number; + token: string; // the token symbol the spender will be approved for + mintAddress: string; + accountAddress?: string; + amount: string | null; +} + +export interface XRPLPollRequest extends NetworkSelectionRequest { + txHash: string; +} + +export enum TransactionResponseStatusCode { + FAILED = -1, + CONFIRMED = 1, +} + +export interface XRPLPollResponse { + network: string; + timestamp: number; + currentLedgerIndex: number; + txHash: string; + txStatus: number; + txLedgerIndex?: number; + txData: TxResponse | null; +} diff --git a/src/chains/xrpl/xrpl.routes.ts b/src/chains/xrpl/xrpl.routes.ts new file mode 100644 index 0000000000..e3664847fa --- /dev/null +++ b/src/chains/xrpl/xrpl.routes.ts @@ -0,0 +1,77 @@ +import { NextFunction, Request, Response, Router } from 'express'; +import { ParamsDictionary } from 'express-serve-static-core'; +import { XRPL } from './xrpl'; +import { verifyXRPLIsAvailable } from './xrpl-middlewares'; +import { asyncHandler } from '../../services/error-handler'; +import { balances, poll } from './xrpl.controllers'; +import { + XRPLBalanceRequest, + XRPLBalanceResponse, + XRPLPollRequest, + XRPLPollResponse, +} from './xrpl.requests'; +import { + validateXRPLBalanceRequest, + validateXRPLPollRequest, +} from './xrpl.validators'; + +export namespace XRPLRoutes { + export const router = Router(); + + export const getXRPL = async (request: Request) => { + const xrpl = await XRPL.getInstance(request.body.network); + await xrpl.init(); + + return xrpl; + }; + + router.use(asyncHandler(verifyXRPLIsAvailable)); + + router.get( + '/', + asyncHandler(async (request: Request, response: Response) => { + const xrpl = await getXRPL(request); + + const rpcUrl = xrpl.rpcUrl; + + response.status(200).json({ + network: xrpl.network, + rpcUrl: rpcUrl, + connection: true, + timestamp: Date.now(), + }); + }) + ); + + router.get( + '/balances', + asyncHandler( + async ( + request: Request, + response: Response, + _next: NextFunction + ) => { + const xrpl = await getXRPL(request); + + validateXRPLBalanceRequest(request.body); + response.status(200).json(await balances(xrpl, request.body)); + } + ) + ); + + // TODO: change this to GET + router.get( + '/poll', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + + validateXRPLPollRequest(request.body); + response.status(200).json(await poll(xrpl, request.body)); + } + ) + ); +} diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts new file mode 100644 index 0000000000..137442aa1b --- /dev/null +++ b/src/chains/xrpl/xrpl.ts @@ -0,0 +1,369 @@ +import { + Client, + Wallet, + LedgerStream, + ValidationStream, + TransactionStream, + PeerStatusStream, + ConsensusStream, + PathFindStream, + TxResponse, + TransactionMetadata, +} from 'xrpl'; +import axios from 'axios'; +import { promises as fs } from 'fs'; +import crypto from 'crypto'; +import fse from 'fs-extra'; +import { TokenListType, walletPath } from '../../services/base'; +import { ConfigManagerCertPassphrase } from '../../services/config-manager-cert-passphrase'; +import { getXRPLConfig } from './xrpl.config'; +import { logger } from '../../services/logger'; +import { TransactionResponseStatusCode } from './xrpl.requests'; + +export type TrustlineInfo = { + id: number; + code: string; + issuer: string; + title: string; + trustlines: number; + placeInTop: null; +}; + +export type TokenBalance = { + currency: string; + issuer?: string; + value: string; +}; + +export class XRPL implements XRPLish { + private static _instances: { [name: string]: XRPL }; + public rpcUrl; + + protected tokenList: TrustlineInfo[] = []; + private _tokenMap: Record = {}; + + private _client: Client; + private _nativeTokenSymbol: string; + private _chain: string; + private _network: string; + private _requestCount: number; + private _metricsLogInterval: number; + private _tokenListSource: string; + private _tokenListType: TokenListType; + + private _ready: boolean = false; + private initializing: boolean = false; + + private constructor(network: string) { + const config = getXRPLConfig('xrpl', network); + + this._chain = 'xrpl'; + this._network = network; + this.rpcUrl = config.network.nodeUrl; + this._nativeTokenSymbol = config.network.nativeCurrencySymbol; + this._tokenListSource = config.network.tokenListSource; + this._tokenListType = config.network.tokenListType; + + this._client = new Client(this.rpcUrl, { + timeout: config.requestTimeout, + connectionTimeout: config.connectionTimeout, + feeCushion: config.feeCushion, + maxFeeXRP: config.maxFeeXRP, + }); + + // this._client.connect(); + + this._requestCount = 0; + this._metricsLogInterval = 300000; // 5 minutes + + this.onValidationReceived(this.requestCounter.bind(this)); + setInterval(this.metricLogger.bind(this), this.metricsLogInterval); + } + + public static getInstance(network: string): XRPL { + if (XRPL._instances === undefined) { + XRPL._instances = {}; + } + if (!(network in XRPL._instances)) { + XRPL._instances[network] = new XRPL(network); + } + + return XRPL._instances[network]; + } + + public static getConnectedInstances(): { [name: string]: XRPL } { + return XRPL._instances; + } + + public get client() { + return this._client; + } + + public onConnected(callback: () => void) { + this._client.on('connected', callback); + } + + public onDisconnected(callback: (code: number) => void) { + this._client.on('disconnected', callback); + } + + public onLedgerClosed(callback: (ledger: LedgerStream) => void) { + this._client.on('ledgerClosed', callback); + } + + public onValidationReceived( + callback: (validation: ValidationStream) => void + ) { + this._client.on('validationReceived', callback); + } + + public onTransaction(callback: (tx: TransactionStream) => void) { + this._client.on('transaction', callback); + } + + public onPeerStatusChange(callback: (status: PeerStatusStream) => void) { + this._client.on('peerStatusChange', callback); + } + + public onConsensusPhase(callback: (phase: ConsensusStream) => void) { + this._client.on('consensusPhase', callback); + } + + public onPathFind(callback: (path: PathFindStream) => void) { + this._client.on('path_find', callback); + } + + public onError(callback: (...err: any[]) => void): void { + this._client.on('error', callback); + } + + async init(): Promise { + if (!this.ready() && !this.initializing) { + this.initializing = true; + await this._client.connect(); + await this.loadTokens(this._tokenListSource, this._tokenListType); + this._ready = true; + this.initializing = false; + } + } + + async loadTokens( + tokenListSource: string, + tokenListType: TokenListType + ): Promise { + this.tokenList = await this.getTokenList(tokenListSource, tokenListType); + if (this.tokenList) { + this.tokenList.forEach((token: TrustlineInfo) => + this._tokenMap[token.code].push(token) + ); + } + } + + async getTokenList( + tokenListSource: string, + tokenListType: TokenListType + ): Promise { + let tokens; + if (tokenListType === 'URL') { + ({ + data: { tokens }, + } = await axios.get(tokenListSource)); + } else { + ({ tokens } = JSON.parse(await fs.readFile(tokenListSource, 'utf8'))); + } + return tokens; + } + + public get storedTokenList(): TrustlineInfo[] { + return this.tokenList; + } + + public getTokenForSymbol(code: string): TrustlineInfo[] | null { + return this._tokenMap[code] ? this._tokenMap[code] : null; + } + + public getWalletFromSeed(seed: string): Wallet { + const wallet = Wallet.fromSeed(seed); + + return wallet; + } + + async getWallet(address: string): Promise { + const path = `${walletPath}/${this.chain}`; + + const encryptedSeed: string = await fse.readFile( + `${path}/${address}.json`, + 'utf8' + ); + + const passphrase = ConfigManagerCertPassphrase.readPassphrase(); + if (!passphrase) { + throw new Error('missing passphrase'); + } + const decrypted = await this.decrypt(encryptedSeed, passphrase); + + return Wallet.fromSeed(decrypted); + } + + async encrypt(secret: string, password: string): Promise { + const algorithm = 'aes-256-ctr'; + const iv = crypto.randomBytes(16); + const salt = crypto.randomBytes(32); + const key = crypto.pbkdf2Sync(password, salt, 5000, 32, 'sha512'); + const cipher = crypto.createCipheriv(algorithm, key, iv); + const encrypted = Buffer.concat([cipher.update(secret), cipher.final()]); + + const ivJSON = iv.toJSON(); + const saltJSON = salt.toJSON(); + const encryptedJSON = encrypted.toJSON(); + + return JSON.stringify({ + algorithm, + iv: ivJSON, + salt: saltJSON, + encrypted: encryptedJSON, + }); + } + + async decrypt(encryptedSecret: string, password: string): Promise { + const hash = JSON.parse(encryptedSecret); + const salt = Buffer.from(hash.salt, 'utf8'); + const iv = Buffer.from(hash.iv, 'utf8'); + + const key = crypto.pbkdf2Sync(password, salt, 5000, 32, 'sha512'); + + const decipher = crypto.createDecipheriv(hash.algorithm, key, iv); + + const decrpyted = Buffer.concat([ + decipher.update(Buffer.from(hash.encrypted, 'hex')), + decipher.final(), + ]); + + return decrpyted.toString(); + } + + async getNativeBalance(wallet: Wallet): Promise { + await this.ensureConnection(); + const balance = await this._client.getXrpBalance(wallet.address); + return balance; + } + + async getAllBalance(wallet: Wallet): Promise> { + await this.ensureConnection(); + const balances: Record = {}; + const respBalances = await this._client.getBalances(wallet.address); + + respBalances.forEach((token) => { + if (token.currency === 'XRP') { + balances[token.currency] = token.value; + } else { + const symbol = token.currency + '.' + token.issuer; + balances[symbol] = token.value; + } + }); + + return balances; + } + + ready(): boolean { + return this._ready; + } + + isConnected(): boolean { + return this._client.isConnected(); + } + + async ensureConnection() { + if (!this.isConnected()) { + await this._client.connect(); + } + } + + public get chain(): string { + return this._chain; + } + + public get network(): string { + return this._network; + } + + public get nativeTokenSymbol(): string { + return this._nativeTokenSymbol; + } + + public requestCounter(): void { + this._requestCount += 1; + } + + public metricLogger(): void { + logger.info( + this.requestCount + + ' request(s) sent in last ' + + this.metricsLogInterval / 1000 + + ' seconds.' + ); + this._requestCount = 0; // reset + } + + public get requestCount(): number { + return this._requestCount; + } + + public get metricsLogInterval(): number { + return this._metricsLogInterval; + } + + public async getCurrentLedgerIndex(): Promise { + await this.ensureConnection(); + const currentIndex = await this.client.getLedgerIndex(); + return currentIndex; + } + + public async getCurrentBlockNumber(): Promise { + const currentIndex = await this.getCurrentLedgerIndex(); + return currentIndex; + } + + public async getTransactionStatusCode( + txData: TxResponse | null + ): Promise { + let txStatus; + if (!txData) { + txStatus = TransactionResponseStatusCode.FAILED; + } else { + if ((txData.result.meta).TransactionResult) { + const result = (txData.result.meta) + .TransactionResult; + txStatus = + result == 'tesSUCCESS' + ? TransactionResponseStatusCode.CONFIRMED + : TransactionResponseStatusCode.FAILED; + } else { + txStatus = TransactionResponseStatusCode.FAILED; + } + } + return txStatus; + } + + async getTransaction(txHash: string): Promise { + await this.ensureConnection(); + const tx_resp = await this._client.request({ + command: 'tx', + transaction: txHash, + binary: false, + }); + + const result = tx_resp; + + return result; + } + + async close() { + if (this._network in XRPL._instances) { + delete XRPL._instances[this._network]; + } + } +} + +export type XRPLish = XRPL; +export const XRPLish = XRPL; diff --git a/src/chains/xrpl/xrpl.validators.ts b/src/chains/xrpl/xrpl.validators.ts new file mode 100644 index 0000000000..acefd6c257 --- /dev/null +++ b/src/chains/xrpl/xrpl.validators.ts @@ -0,0 +1,52 @@ +import { + validateTokenSymbols, + mkValidator, + mkRequestValidator, + RequestValidator, + Validator, + isBase58, + validateTxHash, + validateToken, +} from '../../services/validators'; + +// invalid parameter errors +export const invalidXRPLAddressError: string = + 'The spender param is not a valid XRPL address (20 bytes, base 58 encoded).'; + +export const invalidXRPLPrivateKeyError: string = + 'The privateKey param is not a valid XRPL seed key (16 bytes, base 58 encoded).'; + +// test if a string matches the shape of an XRPL address +export const isXRPLAddress = (str: string): boolean => { + return isBase58(str) && str.length <= 35 && str.charAt(0) == 'r'; +}; + +// test if a string matches the shape of an XRPL seed key +export const isXRPLSeedKey = (str: string): boolean => { + return isBase58(str) && str.charAt(0) == 's'; +}; + +// given a request, look for a key called address that is an Solana address +export const validateXRPLAddress: Validator = mkValidator( + 'address', + invalidXRPLAddressError, + (val) => typeof val === 'string' && isXRPLAddress(val) +); + +// request types and corresponding validators + +export const validateXRPLBalanceRequest: RequestValidator = mkRequestValidator([ + validateXRPLAddress, + validateTokenSymbols, +]); + +export const validateXRPLPollRequest: RequestValidator = mkRequestValidator([ + validateTxHash, +]); + +export const validateXRPLGetTokenRequest: RequestValidator = mkRequestValidator( + [validateToken, validateXRPLAddress] +); + +export const validateXRPLPostTokenRequest: RequestValidator = + mkRequestValidator([validateToken, validateXRPLAddress]); diff --git a/src/chains/xrpl/xrpl_tokens.json b/src/chains/xrpl/xrpl_tokens.json new file mode 100644 index 0000000000..54072bac2e --- /dev/null +++ b/src/chains/xrpl/xrpl_tokens.json @@ -0,0 +1,12370 @@ +[ + { + "id": 31, + "code": "SOLO", + "issuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "title": "Sologenic", + "trustlines": 288976, + "placeInTop": 1 + }, + { + "id": 16602, + "code": "CSC", + "issuer": "rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr", + "title": "CasinoCoin", + "trustlines": 54154, + "placeInTop": 2 + }, + { + "id": 18190, + "code": "CORE", + "issuer": "rcoreNywaoz2ZCQ8Lg2EbSLnGuRBmun6D", + "title": "Coreum", + "trustlines": 65817, + "placeInTop": 3 + }, + { + "id": 248, + "code": "ELS", + "issuer": "rHXuEaRYnnJHbDeuBH5w8yPh5uwNVh5zAg", + "title": "Elysian", + "trustlines": 109452, + "placeInTop": 4 + }, + { + "id": 16605, + "code": "Equilibrium", + "issuer": "rpakCr61Q92abPXJnVboKENmpKssWyHpwu", + "title": "Equilibrium", + "trustlines": 34827, + "placeInTop": 5 + }, + { + "id": 17073, + "code": "BTC", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 18187, + "placeInTop": 6 + }, + { + "id": 16603, + "code": "USD", + "issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "title": "Gatehub", + "trustlines": 110980, + "placeInTop": 7 + }, + { + "id": 17074, + "code": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 26696, + "placeInTop": 8 + }, + { + "id": 16818, + "code": "XPUNK", + "issuer": "rHEL3bM4RFsvF8kbQj3cya8YiDvjoEmxLq", + "title": "XRPL PUNKS", + "trustlines": 12074, + "placeInTop": 9 + }, + { + "id": 103, + "code": "VGB", + "issuer": "rhcyBrowwApgNonehKBj8Po5z4gTyRknaU", + "title": "Vagabond VGB", + "trustlines": 56661, + "placeInTop": 10 + }, + { + "id": 182, + "code": "RPR", + "issuer": "r3qWgpz2ry3BhcRJ8JE6rxM8esrfhuKp4R", + "title": "The Reaper", + "trustlines": 24140, + "placeInTop": 11 + }, + { + "id": 18542, + "code": "xSPECTAR", + "issuer": "rh5jzTCdMRCVjQ7LT6zucjezC47KATkuvv", + "title": "xSPECTAR NFT", + "trustlines": 9868, + "placeInTop": 12 + }, + { + "id": 16826, + "code": "BAY", + "issuer": "r4uq8urnYrT6LnZaadPyusyKCS68HVJtRn", + "title": "Bored Apes XRP Club", + "trustlines": 8247, + "placeInTop": 13 + }, + { + "id": 17296, + "code": "OXP", + "issuer": "rrno7Nj4RkFJLzC4nRaZiLF5aHwcTVon3d", + "title": "onXRP", + "trustlines": 9851, + "placeInTop": 14 + }, + { + "id": 16726, + "code": "ADV", + "issuer": "rPneN8WPHZJaMT9pF4Ynyyq4pZZZSeTuHu", + "title": "AdvisorBid", + "trustlines": 10454, + "placeInTop": 15 + }, + { + "id": 17363, + "code": "xBIBLx", + "issuer": "rnH9o6qdym34K293Pq3FZp5Ko7SpZxEbeG", + "title": "Bibliomp", + "trustlines": 4441, + "placeInTop": 16 + }, + { + "id": 16600, + "code": "EUR", + "issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "title": "Gatehub", + "trustlines": 85461, + "placeInTop": 17 + }, + { + "id": 225, + "code": "XRdoge", + "issuer": "rLqUC2eCPohYvJCEBJ77eCCqVL2uEiczjA", + "title": "XRdoge Labs", + "trustlines": 26611, + "placeInTop": 18 + }, + { + "id": 99, + "code": "XDX", + "issuer": "rMJAXYsbNzhwp7FfYnAsYP5ty3R9XnurPo", + "title": "D.P.MonksFinance LTD", + "trustlines": 28866, + "placeInTop": 19 + }, + { + "id": 18469, + "code": "STX", + "issuer": "rSTAYKxF2K77ZLZ8GoAwTqPGaphAqMyXV", + "title": "StaykX", + "trustlines": 12098, + "placeInTop": 20 + }, + { + "id": 369, + "code": "xSTIK", + "issuer": "rJNV9i4Q6zvRhpE2zjxgkvff3eGHQohZht", + "title": "xSTIK Official", + "trustlines": 24267, + "placeInTop": 21 + }, + { + "id": 18621, + "code": "XLIST", + "issuer": "rNy8hFXoXEaJwkiT6U6ED5CWsfZHELNcnr", + "title": "XList", + "trustlines": 4286, + "placeInTop": 22 + }, + { + "id": 374, + "code": "xCoin", + "issuer": "rXCoYSUnkpygdtfpz3Df8dKQuRZjM9UFi", + "title": "XRPLCOINS", + "trustlines": 27151, + "placeInTop": 23 + }, + { + "id": 17807, + "code": "ShibaNFT", + "issuer": "rnRXAnVZTyattZXEpKpgTyvdm17DpjrzSZ", + "title": "ShibaNFT OFFICIAL", + "trustlines": 23572, + "placeInTop": 24 + }, + { + "id": 375, + "code": "OCW", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "title": "OnChain Whales", + "trustlines": 19078, + "placeInTop": 25 + }, + { + "id": 16660, + "code": "XBLADE", + "issuer": "rfVTbKsRgyurPddRiWaReuiK9Tvms8XGj1", + "title": "XBLADE", + "trustlines": 7449, + "placeInTop": 26 + }, + { + "id": 250, + "code": "Greyhound", + "issuer": "rJWBaKCpQw47vF4rr7XUNqr34i4CoXqhKJ", + "title": "Greyhound", + "trustlines": 10211, + "placeInTop": 27 + }, + { + "id": 18606, + "code": "BPM", + "issuer": "rDBMvpjV6DoWvr3LqMUG8JBgd4QbBoU1E2", + "title": "BPM Wallet", + "trustlines": 2910, + "placeInTop": 28 + }, + { + "id": 16723, + "code": "JUNK", + "issuer": "r4pDJ7bT1rANe9nAdFR9pyVRwtJZQUEFpj", + "title": "Junkie", + "trustlines": 6455, + "placeInTop": 29 + }, + { + "id": 378, + "code": "FKM", + "issuer": "raivZznHUty6vxDYiQD8K5KgwvPKwjpruz", + "title": "FKM", + "trustlines": 11620, + "placeInTop": 30 + }, + { + "id": 16955, + "code": "SGB", + "issuer": "rctArjqVvTHihekzDeecKo6mkTYTUSBNc", + "title": "GateHub SGB", + "trustlines": 23121, + "placeInTop": 31 + }, + { + "id": 17839, + "code": "LUC", + "issuer": "rsygE5ynt2iSasscfCCeqaGBGiFKMCAUu7", + "title": "Lucretius", + "trustlines": 31076, + "placeInTop": 32 + }, + { + "id": 85, + "code": "RDX", + "issuer": "rQa3LW1Au4GxGHzDBkCMKuPcn326w4Wcj2", + "title": "D.P.MonksFinance LTD", + "trustlines": 17418, + "placeInTop": 33 + }, + { + "id": 18740, + "code": "XRDC", + "issuer": "rcLASSiCq8LWcymCHaCgK19QMEvUspuRM", + "title": "XRdogeClassic", + "trustlines": 3319, + "placeInTop": 34 + }, + { + "id": 16724, + "code": "LOX", + "issuer": "rLLJvh6bwj2eTYwzLL484AW6EyH4rdZqWZ", + "title": "LoxNetwork", + "trustlines": 13716, + "placeInTop": 35 + }, + { + "id": 16849, + "code": "HUGETITS", + "issuer": "rEKKMW3MDVgrjPdcJLx49KEhF9ATEQwzPy", + "trustlines": 5454, + "placeInTop": 36 + }, + { + "id": 12, + "code": "CLUB", + "issuer": "r9pAKbAMx3wpMAS9XvvDzLYppokfKWTSq4", + "title": "Club589", + "trustlines": 7113, + "placeInTop": 37 + }, + { + "id": 16735, + "code": "XMEN", + "issuer": "rwc4q24X5Ui76v72WWaNFozBLoZiPj3oKV", + "title": "VerseX", + "trustlines": 10504, + "placeInTop": 38 + }, + { + "id": 16793, + "code": "IGC", + "issuer": "raP6XZypcFR1WkDzaKjv91FtYRCURvePWW", + "title": "iHunt4", + "trustlines": 5514, + "placeInTop": 39 + }, + { + "id": 16685, + "code": "Xoge", + "issuer": "rJMtvf5B3GbuFMrqybh5wYVXEH4QE8VyU1", + "title": "Xoge", + "trustlines": 17536, + "placeInTop": 40 + }, + { + "id": 212, + "code": "XRSHIB", + "issuer": "rN3EeRSxh9tLHAUDmL7Chh3vYYoUafAyyM", + "title": "XR SHIB", + "trustlines": 25681, + "placeInTop": 41 + }, + { + "id": 17058, + "code": "ETC", + "issuer": "rDAN8tzydyNfnNf2bfUQY6iR96UbpvNsze", + "title": "GateHub ETC", + "trustlines": 22783, + "placeInTop": 42 + }, + { + "id": 37, + "code": "xToadz", + "issuer": "rpRW1FumRWhhaLmoYwS1SqEXsnCccvpsAU", + "title": "xToadz & sToadz", + "trustlines": 10227, + "placeInTop": 43 + }, + { + "id": 289, + "code": "xShroom", + "issuer": "rHqLei9xJch13JioYHsDUwWJoz81QQh6LU", + "title": "xShrooms", + "trustlines": 4048, + "placeInTop": 44 + }, + { + "id": 187, + "code": "XGBL", + "issuer": "rMy6sCaDVF1C2BT3qmNG6kgjVDZqZ74uoF", + "title": "Xungible", + "trustlines": 24545, + "placeInTop": 45 + }, + { + "id": 16598, + "code": "CX1", + "issuer": "rKk7mu1dNB25fsPEJ4quoQd5B8QmaxewKi", + "title": "ChorusX", + "trustlines": 60567, + "placeInTop": 46 + }, + { + "id": 18946, + "code": "XSP", + "issuer": "rPUNKXyoE6zZB3nRSTuU7DoUKiQfi8KeK8", + "title": "Standard Punks", + "trustlines": 14368, + "placeInTop": 47 + }, + { + "id": 18789, + "code": "RLT", + "issuer": "rUetS7kbVYJZ76za5ywa1DgViNZMgT9Bvq", + "title": "Rocket Launch", + "trustlines": 5520, + "placeInTop": 48 + }, + { + "id": 292, + "code": "xAliens", + "issuer": "rDbAT7ZnkBBxKYL8myUY68c1chaKLBbNKw", + "trustlines": 4801, + "placeInTop": 49 + }, + { + "id": 18985, + "code": "CHP", + "issuer": "rhFNUEAKyXZmJHsnfJvH8hM12Ydk2icEof", + "title": "Chop Chop", + "trustlines": 10885, + "placeInTop": 50 + }, + { + "id": 16646, + "code": "MLD", + "issuer": "rhJYDuVMQxabTyiWuHQkQyDxr6uZEdpv5u", + "title": "Hurrian", + "trustlines": 28844, + "placeInTop": 51 + }, + { + "id": 16792, + "code": "XGOLD", + "issuer": "r4XSSBVGENvgUrZFijX8xvsJtp9Mi2UPz9", + "title": "XGOLD", + "trustlines": 5634, + "placeInTop": 51 + }, + { + "id": 251, + "code": "FSE", + "issuer": "rs1MKY54miDtMFEGyNuPd3BLsXauFZUSrj", + "title": "strategyengine", + "trustlines": 32019, + "placeInTop": 52 + }, + { + "id": 171, + "code": "TRSRY", + "issuer": "rLBnhMjV6ifEHYeV4gaS6jPKerZhQddFxW", + "title": "TREASURY XRPL", + "trustlines": 28380, + "placeInTop": 53 + }, + { + "id": 299, + "code": "XFLOKI", + "issuer": "rUtXeAXonpFpgKubAa7LxcLd7NFep92T1t", + "title": "XFLOKI", + "trustlines": 13100, + "placeInTop": 54 + }, + { + "id": 16662, + "code": "SwissTech", + "issuer": "raq7pGaYrLZRa88v6Py9V5oWMYEqPYr8Tz", + "trustlines": 20271, + "placeInTop": 55 + }, + { + "id": 89, + "code": "XVR", + "issuer": "rRCA6gnPkvpKmWwfcYxHQ845gFQ5ThYQe", + "title": "VerseX - $XMEN $XVR", + "trustlines": 4971, + "placeInTop": 56 + }, + { + "id": 18693, + "code": "XWAR", + "issuer": "rJAm3vMSiwCZHxLygaTdmiqCUG8YeSJFVy", + "trustlines": 4046, + "placeInTop": 57 + }, + { + "id": 16668, + "code": "Nerian", + "issuer": "rGEekk1PRyozC1ZEZCXL7EZcko4rrrETGh", + "title": "Nerian", + "trustlines": 9994, + "placeInTop": 58 + }, + { + "id": 16635, + "code": "SmartNFT", + "issuer": "rf8dxyFrYWEcUQAM7QXdbbtcRPzjvoQybK", + "title": "SmartNFT", + "trustlines": 18054, + "placeInTop": 59 + }, + { + "id": 16621, + "code": "SEC", + "issuer": "rDN4Ux1WFJJsPCdqdfZgrDZ2icxdAmg2w", + "title": "SEC Coin", + "trustlines": 31347, + "placeInTop": 60 + }, + { + "id": 16725, + "code": "SmartLOX", + "issuer": "rBdZkMKuPnzYVVkyL2DrQKV3DsYt5PPVRh", + "title": "SmartLOX", + "trustlines": 13728, + "placeInTop": 61 + }, + { + "id": 165, + "code": "XSQUAD", + "issuer": "roBYiFtZsTRpWEUw6TtpUCwZCfjcQeRBg", + "trustlines": 11470, + "placeInTop": 62 + }, + { + "id": 16609, + "code": "Schmeckles", + "issuer": "rPxw83ZP6thv7KmG5DpAW4cDW55DZRZ9wu", + "title": "Schmeckles", + "trustlines": 15829, + "placeInTop": 63 + }, + { + "id": 17, + "code": "XQK", + "issuer": "rHKrPGdpaqNRqRvmsiqQhD6azqc4npWoLC", + "title": "XQuake", + "trustlines": 25875, + "placeInTop": 64 + }, + { + "id": 17867, + "code": "$XRPLedgerETF", + "issuer": "raChAW5DVscGKimsCFLhHREpD86NsLF74Z", + "title": "XRPL ETF", + "trustlines": 8998, + "placeInTop": 65 + }, + { + "id": 269, + "code": "FCX", + "issuer": "rwSgqza9DUzr8oPDkJz8xUbPbaxAyoeLus", + "title": "FCX Focus", + "trustlines": 14769, + "placeInTop": 66 + }, + { + "id": 17062, + "code": "BCH", + "issuer": "rcyS4CeCZVYvTiKcxj6Sx32ibKwcDHLds", + "title": "GateHub BCH", + "trustlines": 15477, + "placeInTop": 66 + }, + { + "id": 18994, + "code": "Sanctum", + "issuer": "rNcqT1tds4vdroichNKuTh3Ppd8KAdFHnN", + "title": "Sanctum \u26a1\ufe0f", + "trustlines": 12800, + "placeInTop": 67 + }, + { + "id": 16729, + "code": "MONKEE", + "issuer": "rG5tVG4yoGvMTKiFEEEFpjG3qj2tGMP44o", + "title": "MONKEE MONKEE | NFT GAME", + "trustlines": 5446, + "placeInTop": 68 + }, + { + "id": 16823, + "code": "XDUDE", + "issuer": "rU5LE7X6yyu9DuHsLdHhWSiUVTgpyRK1vz", + "title": "XDUDE", + "trustlines": 5411, + "placeInTop": 69 + }, + { + "id": 18836, + "code": "Alpha", + "issuer": "rT8svWEMW3n5XTgezHEqpRdkuMnTafUfS", + "title": "Alpha Shares", + "trustlines": 5012, + "placeInTop": 70 + }, + { + "id": 18555, + "code": "XLoyalitY", + "issuer": "rfQNEfqLQc5L34rS92Xh5nVtk9NVHbv4Gj", + "title": "XLoyalitY", + "trustlines": 3228, + "placeInTop": 71 + }, + { + "id": 18880, + "code": "MetaLOX", + "issuer": "rDkjv8oQrgzHjjwZNnFAjWWrhRJgg6g56T", + "title": "MetaLOX", + "trustlines": 3224, + "placeInTop": 72 + }, + { + "id": 16631, + "code": "DFI", + "issuer": "rUY6tjGN8PJDVyVFLztRZLmPZ8uTBUfa2Z", + "title": "Denarii", + "trustlines": 6278, + "placeInTop": 73 + }, + { + "id": 16671, + "code": "XBOT", + "issuer": "rn9SkVczEzWQ3siieX7PcBVYpbRPRGbTgq", + "title": "X BOT CLUB", + "trustlines": 6532, + "placeInTop": 74 + }, + { + "id": 18406, + "code": "xTREME", + "issuer": "rwzfKxHPyzBbGidViMB9bw86WDFSFwMW9J", + "title": "XtremeXRPL", + "trustlines": 7951, + "placeInTop": 75 + }, + { + "id": 118, + "code": "NVL", + "issuer": "r458JvSEmmPwrRbEtmrHvWsGqZjq8jK6aE", + "title": "Northern VoIP NVL", + "trustlines": 13452, + "placeInTop": 76 + }, + { + "id": 181, + "code": "XParrot", + "issuer": "r3hmy4JpSHPWuomNsGtCkKrCqALakDAJiJ", + "title": "XParrots Official", + "trustlines": 9379, + "placeInTop": 77 + }, + { + "id": 16742, + "code": "XBAE", + "issuer": "rGc7CTU22AbPg8drYWTYsdGVk6nfssSPBK", + "title": "CLUBXBAE", + "trustlines": 4619, + "placeInTop": 78 + }, + { + "id": 18828, + "code": "xMAGIC", + "issuer": "rBHdammEERq7nxvHkzRzCUu872k3uQYVvg", + "title": "MAGICAL FINANCE", + "trustlines": 23411, + "placeInTop": 79 + }, + { + "id": 22, + "code": "XMETA", + "issuer": "r3XwJ1hr1PtbRvbhuUkybV6tmYzzA11WcB", + "title": "XMETA", + "trustlines": 30142, + "placeInTop": 80 + }, + { + "id": 17802, + "code": "TPR", + "issuer": "rht98AstPWmLPQMrwd9YDrcDoTjw9Tiu4B", + "title": "Tipper", + "trustlines": 17633, + "placeInTop": 81 + }, + { + "id": 18810, + "code": "XPHO", + "issuer": "rsVZrh3cvisTSHFcEZqPK1ioRzxbeG4PBk", + "title": "XRPhone", + "trustlines": 8958, + "placeInTop": 82 + }, + { + "id": 16904, + "code": "LXA", + "issuer": "rMa6n3DhTqwDGwU6ZquX1iSuaY1tCNLziY", + "title": "LamboXapes", + "trustlines": 6332, + "placeInTop": 83 + }, + { + "id": 18943, + "code": "SSM", + "issuer": "rPmrbwPKweofumw3fNBaoGdWDSc6wDKwKd", + "title": "FK\u20a5", + "trustlines": 4462, + "placeInTop": 84 + }, + { + "id": 16869, + "code": "XFLOKIs", + "issuer": "rKKDTqx8Lxw2sf2tUgNR81rvhRDhv5kFpz", + "title": "XFLOKI", + "trustlines": 6183, + "placeInTop": 85 + }, + { + "id": 277, + "code": "HADA", + "issuer": "rsR5JSisuXsbipP6sGdKdz5agjxn8BhHUC", + "title": "HADA Coin", + "trustlines": 7686, + "placeInTop": 86 + }, + { + "id": 16941, + "code": "HADALITE", + "issuer": "rHiPGSMBbzDGpoTPmk2dXaTk12ZV1pLVCZ", + "title": "HADALITE", + "trustlines": 8248, + "placeInTop": 87 + }, + { + "id": 116, + "code": "HCATS", + "issuer": "rGomRdjcf3e5zW5qjD7KU6YZmWbAk8u3hp", + "title": "XRPL HappyCats", + "trustlines": 7065, + "placeInTop": 88 + }, + { + "id": 18623, + "code": "JANGA", + "issuer": "r97C2XF4S5yzL2FzvXjdsyfYnswS4ee8TR", + "title": "Leo King\u2019s \ud83d\udc51 NFT", + "trustlines": 7120, + "placeInTop": 89 + }, + { + "id": 16739, + "code": "PARC", + "issuer": "rE42R1mbjGtMzzFTL5aqpbTrj3TDVq71jo", + "title": "Pixel Ape Rowboat Club", + "trustlines": 11880, + "placeInTop": 90 + }, + { + "id": 18139, + "code": "XRAIN", + "issuer": "rh3tLHbXwZsp7eciw2Qp8g7bN9RnyGa2pF", + "title": "XRPL RAINFOREST / $XRAIN", + "trustlines": 4529, + "placeInTop": 90 + }, + { + "id": 16636, + "code": "editions", + "issuer": "rfXwi3SqywQ2gSsvHgfdVsyZdTVM15BG7Z", + "title": "Editions", + "trustlines": 28590, + "placeInTop": 91 + }, + { + "id": 363, + "code": "GZX", + "issuer": "rNgsoCk6mjBq5jcqitBpg1gdfYhKajXsM2", + "title": "GreenZoneX", + "trustlines": 10026, + "placeInTop": 92 + }, + { + "id": 145, + "code": "TALENT", + "issuer": "r92SQCuWhYoB4w2UnKU7PKj4Mh7jSyemrH", + "title": "TalentChain", + "trustlines": 15209, + "placeInTop": 93 + }, + { + "id": 24, + "code": "XZillas", + "issuer": "rhwVLo1ckgcGSD6j7bF7BCPjuR3tshjbVM", + "title": "XZilla NFTs", + "trustlines": 11613, + "placeInTop": 93 + }, + { + "id": 16838, + "code": "Xpossum", + "issuer": "rfQr7LLaNvG93A3e2h6tjub3pi2oNTbvMA", + "title": "XRPL Awesome Possum", + "trustlines": 2704, + "placeInTop": 93 + }, + { + "id": 234, + "code": "XRPanda", + "issuer": "r9uQt7Y34SwSyKqdb5sMmAqk37rh3Y4V7", + "title": "XRPanda", + "trustlines": 10491, + "placeInTop": 94 + }, + { + "id": 16604, + "code": "Calorie", + "issuer": "rNqGa93B8ewQP9mUwpwqA19SApbf62U7PY", + "title": "Calorie Token", + "trustlines": 24080, + "placeInTop": 95 + }, + { + "id": 371, + "code": "LUSD", + "issuer": "rfL4Sci2ag5hhkpDuqtWYov6j3mshVWLgU", + "title": "Limited Currency", + "trustlines": 12638, + "placeInTop": 96 + }, + { + "id": 183, + "code": "NET", + "issuer": "rHi8oNeNe6JsKTaBbfhrQdEfDXHGhBcTXQ", + "title": "ZanyClub", + "trustlines": 7596, + "placeInTop": 96 + }, + { + "id": 18801, + "code": "OVX", + "issuer": "rDsvn6aJG4YMQdHnuJtP9NLrFp18JYTJUf", + "title": "onXRP", + "trustlines": 3056, + "placeInTop": 97 + }, + { + "id": 18897, + "code": "ArcX", + "issuer": "rHJUjSCYcgzF4CiYvi3UVdvXGbtXGJ6Fmz", + "title": "Arcade X (ARC-X)", + "trustlines": 1921, + "placeInTop": 98 + }, + { + "id": 16942, + "code": "DUCK", + "issuer": "rT5pAVAokKezWrjqnMBF3G8ah4fxVWVVx", + "title": "DuckRocket", + "trustlines": 8179, + "placeInTop": 99 + }, + { + "id": 16754, + "code": "SeagullCoin", + "issuer": "rnqiA8vuNriU9pqD1ZDGFH8ajQBL25Wkno", + "title": "Bored Seagull Club", + "trustlines": 7105, + "placeInTop": 99 + }, + { + "id": 16, + "code": "XWhales", + "issuer": "rw2LTTiDKAWMZeeNwvhxoYHMaCYMJretRR", + "trustlines": 10160, + "placeInTop": 100 + }, + { + "id": 324, + "code": "MLN", + "issuer": "rw5nJ4E54qtJYdX8WW6KeSaZjg67rGCR4K", + "title": "MLNToken", + "trustlines": 13673, + "placeInTop": 100 + }, + { + "id": 168, + "code": "xGoblin", + "issuer": "rpBCGo3e3cKqHoxjghK8AUeWYK2awGA6fB", + "title": "xGoblin", + "trustlines": 6357, + "placeInTop": 100 + }, + { + "id": 136, + "code": "XRSWAN", + "issuer": "rUHoc9nQyZzahE5rq26QHkvBmnvCEoPAhZ", + "title": "XRSwan", + "trustlines": 6414, + "placeInTop": 105 + }, + { + "id": 33, + "code": "XWWP", + "issuer": "roDdiwDhPUs8PVxq3S6buGSgNSmfVu9Xg", + "title": "XWWPlanes", + "trustlines": 6123, + "placeInTop": 108 + }, + { + "id": 241, + "code": "CCN", + "issuer": "rG1bDjT25WyvPz757YC9NqdRKyz9ywF8e8", + "title": "CollegeCoinNetwork", + "trustlines": 17205, + "placeInTop": 109 + }, + { + "id": 16752, + "code": "AFA", + "issuer": "ratAFAXeeKaVuAxuWB9W1LuXD5m7Aqf2BH", + "trustlines": 3206, + "placeInTop": 111 + }, + { + "id": 18275, + "code": "EMBRS", + "issuer": "rPbKMFvHbdEGBog98UjZXRdUx37MFKMfxB", + "title": "Quarter Onion Games", + "trustlines": 5207, + "placeInTop": 112 + }, + { + "id": 16732, + "code": "XRPitbull", + "issuer": "rfGDNXbLExX5BjktQYFgY4kfkK8mNzmuqJ", + "title": "XRPitbull", + "trustlines": 4364, + "placeInTop": 113 + }, + { + "id": 18384, + "code": "XRCS", + "issuer": "rn2aUSJ3mwt72pP4jx8JDyq9ipt9itDqcb", + "title": "XRCS", + "trustlines": 11934, + "placeInTop": 114 + }, + { + "id": 302, + "code": "FAKTURY", + "issuer": "rNwBNkHz3ZAnx77XFjGR8nRnZHw4gKgmpr", + "title": "FAKT\u00dcRY", + "trustlines": 3375, + "placeInTop": 115 + }, + { + "id": 16715, + "code": "DBX", + "issuer": "rHLJNqxCoPXdm4CnLd3w63ZFRqAUU2U4vS", + "title": "Dragonites X", + "trustlines": 19147, + "placeInTop": 116 + }, + { + "id": 261, + "code": "CNFT", + "issuer": "rUte5RZgB68nEU5QfjfM8qD6HtmKo5ebqo", + "title": "CollegeCoinNetwork / XBear NFTs", + "trustlines": 5555, + "placeInTop": 117 + }, + { + "id": 18137, + "code": "XCC", + "issuer": "rKuuRSQM2pTtv8ZrhQbU6kBgusCD79cem3", + "title": "X Charity Club", + "trustlines": 14433, + "placeInTop": 118 + }, + { + "id": 18007, + "code": "SNUB", + "issuer": "rNCRr79JC8YcA8pG4VAzhrshYxahKCodnX", + "title": "SnubNetwork", + "trustlines": 12879, + "placeInTop": 119 + }, + { + "id": 18419, + "code": "UtiliteX", + "issuer": "rKDsnVfFMzdqrU8Bqno37d29L8ZW3hvrf8", + "title": "TREASURY XRPL", + "trustlines": 7440, + "placeInTop": 120 + }, + { + "id": 95, + "code": "TGO", + "issuer": "rTGoNeK6vpu28U2oE5tiqhH3x8z2jBhxv", + "title": "ThingsGoOnline", + "trustlines": 7452, + "placeInTop": 121 + }, + { + "id": 18368, + "code": "GiezwaCoin", + "issuer": "r1taMrSAWRWJkaLhyCpPTnVj3qQmngzvN", + "title": "GiezwaCoin", + "trustlines": 9365, + "placeInTop": 122 + }, + { + "id": 17799, + "code": "xDREAMS", + "issuer": "raJt8azeqGixW8D1xSNLrELJu2m8W9EAit", + "title": "xDREAMS", + "trustlines": 4235, + "placeInTop": 123 + }, + { + "id": 16867, + "code": "XTRUMP", + "issuer": "rhvxJ9FjU1QCinoQStNkoZASbjqfBF7LN1", + "title": "XTRUMP", + "trustlines": 9761, + "placeInTop": 124 + }, + { + "id": 283, + "code": "PALEOCOIN", + "issuer": "rPfuLd1XmVyxkggAiT9fpLQU81GLb6UDZg", + "title": "Paleocoin", + "trustlines": 11851, + "placeInTop": 125 + }, + { + "id": 18590, + "code": "xBay", + "issuer": "rDVvK7xd2M6ZJr9a8suURWLqAeg7FyoDKT", + "title": "xBay", + "trustlines": 11993, + "placeInTop": 126 + }, + { + "id": 161, + "code": "xMochi", + "issuer": "rPaC5HFXToBuqHjSgjhmJaMejrF6DHRXKL", + "title": "xMochiDonuts", + "trustlines": 6718, + "placeInTop": 127 + }, + { + "id": 18935, + "code": "AEN", + "issuer": "rwuBxcFaUAEVYVDZvXvDXpHkEKoE8Zy1KX", + "trustlines": 10359, + "placeInTop": 128 + }, + { + "id": 1, + "code": "MRM", + "issuer": "rNjQ9HZYiBk1WhuscDkmJRSc3gbrBqqAaQ", + "title": "Mr. Mole", + "trustlines": 17995, + "placeInTop": 129 + }, + { + "id": 16787, + "code": "SSE", + "issuer": "rMDQTunsjE32sAkBDbwixpWr8TJdN5YLxu", + "title": "strategyengine", + "trustlines": 11438, + "placeInTop": 130 + }, + { + "id": 17297, + "code": "XRPLT", + "issuer": "rnSW2PRsGk5gX1q7xT5m3iyqrcR62yN9et", + "title": "XRPLToken", + "trustlines": 5175, + "placeInTop": 131 + }, + { + "id": 18833, + "code": "UCB", + "issuer": "rQUFEwFT1YdWbMFvCVh9CwbVndRdFEPdDf", + "title": "UCB Network", + "trustlines": 10101, + "placeInTop": 132 + }, + { + "id": 16711, + "code": "UVX", + "issuer": "r4XUTsMNJoT8Cs6rNHzbif5MpZ7sPH1nWF", + "title": "UnvaxCoin", + "trustlines": 15042, + "placeInTop": 133 + }, + { + "id": 18437, + "code": "XFAMOUS", + "issuer": "r9ZfGV6RpBNA6oewp3WLeyhE5fqvFaMoUs", + "title": "XrpFamous", + "trustlines": 8063, + "placeInTop": 134 + }, + { + "id": 16745, + "code": "xCIV", + "issuer": "rUampeA54U7Fcfwp5cxrRarS37eiaT44HB", + "title": "xCIV Pioneer", + "trustlines": 11001, + "placeInTop": 135 + }, + { + "id": 178, + "code": "XJOY", + "issuer": "rJAvx8FtrLR3RyZyM1LyVQFxxsLdT1PmdS", + "title": "SnwomanYC | Genesis Portal", + "trustlines": 5374, + "placeInTop": 136 + }, + { + "id": 16648, + "code": "xBillie", + "issuer": "rp3d8cQKMS4r68Gx12kcMxGQuyi9nT2Qm4", + "title": "xHustlers", + "trustlines": 4472, + "placeInTop": 137 + }, + { + "id": 144, + "code": "TBBOB", + "issuer": "rMxrnxXHi3aibzQ79B91AtzXBYfbPqbzZK", + "trustlines": 10064, + "placeInTop": 138 + }, + { + "id": 16672, + "code": "XRGary", + "issuer": "rCE2rxDDZtM7qkHAxorjkfLiHX71HtqTY", + "trustlines": 12664, + "placeInTop": 139 + }, + { + "id": 16651, + "code": "LOVE", + "issuer": "rDpdyF9LtYpwRdHZs8sghaPscE8rH9sgfs", + "trustlines": 16553, + "placeInTop": 140 + }, + { + "id": 18193, + "code": "LRUB", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency RUB", + "trustlines": 9095, + "placeInTop": 141 + }, + { + "id": 17290, + "code": "LGBP", + "issuer": "rfL4Sci2ag5hhkpDuqtWYov6j3mshVWLgU", + "title": "Limited Currency", + "trustlines": 9312, + "placeInTop": 142 + }, + { + "id": 191, + "code": "1MC", + "issuer": "rsJvPP7GVdPfe5zmQtvxAJVZAmDUGfhkV1", + "title": "1 Market Coin", + "trustlines": 13027, + "placeInTop": 143 + }, + { + "id": 213, + "code": "Cheetah", + "issuer": "rfLudDNMJeu3yD6wEPj37GJ9LBpQ4u42d6", + "title": "Cheetah", + "trustlines": 9762, + "placeInTop": 144 + }, + { + "id": 18356, + "code": "AnimaCoin", + "issuer": "rGQrZvndQsJV2S5cnSdiRFMPT1Fz1Ccvuj", + "title": "Anima", + "trustlines": 12433, + "placeInTop": 145 + }, + { + "id": 16800, + "code": "XMEMEcoin", + "issuer": "rWE5SePEBuVjJUoKXA6x87hQ59YPZCVD6", + "trustlines": 4816, + "placeInTop": 146 + }, + { + "id": 18756, + "code": "XCHN", + "issuer": "rHMcxniNwG8LG4tfrmNjoAjDBqWkp2b6d7", + "trustlines": 5123, + "placeInTop": 147 + }, + { + "id": 195, + "code": "Peas", + "issuer": "rPAArd4yZAJaDCR5gs41YYmGphfj6yzh3R", + "title": "XRPeas", + "trustlines": 14167, + "placeInTop": 148 + }, + { + "id": 18218, + "code": "LPHP", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency PHP", + "trustlines": 7448, + "placeInTop": 149 + }, + { + "id": 10, + "code": "XAVE", + "issuer": "rn2rjQnzbynPT2xNJhpBQ7e2aLxNnxLAfa", + "title": "XRP avengers", + "trustlines": 8485, + "placeInTop": 150 + }, + { + "id": 17878, + "code": "XrpVault", + "issuer": "rNJhYNbxtRj8upde5BQHRSUssNAoFwhc83", + "title": "The Vault", + "trustlines": 12902, + "placeInTop": 151 + }, + { + "id": 150, + "code": "DRT", + "issuer": "rfDhSfY5JMtCrje7hGxC8Gk6dC5PgNJh63", + "title": "DaRT Art Museum and Marketplace", + "trustlines": 23648, + "placeInTop": 152 + }, + { + "id": 18896, + "code": "XBT", + "issuer": "radcGxrrqjJb7KfJojjrN1Syn36UC5wQQ6", + "title": "XRStakeBet", + "trustlines": 2999, + "placeInTop": 153 + }, + { + "id": 38, + "code": "BumCrack", + "issuer": "rBuFBE8nx5Zpojj6EY3Lfh4sd1CHskFRC7", + "title": "B C XRP", + "trustlines": 13403, + "placeInTop": 154 + }, + { + "id": 16776, + "code": "XRPL3DAPES", + "issuer": "rLBW9d9cfEY4ZFPbgqKzEpoEHjKeLrotWZ", + "title": "3D Ape Club", + "trustlines": 16134, + "placeInTop": 155 + }, + { + "id": 18432, + "code": "XAI", + "issuer": "r9YmVNAQo9TAwDVW9WmUPdX8qRCdpeYKiE", + "title": "X-A.I. NFT", + "trustlines": 20123, + "placeInTop": 156 + }, + { + "id": 18206, + "code": "LJPY", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency JPY", + "trustlines": 8930, + "placeInTop": 157 + }, + { + "id": 16707, + "code": "SINGLE", + "issuer": "rhtmgZUbTzfH6xQKqqzF835uRVPFMqFYRe", + "title": "Single Interest", + "trustlines": 2422, + "placeInTop": 158 + }, + { + "id": 17879, + "code": "HMN", + "issuer": "rNUJ4qxmEEU8Et79nmdypwoDzSsuk437Rz", + "title": "HMN NFT", + "trustlines": 6289, + "placeInTop": 159 + }, + { + "id": 18213, + "code": "LSVC", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency SVC", + "trustlines": 3735, + "placeInTop": 160 + }, + { + "id": 18558, + "code": "GamerXGold", + "issuer": "rMczrvMki7DuXsuMf3zGUrqAmWvLKZNnt2", + "title": "GamerXGold", + "trustlines": 16249, + "placeInTop": 161 + }, + { + "id": 18216, + "code": "LKRW", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency KRW", + "trustlines": 5534, + "placeInTop": 162 + }, + { + "id": 17918, + "code": "XStack", + "issuer": "rHqNZURCvyrx4DZDF6rxYTpeaQnWrNB1tv", + "title": "xStackCoin", + "trustlines": 9752, + "placeInTop": 163 + }, + { + "id": 18036, + "code": "xRaccoon", + "issuer": "rKECgn8LhVwMtHyiGZcRu2H1e5mTo6Gdw", + "title": "xRaccoon", + "trustlines": 4490, + "placeInTop": 164 + }, + { + "id": 18924, + "code": "xGBR", + "issuer": "rnBG7eQk9pPZtfD19uBDyxBUoaYD4HUFWm", + "trustlines": 9475, + "placeInTop": 165 + }, + { + "id": 18990, + "code": "LUCKY", + "issuer": "rUjUNsSxPwTcd3YDTqDtfXHdmZd7C8tFkM", + "trustlines": 8215, + "placeInTop": 166 + }, + { + "id": 18282, + "code": "XRIV", + "issuer": "rMFeo6PiEqeZad4z29ivEj5adGp2TuH2YY", + "trustlines": 7815, + "placeInTop": 167 + }, + { + "id": 18321, + "code": "LIDR", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 5060, + "placeInTop": 168 + }, + { + "id": 17594, + "code": "NICE", + "issuer": "r96uXvCJxe3Yeeo9wCtJsLSpJiFUz2hvsB", + "title": "NICEToken", + "trustlines": 23772, + "placeInTop": 169 + }, + { + "id": 16809, + "code": "SCS", + "issuer": "rHYr9XNQJf1Kury1wGkht7Hrb9d43PqSMw", + "title": "CareShareCoin", + "trustlines": 5919, + "placeInTop": 170 + }, + { + "id": 18794, + "code": "CSX", + "issuer": "rG9eYQaixAV16SWpuDaQAZnfcaJx11DtJz", + "title": "C-S-XRP \u22b9", + "trustlines": 4374, + "placeInTop": 171 + }, + { + "id": 18424, + "code": "XUH", + "issuer": "rftZzz4iroaBENgwAPEgFRk2pHb3QrEDxe", + "title": "UNI HIVE | XUH", + "trustlines": 8135, + "placeInTop": 172 + }, + { + "id": 18060, + "code": "PXLTRBE", + "issuer": "rw9h8CvTmbZbhRwR6u7NPAwjcQuf3CaDoU", + "title": "xPixelTribes NFT", + "trustlines": 7845, + "placeInTop": 173 + }, + { + "id": 18890, + "code": "Gacha", + "issuer": "rsGsuzivHfnAP2S7hNAE4morQBZPdN4en3", + "title": "GACHA Lottery Club", + "trustlines": 3708, + "placeInTop": 174 + }, + { + "id": 16666, + "code": "GorillaGold", + "issuer": "rGQtGHrgN4FK1RcEn83q4t8aK6BobzDEMK", + "title": "GORILLA$Gold XRP", + "trustlines": 2828, + "placeInTop": 175 + }, + { + "id": 18323, + "code": "LVND", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 4483, + "placeInTop": 176 + }, + { + "id": 18583, + "code": "xwest", + "issuer": "rGxTL4RgJeJcN3SySxAQMnMnWXDEELjMfk", + "title": "XrpWest", + "trustlines": 3462, + "placeInTop": 177 + }, + { + "id": 18219, + "code": "LSGD", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency SGD", + "trustlines": 4406, + "placeInTop": 178 + }, + { + "id": 18288, + "code": "MDLR", + "issuer": "rMdLraa4jWJvpjpDpvmDEuVtjo3WwyWDKw", + "trustlines": 8329, + "placeInTop": 179 + }, + { + "id": 16794, + "code": "xianggang", + "issuer": "rMUqLuW4RpBvVAKNoaCubvbXgzuSnf6P8J", + "title": "xianggang.hk", + "trustlines": 10030, + "placeInTop": 180 + }, + { + "id": 16664, + "code": "ALGX", + "issuer": "ravdhDikgTwFsqFrJ3DuhVUYdAbxJLjdNT", + "title": "Cosmonaut NFT - Welcome to the Allegiance!", + "trustlines": 4511, + "placeInTop": 181 + }, + { + "id": 17937, + "code": "Maroo", + "issuer": "rEiqU48BP7iPDipRwU3cPQqo2EbDFi123P", + "title": "Burning Maroo", + "trustlines": 6139, + "placeInTop": 182 + }, + { + "id": 1381, + "code": "BTC", + "issuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", + "title": "GateHub BTC", + "trustlines": 115393, + "placeInTop": null + }, + { + "id": 17804, + "code": "GCB", + "issuer": "rNdwi8ain5ibXNB9A7H3zzKtSxgVzAqqAe", + "trustlines": 33404, + "placeInTop": null + }, + { + "id": 16601, + "code": "ETH", + "issuer": "rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h", + "title": "Gatehub Fifth", + "trustlines": 90505, + "placeInTop": null + }, + { + "id": 320, + "code": "PASA", + "issuer": "rBPtuMc4HBR1SuZyZv8hs7WBVxLBYrzxbY", + "title": "PASA COIN", + "trustlines": 25615, + "placeInTop": null + }, + { + "id": 16599, + "code": "KGE", + "issuer": "rNhSjAMnDJc9tDHH1R4sqggvgDGa8Bwj5T", + "title": "Kinjo Gear", + "trustlines": 21319, + "placeInTop": null + }, + { + "id": 16615, + "code": "Daric", + "issuer": "rK9AtihZZYWAwZQnJCYzZnyW833vbcPXPf", + "title": "Daric", + "trustlines": 21573, + "placeInTop": null + }, + { + "id": 17976, + "code": "XRWeb", + "issuer": "rDegPvsK5c2nzaKTn2PsuPZjs8b3neDDn", + "title": "XRWeb", + "trustlines": 21889, + "placeInTop": null + }, + { + "id": 252, + "code": "CodeCoin", + "issuer": "rGbsKNrVURRfU1WEb1aEqaoyRJDkvssyBa", + "title": "CodeCoin", + "trustlines": 21750, + "placeInTop": null + }, + { + "id": 17676, + "code": "xCBS", + "issuer": "rNvhXtgDdd4Sh3NKLXcUH9Hozs4dqu62we", + "title": "xCBS | XRPL", + "trustlines": 21483, + "placeInTop": null + }, + { + "id": 18353, + "code": "HMC", + "issuer": "raXY8RFAKixBZGw8nhDRHvVCKd2RzeoeLq", + "title": "Himalaya", + "trustlines": 24770, + "placeInTop": null + }, + { + "id": 16617, + "code": "XUM", + "issuer": "r465PJyGWUE8su1oVoatht6cXZJTg1jc2m", + "title": "XUM Universal Money", + "trustlines": 26206, + "placeInTop": null + }, + { + "id": 16644, + "code": "APXX", + "issuer": "rL2sSC2eMm6xYyx1nqZ9MW4AP185mg7N9t", + "title": "ApexSafariXRPL", + "trustlines": 19066, + "placeInTop": null + }, + { + "id": 17671, + "code": "XGF", + "issuer": "rJnn9jdwaBfuyq383hNiX2oowLuLUm2DZD", + "title": "XGF | xGreen Fund", + "trustlines": 18777, + "placeInTop": null + }, + { + "id": 17149, + "code": "PGN", + "issuer": "rPUSoeJaHQzrXATtGniVjwBQQDEtJcdwFq", + "title": "PGN", + "trustlines": 17529, + "placeInTop": null + }, + { + "id": 16611, + "code": "Gift", + "issuer": "rBXXRBZ46rwCkS9mHom3WW8u7gSytb5KcZ", + "title": "XRPLGift", + "trustlines": 18129, + "placeInTop": null + }, + { + "id": 17184, + "code": "XRTemplate", + "issuer": "rMX54z8VgtRhPefzqVkdG3LxsuGdFQcXxr", + "title": "XRTemplate", + "trustlines": 16944, + "placeInTop": null + }, + { + "id": 236, + "code": "Zinfinite", + "issuer": "rGMU2cbbMhzodpecrjLQ2A814DqL8LFxjY", + "title": "Zinfinite", + "trustlines": 16848, + "placeInTop": null + }, + { + "id": 194, + "code": "XRSoftware", + "issuer": "rJZ9Hpaeqy3fdBvjVUjx1fW1bE75HgaJbr", + "title": "XRSoftware", + "trustlines": 15507, + "placeInTop": null + }, + { + "id": 16639, + "code": "NFTL", + "issuer": "r3DCE2UVaqQaGQragAjmwL6kNicF2rw6PL", + "title": "Elliot | NFT-Loving.com", + "trustlines": 15355, + "placeInTop": null + }, + { + "id": 16622, + "code": "CNY", + "issuer": "razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA", + "title": "RippleChina", + "trustlines": 29660, + "placeInTop": null + }, + { + "id": 201, + "code": "ELM", + "issuer": "rQB9HhhBCq2zAVpwQD3jV9ja39DmomdWj1", + "title": "Elements ELM", + "trustlines": 14957, + "placeInTop": null + }, + { + "id": 18110, + "code": "XTriviA", + "issuer": "rhLr8bGvHvBgYXAHNPyXrQAcKGrQ2X5nU4", + "title": "XTriviA", + "trustlines": 14954, + "placeInTop": null + }, + { + "id": 16713, + "code": "GOLD", + "issuer": "rGQtGHrgN4FK1RcEn83q4t8aK6BobzDEMK", + "title": "Eric \ud83c\uddfa\ud83c\uddf8 #GOLD \ud83c\uddfa\ud83c\uddf2 #XRP $10+ \ud83c\uddfa\ud83c\uddf8 #XMETA", + "trustlines": 14949, + "placeInTop": null + }, + { + "id": 16917, + "code": "xHulk", + "issuer": "r43PooeaFyp2cCfqxMkZLu47VKUDaCzQVt", + "title": "xHulk", + "trustlines": 14996, + "placeInTop": null + }, + { + "id": 9, + "code": "BBulldoge", + "issuer": "r3b8BtKC4d8r4Je7PDJhzAgNTLR64seTDu", + "title": "Bully Bulldoge - #BBULLDOGE", + "trustlines": 14549, + "placeInTop": null + }, + { + "id": 17401, + "code": "TipCoin", + "issuer": "rsUjMrcGu8ANoTwv3zUJE6MzSL6K7fMyPU", + "title": "TipCoin", + "trustlines": 15215, + "placeInTop": null + }, + { + "id": 16673, + "code": "BlackFriday", + "issuer": "raFpHssoH3rWkMy9XLjA6NDRW2y44tiFVM", + "title": "Black Friday", + "trustlines": 13377, + "placeInTop": null + }, + { + "id": 16650, + "code": "XWM", + "issuer": "rJzBh2Sktnps8CoLVJeDjj3Y2aDzXhrAFL", + "title": "XWM World Money", + "trustlines": 17655, + "placeInTop": null + }, + { + "id": 16886, + "code": "XRBear", + "issuer": "rKxqkAbT2BQUbtnknSAJon7kX89gUKpZu3", + "title": "XRBear coin", + "trustlines": 13084, + "placeInTop": null + }, + { + "id": 16663, + "code": "OCEAN", + "issuer": "rPCrPJ9Uz988tD1aQVAToioDcCGZ8nbBTn", + "title": "Ocean Assist", + "trustlines": 12827, + "placeInTop": null + }, + { + "id": 235, + "code": "PIN", + "issuer": "rhx9yNhbo7xtTy6rBY8xrUYkuYdyVs5Arb", + "title": "XrPiNFT", + "trustlines": 13570, + "placeInTop": null + }, + { + "id": 17626, + "code": "CNY", + "issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y", + "title": "Ripple Fox", + "trustlines": 17733, + "placeInTop": null + }, + { + "id": 16619, + "code": "POKER", + "issuer": "rfNWXEENu93dvCBnjpFY7mRpprZzBUx8hC", + "title": "POKER", + "trustlines": 14229, + "placeInTop": null + }, + { + "id": 5, + "code": "Cake", + "issuer": "ra1XmvmraMiRYarFrHEU7XDojvRyipU5Vg", + "title": "Cake Coin", + "trustlines": 11637, + "placeInTop": null + }, + { + "id": 16949, + "code": "SPREAD", + "issuer": "rwPzJd39swHT6NfxvgGFYE7q9q7EcqKuKW", + "title": "Spread Love Token", + "trustlines": 12228, + "placeInTop": null + }, + { + "id": 17657, + "code": "MONTEZUMA", + "issuer": "rNJpp2TXWrtFfNs8mbEsrj8gj6XVHfHywD", + "title": "MONTEZUMA", + "trustlines": 12153, + "placeInTop": null + }, + { + "id": 208, + "code": "XRsaitama", + "issuer": "r3nEJus5Ryoo9ckNmY8XHogoPnLfP1unFv", + "title": "XRsaitama", + "trustlines": 12179, + "placeInTop": null + }, + { + "id": 18439, + "code": "FRD", + "issuer": "rEsdnLqeFgXSTeexMAFB7ZauRoMMWvSL4e", + "title": "Ferda Network \ud83d\udc7e", + "trustlines": 12268, + "placeInTop": null + }, + { + "id": 16676, + "code": "XSD", + "issuer": "r9PwqmHiGiE7yAXmG5mk7wSJAeezLqE7Ei", + "title": "XStreetDogs", + "trustlines": 11981, + "placeInTop": null + }, + { + "id": 18259, + "code": "xFlashChain", + "issuer": "rJgcjY1MZJjw946qRqN57V3TGg9PZEA1bw", + "title": "xFlashchain Official", + "trustlines": 11626, + "placeInTop": null + }, + { + "id": 16608, + "code": "SimbaXRP", + "issuer": "rDqwjJ8fUqdyfPjJZ3h93J1XY8hz6CjEYo", + "title": "Simba XRP", + "trustlines": 12056, + "placeInTop": null + }, + { + "id": 18171, + "code": "XrPinoy", + "issuer": "rt3hn4EYMV39xd7pwqMHacJVSu2kEA18x", + "title": "XrPinoy", + "trustlines": 10791, + "placeInTop": null + }, + { + "id": 18574, + "code": "GEN", + "issuer": "r9sSz7JA6NWfPq9cPtEbg4rDBdVuLHi68g", + "title": "ProjectGenesisXrpl", + "trustlines": 11616, + "placeInTop": null + }, + { + "id": 340, + "code": "XDogelon", + "issuer": "rNFKrSUW1xKzDwHz8J9uVAs4GpxtEUoAsF", + "title": "XDogelon", + "trustlines": 11207, + "placeInTop": null + }, + { + "id": 370, + "code": "UMMO", + "issuer": "rfGqDiFegcMm8e9saj48ED74PkotwJCmJd", + "title": "Ummo", + "trustlines": 10875, + "placeInTop": null + }, + { + "id": 180, + "code": "XRMOON", + "issuer": "rBBh2z5wsxE9gcVE2yUU39UntvRMHDKPpq", + "title": "XRMOON COIN", + "trustlines": 11005, + "placeInTop": null + }, + { + "id": 18170, + "code": "XJWL", + "issuer": "rMatwcXVLoRq7yoRR7KNT4Kg8ft8osnJKi", + "title": "xJewelryNFT", + "trustlines": 10318, + "placeInTop": null + }, + { + "id": 18315, + "code": "MORTAL", + "issuer": "rHyCV84JWbmhbCAJYpcd76HXcnhJ4uY4Q7", + "title": "Mortal Kombat", + "trustlines": 9986, + "placeInTop": null + }, + { + "id": 19036, + "code": "OVO", + "issuer": "r3jQzqfGVagsWNbSxMJpQV8VK7jHHmdv5j", + "trustlines": 15978, + "placeInTop": null + }, + { + "id": 16720, + "code": "XRTEACH", + "issuer": "r42RmRk42JYCAvzSifSGAgsZuJBUm2yJSE", + "title": "XRTEACH", + "trustlines": 9477, + "placeInTop": null + }, + { + "id": 367, + "code": "Fluff", + "issuer": "rG81ZwWpJbwvkrSPMWYRNAQ4JrYVGzDZfx", + "title": "xCorgi | Fluff", + "trustlines": 10425, + "placeInTop": null + }, + { + "id": 2, + "code": "WEED", + "issuer": "raDF491dC9XF1guG5zZkWWpNQoFquwt2cp", + "trustlines": 9287, + "placeInTop": null + }, + { + "id": 17959, + "code": "Lil2", + "issuer": "rENNLZVLWMLvwM69h4fR8B43qptEUUG4QH", + "title": "LilShib", + "trustlines": 10031, + "placeInTop": null + }, + { + "id": 366, + "code": "ELVC", + "issuer": "rLb593mo6Nerw7eWvC19gR1fLggeWX6u6s", + "title": "Tuition DeFi Network", + "trustlines": 9811, + "placeInTop": null + }, + { + "id": 16801, + "code": "IRE", + "issuer": "rfTYvAG86Y1L61RQjbxHTyJmphYzHgguCd", + "title": "IRE - Blockchain in Real Estate", + "trustlines": 10438, + "placeInTop": null + }, + { + "id": 16766, + "code": "INL", + "issuer": "rsARtxd6M1RBoGKwoGh4ujCQ9A6iDYEu4s", + "title": "InNewLife", + "trustlines": 15356, + "placeInTop": null + }, + { + "id": 18125, + "code": "Blessed", + "issuer": "rQr6VLuduLeFoWBERechDs3NB2cynoujhQ", + "title": "Blessed Token", + "trustlines": 9175, + "placeInTop": null + }, + { + "id": 131, + "code": "Planet", + "issuer": "rntdU6TyFEjyf8JRkrG3ggJnsFF55zRSq3", + "title": "Planet Coin", + "trustlines": 9242, + "placeInTop": null + }, + { + "id": 123, + "code": "XReddog", + "issuer": "rUWpPmEHBQfb6xgcTtVrGUK5ppztU5GDUF", + "title": "XReddog", + "trustlines": 9265, + "placeInTop": null + }, + { + "id": 16643, + "code": "DIA", + "issuer": "rQfWiU7kRbJBFSXRyQh7GTUeEP6qJ4kp2F", + "title": "DIA_PROJECT official", + "trustlines": 12387, + "placeInTop": null + }, + { + "id": 21, + "code": "EliteX", + "issuer": "rPHqxokbdbRHFZ6RLN4TqJrYd3zF3kE1Vd", + "title": "EliteX", + "trustlines": 8659, + "placeInTop": null + }, + { + "id": 14, + "code": "xNinjas", + "issuer": "r9am4mhbWRByfAU7bYAzmN15pGKT3mDW8p", + "title": "xNinjas", + "trustlines": 12892, + "placeInTop": null + }, + { + "id": 18071, + "code": "PZA", + "issuer": "rBi1kmPw7axgkTDG31GFhtuACaveohfYnj", + "title": "XRPLPIZZA \ud83c\udf55", + "trustlines": 8890, + "placeInTop": null + }, + { + "id": 18295, + "code": "RCN", + "issuer": "rPGxCzMicNJRe1U1CD5QyNjXdLUtdgSa7B", + "title": "ToonRaccoon", + "trustlines": 8559, + "placeInTop": null + }, + { + "id": 16850, + "code": "Women", + "issuer": "rGiv7xKG4ShaRz4KPxgzXC1teMBxnaPyRU", + "title": "Women Coin", + "trustlines": 7767, + "placeInTop": null + }, + { + "id": 16894, + "code": "XRCATE", + "issuer": "rjb1JbdHruegp7vmcPmDU5EzYgTUtuLLK", + "title": "XRcate", + "trustlines": 8356, + "placeInTop": null + }, + { + "id": 86, + "code": "XRPets", + "issuer": "rESKaHTUhDD5mT2p1H99Y3KeJVc6Y7t4vx", + "title": "XRPets", + "trustlines": 7947, + "placeInTop": null + }, + { + "id": 16722, + "code": "XWSB", + "issuer": "rLpL5d9qubKjht8GnkxgnVTQPq9MKNc757", + "title": "XRPwallstreetbets", + "trustlines": 10563, + "placeInTop": null + }, + { + "id": 172, + "code": "QLX", + "issuer": "rMQhWnHuYVmjRcyptPvrueKdLvjQT2Nquc", + "title": "Aquila X", + "trustlines": 11564, + "placeInTop": null + }, + { + "id": 16706, + "code": "CBT", + "issuer": "rJbpn78QJxKpwz8daMCHxHVWauq3xdMn9M", + "title": "CannaToken", + "trustlines": 7748, + "placeInTop": null + }, + { + "id": 17568, + "code": "JPY", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 13287, + "placeInTop": null + }, + { + "id": 199, + "code": "ARUNA", + "issuer": "rfjqDHJ7FQyYFqeaUqTEdpEaxiQLv4LzKm", + "title": "ARUNA XRPL", + "trustlines": 6993, + "placeInTop": null + }, + { + "id": 17685, + "code": "CNY", + "issuer": "rPT74sUcTBTQhkHVD54WGncoqXEAMYbmH7", + "title": "RippleQK", + "trustlines": 11321, + "placeInTop": null + }, + { + "id": 17967, + "code": "XCSI", + "issuer": "rn7jqzyoMbECYPgH1j5AcsegKXNrc8JU91", + "title": "XCSI - Cat Society NFT", + "trustlines": 7310, + "placeInTop": null + }, + { + "id": 157, + "code": "X3DPUNK", + "issuer": "rKGMZbfKhVhmbQU5DwGgF6tgQMzzK5ydxr", + "title": "X3D PUNK", + "trustlines": 8992, + "placeInTop": null + }, + { + "id": 16789, + "code": "Zini", + "issuer": "rGCoUJgDA3CzdohdPr9vX7p1p2kixaASnK", + "title": "Zinfinite", + "trustlines": 6578, + "placeInTop": null + }, + { + "id": 17178, + "code": "XSharkNFT", + "issuer": "rGtBVC62X6vPQP2gpaaHNHGxhNxGx7FEfH", + "title": "XSharkNFT", + "trustlines": 6865, + "placeInTop": null + }, + { + "id": 77, + "code": "XREbook", + "issuer": "rMgfWUcBd45TXyszMb5LnopD5SDnAmDZsR", + "title": "XREbook", + "trustlines": 6950, + "placeInTop": null + }, + { + "id": 16885, + "code": "XSch", + "issuer": "rG8Ccx5i9m3mJQFreD3cyggQH1pd5WziZy", + "title": "xSchnauzer (XRPL)", + "trustlines": 6527, + "placeInTop": null + }, + { + "id": 16887, + "code": "XFLOKIVERSE", + "issuer": "rwCCrZuotr9P5XmfoSY1ibVzRecLcwtxp3", + "title": "XFLOKIVERSE", + "trustlines": 8302, + "placeInTop": null + }, + { + "id": 18613, + "code": "NEKASHI", + "issuer": "r46H9jJc6T5aTkC1J1oFHFFxHG2HXGEndc", + "trustlines": 8079, + "placeInTop": null + }, + { + "id": 16981, + "code": "XRPoo", + "issuer": "rpa7p8yeDFNkNsFEiR1ZHQqxjsLzBUmwzD", + "title": "XRPoo", + "trustlines": 6325, + "placeInTop": null + }, + { + "id": 18446, + "code": "99D", + "issuer": "rNg8LsQefsxWF2snf4BaVgg7CjAtz6BYVc", + "title": "99DAYS", + "trustlines": 14829, + "placeInTop": null + }, + { + "id": 18964, + "code": "HDT", + "issuer": "rEmLBstEaNQqgodQnTMUn9UtkRPi3ieAWu", + "title": "HoldDiamondToken", + "trustlines": 8522, + "placeInTop": null + }, + { + "id": 17684, + "code": "BTC", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "title": "BTC 2 Ripple", + "trustlines": 10166, + "placeInTop": null + }, + { + "id": 28, + "code": "XShibanu", + "issuer": "rpD9gvxp7RCKrTrYAyTdueF9ApApbP5ZnN", + "title": "XShibanu", + "trustlines": 7601, + "placeInTop": null + }, + { + "id": 26, + "code": "LogX", + "issuer": "rw1mh3E436LfF2MDrHJNm65wbRG2ZCM3kv", + "trustlines": 6262, + "placeInTop": null + }, + { + "id": 16612, + "code": "XPG", + "issuer": "rfJwWjrxZU5Vsk9sTVXKLdyrce1CDTjhDf", + "title": "XRPiggies Official", + "trustlines": 8162, + "placeInTop": null + }, + { + "id": 16836, + "code": "SKS", + "issuer": "rNspzJyaq7D2aHB8npk1pg2NuR8BrJ78L3", + "trustlines": 8073, + "placeInTop": null + }, + { + "id": 16876, + "code": "SPOOKYX", + "issuer": "r3L2Lcnm3o2tnDw2fmNiVJDKtFDYTgUksj", + "title": "SPOOKYX", + "trustlines": 6483, + "placeInTop": null + }, + { + "id": 17930, + "code": "BTC", + "issuer": "rpJZ5WyotdphojwMLxCr2prhULvG3Voe3X", + "trustlines": 5947, + "placeInTop": null + }, + { + "id": 18014, + "code": "OnlyFans", + "issuer": "rhvfUwU2qkNfeUdrDp28xF4xXDxu6xhBVz", + "title": "OnlyFansXRP", + "trustlines": 7123, + "placeInTop": null + }, + { + "id": 377, + "code": "TEEF", + "issuer": "rfcdWJmFhMVomRM5MqniSZuYfCyZyvRkkh", + "title": "teefcoin", + "trustlines": 7141, + "placeInTop": null + }, + { + "id": 16750, + "code": "XRdragon", + "issuer": "rEMzCUeRZbx6ydthbi2RVNYdRbuqqsrYof", + "title": "XRdragon", + "trustlines": 6804, + "placeInTop": null + }, + { + "id": 333, + "code": "XLE", + "issuer": "rnQPyTLGVD1exGZCBD9XcXvbedMG7Sy4iU", + "title": "XLEcoin", + "trustlines": 6077, + "placeInTop": null + }, + { + "id": 17045, + "code": "nexit", + "issuer": "r9prnHupoaKPkGkhiPVtcUJjZ2uUHymtX1", + "title": "Nexit", + "trustlines": 5874, + "placeInTop": null + }, + { + "id": 245, + "code": "BTCX", + "issuer": "rBnuiD54Mk4FmeU8WhBfstxnB5XEFt4QBQ", + "title": "Bitcoin X", + "trustlines": 8889, + "placeInTop": null + }, + { + "id": 253, + "code": "XTK", + "issuer": "rXTKdHWuppSjkbiKoEv53bfxHAn1MxmTb", + "title": "Kudos", + "trustlines": 24440, + "placeInTop": null + }, + { + "id": 16624, + "code": "FUX", + "issuer": "r9S8MujtHbaVfoLeSdGyXus9TR9UbgyD3Y", + "title": "FUX", + "trustlines": 9795, + "placeInTop": null + }, + { + "id": 217, + "code": "KAT", + "issuer": "rEmWWStdxxLUtTAzhRS5VWvtjtNUwWcfeM", + "title": "Katz Army", + "trustlines": 8796, + "placeInTop": null + }, + { + "id": 18212, + "code": "XCHANGE", + "issuer": "rwdsHNyqF3i6iEbpD5mXytNNEdHQdEMHjm", + "title": "XCHANGE", + "trustlines": 5315, + "placeInTop": null + }, + { + "id": 16734, + "code": "SPK", + "issuer": "rBGuYfE3H3AyhWANSe4fKrf8z1aF7hFznB", + "title": "SPARKER TOKEN $SPK", + "trustlines": 6048, + "placeInTop": null + }, + { + "id": 16670, + "code": "STC", + "issuer": "r4J3XtWvu869Jnk2zLp9jnzcA37VeqpPiA", + "title": "Streetcoin", + "trustlines": 5092, + "placeInTop": null + }, + { + "id": 163, + "code": "DKP", + "issuer": "rM7zpZQBfz9y2jEkDrKcXiYPitJx9YTS1J", + "title": "DragonKill", + "trustlines": 10358, + "placeInTop": null + }, + { + "id": 18430, + "code": "CRX", + "issuer": "r4RHp5hGQvi3tibM7Zpj4Hzr2DK97uSrsF", + "title": "CRX", + "trustlines": 4826, + "placeInTop": null + }, + { + "id": 270, + "code": "LuckyCharms", + "issuer": "rECNBBTFzyqdMMp44V74DAdqctvi2qDTPd", + "title": "LuckyCharmsCoin", + "trustlines": 6675, + "placeInTop": null + }, + { + "id": 109, + "code": "GRIME", + "issuer": "r3Xh1GTVBPooVkc5jWSAtzKXQPPPFJKgb9", + "title": "Grime Coin", + "trustlines": 15670, + "placeInTop": null + }, + { + "id": 27, + "code": "IRV", + "issuer": "rueUkJYAQnP9wqHrKQXrsCwXeRziVn377", + "trustlines": 4772, + "placeInTop": null + }, + { + "id": 359, + "code": "RUG", + "issuer": "rfEJ1ksD22TsCy8hdKoJuC6Xfc33VCKPUs", + "title": "TheRugPhxProject", + "trustlines": 5644, + "placeInTop": null + }, + { + "id": 17707, + "code": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "title": "SnapSwap", + "trustlines": 7848, + "placeInTop": null + }, + { + "id": 223, + "code": "Arcverse", + "issuer": "rUwA9KAe3i3W2yhqh3Lc4XTJCo4bTpdPcX", + "title": "Arcverse", + "trustlines": 4753, + "placeInTop": null + }, + { + "id": 16884, + "code": "XRS", + "issuer": "rfM28hjg87v52SVyCxHuG1VnqqDpb4Bm5Y", + "title": "XRSQUID", + "trustlines": 5681, + "placeInTop": null + }, + { + "id": 18604, + "code": "MDK", + "issuer": "r4pDW1tz1ynuqBAnaf4ReDwpZQCKGv3qdN", + "title": "MR. Desert King", + "trustlines": 8617, + "placeInTop": null + }, + { + "id": 18477, + "code": "FUEL", + "issuer": "rJ4KcB9GueMDYzYzr7XR3j4jd82gG7tbEt", + "title": "Fuel Save", + "trustlines": 4501, + "placeInTop": null + }, + { + "id": 16731, + "code": "XSAMO", + "issuer": "rMVufScdiavFvGzVBZryTTRu2DxRuEqos5", + "title": "xSamoyed", + "trustlines": 6374, + "placeInTop": null + }, + { + "id": 18850, + "code": "WWW", + "issuer": "rpzuRVqQmQeh5Pw2JYgNgLUBdinuNk4ozR", + "title": "World Wild Web on XRP \ud83c\udf10", + "trustlines": 4586, + "placeInTop": null + }, + { + "id": 16736, + "code": "XHASBULL", + "issuer": "rpMevF5zLLyFm2eNd9KUMe6bcyafRHuSnh", + "title": "XRP Hasbulla", + "trustlines": 5824, + "placeInTop": null + }, + { + "id": 17209, + "code": "ALV", + "issuer": "raEQc5krJ2rUXyi6fgmUAf63oAXmF7p6jp", + "title": "Allvor", + "trustlines": 6750, + "placeInTop": null + }, + { + "id": 18205, + "code": "XSHIO", + "issuer": "rGxSsVktuy9aCzckSbTf3yQissD3oD2va5", + "title": "Xshio NFT", + "trustlines": 9700, + "placeInTop": null + }, + { + "id": 17921, + "code": "TROPA", + "issuer": "r9BSm9VwgFKCsjksYuzHJ1QebneFyrS5bu", + "title": "CrypTropa_NFT", + "trustlines": 11884, + "placeInTop": null + }, + { + "id": 16900, + "code": "EGG", + "issuer": "rfPhEGpVC72d29oxghgA9Zjuas65pMg1Df", + "title": "EggsRP", + "trustlines": 5139, + "placeInTop": null + }, + { + "id": 294, + "code": "CCR", + "issuer": "rPAKJxHcHEkjPtpsSTGGMWLvpibZiuRJHi", + "trustlines": 4287, + "placeInTop": null + }, + { + "id": 16956, + "code": "BabyThug", + "issuer": "rHGjhK35BEH42VYQh5cSXADNHh6HeWj116", + "title": "BabyThug", + "trustlines": 4325, + "placeInTop": null + }, + { + "id": 281, + "code": "PXCS", + "issuer": "rNwgtMaqziGqLiDzeXE91B8D2cB5m2fDhc", + "trustlines": 4330, + "placeInTop": null + }, + { + "id": 16847, + "code": "ShibaInuX", + "issuer": "r4N5gak4yRXHyCvTbJggHvbYLRCbM8E9YT", + "title": "ShibaInuX ", + "trustlines": 6431, + "placeInTop": null + }, + { + "id": 18093, + "code": "MetaGEX", + "issuer": "r3zLnFU24bSsYrAo55mJxYXccnojjCHmpK", + "title": "MetaGEX", + "trustlines": 4536, + "placeInTop": null + }, + { + "id": 346, + "code": "XSaitama", + "issuer": "rwUSJdemymbvn8WRa3WutKkVMrjkLpiFNy", + "title": "XSaitama", + "trustlines": 4312, + "placeInTop": null + }, + { + "id": 17259, + "code": "NBA", + "issuer": "rfHALygLHj6EwfXNSFCSLX6Kp3yfFSpuuP", + "title": "NBA Coin || XRPL", + "trustlines": 11529, + "placeInTop": null + }, + { + "id": 16740, + "code": "SURV", + "issuer": "rHrjHawcjA2TihAqtE7nWzArpWkiFseHM4", + "title": "SURV", + "trustlines": 4520, + "placeInTop": null + }, + { + "id": 16952, + "code": "Hellbound", + "issuer": "rML7dFchDJHHMwgCbSYE26TM2qdYnBWe9r", + "title": "Hellbound", + "trustlines": 4196, + "placeInTop": null + }, + { + "id": 16848, + "code": "XFIT", + "issuer": "rwAe5VFjRhwwgCnAGmEfb33kkWYqR8mnoh", + "title": "XFitness", + "trustlines": 5497, + "placeInTop": null + }, + { + "id": 16798, + "code": "CAP", + "issuer": "rBwMPfwkB3kFJd65ui4JZmDvEzEequMLnJ", + "title": "Captain Coin", + "trustlines": 4379, + "placeInTop": null + }, + { + "id": 16899, + "code": "BabyXRdoge", + "issuer": "rH9mTEofDstHfgT83kutpT5fJgKxKBAvqv", + "title": "BabyXRdoge", + "trustlines": 5036, + "placeInTop": null + }, + { + "id": 52, + "code": "xNUTz", + "issuer": "rNutz8XfqZgVRDhtdo6Z2MeuVaqB9zfZkw", + "title": "XRPL NUTZ", + "trustlines": 5053, + "placeInTop": null + }, + { + "id": 16930, + "code": "MRG", + "issuer": "rHpwTr391FggkYS1PPkfkM4NGTgF57YuLC", + "title": "Merge MRG", + "trustlines": 5200, + "placeInTop": null + }, + { + "id": 16903, + "code": "xBullwife", + "issuer": "rHNdmrLoen999m7HJpTDVPtazcRzn9DMdH", + "title": "xBullwife", + "trustlines": 3942, + "placeInTop": null + }, + { + "id": 17999, + "code": "YOL", + "issuer": "rMJEj344HVDBVy5KH8fcnDfwQCmQ4H2HPF", + "title": "yourOwnLanguage NFT", + "trustlines": 8663, + "placeInTop": null + }, + { + "id": 16678, + "code": "BRM", + "issuer": "rBpYMFddcphoZDv5MswtyMp9hV6b49bEcV", + "title": "BerylliumNFT", + "trustlines": 4079, + "placeInTop": null + }, + { + "id": 16728, + "code": "XElon", + "issuer": "rfwbcU8RkmVaEEHhX2yqvLyw5xhaugbvSm", + "title": "XElon", + "trustlines": 5536, + "placeInTop": null + }, + { + "id": 11, + "code": "XMoon", + "issuer": "rEvV6gWZn2HgEr3nkj3xBmgCTqyWcknjMD", + "title": "XMoon", + "trustlines": 6506, + "placeInTop": null + }, + { + "id": 17974, + "code": "UFm", + "issuer": "rUNFLAKEnWicv4XsnoZrmSN1pXSWSMgZXc", + "title": "Unflake", + "trustlines": 7816, + "placeInTop": null + }, + { + "id": 18005, + "code": "KATANA", + "issuer": "r3uVTkdzg8BGvfChrpMetH5noSn5XawzMz", + "title": "xKATANA", + "trustlines": 7137, + "placeInTop": null + }, + { + "id": 16881, + "code": "XrplSquid", + "issuer": "rbgAh9aQV7ij8cNifgFgtiebU6vm9yXci", + "title": "Xrpl Squid", + "trustlines": 4071, + "placeInTop": null + }, + { + "id": 16804, + "code": "RCN", + "issuer": "r4GquJLRTAmEMLECBKaSMLB8pV4dmLWNxX", + "trustlines": 6501, + "placeInTop": null + }, + { + "id": 249, + "code": "MGS", + "issuer": "rHP4bHzghBdzskqcaPciL5WRGkHosB5zYx", + "title": "MG.Social", + "trustlines": 13074, + "placeInTop": null + }, + { + "id": 16748, + "code": "XLOTTO", + "issuer": "rHoPikA81frt2FSNaW5ZHdv6LfKZfeqguY", + "title": "XRP Lotto", + "trustlines": 3971, + "placeInTop": null + }, + { + "id": 315, + "code": "XBE", + "issuer": "rJvcZcc8BfJXvWtV7sGJPNXTgdv8AvXKRJ", + "title": "XBE | XBee Mekaverse NFT", + "trustlines": 6004, + "placeInTop": null + }, + { + "id": 16819, + "code": "EroticCoin", + "issuer": "rN7kuYm6QbuVHtcybjZcdjvHP1XNFtNjah", + "title": "EroticCoin", + "trustlines": 4465, + "placeInTop": null + }, + { + "id": 67, + "code": "ConservationCoin", + "issuer": "rU6JmvUpdxsx8gxLLgfcRhfz5ASNmGpSwm", + "title": "ConservationCoin", + "trustlines": 9868, + "placeInTop": null + }, + { + "id": 17929, + "code": "JPY", + "issuer": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN", + "title": "TokyoJPY", + "trustlines": 7538, + "placeInTop": null + }, + { + "id": 18785, + "code": "xMFA", + "issuer": "rLBxKkudr4qCU3R6d3o1uKHmqrTqeyjSdr", + "title": "Mining Farm", + "trustlines": 5054, + "placeInTop": null + }, + { + "id": 314, + "code": "XCROC", + "issuer": "rpLw9SsJz52DpYWL5ohS2s71FgVeohHACQ", + "title": "XCROC", + "trustlines": 4812, + "placeInTop": null + }, + { + "id": 17342, + "code": "CNY", + "issuer": "rnuF96W4SZoCJmbHYBFoJZpR8eCaxNvekK", + "title": "rippleCN", + "trustlines": 5721, + "placeInTop": null + }, + { + "id": 16755, + "code": "XSMARTFOX", + "issuer": "rHRMqfYAPc73RtVeaywbESc3Zd49Ly9jyG", + "title": "XSMARTFOX", + "trustlines": 4163, + "placeInTop": null + }, + { + "id": 17077, + "code": "BANK", + "issuer": "rN7HDdMy9uq6tKY27ZhGTamZ1RG6pMWubE", + "title": "BANK", + "trustlines": 4767, + "placeInTop": null + }, + { + "id": 140, + "code": "FYX", + "issuer": "r91QDSnUm99ty84TNsv5ai4HufjFMsHJDF", + "title": "Fyrox", + "trustlines": 5370, + "placeInTop": null + }, + { + "id": 336, + "code": "BuffKittyRewards", + "issuer": "rHingngfqfmXVL4FKaeAsyrdW9eGVkGWr8", + "title": "XKITTY", + "trustlines": 3204, + "placeInTop": null + }, + { + "id": 239, + "code": "RDG", + "issuer": "rgJsp6hH3mXopHyW8yM85rJYSVy8NYgWr", + "trustlines": 3468, + "placeInTop": null + }, + { + "id": 18385, + "code": "MEOW", + "issuer": "rh1MUWz6HJGWQgRUwsVeRdiKR2cDBkZDDj", + "title": "MeowXrplsNfts", + "trustlines": 6000, + "placeInTop": null + }, + { + "id": 63, + "code": "Franklin", + "issuer": "rMgnr9ePkQbY23ZjzCyCu977viGTjRyFBT", + "title": "Franklin's", + "trustlines": 5237, + "placeInTop": null + }, + { + "id": 16733, + "code": "GUP", + "issuer": "rPrTFNxEEk29356c7LuriRTpornftmzG2c", + "title": "Guppiez", + "trustlines": 3378, + "placeInTop": null + }, + { + "id": 93, + "code": "Xcicada", + "issuer": "rE55raRDWcy4car7fhFQTaQ1ToYTyCDzz8", + "title": "Xcicada", + "trustlines": 6437, + "placeInTop": null + }, + { + "id": 70, + "code": "XWARS", + "issuer": "rLC88EkvQUmVM3PVzDejTBzRGx1hKwBsUf", + "trustlines": 7688, + "placeInTop": null + }, + { + "id": 18414, + "code": "LUAH", + "issuer": "rnVFEE67uFP4vNKtb2fPZwCuJAek5tnPrz", + "title": "Limited USD (LUSD)", + "trustlines": 4124, + "placeInTop": null + }, + { + "id": 16761, + "code": "XBCP", + "issuer": "rsxRfhJh9GZe1dcjH7XwL9rSr6AjQmiM9A", + "title": "XBCP | BLUECHIPX", + "trustlines": 3221, + "placeInTop": null + }, + { + "id": 16682, + "code": "XRYODA", + "issuer": "r344ghnxmqQf5GsyZobTwtAEn39kUcjVV1", + "title": "XRBB YODA", + "trustlines": 5331, + "placeInTop": null + }, + { + "id": 16853, + "code": "TULDOK", + "issuer": "r9qGMJMreNBYdEqJ7mNrUjyCj44fDUEe1G", + "title": "$TULDOK COIN", + "trustlines": 3028, + "placeInTop": null + }, + { + "id": 18893, + "code": "TSX", + "issuer": "rHTC7FLrK4NWnZRduLmXUDGWVQMrXarAvB", + "title": "Teleport", + "trustlines": 6146, + "placeInTop": null + }, + { + "id": 184, + "code": "xCorgi", + "issuer": "rERY8GCz7A1AN1RkDN1CkPVh8ids6PNgPP", + "title": "xCorgi | Fluff", + "trustlines": 6363, + "placeInTop": null + }, + { + "id": 18780, + "code": "LGX", + "issuer": "rP5VrjHKXTFwp1WYwVvhAc9KU9YmbN3rLC", + "title": "Logos XRP", + "trustlines": 3789, + "placeInTop": null + }, + { + "id": 18415, + "code": "KiLLeRcOiN", + "issuer": "rJMZZfwM1s3Pgy4fKzEgmK7YHf9wpUBg6w", + "trustlines": 2965, + "placeInTop": null + }, + { + "id": 66, + "code": "XGM", + "issuer": "rseMc5T1mqfMQB4pWt6XwwMKG6YhWEGnFv", + "title": "xGm", + "trustlines": 9427, + "placeInTop": null + }, + { + "id": 16861, + "code": "XPoets", + "issuer": "raQ1XkLHnUsrYqApWzX3pVirZdArG2h7x1", + "title": "XPoets", + "trustlines": 3046, + "placeInTop": null + }, + { + "id": 16844, + "code": "XHN", + "issuer": "rD1SDBGrD5XNN6NrXQWistmzfGX22foe3H", + "trustlines": 3874, + "placeInTop": null + }, + { + "id": 16953, + "code": "TRUMP", + "issuer": "rNRh1YobQDUuFyiXnUY9NJB7b2MLDJJGHg", + "title": "TrumpCoin", + "trustlines": 2778, + "placeInTop": null + }, + { + "id": 16620, + "code": "ANBU", + "issuer": "ratHp7D5w5bhDyxz9hiJGbBhny5kzJRu1f", + "title": "AnbuLegends", + "trustlines": 9571, + "placeInTop": null + }, + { + "id": 16943, + "code": "xBull", + "issuer": "rNpNdUKp631RXzqxbjjCYsJp6g8Q5T5cGh", + "title": "xBull", + "trustlines": 4777, + "placeInTop": null + }, + { + "id": 17823, + "code": "XDR", + "issuer": "r4SQf7WwtrbEXmv7XLasLiSvpoYRq6urQs", + "title": "XRP DROID", + "trustlines": 6291, + "placeInTop": null + }, + { + "id": 18148, + "code": "FLNGL", + "issuer": "rpzHixvdL2hZ6Y1kmkW4tFnnShq4Vgu691", + "title": "TheXFallenAngel", + "trustlines": 6054, + "placeInTop": null + }, + { + "id": 321, + "code": "Ethos", + "issuer": "rwxjYdik47PBJ2UsxseeoFdsa2LdAdaSQW", + "title": "Ethos", + "trustlines": 4679, + "placeInTop": null + }, + { + "id": 16769, + "code": "DNSK", + "issuer": "rDNSKmHYj6NZfy1hvxNaT1kyUChvJFerkd", + "title": "DNSK POKERHUB", + "trustlines": 2701, + "placeInTop": null + }, + { + "id": 247, + "code": "SPORT", + "issuer": "rwxwUwWpWAQK6jFRcW12Vx97riBu91MDPi", + "trustlines": 3980, + "placeInTop": null + }, + { + "id": 306, + "code": "Cultured", + "issuer": "rpFrFgdGnaQL7KFc9SPDYMd6YS3m4smscV", + "title": "Cultured Coin", + "trustlines": 3345, + "placeInTop": null + }, + { + "id": 18378, + "code": "OMENP", + "issuer": "rNS9dmNa8wNheRD5mZ8R4YtyWt7ySsYw2V", + "title": "OMEN Ltd.", + "trustlines": 5896, + "placeInTop": null + }, + { + "id": 16828, + "code": "sachay", + "issuer": "raj1sz7iCt6ZmGPp1vhZ7DjRjgxcTA889h", + "title": "SachayCoin", + "trustlines": 2795, + "placeInTop": null + }, + { + "id": 18733, + "code": "xRiddler", + "issuer": "rMKehfMwCNTBo3KoCZvqea35KNyatus3hy", + "title": "xRiddler", + "trustlines": 4882, + "placeInTop": null + }, + { + "id": 16747, + "code": "XBuddha", + "issuer": "rJRE6wwTSsPe5A7rRC2R3v2FbPVY7rbxuN", + "title": "XBuddha", + "trustlines": 2718, + "placeInTop": null + }, + { + "id": 16606, + "code": "XCASTLE", + "issuer": "rpU1YBKBCE3rnms5vwgXHHouLapVZv7KDu", + "title": "XCastleCoin", + "trustlines": 5612, + "placeInTop": null + }, + { + "id": 18702, + "code": "SNX", + "issuer": "rUhkGuauku4yw2tAPGZCV7atBSRXkgZ6pV", + "trustlines": 2925, + "placeInTop": null + }, + { + "id": 16770, + "code": "Voodoo", + "issuer": "rPE6vTZZyu32TK6FYtSd443TR9HXvayL5", + "title": "Voodoo", + "trustlines": 2594, + "placeInTop": null + }, + { + "id": 337, + "code": "ZEN", + "issuer": "rD2C8cVUEdu1o6hiaouxzMpZELRYnVZnh4", + "title": "nowandzen", + "trustlines": 2817, + "placeInTop": null + }, + { + "id": 18748, + "code": "ASH", + "issuer": "rGETqxutoKZ6VcXTE7Yh1fyaUj5pro7PuE", + "title": "AfterShock", + "trustlines": 3101, + "placeInTop": null + }, + { + "id": 16845, + "code": "1Mill", + "issuer": "rPWsEKUdVCgrKVM8x3ZbcdWY3eKTckziVH", + "title": "1MillClubXRPL", + "trustlines": 3147, + "placeInTop": null + }, + { + "id": 18616, + "code": "GAMECOIN", + "issuer": "rGRbhMeHyi5yi1x5vkFrZD8sHzkr5HfH3g", + "title": "GAMECOIN", + "trustlines": 2926, + "placeInTop": null + }, + { + "id": 16984, + "code": "xKyouko", + "issuer": "rpyNDFjy2W2wE3o5hB9HUiDCwvz26LeuXf", + "title": "xKyouko", + "trustlines": 3797, + "placeInTop": null + }, + { + "id": 18326, + "code": "LZAR", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 2513, + "placeInTop": null + }, + { + "id": 18637, + "code": "XMF", + "issuer": "rP6pULkW5nNsWYsy1wcf4p4YfkGrKCHeRe", + "title": "XMafia", + "trustlines": 6167, + "placeInTop": null + }, + { + "id": 139, + "code": "VCOXRP", + "issuer": "rDecvvWKRtfFhSB33nHWMZ6aZfknA1ngvo", + "title": "Valor University of the Future", + "trustlines": 2341, + "placeInTop": null + }, + { + "id": 16607, + "code": "MATE", + "issuer": "rpWapSyB4dPhjMms2cYMEmTfc6BGpbpZBv", + "title": "Mates | Premium NFTs", + "trustlines": 8484, + "placeInTop": null + }, + { + "id": 16846, + "code": "xdrop", + "issuer": "rwCZMiLKFd1Nct6MAJuhcD6ALv1h4UsyKf", + "title": "XdropXrpl", + "trustlines": 2565, + "placeInTop": null + }, + { + "id": 16858, + "code": "XPoems", + "issuer": "rH8hYUS6jr9dk3W96ZCYp8fcuwk5FGQEJG", + "title": "XPoets", + "trustlines": 2393, + "placeInTop": null + }, + { + "id": 18220, + "code": "LTWD", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency TWD", + "trustlines": 2855, + "placeInTop": null + }, + { + "id": 193, + "code": "XCRA", + "issuer": "rNFDYKcnrUCjxaoUFzBYSiFHs2NLmXT1Gp", + "title": "XCRA NFT", + "trustlines": 6237, + "placeInTop": null + }, + { + "id": 18610, + "code": "JUMBO", + "issuer": "rLAstRsF3MWQfQw11gEsPyWvmR2oe5zXjE", + "title": "FLYING JUMBO", + "trustlines": 4313, + "placeInTop": null + }, + { + "id": 23, + "code": "MWG", + "issuer": "rGfKifFcz1mpobiRyKwNL7MoVjDoiefeYT", + "title": "MotionWreck Games", + "trustlines": 7097, + "placeInTop": null + }, + { + "id": 309, + "code": "xShasta", + "issuer": "rne1owrwbP5Q4W8LvH9BM9shjWNQ5dKwqZ", + "title": "xShasta", + "trustlines": 3636, + "placeInTop": null + }, + { + "id": 16957, + "code": "XRZodiac", + "issuer": "rwuPyekh28TpWgm2BZmC8vEddEmFSf4jb", + "title": "Zodiac XRP", + "trustlines": 2115, + "placeInTop": null + }, + { + "id": 275, + "code": "XRLZ", + "issuer": "rPVaiUCm98WPCtQbLhXkSk3prFR1aTZTmx", + "title": "XRILLAZ", + "trustlines": 3845, + "placeInTop": null + }, + { + "id": 16841, + "code": "XSHIB", + "issuer": "rJ1bzYHJq8MpGpBaw4mBChuUcAUME44CVg", + "title": "XSHIB", + "trustlines": 2750, + "placeInTop": null + }, + { + "id": 16962, + "code": "BNM", + "issuer": "rHEULEU5TQwCpnMoJVkaPX9TB9UrXWrZtF", + "title": "Block News Media (BNM,DAO)", + "trustlines": 2379, + "placeInTop": null + }, + { + "id": 329, + "code": "XSPUNK", + "issuer": "rPB7vXTeWrQ5nxEpQbLvay8nfJUJfeecYL", + "title": "XSPUNK", + "trustlines": 4810, + "placeInTop": null + }, + { + "id": 271, + "code": "INVST", + "issuer": "rsoiJStU1o7PLZcoqyQRsCtfWqchdNrcBZ", + "title": "InvestorCoin", + "trustlines": 2815, + "placeInTop": null + }, + { + "id": 18584, + "code": "ORG", + "issuer": "rPq1ReXJ8uEyyRf2BTMu5bj7nKsQ4JTgBu", + "title": "Snub Network", + "trustlines": 2574, + "placeInTop": null + }, + { + "id": 18688, + "code": "XRsilver", + "issuer": "rEAYBYLaar6Bih2ZZXTNWC14XcdRGeHoVk", + "title": "XRsilver \u2739", + "trustlines": 4760, + "placeInTop": null + }, + { + "id": 16727, + "code": "BMG", + "issuer": "rpR32FXJoGv5K9Qwyu3cW8dSCWeu29cy88", + "title": "Bullish Moments", + "trustlines": 2676, + "placeInTop": null + }, + { + "id": 17055, + "code": "DSH", + "issuer": "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + "title": "GateHub DASH", + "trustlines": 13352, + "placeInTop": null + }, + { + "id": 18316, + "code": "RELM", + "issuer": "rKWXCzjycHKiV5c5VzR8M5NXbz5AJjB1fd", + "title": "ELEMENTUM RELMVERSE", + "trustlines": 4637, + "placeInTop": null + }, + { + "id": 16737, + "code": "Xgirls", + "issuer": "rDLfe8aoNDtrVA5BZC79j37cmhSYCHdHpm", + "title": "Xgirls", + "trustlines": 8106, + "placeInTop": null + }, + { + "id": 18489, + "code": "XAnime", + "issuer": "r9ij4uZm8tmDuWUigymNQZRkKRevGV3vv5", + "title": "XAnime Official", + "trustlines": 3725, + "placeInTop": null + }, + { + "id": 167, + "code": "BAYNANA", + "issuer": "rJ9uU9jKxNcsNQM2CLKUhPxNn8P4xmhDVq", + "title": "BAYNANA", + "trustlines": 3273, + "placeInTop": null + }, + { + "id": 18325, + "code": "LARS", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 2572, + "placeInTop": null + }, + { + "id": 16637, + "code": "COLORS", + "issuer": "r4R8yypDDRZjBBLsUmT3KNusmVPYs5YqSV", + "title": "XRPL COLORS", + "trustlines": 5148, + "placeInTop": null + }, + { + "id": 16653, + "code": "Becky", + "issuer": "rMNtmZNvBQqjd9AXyn9Gt4n2FVNt5LZiRD", + "title": "Bud Buddies", + "trustlines": 2946, + "placeInTop": null + }, + { + "id": 301, + "code": "DUSTNFT", + "issuer": "rpnZtxkk8hcNzPkaKF76wwoFVzcd9T211j", + "title": "DuStToToKeN", + "trustlines": 2095, + "placeInTop": null + }, + { + "id": 17938, + "code": "xSD", + "issuer": "rGbNLwrYNuPim39crh1bjyyauefF1VHPoj", + "title": "XrpWest", + "trustlines": 5147, + "placeInTop": null + }, + { + "id": 16812, + "code": "Bitcorn", + "issuer": "rMDtnJDp9naCLVhrUJM6t6baTcijmM8PtG", + "title": "Bitcorn", + "trustlines": 2242, + "placeInTop": null + }, + { + "id": 169, + "code": "POLAR", + "issuer": "rfdistkMFGQ7HAgu5JvsQdRHbm15EMgWmX", + "title": "xrPolarBears", + "trustlines": 8484, + "placeInTop": null + }, + { + "id": 17953, + "code": "Chamillions", + "issuer": "r3DuEtGbKxbvkJ3evEEmveCNzw5JTUaHed", + "title": "Chamillions | XRPL", + "trustlines": 2913, + "placeInTop": null + }, + { + "id": 18757, + "code": "PIT", + "issuer": "rN7NMsuSzrRJR3q969CQ2ctgLjnLcLZRer", + "title": "MR. Desert King", + "trustlines": 2562, + "placeInTop": null + }, + { + "id": 16638, + "code": "LEUR", + "issuer": "rfL4Sci2ag5hhkpDuqtWYov6j3mshVWLgU", + "title": "Limited Currency", + "trustlines": 3579, + "placeInTop": null + }, + { + "id": 104, + "code": "XRhino", + "issuer": "r4Bej4WbpCdSpYCvi1rETFyR6X2bzJYHoN", + "title": "XRhino NFT ( XRPL )", + "trustlines": 3621, + "placeInTop": null + }, + { + "id": 231, + "code": "LCNY", + "issuer": "rfL4Sci2ag5hhkpDuqtWYov6j3mshVWLgU", + "title": "Limited Currency", + "trustlines": 3000, + "placeInTop": null + }, + { + "id": 18079, + "code": "Universal", + "issuer": "r3Q5nzkGA9WDz2gCZ3ZSEy2yYseSASru3m", + "title": "universal-digital-assets", + "trustlines": 1870, + "placeInTop": null + }, + { + "id": 16859, + "code": "DarksideCoin", + "issuer": "rLL1yVgd48DX82zSSZrewqHWLvtQy2HN2X", + "trustlines": 1773, + "placeInTop": null + }, + { + "id": 18084, + "code": "Snow", + "issuer": "rBH11ewwW35V6fuyo79tYQvDn1C8MiHHo7", + "title": "xSNOW", + "trustlines": 1790, + "placeInTop": null + }, + { + "id": 17114, + "code": "LINR", + "issuer": "rfL4Sci2ag5hhkpDuqtWYov6j3mshVWLgU", + "title": "Limited Currency", + "trustlines": 6536, + "placeInTop": null + }, + { + "id": 17078, + "code": "XBIDEN", + "issuer": "rJKvGk8EEdmkrQuu24oJ6cSerMw7gT2jKz", + "title": "Joe Biden #XBIDEN #XRPL", + "trustlines": 2720, + "placeInTop": null + }, + { + "id": 17863, + "code": "XON", + "issuer": "rswdYCe1dy1LK2wA1Kcs9j8Za3UupBes3K", + "title": "XON", + "trustlines": 2086, + "placeInTop": null + }, + { + "id": 16610, + "code": "xPiggies", + "issuer": "rGH6hm9YcSog35ZuxKkbKbKftMmaTh9vXt", + "title": "XRPiggies", + "trustlines": 4104, + "placeInTop": null + }, + { + "id": 16824, + "code": "Dalgona", + "issuer": "rn88jxrNUWBfQR9hfoB2MDjiGcvbiV7M3o", + "title": "Dalgona Candy official", + "trustlines": 2413, + "placeInTop": null + }, + { + "id": 17975, + "code": "XRBytes", + "issuer": "rBYTeShhNnTGqUTXAd2dCPd6rVYVD1qeHL", + "title": "XRBytes", + "trustlines": 4377, + "placeInTop": null + }, + { + "id": 18533, + "code": "HOGX", + "issuer": "rwM9XzFHdCxnpNFjanc4Sd3N96cz8ip5pQ", + "title": "HOG X", + "trustlines": 2899, + "placeInTop": null + }, + { + "id": 16813, + "code": "AVegazCoin", + "issuer": "rGmjWPmd5oCJcvG7N45npLRaqL62whVFfJ", + "title": "AVegazCoin", + "trustlines": 1966, + "placeInTop": null + }, + { + "id": 17996, + "code": "XDRT", + "issuer": "r9Xw2WnvPBbgur6gqnevmyuRofWZuC4iNt", + "title": "XDRT_office", + "trustlines": 7389, + "placeInTop": null + }, + { + "id": 17190, + "code": "111", + "issuer": "rpwnicj25Rh3QEmdxCLRYVTf69ABHP9soy", + "title": "Angel Coin", + "trustlines": 2594, + "placeInTop": null + }, + { + "id": 16822, + "code": "AngelNFT", + "issuer": "rD6VQ6Qx95sVaYbLM2Qnqn6Fde4ijaaqsu", + "title": "Angel Coin", + "trustlines": 2126, + "placeInTop": null + }, + { + "id": 16655, + "code": "BoredPandas", + "issuer": "rMaPkHEY5zVoZ3XV482tm4fuMRnJTuoAZp", + "trustlines": 2397, + "placeInTop": null + }, + { + "id": 284, + "code": "Fractal", + "issuer": "rw9oZfkjNkaarwpyrTLjfHzfnUuoGFY8V8", + "title": "NFT Labs", + "trustlines": 2753, + "placeInTop": null + }, + { + "id": 18397, + "code": "XRSaga", + "issuer": "rnb3HAXaQX6J2RA2dZaTeNNDKMvc4N7B9k", + "title": "XRSaga", + "trustlines": 2770, + "placeInTop": null + }, + { + "id": 16661, + "code": "Xapes", + "issuer": "rB8Lrw5eY42Kk6DE6AwMiC1K4tyudg8URB", + "title": "Gorilla X Warfare", + "trustlines": 5253, + "placeInTop": null + }, + { + "id": 18675, + "code": "3DAPES", + "issuer": "rP58cvnsDBXmmGi2Cp4CZ2UV6v5muFFntZ", + "title": "3DAPES \u2122", + "trustlines": 9564, + "placeInTop": null + }, + { + "id": 18598, + "code": "XBAg", + "issuer": "rauUywaCisP1MaeZ67DHf4qKmXUBz7Traq", + "title": "B I T H E R O", + "trustlines": 2962, + "placeInTop": null + }, + { + "id": 18509, + "code": "chilledchimps", + "issuer": "rMSzPTgQVfZVQViNrG5zGA8899orcyavFL", + "title": "chilled x chimps", + "trustlines": 4209, + "placeInTop": null + }, + { + "id": 16875, + "code": "XPoet", + "issuer": "rHp991Ms5hV5f4NYoeUeCymmmAFMMt2to8", + "title": "XPoets", + "trustlines": 1561, + "placeInTop": null + }, + { + "id": 16920, + "code": "ChildSupport", + "issuer": "rU97WDGnPtrUXT1qw1Fqk5VKS41gi8UQgD", + "title": "Child Support Token", + "trustlines": 4071, + "placeInTop": null + }, + { + "id": 16630, + "code": "VCZ", + "issuer": "rJ3GrAXX821D7XnnMgndBGe6CgZnEHXr1o", + "title": "VincenzoNFT", + "trustlines": 2078, + "placeInTop": null + }, + { + "id": 4, + "code": "xCross", + "issuer": "rDZzGy4YnpjKSvWsToHRCcGvcyinjuKEv9", + "title": "XRPCross", + "trustlines": 5639, + "placeInTop": null + }, + { + "id": 17018, + "code": "PNP", + "issuer": "r98XNSpxf7fHLfdBupcjiMWMdNmhq74Pnp", + "trustlines": 1477, + "placeInTop": null + }, + { + "id": 18448, + "code": "DOG", + "issuer": "rHkqAyggqiSvhBdrvfbCbzqbD6nUyGy2CQ", + "title": "Doggy coin", + "trustlines": 3653, + "placeInTop": null + }, + { + "id": 16649, + "code": "XReefs", + "issuer": "rN1fBKQdLRcMUG8VCDihKPqmTgBfgSu4rm", + "trustlines": 4228, + "placeInTop": null + }, + { + "id": 19042, + "code": "BTC", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 4031, + "placeInTop": null + }, + { + "id": 17876, + "code": "EMOJISQUAD", + "issuer": "rpFNHu3W8g6CvvdCgvwRaQidpRGrGnpwg3", + "title": "XRemojisquad", + "trustlines": 1460, + "placeInTop": null + }, + { + "id": 18214, + "code": "LBRL", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency BRL", + "trustlines": 2682, + "placeInTop": null + }, + { + "id": 246, + "code": "XSG", + "issuer": "rN7RrccWk5hbZQEX4f5ED3zuLuFKNiT64", + "trustlines": 1788, + "placeInTop": null + }, + { + "id": 260, + "code": "MAYC", + "issuer": "rfdjz9HVpd5ixQE6LBK2cZT9XhJEYpgBtC", + "title": "Monster Ape XRP Club", + "trustlines": 2488, + "placeInTop": null + }, + { + "id": 17536, + "code": "BRL", + "issuer": "rfNZPxoZ5Uaamdp339U9dCLWz2T73nZJZH", + "title": "Rippex", + "trustlines": 2542, + "placeInTop": null + }, + { + "id": 154, + "code": "xVerse", + "issuer": "rfQ6YtuSMVY25mJ9XhN35gHiwotiASvfyS", + "title": "xVerse", + "trustlines": 3802, + "placeInTop": null + }, + { + "id": 17012, + "code": "HOG", + "issuer": "rwwjSYw5X2YPWRZ6cebeYUFkBLyh8tT71B", + "trustlines": 2344, + "placeInTop": null + }, + { + "id": 18123, + "code": "XCHAM", + "issuer": "rKmrHQFrixTEV4z8JPhFLFyzsN8ZGtYkS5", + "title": "XCHAMELEONS / $XCHAM XRPL \ud83e\udd8e", + "trustlines": 5780, + "placeInTop": null + }, + { + "id": 17309, + "code": "XLM", + "issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y", + "title": "Ripple Fox", + "trustlines": 2344, + "placeInTop": null + }, + { + "id": 288, + "code": "DTT", + "issuer": "r3ygvUawCTEPZ7kosTktJ963K3KcrGXbGS", + "title": "DuStToToKeN", + "trustlines": 1762, + "placeInTop": null + }, + { + "id": 297, + "code": "XSWAP", + "issuer": "rDBWu8fWvxaY9dgNQXy2MYVtm3TRNY5FqE", + "trustlines": 2218, + "placeInTop": null + }, + { + "id": 16907, + "code": "ZULU", + "issuer": "rLcoMGVSZDWz3rWheEbPpeQMq2MNfmUoLb", + "title": "JustBull", + "trustlines": 1345, + "placeInTop": null + }, + { + "id": 18320, + "code": "LAED", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 2169, + "placeInTop": null + }, + { + "id": 300, + "code": "XKITTY", + "issuer": "rDzxZghEmGVRAvbpNSDbHGPgETkqHiZ5di", + "title": "XKITTY", + "trustlines": 2207, + "placeInTop": null + }, + { + "id": 17188, + "code": "SENIACOIN", + "issuer": "rpoHuxKPrqTfkH7L4cQwtNRKZzszWG6uyZ", + "trustlines": 1978, + "placeInTop": null + }, + { + "id": 18327, + "code": "LCAD", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 1917, + "placeInTop": null + }, + { + "id": 18301, + "code": "XDV", + "issuer": "rUd5EHPczA1dxLkc43svhfX5htRvQzFHdX", + "title": "The Vault", + "trustlines": 2148, + "placeInTop": null + }, + { + "id": 16922, + "code": "cryptopokecoin", + "issuer": "rMWEKWXeSFEM64mUfA83TNvBh8pogRQzNw", + "title": "Cryptopokecoin", + "trustlines": 1264, + "placeInTop": null + }, + { + "id": 18215, + "code": "LVEB", + "issuer": "rnyGDFEqnNwpyzievKCMhHUi4xs6HnUqPA", + "title": "Limited Currency USD", + "trustlines": 2396, + "placeInTop": null + }, + { + "id": 17637, + "code": "QAU", + "issuer": "r3ttJ41YnMrKiLqGUXJpQE8urqyMGjC8vE", + "title": "GateHub Fifth", + "trustlines": 7903, + "placeInTop": null + }, + { + "id": 15, + "code": "BisonArmy", + "issuer": "rKewBkTsHrjWiDTnsrAB2kjTg9bkCTMMyc", + "title": "Bison Army", + "trustlines": 14137, + "placeInTop": null + }, + { + "id": 18811, + "code": "XRPLTGT", + "issuer": "rspdZ6yA1VKPcA2fJxKeWUfmcrqRrF9CJ9", + "trustlines": 1289, + "placeInTop": null + }, + { + "id": 18545, + "code": "MKT", + "issuer": "rMD4eSH2doKurPNy6K3bQ6Hx1Z7EhoXnY2", + "title": "MetaKnight360 XRPL", + "trustlines": 3663, + "placeInTop": null + }, + { + "id": 16830, + "code": "LIFE", + "issuer": "rMmg2nrgRAJupwXJRAtEMqmezzHFAStE76", + "title": "LIFE COIN FUND", + "trustlines": 2189, + "placeInTop": null + }, + { + "id": 16889, + "code": "XCHIBI", + "issuer": "rKjxYk6pocyAJHD8k34tq1h6FGYCkwsoLG", + "title": "XCHIBI | NFT Collection", + "trustlines": 4322, + "placeInTop": null + }, + { + "id": 18922, + "code": "THECITYOFGIEZWA", + "issuer": "raiata7DoTJL5CMHuYC8BYGE6J4psyCMcE", + "title": "THE CITY OF GIEZWA", + "trustlines": 1424, + "placeInTop": null + }, + { + "id": 18258, + "code": "XDOWO", + "issuer": "r3RBkBa2hvfW2iZLj8yKo9PDeFCH9n9Ww1", + "trustlines": 1834, + "placeInTop": null + }, + { + "id": 16771, + "code": "FloydCoin", + "issuer": "rKF4FfwcjQK6pCJPFZ6xYmvmgynaf5DXTw", + "title": "George Floyd Coin", + "trustlines": 1204, + "placeInTop": null + }, + { + "id": 18004, + "code": "DSF", + "issuer": "rsiSiZosBx2kiz1sRd1NEi838qWumVXzqx", + "title": "DeepSpaceFlare", + "trustlines": 2338, + "placeInTop": null + }, + { + "id": 18447, + "code": "xALPACA", + "issuer": "rhjyUA6MVvx26XnRZsTfFkPGkyov7s2H4d", + "title": "Alpaca NFTs", + "trustlines": 1796, + "placeInTop": null + }, + { + "id": 106, + "code": "Xvroom", + "issuer": "rNio2YtPkiAvQMTFSCQPmPhjMdkMzWrLAS", + "title": "Xvroom", + "trustlines": 3564, + "placeInTop": null + }, + { + "id": 16642, + "code": "OSSA", + "issuer": "rEidsGjRCX8VYQdMtY9hLMzj8cNMFdqzWp", + "title": "Calvaria", + "trustlines": 5134, + "placeInTop": null + }, + { + "id": 16964, + "code": "CubanCoin", + "issuer": "rsF3pzhPeSFdMKCVWuRF9k36Xt1qXFyLsF", + "trustlines": 2519, + "placeInTop": null + }, + { + "id": 203, + "code": "LogoArt", + "issuer": "rGQLkT1U7K3S9pGiXFrQKtsVuLHc9PumKY", + "title": "LogoArt", + "trustlines": 4161, + "placeInTop": null + }, + { + "id": 18968, + "code": "DTG", + "issuer": "rqUktVQrvc7f7mMHxLk4mvqPXVut4cUzP", + "title": "DatingXRPL", + "trustlines": 2092, + "placeInTop": null + }, + { + "id": 17214, + "code": "ZERO", + "issuer": "rfH4wavPbouU5bceEuP2Y7vrwVzKLdZero", + "trustlines": 1681, + "placeInTop": null + }, + { + "id": 18596, + "code": "ATMT", + "issuer": "rPXFg78B1b4ZSisfdibXztT5kPmp5HTDXe", + "title": "ARNILLA STUDIOS", + "trustlines": 8170, + "placeInTop": null + }, + { + "id": 18778, + "code": "XJMP", + "issuer": "rB64rftMocgcJtYN1E7P1B75z1JmrVUWnW", + "title": "XJumper", + "trustlines": 3071, + "placeInTop": null + }, + { + "id": 16913, + "code": "PHP", + "issuer": "rBpQSMUo4pJGcwkDJxhxg9K9Vmq4Kt4d8H", + "title": "Xrpl Squid", + "trustlines": 1747, + "placeInTop": null + }, + { + "id": 16746, + "code": "XBUGS", + "issuer": "rLAMN7QmVfrGJdt6mb4pZsp8rSjKosTfaJ", + "title": "XRBUGS", + "trustlines": 2952, + "placeInTop": null + }, + { + "id": 16878, + "code": "XPoem", + "issuer": "rBXM5Q2GaWqt3ARThHAd9A3Q6MBPwdAJG8", + "title": "XPoets", + "trustlines": 1206, + "placeInTop": null + }, + { + "id": 244, + "code": "XLION", + "issuer": "rU6ANcasdcDmRiF9JuW2rj7KYmPGKAEUYy", + "title": "LAME LIONS XRPL / $XLION", + "trustlines": 4312, + "placeInTop": null + }, + { + "id": 16797, + "code": "GigCoin", + "issuer": "rEF5RN6VkfEvkHeK9tkTodV8Cj9bhnM1Qj", + "title": "GigCoin", + "trustlines": 4152, + "placeInTop": null + }, + { + "id": 16988, + "code": "EGL", + "issuer": "r44zdwNnFAYFW89hUhsz52m31paXnWvySg", + "trustlines": 2753, + "placeInTop": null + }, + { + "id": 18065, + "code": "KNIFES", + "issuer": "rBdhf3SADW7ZXk6VaCDM9Pzq5FkKngNVF7", + "title": "META WEAPON (XRPL)", + "trustlines": 5706, + "placeInTop": null + }, + { + "id": 16827, + "code": "NerdX", + "issuer": "rUBbS5qeACf13KtqGUigr9rUDqZL7PLAT7", + "title": "NerdX", + "trustlines": 2322, + "placeInTop": null + }, + { + "id": 16803, + "code": "KING", + "issuer": "rPxEDPLfNp18mmhnmgn8pnxdWqo8UfnhRy", + "title": "King Coin", + "trustlines": 4076, + "placeInTop": null + }, + { + "id": 330, + "code": "xLordeEdge", + "issuer": "rnQPyTLGVD1exGZCBD9XcXvbedMG7Sy4iU", + "title": "XLEcoin", + "trustlines": 3743, + "placeInTop": null + }, + { + "id": 17152, + "code": "XmemeGoldBox", + "issuer": "r9w99F78djkcfJ6X9AKuowGvk42YYs4Fz4", + "title": "XmemeGoldBox", + "trustlines": 1146, + "placeInTop": null + }, + { + "id": 16674, + "code": "XGalaxyDust", + "issuer": "rJ3SiADSaiMu5YaygY94pE4X4PCwHECg7a", + "title": "XGalaxy", + "trustlines": 1035, + "placeInTop": null + }, + { + "id": 30, + "code": "XRParmy", + "issuer": "rDFqUqByvp86TmNppRT4svFrpZwnsymnvU", + "title": "XRP Army Coin", + "trustlines": 4945, + "placeInTop": null + }, + { + "id": 18752, + "code": "XME", + "issuer": "rana9qGVUedcBGeEXjECMnTcMRQvL9GCvW", + "trustlines": 2571, + "placeInTop": null + }, + { + "id": 16768, + "code": "LFGO", + "issuer": "rBETMo1JSfigtMfU8kBem95vKcFmjG9gSw", + "trustlines": 2953, + "placeInTop": null + }, + { + "id": 263, + "code": "GUY", + "issuer": "rJmKe5N1JPa4ocst5MAbVpu3Ggzv4kdVku", + "title": "XRPL GUYS / $GUY", + "trustlines": 2344, + "placeInTop": null + }, + { + "id": 348, + "code": "HappyRacoon", + "issuer": "rBq2VLqRcthMU57n6Nw9125PF1Mxng4uyq", + "title": "HappyRacoon", + "trustlines": 4737, + "placeInTop": null + }, + { + "id": 170, + "code": "XMW", + "issuer": "ra2hRLUM28o9XYEHdyqKvFurXa5EYNqApE", + "title": "MicroWorld", + "trustlines": 3450, + "placeInTop": null + }, + { + "id": 18807, + "code": "JPY", + "issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6", + "title": "Ripple Trade Japan", + "trustlines": 2668, + "placeInTop": null + }, + { + "id": 18976, + "code": "HDG", + "issuer": "rsKKfXJ3txRq12JSs9xxv9BMBYGFWWCQNo", + "title": "HolddiamondGold", + "trustlines": 1450, + "placeInTop": null + }, + { + "id": 310, + "code": "xSHIBOSHIS", + "issuer": "rUH4VReNsA3eQZY86TAbErBz49GdZGqHzJ", + "title": "xSHIBOSHIS", + "trustlines": 3638, + "placeInTop": null + }, + { + "id": 17848, + "code": "KRW", + "issuer": "rUkMKjQitpgAM5WTGk79xpjT38DEJY283d", + "title": "Pax Moneta", + "trustlines": 1999, + "placeInTop": null + }, + { + "id": 17107, + "code": "REP", + "issuer": "rckzVpTnKpP4TJ1puQe827bV3X4oYtdTP", + "title": "GateHub REP", + "trustlines": 18818, + "placeInTop": null + }, + { + "id": 16790, + "code": "GomiCoin", + "issuer": "r3jDb7aM3UDGSttVNcC7pxz1akGmPRsDkN", + "trustlines": 1934, + "placeInTop": null + }, + { + "id": 158, + "code": "XCL", + "issuer": "rswu4hb6GhDse1CGEisZrYhCCAPPbDDcAp", + "title": "XCLONE NFTS", + "trustlines": 4152, + "placeInTop": null + }, + { + "id": 17040, + "code": "xDave", + "issuer": "rDaVeTZrFWNz9uFxf3bqdPTpLAsNc1fDF7", + "trustlines": 1266, + "placeInTop": null + }, + { + "id": 214, + "code": "XHH", + "issuer": "rJWRqdKTRfqoeHERBDJukWWNTRrnwayDca", + "title": "HodlHippos", + "trustlines": 3583, + "placeInTop": null + }, + { + "id": 18684, + "code": "FOX", + "issuer": "rNrLLdKFEZaw1Bao6Twjes7Jm4jHg1rthU", + "trustlines": 1413, + "placeInTop": null + }, + { + "id": 113, + "code": "XMZ", + "issuer": "rfyE95hzWAE93h2sKvW92nX3Mf6eC3Vs7D", + "title": "xrplmonkeyz", + "trustlines": 2289, + "placeInTop": null + }, + { + "id": 16829, + "code": "FGARY", + "issuer": "rKYsFjADNw4ZVdN5CEvpDUBJndktRGYUrK", + "title": "F**k Gary", + "trustlines": 941, + "placeInTop": null + }, + { + "id": 16652, + "code": "xThug", + "issuer": "rULz7kXrUkXDqPvW5t3PrBn8XJ3GJytqUi", + "title": "xThug", + "trustlines": 4342, + "placeInTop": null + }, + { + "id": 82, + "code": "JSC", + "issuer": "rJ726mi7cXLfsnuehL84nmjjK14Rmx2ZQG", + "title": "Junkies | The Junkiverse | Junction", + "trustlines": 3686, + "placeInTop": null + }, + { + "id": 18453, + "code": "V3SafeMoon", + "issuer": "rENCYqb6HKcCRbRWBuufG1vwZp7JFe6pBE", + "title": "SafeMoonV3XRPL", + "trustlines": 3115, + "placeInTop": null + }, + { + "id": 16831, + "code": "BADxTruckzNFT", + "issuer": "rKas8SuSUBs7qQ7TaC1fQw3sviXBethJ2r", + "title": "BADxTruckz", + "trustlines": 1564, + "placeInTop": null + }, + { + "id": 18846, + "code": "nftwest", + "issuer": "rGxTL4RgJeJcN3SySxAQMnMnWXDEELjMfk", + "trustlines": 2279, + "placeInTop": null + }, + { + "id": 16893, + "code": "xLetsGoBrandon", + "issuer": "rpXQdkj19vvcEdoF6vqkbEkkFkyn4QN2sq", + "title": "XTRUMP", + "trustlines": 2081, + "placeInTop": null + }, + { + "id": 17153, + "code": "XmemeGoldenSmile", + "issuer": "rKNfnXcioJdKikc5hcwSrcxaqjdHwYRqMU", + "title": "XmemeGoldenSmile", + "trustlines": 942, + "placeInTop": null + }, + { + "id": 17087, + "code": "FEST", + "issuer": "rGXQdR5z5ykUjAUnyxJBT66ZpLnCua31q5", + "trustlines": 929, + "placeInTop": null + }, + { + "id": 17590, + "code": "XAG", + "issuer": "rpG9E7B3ocgaKqG7vmrsu3jmGwex8W4xAG", + "title": "Xrpalike Gene", + "trustlines": 1547, + "placeInTop": null + }, + { + "id": 79, + "code": "SLX", + "issuer": "rpzM9uVZ8NqQA1pcamhtd9afKaWLLG5rke", + "title": "Skullaxa", + "trustlines": 4279, + "placeInTop": null + }, + { + "id": 17710, + "code": "EUR", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "title": "SnapSwap", + "trustlines": 1833, + "placeInTop": null + }, + { + "id": 16699, + "code": "Xmonk", + "issuer": "rKuFp7TsTtQk7EXQthSAqiz7rwiFsaVtzG", + "trustlines": 5330, + "placeInTop": null + }, + { + "id": 209, + "code": "PENG", + "issuer": "rf668i9G2d6Xd39tZF19uKGLV3XsvrxFw7", + "title": "TheXRPenguins", + "trustlines": 3898, + "placeInTop": null + }, + { + "id": 17695, + "code": "ADA", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 1318, + "placeInTop": null + }, + { + "id": 285, + "code": "XSplash", + "issuer": "rw3Xyya7wcGvJsbuF2oSVY5BcR9pvbtRAR", + "title": "Xsplash", + "trustlines": 2369, + "placeInTop": null + }, + { + "id": 16961, + "code": "BANANA", + "issuer": "r3KSyXmYTYd6wd6ZwtrbEhQMjnJW3xpK4j", + "title": "Club X NFT - Bearable Bulls coming soon", + "trustlines": 1576, + "placeInTop": null + }, + { + "id": 16786, + "code": "XRPayNet", + "issuer": "r9rRLst96Ue4YTDQkWWkX1ePB6p6Ye4FkA", + "title": "XRPayNet", + "trustlines": 2510, + "placeInTop": null + }, + { + "id": 17024, + "code": "XMEMEXquisiteGcoin", + "issuer": "rDCCBV7riXcnQ3r345sqGJfqXFLpCfMUqd", + "title": "XMEMEXquisiteGcoin", + "trustlines": 856, + "placeInTop": null + }, + { + "id": 17708, + "code": "RJP", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 1844, + "placeInTop": null + }, + { + "id": 18561, + "code": "CAD", + "issuer": "r3ADD8kXSUKHd6zTCKfnKT3zV9EZHjzp1S", + "title": "RippleUnion", + "trustlines": 992, + "placeInTop": null + }, + { + "id": 18296, + "code": "SeagullMansions", + "issuer": "rHr4mUQjRusoNNYnzCp5BFumyWjycgVHJS", + "trustlines": 1808, + "placeInTop": null + }, + { + "id": 18465, + "code": "USDC", + "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "title": "GateHub USDC", + "trustlines": 3407, + "placeInTop": null + }, + { + "id": 16999, + "code": "RIBBITS", + "issuer": "rPmb5BPBAbE9jmNaFXNPH5kZEPDpRxaY77", + "trustlines": 1749, + "placeInTop": null + }, + { + "id": 16796, + "code": "MGT", + "issuer": "rwpQQdPJZmnGFNrPfbtCjtStdBxZK8qs3W", + "trustlines": 2857, + "placeInTop": null + }, + { + "id": 17533, + "code": "XNF", + "issuer": "rPKSvQ1qFAksr7hzk2wC2xtqSqFbxP3wvg", + "title": "NoFiatCoin", + "trustlines": 1495, + "placeInTop": null + }, + { + "id": 17189, + "code": "EmojiCoin", + "issuer": "rP7RsraS3viv9kGb4XNCY1N7JyaniDJB2z", + "title": "EmojiCoin", + "trustlines": 746, + "placeInTop": null + }, + { + "id": 155, + "code": "XALANA", + "issuer": "r8zkzkjvHxTFDkHJrqpssCd5HDUjCgxck", + "title": "Xalana", + "trustlines": 7170, + "placeInTop": null + }, + { + "id": 16704, + "code": "EGG", + "issuer": "rfwgeFU65NdYN5xDmogu9gB3hroYFhAqnL", + "title": "$BUN/$EGG", + "trustlines": 1098, + "placeInTop": null + }, + { + "id": 18692, + "code": "BTFD", + "issuer": "rERyZhzaBHjMcm3Cm1Gh8c4Vw1gPusFuLf", + "title": "Buythedip", + "trustlines": 842, + "placeInTop": null + }, + { + "id": 176, + "code": "BMA", + "issuer": "rVosg6X2B873FwaVZPM44CMjccSG5Ha6E", + "title": "BMAC - Blind Mummy Apes Club", + "trustlines": 3044, + "placeInTop": null + }, + { + "id": 18918, + "code": "CQR", + "issuer": "rpuEFKFoKESeoTMusx73znFGjbPqxiTPyg", + "title": "CQRFinance", + "trustlines": 1115, + "placeInTop": null + }, + { + "id": 138, + "code": "CCB", + "issuer": "rBxoALcypxTDYbkJr8MWLeUPYq64vAQpT4", + "title": "CucumberBrothers", + "trustlines": 6945, + "placeInTop": null + }, + { + "id": 19076, + "code": "ETH", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 2407, + "placeInTop": null + }, + { + "id": 17359, + "code": "NUDES", + "issuer": "rUckw1CEPF2vacf2o53kLDk4oLZd7Ha4LT", + "title": "Nudes Token", + "trustlines": 701, + "placeInTop": null + }, + { + "id": 18535, + "code": "XGBALLZ", + "issuer": "rPa8cmndFgf5sexbYDGaU3zx83qAWrBKrc", + "title": "XungiBallz", + "trustlines": 2411, + "placeInTop": null + }, + { + "id": 17588, + "code": "JCB", + "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n", + "trustlines": 5952, + "placeInTop": null + }, + { + "id": 17159, + "code": "xToadzXXL", + "issuer": "rhiagNCMcF342muHW6oMgdsf9uutCWv5Vw", + "title": "xToadz & sToadz", + "trustlines": 1540, + "placeInTop": null + }, + { + "id": 317, + "code": "SigmaFinance", + "issuer": "rpU72WXz1b9bnq4ZePgMvddovTwFXxY2Ar", + "title": "Sigma Finance", + "trustlines": 3227, + "placeInTop": null + }, + { + "id": 355, + "code": "xPEPE", + "issuer": "rw5e5krAvv1DrWyzmEr1NtNzg5jR26u5Gj", + "title": "xPEPE", + "trustlines": 1473, + "placeInTop": null + }, + { + "id": 17922, + "code": "CNY", + "issuer": "rM8199qFwspxiWNZRChZdZbGN5WrCepVP1", + "title": "DotPayco", + "trustlines": 1231, + "placeInTop": null + }, + { + "id": 293, + "code": "xKangaMK1", + "issuer": "rPwdrA6YFGR6k5rPyT6QPx7MrQAavUtyz5", + "title": "Combat Kanga", + "trustlines": 3022, + "placeInTop": null + }, + { + "id": 19106, + "code": "WTC", + "issuer": "rHZP1pA3THovFqvwgtM2tnJWrS54hSrDiG", + "trustlines": 801, + "placeInTop": null + }, + { + "id": 16721, + "code": "SRCIC", + "issuer": "rPnUe2Weo2JhMaXzkLHVkvdLxvVppEdkcr", + "title": "Sinceresponse", + "trustlines": 1683, + "placeInTop": null + }, + { + "id": 16679, + "code": "Xdinominers", + "issuer": "rH35xqtkhQPzYr8jyzp1rHbiG17KpuXa1s", + "title": "Xislanders_XDNT_Xdinos", + "trustlines": 3394, + "placeInTop": null + }, + { + "id": 18458, + "code": "MINT", + "issuer": "rwCsCz93A1svS6Yv8hFqUeKLdTLhBpvqGD", + "title": "\ud83e\ude84 MagicMint \ud83c\udf43", + "trustlines": 1736, + "placeInTop": null + }, + { + "id": 16692, + "code": "BZS", + "issuer": "rwNRkFw1zkELgiaGDEAYVhtZo9ogxSAXPD", + "title": "BALL ZACK SYNDICATE", + "trustlines": 2178, + "placeInTop": null + }, + { + "id": 17028, + "code": "IWC", + "issuer": "rPjWvuHydeXvkbXnjEX6gutkzxuN23Wuh4", + "title": "Islamic World Coin Community", + "trustlines": 896, + "placeInTop": null + }, + { + "id": 17761, + "code": "DOG", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 1630, + "placeInTop": null + }, + { + "id": 17321, + "code": "JKY", + "issuer": "rMi5Xw5Wi99SAqFfuWNAJZXwEMhBk4syHG", + "trustlines": 596, + "placeInTop": null + }, + { + "id": 17023, + "code": "AZHT", + "issuer": "rpNFcwPV9STzW8jaa2Hqt3MhbSRnkbkAof", + "title": "Joe(XRP)Cool", + "trustlines": 1712, + "placeInTop": null + }, + { + "id": 18723, + "code": "XRPhone", + "issuer": "rGzzsoiXsM3RmHU9Fu9eDQ6hoNoouyQiNT", + "title": "XRPhone", + "trustlines": 16299, + "placeInTop": null + }, + { + "id": 17587, + "code": "CNY", + "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n", + "trustlines": 2925, + "placeInTop": null + }, + { + "id": 18195, + "code": "Modulr", + "issuer": "rzxFbGjPaW6jE2WyzqmEHKhbSQmMkkJdr", + "trustlines": 571, + "placeInTop": null + }, + { + "id": 17674, + "code": "X20", + "issuer": "rNmdY5wPVTx9awvX6WyDfJwbdgLzc8AY8c", + "title": "XLS-20.art", + "trustlines": 8838, + "placeInTop": null + }, + { + "id": 16842, + "code": "icoin", + "issuer": "rJSTh1VLk52tFC3VRXkNWu7Q4nYmfZv7BZ", + "title": "Icoin", + "trustlines": 7614, + "placeInTop": null + }, + { + "id": 358, + "code": "xLoveMonster", + "issuer": "rsQRCHYZzsSjMpAs63TtPFkittdJzY3gET", + "title": "Love Monster", + "trustlines": 12418, + "placeInTop": null + }, + { + "id": 19104, + "code": "JPY", + "issuer": "rDKcJtUW5b8URJB5AVb41jkTeETfXWwahe", + "trustlines": 2469, + "placeInTop": null + }, + { + "id": 295, + "code": "PPT", + "issuer": "rpdRSNVE5UU26ewDzJZs4afEwkTvCHXwVA", + "title": "Pretty Pandas SGB", + "trustlines": 1947, + "placeInTop": null + }, + { + "id": 17877, + "code": "XZOMBIE", + "issuer": "rpwJD6xNmDTq28jcBmAem8KMGLSYChZRpR", + "title": "XZOMBIE", + "trustlines": 3217, + "placeInTop": null + }, + { + "id": 18638, + "code": "PCSH", + "issuer": "rEQqDLHYBA5cLuVpm3by6kaThvZQJL8q68", + "title": "xPEPE", + "trustlines": 855, + "placeInTop": null + }, + { + "id": 326, + "code": "HIGH", + "issuer": "raDF491dC9XF1guG5zZkWWpNQoFquwt2cp", + "trustlines": 2828, + "placeInTop": null + }, + { + "id": 18224, + "code": "GCB", + "issuer": "rUXUGrsSgqya9CxiA4oxzSgNmDhsb6qZ6c", + "trustlines": 601, + "placeInTop": null + }, + { + "id": 17355, + "code": "XZV", + "issuer": "r4JgusJLQt1sMsUEnXSFAXPNnbCocs6Fxf", + "title": "Officialexcluzeev", + "trustlines": 6604, + "placeInTop": null + }, + { + "id": 177, + "code": "KegsRP", + "issuer": "rJzCA6r1UXKLxVpLf31XVb5Na57hYYZ6n2", + "title": "KegsRPIssuer", + "trustlines": 1971, + "placeInTop": null + }, + { + "id": 318, + "code": "Munchies", + "issuer": "raDF491dC9XF1guG5zZkWWpNQoFquwt2cp", + "trustlines": 2825, + "placeInTop": null + }, + { + "id": 18512, + "code": "HAV", + "issuer": "rPjKLDFj3jXMTBFFw8ZUZbjrTWm63DEPYN", + "trustlines": 895, + "placeInTop": null + }, + { + "id": 16834, + "code": "Yonix", + "issuer": "rHXqJwSnD21pFY7NyRuZ1AUX7WYaK7V7sJ", + "title": "Yoniverse", + "trustlines": 1606, + "placeInTop": null + }, + { + "id": 18913, + "code": "ALBA", + "issuer": "rPzvaUJzQ7EVxuPYXPfwLtSRXGpfxDGt9h", + "title": "ALBA", + "trustlines": 521, + "placeInTop": null + }, + { + "id": 18459, + "code": "USDT", + "issuer": "rcvxE9PS9YBwxtGg1qNeewV6ZB3wGubZq", + "title": "GateHub USDT", + "trustlines": 1737, + "placeInTop": null + }, + { + "id": 17052, + "code": "LTC", + "issuer": "rcRzGWq6Ng3jeYhqnmM4zcWcUh69hrQ8V", + "title": "GateHub LTC", + "trustlines": 2216, + "placeInTop": null + }, + { + "id": 110, + "code": "XRSharPei", + "issuer": "rQHp4ipKWh4M53omvbkhwZXYjwQVXXycPb", + "title": "Linsey Bracken", + "trustlines": 539, + "placeInTop": null + }, + { + "id": 6, + "code": "TAC", + "issuer": "rE2EBYiPVALWzHob18YK47P8HgkEv6T1pV", + "title": "Trippy Apes Club", + "trustlines": 3000, + "placeInTop": null + }, + { + "id": 137, + "code": "Arnies", + "issuer": "rfzRVCV35aXN2xNRf2hhq3o8DFDpUt8DEa", + "title": "ARNIE'S NFT (XRPL)", + "trustlines": 3071, + "placeInTop": null + }, + { + "id": 16710, + "code": "XSTX", + "issuer": "r9KanwHSeSYB9DXfgc3sDuoE3sRsnQv4wo", + "title": "XSTX", + "trustlines": 3818, + "placeInTop": null + }, + { + "id": 16898, + "code": "XPoesy", + "issuer": "rMoDRBbLkYBRvagYZZakCtydEzMVwvrcqL", + "title": "XPoets", + "trustlines": 1478, + "placeInTop": null + }, + { + "id": 16778, + "code": "XDNT", + "issuer": "rnijBVqpT1RpMkLS9BsUZBneFeFrjz2h94", + "trustlines": 1210, + "placeInTop": null + }, + { + "id": 16616, + "code": "CFC", + "issuer": "rsxUkmjnAn8PRDz8RYrPusb9mTDYn5NqG8", + "trustlines": 10496, + "placeInTop": null + }, + { + "id": 16762, + "code": "xLabor", + "issuer": "rP4DR419Lafe6RPyS1vbFXW7DPpUHw2iR", + "title": "Xlabor", + "trustlines": 2306, + "placeInTop": null + }, + { + "id": 16960, + "code": "BAPE", + "issuer": "r3KSyXmYTYd6wd6ZwtrbEhQMjnJW3xpK4j", + "title": "Club X NFT - Bearable Bulls coming soon", + "trustlines": 1401, + "placeInTop": null + }, + { + "id": 17970, + "code": "FNGRS", + "issuer": "rwJfQcN7EzaDcpcmCQYef8mxpsHZceeWAC", + "title": "xFingeRs NFT", + "trustlines": 1899, + "placeInTop": null + }, + { + "id": 17051, + "code": "XAU", + "issuer": "rcoef87SYMJ58NAFx7fNM5frVknmvHsvJ", + "title": "BPG Kovine", + "trustlines": 5381, + "placeInTop": null + }, + { + "id": 17956, + "code": "XPixelPanties", + "issuer": "rMuPmDnooeqcjf28ZxsTF5Z1i2wa81Bw6z", + "title": "Xpixelpanties", + "trustlines": 1109, + "placeInTop": null + }, + { + "id": 19044, + "code": "BTC", + "issuer": "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX", + "title": "Dividend Rippler", + "trustlines": 1119, + "placeInTop": null + }, + { + "id": 18163, + "code": "Krill", + "issuer": "r9FGdbCdjjRAWkn37kKgCQDsdm3NzgHieD", + "title": "XWhales Official", + "trustlines": 1690, + "placeInTop": null + }, + { + "id": 17943, + "code": "STR", + "issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y", + "title": "Ripple Fox", + "trustlines": 869, + "placeInTop": null + }, + { + "id": 17691, + "code": "GKO", + "issuer": "ruazs5h1qEsqpke88pcqnaseXdm6od2xc", + "title": "Geckocoin", + "trustlines": 985, + "placeInTop": null + }, + { + "id": 18481, + "code": "BAFC", + "issuer": "r1qbiB6ib51kqNuipc3Asja4itGv2hUeY", + "title": "Bored Ape Football Club", + "trustlines": 2139, + "placeInTop": null + }, + { + "id": 19054, + "code": "JPY", + "issuer": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg", + "title": "Digital Gate Japan", + "trustlines": 843, + "placeInTop": null + }, + { + "id": 185, + "code": "FatCat", + "issuer": "rQ9oCoSZx72ztd1rpifUnGfwCwUdnr4Rds", + "title": "Fat Cats", + "trustlines": 2102, + "placeInTop": null + }, + { + "id": 16780, + "code": "SEAGULLAPARTMENTS", + "issuer": "rKjevbXgCs6sP8XTLz6SgcE5RKCLuiS1r3", + "title": "Bored Seagull Club", + "trustlines": 950, + "placeInTop": null + }, + { + "id": 17410, + "code": "CUTE", + "issuer": "rMyLn261pBVKtgMbzFpbAsFFBL7ksbFvvM", + "title": "CUTE Token XRPL", + "trustlines": 894, + "placeInTop": null + }, + { + "id": 132, + "code": "xCuisine", + "issuer": "rQKMxxEjhZcH1dXcReQdPGWWtf8zkiwukK", + "title": "Master Chef_Cuisine NFT", + "trustlines": 3783, + "placeInTop": null + }, + { + "id": 18492, + "code": "XLUX", + "issuer": "rsA8pK4EbEHLBv6HPpk1r1ND8RchPabTHC", + "title": "Lux Lions NFT | Bringing the Lux to Luxury", + "trustlines": 711, + "placeInTop": null + }, + { + "id": 18676, + "code": "AAX", + "issuer": "r4xyzqvcJFk9Vghfvmp9dX1LUdesN43F5H", + "title": "Anonymous Astronauts", + "trustlines": 1479, + "placeInTop": null + }, + { + "id": 17655, + "code": "ULT", + "issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y", + "title": "Ripple Fox", + "trustlines": 690, + "placeInTop": null + }, + { + "id": 17402, + "code": "XRPHOENIX", + "issuer": "rMDxGz8kg1TKBNBC8C9n3pYGEQ9MsQFJHG", + "title": "XRPHOENIX", + "trustlines": 1473, + "placeInTop": null + }, + { + "id": 18585, + "code": "KODOKU", + "issuer": "rJa1mYRd57AGqoZfWK2kD9UhemQDE4osHE", + "title": "XRPL KODOKU", + "trustlines": 1110, + "placeInTop": null + }, + { + "id": 19011, + "code": "XAUR", + "issuer": "rpHNkyZpZTpW9w8zpxUcwAbgLNCGyLHPFm", + "trustlines": 8676, + "placeInTop": null + }, + { + "id": 18873, + "code": "PHOENIX", + "issuer": "rEpsJ5NaJpQisqmggsCL3TA2YeevgbAFGu", + "title": "PhoenixSwap", + "trustlines": 941, + "placeInTop": null + }, + { + "id": 17542, + "code": "JED", + "issuer": "rMUVZiUKrcZNqfpgNJRqQjpatGCPZSdrB8", + "title": "Got Jed?", + "trustlines": 593, + "placeInTop": null + }, + { + "id": 141, + "code": "INDI", + "issuer": "r3Uqw95RzBpcFCSVNphAgWWhjsQTxG5xvV", + "title": "Indigena", + "trustlines": 1938, + "placeInTop": null + }, + { + "id": 16985, + "code": "XLLAMAS", + "issuer": "rM7eceNskZB43X7aCDe22moJBEw9u42aN8", + "title": "Llamapalooza", + "trustlines": 1778, + "placeInTop": null + }, + { + "id": 17392, + "code": "xrplapps", + "issuer": "rBMPRPhbJPYER9k4HDwJY9PwrFQoB5wu89", + "trustlines": 378, + "placeInTop": null + }, + { + "id": 17762, + "code": "RBJ", + "issuer": "rEYD5k6kZPARQbKfB9GpiNPDY93kRQgPKj", + "title": "Ripple-Bank-Japan", + "trustlines": 339, + "placeInTop": null + }, + { + "id": 18191, + "code": "Greenfuel", + "issuer": "rEbZTML6KCMwRMqD6hRTw6K8kqKgLRnEH1", + "title": "GreenFuel", + "trustlines": 2490, + "placeInTop": null + }, + { + "id": 18741, + "code": "StakeXR", + "issuer": "rL5777XtQsBcwwRFWFuvcp5R8xNhTyRJrM", + "title": "StakeXR", + "trustlines": 1874, + "placeInTop": null + }, + { + "id": 18895, + "code": "EUR", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 953, + "placeInTop": null + }, + { + "id": 18534, + "code": "Xcrusader", + "issuer": "rhXa7avJF65iC27j6KRDRrWo2mVqrfVUmZ", + "title": "Xcrusader", + "trustlines": 2793, + "placeInTop": null + }, + { + "id": 18033, + "code": "DEBRIS", + "issuer": "r3FajLvMRbDCJ2n1NH3F6P1ZtpsfL7h9tm", + "title": "Debris", + "trustlines": 2757, + "placeInTop": null + }, + { + "id": 17388, + "code": "SGB", + "issuer": "rhrFfvzZAytd8UHPH87UHMgHQ18nnLbpgN", + "title": "GateHub SGB", + "trustlines": 2720, + "placeInTop": null + }, + { + "id": 18818, + "code": "PAW", + "issuer": "rD9V13zAF1RoLUDiCqyi6BwVQe4oWVaNgH", + "trustlines": 822, + "placeInTop": null + }, + { + "id": 112, + "code": "MZT", + "issuer": "r3657An8zaoTVae8mkyamKp4ddELSFKmcn", + "title": "MZT", + "trustlines": 9196, + "placeInTop": null + }, + { + "id": 47, + "code": "Galaxy", + "issuer": "r3Si5cWtHZZuKXQERoaTKc7Vhw53RdGi2A", + "title": "GALAXY", + "trustlines": 3650, + "placeInTop": null + }, + { + "id": 18539, + "code": "GOTV", + "issuer": "r9DS9LyzgvBhBm5WPsgW6kGzUpddyszkUV", + "title": "Guardians Of The Vault", + "trustlines": 1077, + "placeInTop": null + }, + { + "id": 16681, + "code": "TeddyBoys", + "issuer": "rUQp5C29SacMCbUrhrQYHGkqSQNsHULARQ", + "title": "TEDDY BOYS NFT | XRPL", + "trustlines": 6963, + "placeInTop": null + }, + { + "id": 18921, + "code": "XPD", + "issuer": "rfMc2D2GpZiHiXbq44xQHAC2bGK1qbtMA3", + "trustlines": 565, + "placeInTop": null + }, + { + "id": 17389, + "code": "ImmortalXKX", + "issuer": "rpAx21eFgJA6n8UWZfPjmkvaFzHzuvS8t6", + "title": "Crypto Prince", + "trustlines": 401, + "placeInTop": null + }, + { + "id": 18965, + "code": "XGO!", + "issuer": "rnucXjMr92b229V2KHgjG6V1Hz3JjsgqQN", + "title": "XGameOn", + "trustlines": 3198, + "placeInTop": null + }, + { + "id": 276, + "code": "PAY", + "issuer": "rMH8M1a1vWi6EwySMVwrnPLg6R6PzDpBVT", + "title": "XRPL Pay", + "trustlines": 2631, + "placeInTop": null + }, + { + "id": 18602, + "code": "AlienJump", + "issuer": "rajXWmVmQrFF8Bj1nzkT9JaWhPFXu1EoDw", + "title": "AlienJumpXrpl", + "trustlines": 1281, + "placeInTop": null + }, + { + "id": 18203, + "code": "MEMORY", + "issuer": "rfTsfrviaNgJ1URQf4FVWjsr59ULr1FNgu", + "trustlines": 722, + "placeInTop": null + }, + { + "id": 222, + "code": "xPug", + "issuer": "rNxNMb7h5ZT86EXy6nHtxSpf4W8ZMLNy6b", + "title": "xPug", + "trustlines": 3856, + "placeInTop": null + }, + { + "id": 16959, + "code": "UnionCoin", + "issuer": "rMYeffeDZeJtMWDM7StFj3hQxpB5KL6hfa", + "title": "UNION COIN", + "trustlines": 2968, + "placeInTop": null + }, + { + "id": 19045, + "code": "BTC", + "issuer": "rJHygWcTLVpSXkowott6kzgZU6viQSVYM1", + "title": "Justcoin", + "trustlines": 670, + "placeInTop": null + }, + { + "id": 18340, + "code": "GALA", + "issuer": "rf5YPb9y9P3fTjhxNaZqmrwaj5ar8PG1gM", + "title": "GateHub GALA", + "trustlines": 1193, + "placeInTop": null + }, + { + "id": 19105, + "code": "USD", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 663, + "placeInTop": null + }, + { + "id": 17283, + "code": "Boss", + "issuer": "r9bBb26SkJDW9dEXKFHkZdJvQVXE5pTwTM", + "title": "Bostonkrypto", + "trustlines": 267, + "placeInTop": null + }, + { + "id": 18157, + "code": "2CD", + "issuer": "rDxLqyy7UEAbbz1kC9HqnjLXae3KZLPNPM", + "title": "2Cold", + "trustlines": 696, + "placeInTop": null + }, + { + "id": 18562, + "code": "KRW", + "issuer": "rwdRX4jgRB5rZkbbbG7ezVTBKdBNKVrtpR", + "title": "Digital Gate Korea", + "trustlines": 646, + "placeInTop": null + }, + { + "id": 16779, + "code": "XRbitcoincash", + "issuer": "rEjwniYhYR5QDZzK1a1x2359j8j8N43Ypw", + "title": "XRbitcoincash", + "trustlines": 2447, + "placeInTop": null + }, + { + "id": 18537, + "code": "WEN", + "issuer": "rp2Em1RYo9sxJPJAMe9bDCkdDhirqUYQZC", + "title": "Club X NFT - Bearable Bulls coming soon", + "trustlines": 336, + "placeInTop": null + }, + { + "id": 17532, + "code": "XEC", + "issuer": "rNx3ty1w48iAa4JDfqhZk6bphwXxvCEtZP", + "title": "El-coin", + "trustlines": 1661, + "placeInTop": null + }, + { + "id": 18798, + "code": "XMG", + "issuer": "rQnMyMX5abtQrrsMm2rjws3agapxyKiPGZ", + "title": "XMG - XRPL Gaming Ecosystem", + "trustlines": 824, + "placeInTop": null + }, + { + "id": 16703, + "code": "DRIPPED", + "issuer": "rKtfM2S7EVkp3vF5WVM44L8RyGjrmE4ZG5", + "title": "DRIPPED", + "trustlines": 2345, + "placeInTop": null + }, + { + "id": 16781, + "code": "punkdninjas", + "issuer": "r95N6Va2n2EdKGnDCGNKLgtXBxpd1Xy3MH", + "title": "PunkdNinjas", + "trustlines": 2020, + "placeInTop": null + }, + { + "id": 19058, + "code": "USD", + "issuer": "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9", + "title": "Payroutes", + "trustlines": 756, + "placeInTop": null + }, + { + "id": 17592, + "code": "JUD", + "issuer": "r9qouqdcygAWyoFGPxwMN3LoJkpHJHoCBd", + "trustlines": 359, + "placeInTop": null + }, + { + "id": 19053, + "code": "JPY", + "issuer": "r9ZFPSb1TFdnJwbTMYHvVwFK1bQPUCVNfJ", + "title": "Ripple Exchange Tokyo", + "trustlines": 552, + "placeInTop": null + }, + { + "id": 18283, + "code": "Animabox", + "issuer": "rHxrNp8ruthrrrmhBdcQosNQNRQ1QTzi1o", + "title": "\ud835\udc00\ud835\udc27\ud835\udc22\ud835\udc26\ud835\udc1a\ud835\udc01\ud835\udc28\ud835\udc31 \ud835\udc0d\ud835\udc05\ud835\udc13 on XRPL \ud83d\udc0d", + "trustlines": 2095, + "placeInTop": null + }, + { + "id": 313, + "code": "xPizza", + "issuer": "rUMwLWFzwcD2topAKsg5NBbhq7dtu4gRCU", + "title": "XRPizzaParty", + "trustlines": 2776, + "placeInTop": null + }, + { + "id": 16948, + "code": "Diamond", + "issuer": "rfSTRox25KWznAtKCLqJ29zPxwErkhbX49", + "title": "Diamond", + "trustlines": 2175, + "placeInTop": null + }, + { + "id": 18536, + "code": "ZANIMAL", + "issuer": "rLacyEG9x5q3VXtoS3AnobnfedKmU89f6Q", + "trustlines": 582, + "placeInTop": null + }, + { + "id": 17558, + "code": "ICE", + "issuer": "r4H3F9dDaYPFwbrUsusvNAHLz2sEZk4wE5", + "trustlines": 578, + "placeInTop": null + }, + { + "id": 17405, + "code": "VRB", + "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n", + "trustlines": 3737, + "placeInTop": null + }, + { + "id": 17740, + "code": "BCC", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 596, + "placeInTop": null + }, + { + "id": 100, + "code": "XKINGAPES", + "issuer": "rK6H71AGq5ndQ3ChRL3rKABB14qmj68GwW", + "title": "XKING APES", + "trustlines": 3875, + "placeInTop": null + }, + { + "id": 17485, + "code": "SCZ", + "issuer": "rScheetz28HPqwAo3sxCtqKxU5TPWbLEf", + "title": "Scheetz", + "trustlines": 326, + "placeInTop": null + }, + { + "id": 17412, + "code": "xGaf", + "issuer": "rGidCc3AXaFzHLJrZSd1R9u7nM2YKKtUFK", + "title": "xGAF", + "trustlines": 3864, + "placeInTop": null + }, + { + "id": 18947, + "code": "ALPHA", + "issuer": "rwSqY5LuiU6nZutikF2Ubnm47MwDnaKGUz", + "title": "3DAPES\u2122", + "trustlines": 897, + "placeInTop": null + }, + { + "id": 162, + "code": "DONKY", + "issuer": "rMZnj5DdExmoUmm9QnEgmiKw3QEA9oRdjL", + "title": "XRDonkey", + "trustlines": 3150, + "placeInTop": null + }, + { + "id": 228, + "code": "DOG", + "issuer": "rbUNsWhC4vqUwcsaWeLH9FBnMNBkxAka4", + "title": "XRP DOG", + "trustlines": 1127, + "placeInTop": null + }, + { + "id": 18716, + "code": "NIO", + "issuer": "rawT5z6bFKigVRHqu29TdZ6aBejvMEqxxT", + "title": "Nitro", + "trustlines": 384, + "placeInTop": null + }, + { + "id": 18802, + "code": "xRYO", + "issuer": "rLPZJ6xQkq36DurkpqsQkggMpMktPVUrVD", + "trustlines": 396, + "placeInTop": null + }, + { + "id": 18934, + "code": "LTC", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 1566, + "placeInTop": null + }, + { + "id": 19041, + "code": "BTC", + "issuer": "rKxKhXZCeSDsbkyB8DVgxpjy5AHubFkMFe", + "title": "Rippex", + "trustlines": 1177, + "placeInTop": null + }, + { + "id": 18796, + "code": "BabyXRP", + "issuer": "rHMNE7vcmAQQqdkuKzTTEg9SJY7nZVvgUV", + "trustlines": 562, + "placeInTop": null + }, + { + "id": 233, + "code": "XCEL", + "issuer": "rDZU6y1wDuZPLxkUkr6GFwE6EdJtJu3Lk3", + "trustlines": 1321, + "placeInTop": null + }, + { + "id": 17851, + "code": "FLEA", + "issuer": "r3G2QxjkCJCqDub6z3SSrAVoJQVEsJ2NtU", + "trustlines": 596, + "placeInTop": null + }, + { + "id": 17931, + "code": "USD", + "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n", + "trustlines": 3897, + "placeInTop": null + }, + { + "id": 18617, + "code": "Bits", + "issuer": "rnYtXosnrsbtVTRkMNfBX2CHzjPQom1WJv", + "title": "X-A.I. NFT", + "trustlines": 759, + "placeInTop": null + }, + { + "id": 18359, + "code": "XCLOWN", + "issuer": "rEg5q7fmkF387T85T1b3dzViEfspMzcccJ", + "title": "xrpl clown", + "trustlines": 1605, + "placeInTop": null + }, + { + "id": 18843, + "code": "XCC", + "issuer": "r3fJbmdnFhQwHKxD1QDwMfmras9jLoh4RW", + "title": "XCleanCoin (XCC)", + "trustlines": 2201, + "placeInTop": null + }, + { + "id": 17819, + "code": "FMM", + "issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y", + "title": "Ripple Fox", + "trustlines": 438, + "placeInTop": null + }, + { + "id": 18054, + "code": "ILS", + "issuer": "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9", + "title": "Payroutes", + "trustlines": 380, + "placeInTop": null + }, + { + "id": 17548, + "code": "RIP", + "issuer": "rEYD5k6kZPARQbKfB9GpiNPDY93kRQgPKj", + "title": "Ripple-Bank-Japan", + "trustlines": 202, + "placeInTop": null + }, + { + "id": 17665, + "code": "CCK", + "issuer": "rp2PaYDxVwDvaZVLEQv7bHhoFQEyX1mEx7", + "title": "P2pay-Cold", + "trustlines": 228, + "placeInTop": null + }, + { + "id": 361, + "code": "Tribe", + "issuer": "rh6MHdcZDRwfcqFyAQP4bvZvKTyArnTR2W", + "title": "Tribe XRPL", + "trustlines": 9750, + "placeInTop": null + }, + { + "id": 16659, + "code": "XLINDEN", + "issuer": "rNjHfrvYTbCXMkkPDfLF6RtFcr4YbK3UL5", + "title": "XLINDEN", + "trustlines": 1700, + "placeInTop": null + }, + { + "id": 17796, + "code": "589", + "issuer": "rstHc6J8zLvrK8HdoNX4Mj66FQRVkUSTfb", + "title": "589", + "trustlines": 660, + "placeInTop": null + }, + { + "id": 16856, + "code": "BSC", + "issuer": "rsdK4XBXoG5JUHwR56Deq2ncTqjr4TxSi7", + "title": "Bored Seagull Club", + "trustlines": 2363, + "placeInTop": null + }, + { + "id": 94, + "code": "Cannabis", + "issuer": "rDdVN3HqhJb36XXLHpbeVmHxuUCfx7ryRm", + "title": "XCANNABIS", + "trustlines": 4678, + "placeInTop": null + }, + { + "id": 120, + "code": "CCC", + "issuer": "r433dvTW1cM1JGWTeRXTPTbCA87BcUoScj", + "title": "Content Connections Coin (CCC)", + "trustlines": 7023, + "placeInTop": null + }, + { + "id": 16839, + "code": "GodBit", + "issuer": "rKzPmuWL2pExMTqudrsHDySWY6y6nNVKxT", + "title": "GodBit", + "trustlines": 1766, + "placeInTop": null + }, + { + "id": 17914, + "code": "YZK", + "issuer": "rJr2Jy71Mb6kqv4iux79wvadq2LtqLSsdz", + "title": "Yozok", + "trustlines": 4238, + "placeInTop": null + }, + { + "id": 17847, + "code": "STR", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 1385, + "placeInTop": null + }, + { + "id": 19047, + "code": "BTC", + "issuer": "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9", + "title": "Payroutes", + "trustlines": 550, + "placeInTop": null + }, + { + "id": 81, + "code": "koi", + "issuer": "rBzhSC1vWsMXgMSWoiE2m6bHmXynyDxkUN", + "title": "IXRPWORLD", + "trustlines": 3544, + "placeInTop": null + }, + { + "id": 18643, + "code": "XPIRATES", + "issuer": "rKZNq2QU6TcHnBDEpEhnikMfmuUQedKg51", + "title": "xPirates", + "trustlines": 876, + "placeInTop": null + }, + { + "id": 259, + "code": "CWC", + "issuer": "rHm8VBgtAwKAWyueyhrDxDfwQ5k2M6Tz3W", + "title": "ClownWorldCoin", + "trustlines": 1502, + "placeInTop": null + }, + { + "id": 17269, + "code": "PHX", + "issuer": "rfEJ1ksD22TsCy8hdKoJuC6Xfc33VCKPUs", + "title": "TheRugPhxProject", + "trustlines": 633, + "placeInTop": null + }, + { + "id": 17463, + "code": "RPS", + "issuer": "r9wx4BoPPDtNYMkEY7Xj5bJK25JfyNwSZ", + "title": "Ripplerps", + "trustlines": 201, + "placeInTop": null + }, + { + "id": 18838, + "code": "WAKEx", + "issuer": "rpxVbcAHxZPPPJJF9NE3e9FN4eSJLTs6NL", + "title": "WAKEx", + "trustlines": 511, + "placeInTop": null + }, + { + "id": 18827, + "code": "XDEX", + "issuer": "rPXRmaPf4PDXikmXF1nAyxxVpyyVhyx1We", + "trustlines": 3743, + "placeInTop": null + }, + { + "id": 19015, + "code": "USD", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "title": "The Rock Trading", + "trustlines": 554, + "placeInTop": null + }, + { + "id": 354, + "code": "XREX", + "issuer": "rPasXvMsRmrqmK9HMT5MQp8ie8ZVoFBwtG", + "title": "XREX", + "trustlines": 4051, + "placeInTop": null + }, + { + "id": 17032, + "code": "SOF", + "issuer": "rGwNpLfBtJ9t1q8G1e3pgKa1n1ibgjqRph", + "title": "SoFCenter", + "trustlines": 1470, + "placeInTop": null + }, + { + "id": 18162, + "code": "MTM", + "issuer": "rMtMPTwyU6jWTS18sJFnii2kCoBioUEure", + "trustlines": 528, + "placeInTop": null + }, + { + "id": 18687, + "code": "PXP", + "issuer": "rPXPpTDcGL2zhNS2DFCbsep97H6sraTT7s", + "trustlines": 364, + "placeInTop": null + }, + { + "id": 18894, + "code": "EUR", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "title": "The Rock Trading", + "trustlines": 441, + "placeInTop": null + }, + { + "id": 16688, + "code": "DBDXR", + "issuer": "rH3jeL5UgE8KKJYSN8LoQX2mgh2kZoja4x", + "title": "Digital Black Dollar 1.0", + "trustlines": 1055, + "placeInTop": null + }, + { + "id": 17554, + "code": "NXT", + "issuer": "ra9eZxMbJrUcgV8ui7aPc161FgrqWScQxV", + "title": "Peercover", + "trustlines": 413, + "placeInTop": null + }, + { + "id": 64, + "code": "ELF", + "issuer": "rKhkLTgZrAaHXY5NzquXfJmXX3TRpV1Dmv", + "trustlines": 5065, + "placeInTop": null + }, + { + "id": 16795, + "code": "CentralReserve", + "issuer": "rfAPdqD3W33CHAGMb4P7tpJVyNE1AGuZHg", + "trustlines": 1942, + "placeInTop": null + }, + { + "id": 16843, + "code": "Xalienware", + "issuer": "r9T7w5KuVhb9B5Ddj6ps5AhnZNikAY2caK", + "title": "Xalienware", + "trustlines": 3885, + "placeInTop": null + }, + { + "id": 16986, + "code": "STREAM", + "issuer": "rE3BGaHdGfMYF5kXsWkkiV7DUyr7aRXKGF", + "title": "STREAM", + "trustlines": 3989, + "placeInTop": null + }, + { + "id": 17015, + "code": "QUX", + "issuer": "rfEG1uxjAqeLv8joDqeUqomssVWSpoX6h4", + "trustlines": 1328, + "placeInTop": null + }, + { + "id": 16799, + "code": "XNC", + "issuer": "rfoD7GTb7UNt8dkC6PVrx234N4ivRqf6LF", + "title": "XrpNurse", + "trustlines": 2556, + "placeInTop": null + }, + { + "id": 17535, + "code": "XPY", + "issuer": "rEVxaqydnDTqiLC6ivwEAAG4j21zS2LuLe", + "trustlines": 185, + "placeInTop": null + }, + { + "id": 16837, + "code": "XSL", + "issuer": "rLA1aoxTqUx7RYPLBZ4XmzjWNNUZcQkL95", + "trustlines": 734, + "placeInTop": null + }, + { + "id": 16882, + "code": "BMZ", + "issuer": "rsH7ktoKiuXx1zefgFreuaNGjUAt7ZWRPY", + "title": "xrplmonkeyz", + "trustlines": 2306, + "placeInTop": null + }, + { + "id": 18189, + "code": "ApesPunk", + "issuer": "rD7JGkzLtHUuKiyhiWuKvwU23mkD8kmQ5Z", + "title": "ApesPunk - XRPL", + "trustlines": 2200, + "placeInTop": null + }, + { + "id": 18252, + "code": "XMKAT", + "issuer": "r38btybd7kRqPctNJmcsf9SrCC3uzjPccf", + "title": "LAME LIONS XRPL \ud83d\udc51", + "trustlines": 1119, + "placeInTop": null + }, + { + "id": 17082, + "code": "XKF", + "issuer": "rG6RZ93xFrgCcXhWdh2hAWDeDwydMvS19J", + "title": "CombatKangaFuel", + "trustlines": 1329, + "placeInTop": null + }, + { + "id": 19043, + "code": "BTC", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "title": "The Rock Trading", + "trustlines": 536, + "placeInTop": null + }, + { + "id": 373, + "code": "UsaveMe", + "issuer": "rLL124XfvHPNJGMkrScm2HMACA2hkzKsL7", + "title": "Usaveme", + "trustlines": 4821, + "placeInTop": null + }, + { + "id": 17700, + "code": "MXN", + "issuer": "rG6FZ31hDHN1K5Dkbma3PSB5uVCuVVRzfn", + "title": "Bitso", + "trustlines": 471, + "placeInTop": null + }, + { + "id": 16906, + "code": "XRGold", + "issuer": "rGMjJsTD6LgQo2usixuox16PSwm4m9X5Ym", + "trustlines": 1837, + "placeInTop": null + }, + { + "id": 124, + "code": "TESLA", + "issuer": "r91SCjzULbyv3Y4o1Czem5pcYYxjNJuaNo", + "title": "$TESLA", + "trustlines": 5120, + "placeInTop": null + }, + { + "id": 18856, + "code": "xAds", + "issuer": "rsMQEnuvEqzeNPu7ZNpGctGBrWLv4x2mb2", + "title": "xAds - XRPL", + "trustlines": 612, + "placeInTop": null + }, + { + "id": 16775, + "code": "XRbitcoin", + "issuer": "rGQaHbQHCsTLQtboQPwUBasXjLvk8uDbpT", + "title": "XRbitcoin", + "trustlines": 704, + "placeInTop": null + }, + { + "id": 17849, + "code": "NZD", + "issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc", + "title": "Coinex", + "trustlines": 1318, + "placeInTop": null + }, + { + "id": 18625, + "code": "CTC", + "issuer": "rHZP1pA3THovFqvwgtM2tnJWrS54hSrDiG", + "trustlines": 213, + "placeInTop": null + }, + { + "id": 19075, + "code": "KRW", + "issuer": "rPxU6acYni7FcXzPCMeaPSwKcuS2GTtNVN", + "title": "EXRP", + "trustlines": 500, + "placeInTop": null + }, + { + "id": 17557, + "code": "DOG", + "issuer": "ra9eZxMbJrUcgV8ui7aPc161FgrqWScQxV", + "title": "Peercover", + "trustlines": 290, + "placeInTop": null + }, + { + "id": 16744, + "code": "MooM", + "issuer": "rDWgjsCDx93gscZ99MXZAcymiNaBHrzk9H", + "title": "XRP MooM", + "trustlines": 1160, + "placeInTop": null + }, + { + "id": 18460, + "code": "WXRP", + "issuer": "rEa5QY8tdbjgitLyfKF1E5Qx3VGgvbUhB3", + "title": "GateHub WXRP", + "trustlines": 836, + "placeInTop": null + }, + { + "id": 174, + "code": "xHAM", + "issuer": "rQ9BmiUbMus85EiPQKYnMxMA8KBagawbQy", + "title": "xHustlers", + "trustlines": 985, + "placeInTop": null + }, + { + "id": 18001, + "code": "XMasons", + "issuer": "rfS2WUDbrMn25MbnD9XHivbQ5oYsdpvqLm", + "title": "Xmasons", + "trustlines": 8763, + "placeInTop": null + }, + { + "id": 319, + "code": "XLGBTQ", + "issuer": "r4cXkPESi5UsWuqC3yaHczitiH4oXAExh", + "title": "XLGBTQCOIN", + "trustlines": 1590, + "placeInTop": null + }, + { + "id": 17005, + "code": "XRP2", + "issuer": "r9rRLst96Ue4YTDQkWWkX1ePB6p6Ye4FkA", + "title": "XRPayNet", + "trustlines": 2351, + "placeInTop": null + }, + { + "id": 18338, + "code": "TUSK", + "issuer": "raMsKbJcRYLF1nwhWPntqXuwBdtftfi234", + "title": "Sturdy Hogs Society", + "trustlines": 1480, + "placeInTop": null + }, + { + "id": 198, + "code": "Xwizard", + "issuer": "rUifimbEkHzyQYM88iJzhUnFSYPvAkiFtZ", + "title": "CbotLabsInc.", + "trustlines": 2368, + "placeInTop": null + }, + { + "id": 16945, + "code": "SNG", + "issuer": "rBuoRCgafUXHamjGwB8wRBZFqPAbdLiUni", + "title": "ShopNGo", + "trustlines": 2046, + "placeInTop": null + }, + { + "id": 18057, + "code": "DIGI", + "issuer": "rnWyNGvkj2ZewctN3HNNbY5dLibFQPX3C1", + "title": "digifish", + "trustlines": 2110, + "placeInTop": null + }, + { + "id": 18541, + "code": "ApeX", + "issuer": "rG7RzbDAnaNW2fVrpjQtqmtcdArdQJoUxK", + "title": "ApeX", + "trustlines": 1386, + "placeInTop": null + }, + { + "id": 18840, + "code": "xOracleDNA", + "issuer": "rhTNWsKKaQwDvVZPXE9QtLPyreZmDZ99gN", + "trustlines": 157, + "placeInTop": null + }, + { + "id": 17534, + "code": "YOU", + "issuer": "rGvWKKaVboTqY6GwP7afhDkYZ5qQMTvora", + "title": "Youcoin", + "trustlines": 229, + "placeInTop": null + }, + { + "id": 72, + "code": "XAnime", + "issuer": "rHjnXe9uqesd1jKbcTsekG1Jp5ZYHPCWWR", + "trustlines": 3990, + "placeInTop": null + }, + { + "id": 19021, + "code": "xDood", + "issuer": "rBWSH3LmPtEfUrAF3QwWUWegvJyfEPjVkX", + "trustlines": 895, + "placeInTop": null + }, + { + "id": 3, + "code": "XXXRP", + "issuer": "rJiBnVT8dxj2QrGu84MH3qWtDzXMTR7QUJ", + "title": "XXXRP Token", + "trustlines": 3020, + "placeInTop": null + }, + { + "id": 18749, + "code": "IWC", + "issuer": "rNiFYEzNcwYnQ9cbt1fagK5gmVMXEcxGNy", + "title": "IWC", + "trustlines": 146, + "placeInTop": null + }, + { + "id": 17556, + "code": "XMF", + "issuer": "rHKTzLQjXo7JM7JrKEZo2jyDtC7iqS6NJp", + "title": "Mastersfg2", + "trustlines": 274, + "placeInTop": null + }, + { + "id": 18842, + "code": "xOracleLAND", + "issuer": "rhTNWsKKaQwDvVZPXE9QtLPyreZmDZ99gN", + "trustlines": 146, + "placeInTop": null + }, + { + "id": 17547, + "code": "HLD", + "issuer": "rP9SFQTj7FY3KXGw3e9EvgrPYGpq1pd4yy", + "trustlines": 284, + "placeInTop": null + }, + { + "id": 364, + "code": "Keanu", + "issuer": "rPFfFbAjVMjTKGzHnJQkpfLUNBwor7RQJY", + "title": "Keanu crypto", + "trustlines": 5478, + "placeInTop": null + }, + { + "id": 16684, + "code": "XRPMASKZ", + "issuer": "rPB7bqp3wHBHhVrpfjZfGXotegxfrYcQCW", + "title": "XRPMaskz", + "trustlines": 3154, + "placeInTop": null + }, + { + "id": 16852, + "code": "META", + "issuer": "r9fTXcvbkgJ9ggEgjV3UmzM7sqs9Phb177", + "title": "META", + "trustlines": 2954, + "placeInTop": null + }, + { + "id": 17279, + "code": "JNG", + "issuer": "rhbQYNVTzBzrv75igTeePyHxGzJ8L8P532", + "trustlines": 2209, + "placeInTop": null + }, + { + "id": 17709, + "code": "MFS", + "issuer": "razccN2G1Q3UcGSiRjGNM8xqgowMxDiGhv", + "title": "MFS", + "trustlines": 138, + "placeInTop": null + }, + { + "id": 17538, + "code": "BIG", + "issuer": "rHoUTGMxWKbrTTF8tpAjysjpu8PWrbt1Wx", + "title": "Smartys", + "trustlines": 202, + "placeInTop": null + }, + { + "id": 18487, + "code": "APE", + "issuer": "rHXHbdcVjHHcmyX7X76VZzmMvBkF4wehsU", + "title": "\ud83e\uddea\ud83e\uddea BMAC - Blind Mummy Apes Club\ud83d\udc99\ud83d\udc9b", + "trustlines": 480, + "placeInTop": null + }, + { + "id": 18694, + "code": "XCOIN", + "issuer": "rBw4swPcXBAgZUcYBYqcpSrCf25cu34nc8", + "trustlines": 1817, + "placeInTop": null + }, + { + "id": 18845, + "code": "xOracleRECR", + "issuer": "rhTNWsKKaQwDvVZPXE9QtLPyreZmDZ99gN", + "trustlines": 140, + "placeInTop": null + }, + { + "id": 17540, + "code": "FRX", + "issuer": "rPA73XSVQ3UEz7gSixREhMzBMtF4RGZPie", + "title": "Frinkcoin", + "trustlines": 130, + "placeInTop": null + }, + { + "id": 16714, + "code": "INT", + "issuer": "rwLMRFbMgihwDKAn7dnLdsNztXGbFafS9u", + "title": "InTroitStudio", + "trustlines": 1955, + "placeInTop": null + }, + { + "id": 18210, + "code": "BisonG", + "issuer": "rn3Gd6hQqc5ZK1VvfyWyrzhad3aTdPaFLs", + "title": "BisonGrass", + "trustlines": 1556, + "placeInTop": null + }, + { + "id": 18257, + "code": "AURORA", + "issuer": "rPnac2Qunw9ofqiY5ur4jd83dNMbk5THYh", + "title": "AstralsOnX", + "trustlines": 4563, + "placeInTop": null + }, + { + "id": 18402, + "code": "xDoCash", + "issuer": "rNsUnCdoFL2498VFc3aH84dSyrotP2mzLD", + "trustlines": 351, + "placeInTop": null + }, + { + "id": 19089, + "code": "XLE", + "issuer": "rwCkSpBEyhhMjB1Yz17dpTJZ1UPg7ppsgU", + "trustlines": 13857, + "placeInTop": null + }, + { + "id": 90, + "code": "XTIM", + "issuer": "rsr4t5kg3gUZwEwMMcymWeus5hfpDSMQbG", + "title": "XTIMfund", + "trustlines": 4913, + "placeInTop": null + }, + { + "id": 18844, + "code": "xOracleNEO", + "issuer": "rhTNWsKKaQwDvVZPXE9QtLPyreZmDZ99gN", + "trustlines": 128, + "placeInTop": null + }, + { + "id": 19100, + "code": "xMalaya", + "issuer": "rLYcBjVF5vXhBVdWQELcv7FyRfRuakPnzr", + "title": "Malaya Infinity", + "trustlines": 2740, + "placeInTop": null + }, + { + "id": 338, + "code": "XRXumm", + "issuer": "rKdCXSihHYED8Ree8HwKBgE9GPWJ8nduqb", + "title": "XRXumm", + "trustlines": 1849, + "placeInTop": null + }, + { + "id": 16718, + "code": "LoveLand", + "issuer": "r325Com8CdPAmEuYABTz3djfP4KEHurU2z", + "title": "Love Monster \uea00", + "trustlines": 1185, + "placeInTop": null + }, + { + "id": 16686, + "code": "DNINE", + "issuer": "rhrKhg5XkwA4ttHrGsnizXXmZUe2TpnRFs", + "title": "Divine Nine NFT", + "trustlines": 1855, + "placeInTop": null + }, + { + "id": 18284, + "code": "ROK", + "issuer": "rn4f4cpyDSriqdohMTRkwSKt1ga7D1q25H", + "trustlines": 137, + "placeInTop": null + }, + { + "id": 18953, + "code": "xr77", + "issuer": "rcLASSiCq8LWcymCHaCgK19QMEvUspuRM", + "title": "XR77", + "trustlines": 898, + "placeInTop": null + }, + { + "id": 18957, + "code": "XGOLD", + "issuer": "rDHZhSSatHAG4ubxsDedRUgTXCJAWRvpo2", + "title": "Xgoldxrpl", + "trustlines": 2834, + "placeInTop": null + }, + { + "id": 17551, + "code": "XAU", + "issuer": "r9Dr5xwkeLegBeXq6ujinjSBLQzQ1zQGjH", + "title": "Ripple Singapore", + "trustlines": 303, + "placeInTop": null + }, + { + "id": 18266, + "code": "xOPS", + "issuer": "rGY49448umDTQtAjMLBA6t7HHZZSJoC26m", + "trustlines": 595, + "placeInTop": null + }, + { + "id": 18841, + "code": "xOracleGG", + "issuer": "rhTNWsKKaQwDvVZPXE9QtLPyreZmDZ99gN", + "trustlines": 133, + "placeInTop": null + }, + { + "id": 18274, + "code": "NFTR", + "issuer": "rBGi34LU3FFd5dnVD2bWbdMxJEkSVfwWpe", + "title": "Bitcoin X", + "trustlines": 1119, + "placeInTop": null + }, + { + "id": 19016, + "code": "USD", + "issuer": "ra9eZxMbJrUcgV8ui7aPc161FgrqWScQxV", + "title": "Peercover", + "trustlines": 309, + "placeInTop": null + }, + { + "id": 119, + "code": "House", + "issuer": "rJ9XgtypsnSJt3x6R1crbs7qFRqa6YvcRi", + "title": "Meta House", + "trustlines": 2029, + "placeInTop": null + }, + { + "id": 17337, + "code": "VGO", + "issuer": "rPYJU7Un1ayZ9uHe3QQm8fRiGKZnGYNzhv", + "title": "Vagabond VGO", + "trustlines": 1180, + "placeInTop": null + }, + { + "id": 18634, + "code": "GXWCoin", + "issuer": "rBaQ4z5kpEwH84cz1Gkve4wCpz5g9xrvpz", + "trustlines": 341, + "placeInTop": null + }, + { + "id": 18917, + "code": "XWC", + "issuer": "rwdpB514zQMLx4Uy5feJBJUtx6LJZMaZLm", + "title": "XRPL WORLD CUP", + "trustlines": 609, + "placeInTop": null + }, + { + "id": 18834, + "code": "WSTH", + "issuer": "rnwTawWCLjXKroP7CBLFLxpidSCR3UoHKG", + "trustlines": 704, + "placeInTop": null + }, + { + "id": 18891, + "code": "JEN", + "issuer": "rntNAksJQhpyxvPWoAVtgS8ML1SsrpZuuw", + "trustlines": 783, + "placeInTop": null + }, + { + "id": 16968, + "code": "BMS", + "issuer": "rpR32FXJoGv5K9Qwyu3cW8dSCWeu29cy88", + "title": "Bullish Moments", + "trustlines": 926, + "placeInTop": null + }, + { + "id": 18931, + "code": "MOB", + "issuer": "rBqn27sZcfjR8LvcgESPBjgrMybRkk11da", + "title": "M.O.B", + "trustlines": 537, + "placeInTop": null + }, + { + "id": 18933, + "code": "XrAkita", + "issuer": "rJzgPGTrjLV1JHivpu7428WMti6pACz44V", + "trustlines": 438, + "placeInTop": null + }, + { + "id": 127, + "code": "GBL", + "issuer": "rfQvbpZvihhRULL7mJhaSYgK3TUcZq3NAp", + "title": "GBL", + "trustlines": 5766, + "placeInTop": null + }, + { + "id": 18978, + "code": "USD", + "issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y", + "title": "Ripple Fox", + "trustlines": 836, + "placeInTop": null + }, + { + "id": 16773, + "code": "META$", + "issuer": "rNtqjpucnCuxhKVtMy2iefRofPFVXFD5PR", + "title": "META$", + "trustlines": 3380, + "placeInTop": null + }, + { + "id": 17784, + "code": "USD", + "issuer": "r9Dr5xwkeLegBeXq6ujinjSBLQzQ1zQGjH", + "title": "Ripple Singapore", + "trustlines": 341, + "placeInTop": null + }, + { + "id": 18987, + "code": "BTC", + "issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc", + "title": "Coinex", + "trustlines": 1172, + "placeInTop": null + }, + { + "id": 16808, + "code": "Drip", + "issuer": "rhWbFN1Q3owPfYY3FjMmKTzTXs6A3uKVtm", + "title": "XCoinDropper", + "trustlines": 1589, + "placeInTop": null + }, + { + "id": 18721, + "code": "Greenpower", + "issuer": "rhNv8T5waDTyxbSVfiFGn7E1b45CUj5Ae6", + "title": "Greenpower", + "trustlines": 873, + "placeInTop": null + }, + { + "id": 18849, + "code": "ASX", + "issuer": "r3RqTN9tso5TB9HuB4aeiDmrWZEuMSnP8Q", + "trustlines": 12400, + "placeInTop": null + }, + { + "id": 19056, + "code": "USD", + "issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc", + "title": "Coinex", + "trustlines": 1218, + "placeInTop": null + }, + { + "id": 18911, + "code": "EmiC", + "issuer": "rBAtoMmpDbyQytkonpuz1fzcf4htdMXzqK", + "title": "Emirate Club", + "trustlines": 7741, + "placeInTop": null + }, + { + "id": 19008, + "code": "XRD3", + "issuer": "rD3hZoj626diqEA5S8FpJymz8R6X8ffVqS", + "title": "xRonin Labs | SOLD OUT!", + "trustlines": 298, + "placeInTop": null + }, + { + "id": 16791, + "code": "TGC", + "issuer": "r4ZPfDiqxQRvcwXoriEHjR9a9DkuboYyZ8", + "trustlines": 795, + "placeInTop": null + }, + { + "id": 16905, + "code": "XRPugs", + "issuer": "rsfPbNNHWz5262XgaYtrWvTgfX34seaNgh", + "title": "XRPugs", + "trustlines": 3203, + "placeInTop": null + }, + { + "id": 219, + "code": "PEENZ", + "issuer": "rLDEELKZST8eRYpNPS1hr6LWZbQnZ9NkYy", + "title": "XRPeenz", + "trustlines": 1852, + "placeInTop": null + }, + { + "id": 16669, + "code": "EMB", + "issuer": "rfQYLw1szMWWCob7VtAGtZtYtmLtCCPLLs", + "title": "EMOTION BOX TOKEN", + "trustlines": 3035, + "placeInTop": null + }, + { + "id": 18551, + "code": "InfantXapes", + "issuer": "rsz5MtMPLfrT5ZHFP4TUigMwMzugACRKhs", + "title": "Gorilla X Warfare", + "trustlines": 332, + "placeInTop": null + }, + { + "id": 298, + "code": "OPI", + "issuer": "rLekuNt7xSRBJKBtxr4nqa3obGmUa9nmYZ", + "trustlines": 885, + "placeInTop": null + }, + { + "id": 19077, + "code": "ETC", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 407, + "placeInTop": null + }, + { + "id": 130, + "code": "DBD", + "issuer": "rMuUAyEwGpQAKKHWjEjJdMWqDQp9mbDrVU", + "title": "Digital Black Dollar 1.0", + "trustlines": 3660, + "placeInTop": null + }, + { + "id": 17560, + "code": "XMG", + "issuer": "rHKTzLQjXo7JM7JrKEZo2jyDtC7iqS6NJp", + "title": "Mastersfg2", + "trustlines": 277, + "placeInTop": null + }, + { + "id": 18209, + "code": "XASTRAL", + "issuer": "rwCNcLU7Q2pSz9tkLVQaAg5dN4NhXfKXKX", + "title": "AstralsOnX", + "trustlines": 3738, + "placeInTop": null + }, + { + "id": 18755, + "code": "MTBX", + "issuer": "rJJfuHfwrYLkbzWeoiuQZbzv7Ffi9GEgn7", + "title": "Metabox | $MTBX", + "trustlines": 7166, + "placeInTop": null + }, + { + "id": 40, + "code": "FireFly", + "issuer": "rPo2RQ7JsCSd3WeSKm75XFNZE3oNFPDAbu", + "trustlines": 3951, + "placeInTop": null + }, + { + "id": 360, + "code": "KOLS", + "issuer": "rUThGrdjD3q227DtSAKtviXqpicWjKG9xK", + "title": "KOLS", + "trustlines": 5669, + "placeInTop": null + }, + { + "id": 16976, + "code": "Xange", + "issuer": "r3V7BTuupF5mACHg2vpDRotDqGbMPh5GMv", + "trustlines": 937, + "placeInTop": null + }, + { + "id": 17129, + "code": "VERIFYTEST", + "issuer": "rrhsaymUStZEyGpLP1FBPxkpfNoroAQHtV", + "trustlines": 352, + "placeInTop": null + }, + { + "id": 17841, + "code": "Xrock", + "issuer": "rockUQqYKy3vpCtVnF7yigDiZw887rhN7", + "title": "XRock", + "trustlines": 1816, + "placeInTop": null + }, + { + "id": 16811, + "code": "MAYA", + "issuer": "rGk8tKECA9fxh38ikDAJYn1NSdDcYdY6XF", + "title": "MAYABLUE.X", + "trustlines": 1300, + "placeInTop": null + }, + { + "id": 17591, + "code": "STR", + "issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc", + "title": "Coinex", + "trustlines": 1108, + "placeInTop": null + }, + { + "id": 17791, + "code": "USD", + "issuer": "rPDXxSZcuVL3ZWoyU82bcde3zwvmShkRyF", + "title": "WisePass", + "trustlines": 195, + "placeInTop": null + }, + { + "id": 16820, + "code": "MSNGR2", + "issuer": "rHAhxoWNM8vNt5VENuTtS2UAF6BHpDfM7", + "title": "Center for Collaborative Economics", + "trustlines": 651, + "placeInTop": null + }, + { + "id": 17095, + "code": "Earnie", + "issuer": "rsK7bK78azgdi1hD9ZWNXQwmYQ5xrSVE25", + "title": "Earnie", + "trustlines": 3030, + "placeInTop": null + }, + { + "id": 18865, + "code": "JPY", + "issuer": "rUFcQnmGYEuvJVJnZy8QBc9ouKJbpvK7QU", + "title": "DigitalCurrency-ALL", + "trustlines": 228, + "placeInTop": null + }, + { + "id": 339, + "code": "XGalaxy", + "issuer": "rh61FBhuEwAMQ9yArSaEFd8g4dZpJoj6AW", + "title": "XGalaxy", + "trustlines": 3277, + "placeInTop": null + }, + { + "id": 17256, + "code": "Churchcoin", + "issuer": "rKLKvCbtDhNzVzJCvfts6yrzvNUTxYjAi1", + "title": "ChristJesus", + "trustlines": 203, + "placeInTop": null + }, + { + "id": 18503, + "code": "XAU", + "issuer": "rrh7rf1gV2pXAoqA8oYbpHd8TKv5ZQeo67", + "title": "GBI", + "trustlines": 411, + "placeInTop": null + }, + { + "id": 18594, + "code": "LCC", + "issuer": "rG9Fo4mgx5DEZp7zKUEchs3R3jSMbx3NhR", + "trustlines": 1009, + "placeInTop": null + }, + { + "id": 29, + "code": "XRPZclan", + "issuer": "rHuJFGGuN47uzehRxt9GXXsrSaJpVV1zTD", + "title": "Marko Kucic", + "trustlines": 2295, + "placeInTop": null + }, + { + "id": 17223, + "code": "CreatureNFT", + "issuer": "rGWUGk9BUnknFeyPHSxmkv6UPnt4Zs4c92", + "title": "CreatureNFT", + "trustlines": 1215, + "placeInTop": null + }, + { + "id": 18420, + "code": "Loot", + "issuer": "rwJacRXodYJbmJgtjkPrq3zJ2WznCUNHB8", + "title": "xPixelTribes NFT", + "trustlines": 2796, + "placeInTop": null + }, + { + "id": 18633, + "code": "BIOX", + "issuer": "rELctAeDJweX171c359zo8V6zbmgosskkL", + "title": "BIOX\u20223DNFT 2\u00d7\u00d7\u00d7\u2022", + "trustlines": 1427, + "placeInTop": null + }, + { + "id": 18977, + "code": "CNY", + "issuer": "rJ1adrpGS3xsnQMb9Cw54tWJVFPuSdZHK", + "title": "Iripplepay", + "trustlines": 147, + "placeInTop": null + }, + { + "id": 16656, + "code": "ZAC", + "issuer": "rnFkVZytG6YZEGihGeGud9QPqjg4VgSXQg", + "title": "ZAC - NFT | Metaverse", + "trustlines": 1424, + "placeInTop": null + }, + { + "id": 18405, + "code": "SaveUkraine", + "issuer": "rERVWdcnYcWX66c3jH8BBTig47BUkF4bhN", + "title": "Save Ukraine! \ud83c\uddfa\ud83c\udde6", + "trustlines": 483, + "placeInTop": null + }, + { + "id": 17692, + "code": "RDD", + "issuer": "ra9eZxMbJrUcgV8ui7aPc161FgrqWScQxV", + "title": "Peercover", + "trustlines": 133, + "placeInTop": null + }, + { + "id": 19049, + "code": "BTC", + "issuer": "rnuF96W4SZoCJmbHYBFoJZpR8eCaxNvekK", + "title": "rippleCN", + "trustlines": 295, + "placeInTop": null + }, + { + "id": 18854, + "code": "XBeards", + "issuer": "rPdNJ8vZtneXFnmpxfe6bN3pSiwdXKsz6t", + "trustlines": 12598, + "placeInTop": null + }, + { + "id": 17085, + "code": "AURA", + "issuer": "rN2FLTp3TY3skA5axWFDBZJ1PFQMjaQ6V8", + "title": "MAYABLUE.X", + "trustlines": 910, + "placeInTop": null + }, + { + "id": 17693, + "code": "SGD", + "issuer": "r9Dr5xwkeLegBeXq6ujinjSBLQzQ1zQGjH", + "title": "Ripple Singapore", + "trustlines": 319, + "placeInTop": null + }, + { + "id": 18600, + "code": "xBTS", + "issuer": "rap2LWtavwsczeUVeWrXBQvizkTnhxWvLU", + "title": "BTSSTUDIOS", + "trustlines": 1289, + "placeInTop": null + }, + { + "id": 147, + "code": "Kraken", + "issuer": "rDrMVcBp81Zu2PsRXUSLnhojDJ3ARKmFa5", + "title": "KrakenNFT", + "trustlines": 4596, + "placeInTop": null + }, + { + "id": 17913, + "code": "XGOLF", + "issuer": "raJkCbedyFbLEETxAsGdEhVmdFe2BNJguT", + "title": "ApeX GOLF CLUB", + "trustlines": 3801, + "placeInTop": null + }, + { + "id": 18937, + "code": "JPY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 219, + "placeInTop": null + }, + { + "id": 186, + "code": "XRPETE", + "issuer": "rwNMJ7S9cLMUeBG391eEk9psw4KcPpANKC", + "title": "Digital Black Dollar 1.0", + "trustlines": 3388, + "placeInTop": null + }, + { + "id": 16805, + "code": "DKS", + "issuer": "rpPEq83L7m5uVQ8sQ39eDSqwuWKnq9BHWF", + "trustlines": 1176, + "placeInTop": null + }, + { + "id": 97, + "code": "XrplEstate", + "issuer": "rfHXsDASoEphWsH6V2fmHyEbytkDscDZSA", + "title": "XRPL.ESTATE", + "trustlines": 3854, + "placeInTop": null + }, + { + "id": 17221, + "code": "XRPoodle", + "issuer": "rp1EfhEfouAZpvrLEGxWymLmPcEgJgUoCL", + "title": "XRPoodle", + "trustlines": 1389, + "placeInTop": null + }, + { + "id": 17550, + "code": "XAG", + "issuer": "r9Dr5xwkeLegBeXq6ujinjSBLQzQ1zQGjH", + "title": "Ripple Singapore", + "trustlines": 208, + "placeInTop": null + }, + { + "id": 17689, + "code": "FLC", + "issuer": "rKUxrnNCpXCXAz49Vhtoo6VLfsV7yxU7x3", + "title": "FLCFree", + "trustlines": 194, + "placeInTop": null + }, + { + "id": 17690, + "code": "BTC", + "issuer": "rPDXxSZcuVL3ZWoyU82bcde3zwvmShkRyF", + "title": "WisePass", + "trustlines": 129, + "placeInTop": null + }, + { + "id": 17743, + "code": "AGC", + "issuer": "rfqspG4S6H3NLYGecx3FHHYjPeYSNCdApf", + "trustlines": 131, + "placeInTop": null + }, + { + "id": 18360, + "code": "L3X", + "issuer": "rJUcFtzXGGnNfvsxYp9wVUmwmZikr8gS9G", + "trustlines": 677, + "placeInTop": null + }, + { + "id": 18691, + "code": "MAYUNG", + "issuer": "rMUWw9gAAA3tPcsE16EXxQfepa4Bz7uqjr", + "trustlines": 935, + "placeInTop": null + }, + { + "id": 204, + "code": "XRV", + "issuer": "rMav3Zzkoy4xBnowXkfJ4VGqCWowRvo91q", + "trustlines": 2289, + "placeInTop": null + }, + { + "id": 17318, + "code": "MinerCoin", + "issuer": "rJcDBGuShjinAbX2RU9uh1fkjhaSsBnA2Y", + "trustlines": 532, + "placeInTop": null + }, + { + "id": 17697, + "code": "ZPR", + "issuer": "rUSrgqXVFEw5ZaruTqiDrXDecRuGnhFqu8", + "title": "Z-payment", + "trustlines": 183, + "placeInTop": null + }, + { + "id": 18983, + "code": "BTRAX", + "issuer": "r9K7XjYJ1gYbiYMUpFH1kZMWRpXHjg62EN", + "title": "BTRAX", + "trustlines": 123, + "placeInTop": null + }, + { + "id": 17837, + "code": "RMB", + "issuer": "rLbppnUciWQ2AfLkcdTfBwb1yh97Kc9cfx", + "trustlines": 143, + "placeInTop": null + }, + { + "id": 18483, + "code": "xDoKids", + "issuer": "rPXAnR6jGjhLKDkULLTYxHTLJD6YaB8LRg", + "trustlines": 208, + "placeInTop": null + }, + { + "id": 19038, + "code": "GBP", + "issuer": "rBycsjqxD8RVZP5zrrndiVtJwht7Z457A8", + "title": "Ripula", + "trustlines": 154, + "placeInTop": null + }, + { + "id": 19060, + "code": "LTC", + "issuer": "rJHygWcTLVpSXkowott6kzgZU6viQSVYM1", + "title": "Justcoin", + "trustlines": 199, + "placeInTop": null + }, + { + "id": 17822, + "code": "WormiesPixel", + "issuer": "rUAB829ttzH55qJWEoTFTyyoFCCk3ffFLL", + "title": "WormiesPixel NFT", + "trustlines": 2484, + "placeInTop": null + }, + { + "id": 18926, + "code": "TPD", + "issuer": "rahwRP4um493Vj1AbNwXauNhq9NKaBGEGw", + "trustlines": 498, + "placeInTop": null + }, + { + "id": 16675, + "code": "Sports", + "issuer": "rUG5J5tUSXba4qEhQCQYAZuQuoGHbLyoSb", + "trustlines": 1799, + "placeInTop": null + }, + { + "id": 18022, + "code": "GBP", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 220, + "placeInTop": null + }, + { + "id": 18830, + "code": "BTC", + "issuer": "ra9eZxMbJrUcgV8ui7aPc161FgrqWScQxV", + "title": "Peercover", + "trustlines": 193, + "placeInTop": null + }, + { + "id": 17284, + "code": "WXWARS", + "issuer": "rpuxwHsnZzAd9GiX1kdkgmxgxmGEiH8bmS", + "trustlines": 510, + "placeInTop": null + }, + { + "id": 18421, + "code": "Bitcoin", + "issuer": "rDwXoFKfXEd1cvobSKbfziJ6MZXCgeeSGd", + "trustlines": 117, + "placeInTop": null + }, + { + "id": 18905, + "code": "XDUDEZ", + "issuer": "r39XAsDCCeTm9xSMHXbJVGQ9xGpSgBrHmb", + "trustlines": 279, + "placeInTop": null + }, + { + "id": 17022, + "code": "CoinShares", + "issuer": "rG8656fyrmQVvDfnA5FCYkRpGNUDWXqkQ2", + "title": "CoinShares", + "trustlines": 1148, + "placeInTop": null + }, + { + "id": 17053, + "code": "UMKM", + "issuer": "rLBiwuD6frqK7kZoHeCxRip5m11VZAtgAE", + "title": "UMKM", + "trustlines": 1900, + "placeInTop": null + }, + { + "id": 75, + "code": "UFO", + "issuer": "rD8eKQc7nwVDPspWn87oP8SVaio2kYvuGe", + "trustlines": 2964, + "placeInTop": null + }, + { + "id": 17466, + "code": "JerryCoin", + "issuer": "rHMSpKBiqJgStVrRWXp2m7yXouEGKZaDsW", + "title": "JerryCoin (XRPL)", + "trustlines": 119, + "placeInTop": null + }, + { + "id": 17756, + "code": "BTC", + "issuer": "rMLtFZE6ZvLkiRet8Bncs8EZEWQYXyXixN", + "title": "Christian", + "trustlines": 226, + "placeInTop": null + }, + { + "id": 265, + "code": "XFLAM", + "issuer": "rHNVwZbDdYG5v6LwTGLkxHfqkbahp82C2K", + "title": "XFLAM NFT", + "trustlines": 1417, + "placeInTop": null + }, + { + "id": 243, + "code": "SAP", + "issuer": "r3eSGpqe6TQViqWPusD9EUZCZRAp48SNRz", + "title": "Sapling", + "trustlines": 2673, + "placeInTop": null + }, + { + "id": 16810, + "code": "DIamondsAreForever", + "issuer": "rtFGbpDE4aG5RgpweVao4439BoPoCmzai", + "trustlines": 428, + "placeInTop": null + }, + { + "id": 18317, + "code": "TBG", + "issuer": "rLmEVaa5r6pAr41UjYu2MveQYdnAhB9qnV", + "title": "The Bird Gang 3D (on XRPL)", + "trustlines": 1598, + "placeInTop": null + }, + { + "id": 19078, + "code": "REP", + "issuer": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS", + "title": "Mr. Exchange", + "trustlines": 316, + "placeInTop": null + }, + { + "id": 17932, + "code": "BTS", + "issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y", + "title": "Ripple Fox", + "trustlines": 106, + "placeInTop": null + }, + { + "id": 18085, + "code": "VIOLITY", + "issuer": "rahh5M9CqSH8GU7iN2yWVD8SmLqvjqb72q", + "title": "VIOLITY COIN", + "trustlines": 6210, + "placeInTop": null + }, + { + "id": 18445, + "code": "SER", + "issuer": "rq7vZn33BtRippR8Nv63ho5dDcwuXhKfm", + "title": "\ud83e\uddea\ud83e\uddea BMAC - Blind Mummy Apes Club\ud83d\udc99\ud83d\udc9b", + "trustlines": 187, + "placeInTop": null + }, + { + "id": 19063, + "code": "LTC", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "title": "The Rock Trading", + "trustlines": 254, + "placeInTop": null + }, + { + "id": 17919, + "code": "ShinaInu", + "issuer": "rNRZeJgJrkrMp5VLxrB1yMvZC8jswddqoG", + "title": "Shina Inu XRP", + "trustlines": 1298, + "placeInTop": null + }, + { + "id": 18068, + "code": "xSOLDIER", + "issuer": "rNvEKeW4YidGSgV35swkkzFXeUCKRAHZrv", + "title": "xSOLDIER", + "trustlines": 1988, + "placeInTop": null + }, + { + "id": 257, + "code": "BDH", + "issuer": "r4MEoGepHyv1ZeBNM7R7k4GdmBiXnTDE9t", + "trustlines": 1451, + "placeInTop": null + }, + { + "id": 19107, + "code": "JPY", + "issuer": "rUT8s655PfCmd6GLek6LDefVsEHFnSW8Tg", + "title": "El-live", + "trustlines": 1495, + "placeInTop": null + }, + { + "id": 379, + "code": "MMX", + "issuer": "rEYnYboUF5z8QWQ8SsJTkARNkKGrA6Zgpc", + "trustlines": 4357, + "placeInTop": null + }, + { + "id": 16816, + "code": "DuckCoin", + "issuer": "rPM611tLXe65iT22sX4SdmgjiFq5Je9ubm", + "title": "Duck Art NFT@Japanese Creator", + "trustlines": 438, + "placeInTop": null + }, + { + "id": 17360, + "code": "PIKACHU", + "issuer": "rGk8tKECA9fxh38ikDAJYn1NSdDcYdY6XF", + "title": "MAYABLUE.X", + "trustlines": 210, + "placeInTop": null + }, + { + "id": 17470, + "code": "HULK", + "issuer": "rGk8tKECA9fxh38ikDAJYn1NSdDcYdY6XF", + "title": "MAYABLUE.X", + "trustlines": 144, + "placeInTop": null + }, + { + "id": 18045, + "code": "JCS", + "issuer": "rPU6sXCNzsjcTUEmgJQ5SxDUzY2y1RyYKd", + "title": "Jesus Christ Saves token (JCS)", + "trustlines": 755, + "placeInTop": null + }, + { + "id": 18683, + "code": "CPUNK", + "issuer": "rBEm7MdHwVpg9yhW5ygZCsFRDteL7qDhkL", + "trustlines": 310, + "placeInTop": null + }, + { + "id": 44, + "code": "Multiverse", + "issuer": "rwSL2dcMrF3Qgt6NzEmctEy8XdQBMbM7nG", + "title": "Multiverse", + "trustlines": 3454, + "placeInTop": null + }, + { + "id": 273, + "code": "xGIL", + "issuer": "rBTGEj2yAxMz6VLCd97wB6sG1htNmFuNLX", + "title": "The Xennials", + "trustlines": 654, + "placeInTop": null + }, + { + "id": 18454, + "code": "XRAPTOR", + "issuer": "r4kfpQUbs716GpcE8peKmDHaSGQwXZHBqb", + "title": "XRaptor", + "trustlines": 4053, + "placeInTop": null + }, + { + "id": 18652, + "code": "MAGIC", + "issuer": "rwCsCz93A1svS6Yv8hFqUeKLdTLhBpvqGD", + "trustlines": 202, + "placeInTop": null + }, + { + "id": 18764, + "code": "XSF", + "issuer": "rnZJrGXURD4VubtB5aDRBCsfphmbWBBokT", + "trustlines": 331, + "placeInTop": null + }, + { + "id": 17286, + "code": "XRMETA", + "issuer": "rwnNN6rQSi65U3agEB1jSaD5xc2sWRrNhA", + "title": "XR Meta Worlds", + "trustlines": 3026, + "placeInTop": null + }, + { + "id": 17291, + "code": "CAT", + "issuer": "rnY5qZei5KYoGV1FKv5mPEfC1CAR3aGk38", + "title": "XCATGIRL", + "trustlines": 478, + "placeInTop": null + }, + { + "id": 17758, + "code": "CAD", + "issuer": "raBDVR7JFq3Yho2jf7mcx36sjTwpRJJrGU", + "title": "Bluzelle", + "trustlines": 164, + "placeInTop": null + }, + { + "id": 18142, + "code": "BIRDIEBUCKZ", + "issuer": "rLazWTdfkhFsAscNyr1zCZDM7nxW17AALK", + "title": "BIRDIEBUCKZ", + "trustlines": 1737, + "placeInTop": null + }, + { + "id": 18063, + "code": "SANtoken", + "issuer": "rhZVq1wrgGqWTxoP217H6WxLEGmh3U1Tie", + "title": "Satoshi San", + "trustlines": 8919, + "placeInTop": null + }, + { + "id": 19061, + "code": "LTC", + "issuer": "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9", + "title": "Payroutes", + "trustlines": 189, + "placeInTop": null + }, + { + "id": 16890, + "code": "XRI", + "issuer": "rBhGj6Hn2MMDe3Jf9toPmqGRxgYbq6RHTR", + "trustlines": 1600, + "placeInTop": null + }, + { + "id": 18019, + "code": "BTC", + "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n", + "trustlines": 2428, + "placeInTop": null + }, + { + "id": 18579, + "code": "DFG", + "issuer": "rUfSfEZVyAJDZaRciWYGRAFfB61Wri7LQJ", + "title": "xDeepGifs", + "trustlines": 10137, + "placeInTop": null + }, + { + "id": 18832, + "code": "XPBK", + "issuer": "rDKWKyBQ1pRjLk4v21MZDd2TeZcsGMfQLR", + "trustlines": 433, + "placeInTop": null + }, + { + "id": 18988, + "code": "USD", + "issuer": "r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X", + "title": "WeExchange", + "trustlines": 124, + "placeInTop": null + }, + { + "id": 114, + "code": "UtopiaCoin", + "issuer": "rBfJyugMXyLuE42CM8fVcMBvGt7EhYK9Yh", + "title": "world Utopia", + "trustlines": 2566, + "placeInTop": null + }, + { + "id": 153, + "code": "FUNG", + "issuer": "rHJZySo32ni2VFcnAbevhkEBRBn42wmW6b", + "title": "The Fungal Foundation", + "trustlines": 4386, + "placeInTop": null + }, + { + "id": 264, + "code": "Xmas", + "issuer": "r3KSsS21XwJQgNz4k7MnrHDXgueFYRfn3g", + "title": "Xmas", + "trustlines": 2359, + "placeInTop": null + }, + { + "id": 18318, + "code": "RVRSE", + "issuer": "rHfxbz1LayuEv6GiS46PHmQiW61wDnmCZy", + "title": "ELEMENTUM RELMVERSE", + "trustlines": 1658, + "placeInTop": null + }, + { + "id": 18839, + "code": "XPCH", + "issuer": "rUAWSkedYnTK42QRZeUzNDqZFJZBTPCHrk", + "title": "XPCHANGER", + "trustlines": 1090, + "placeInTop": null + }, + { + "id": 17352, + "code": "VENOM", + "issuer": "rN2FLTp3TY3skA5axWFDBZJ1PFQMjaQ6V8", + "title": "MAYABLUE.X", + "trustlines": 248, + "placeInTop": null + }, + { + "id": 19039, + "code": "AUD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 129, + "placeInTop": null + }, + { + "id": 121, + "code": "XCHEESEMOON", + "issuer": "rE6F5x9SCyMouBBBRKyoxvpdUkxiTb5wtq", + "title": "XCHEESEMOON", + "trustlines": 3568, + "placeInTop": null + }, + { + "id": 211, + "code": "PIXELS", + "issuer": "rJs4bB6dTHY7GBva5TfT4usBk8wnGdZ4CA", + "title": "PixelsNFT", + "trustlines": 2465, + "placeInTop": null + }, + { + "id": 16817, + "code": "MSNGR1", + "issuer": "rHAhxoWNM8vNt5VENuTtS2UAF6BHpDfM7", + "title": "Center for Collaborative Economics", + "trustlines": 284, + "placeInTop": null + }, + { + "id": 58, + "code": "NSM", + "issuer": "rhtR4fZnbCBpEMk6mqdYrXpy1vNUbAcgYM", + "trustlines": 1757, + "placeInTop": null + }, + { + "id": 71, + "code": "NGoat", + "issuer": "rEs1552ZJS5N9g5L612XSYd7KTuawa8Gtq", + "title": "NGoat", + "trustlines": 3065, + "placeInTop": null + }, + { + "id": 16954, + "code": "TWETTY", + "issuer": "raVFV5cMjZYxHxQ4c67YYffyzjSQCXKUDZ", + "title": "TWETTY TOKEN", + "trustlines": 3211, + "placeInTop": null + }, + { + "id": 17334, + "code": "Iqc", + "issuer": "rBfhKxjwEhmnU7jXgAwdvjB3qjW7RhFkGk", + "title": "IQ cutie", + "trustlines": 256, + "placeInTop": null + }, + { + "id": 18021, + "code": "CHF", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 102, + "placeInTop": null + }, + { + "id": 18605, + "code": "INT", + "issuer": "rh3c9m4unJ2HUpeCoMT5UZ3EDRzd7uBaG2", + "title": "INFINITUM", + "trustlines": 15985, + "placeInTop": null + }, + { + "id": 18885, + "code": "ASS", + "issuer": "rNfPfYv4Ysmrcwz7VTBs6GMhh5GSXexNA8", + "trustlines": 141, + "placeInTop": null + }, + { + "id": 17205, + "code": "FDG", + "issuer": "r9ajH627G23Ex8AAxYkdGaK8eXJ8EapRJE", + "trustlines": 583, + "placeInTop": null + }, + { + "id": 17864, + "code": "MOOD", + "issuer": "rGQtGHrgN4FK1RcEn83q4t8aK6BobzDEMK", + "title": "MOOD", + "trustlines": 999, + "placeInTop": null + }, + { + "id": 18248, + "code": "XRSHINJA", + "issuer": "rsGXGiNsvpCXCm3oybsWqXgrw5BEhY6rRu", + "title": "XRshinja", + "trustlines": 1359, + "placeInTop": null + }, + { + "id": 18456, + "code": "BON", + "issuer": "rHkqAyggqiSvhBdrvfbCbzqbD6nUyGy2CQ", + "title": "Doggy coin", + "trustlines": 257, + "placeInTop": null + }, + { + "id": 18910, + "code": "xHOPE", + "issuer": "rsNrejFNpzZeup75gchNotWeJZDCU1cXKz", + "title": "xHOPE", + "trustlines": 622, + "placeInTop": null + }, + { + "id": 237, + "code": "XrDragonFly", + "issuer": "rKMqegsS5uQbEDU8aDAq2NDtjmDvw465dS", + "trustlines": 1989, + "placeInTop": null + }, + { + "id": 376, + "code": "CodeFuel", + "issuer": "rsF5u4uqCQeRCUkdcBqdabbWTdytw9HxWW", + "title": "CodeCoin", + "trustlines": 617, + "placeInTop": null + }, + { + "id": 17479, + "code": "GALACTUS", + "issuer": "rGk8tKECA9fxh38ikDAJYn1NSdDcYdY6XF", + "title": "MAYABLUE.X", + "trustlines": 104, + "placeInTop": null + }, + { + "id": 17486, + "code": "CHARIZARD", + "issuer": "rGk8tKECA9fxh38ikDAJYn1NSdDcYdY6XF", + "title": "MAYABLUE.X", + "trustlines": 144, + "placeInTop": null + }, + { + "id": 19057, + "code": "USD", + "issuer": "rpDMez6pm6dBve2TJsmDpv7Yae6V5Pyvy2", + "title": "Lake BTC", + "trustlines": 178, + "placeInTop": null + }, + { + "id": 232, + "code": "xDef", + "issuer": "r41kez71qtoNGjMGU8bAFHKt7LCTPdupHi", + "title": "def", + "trustlines": 722, + "placeInTop": null + }, + { + "id": 17183, + "code": "Q17", + "issuer": "rBfhKxjwEhmnU7jXgAwdvjB3qjW7RhFkGk", + "title": "IQ cutie", + "trustlines": 501, + "placeInTop": null + }, + { + "id": 18286, + "code": "XREnergy", + "issuer": "rhc5KqhFNvqwcEYzLqN1V7ypZjQdM7FSXi", + "title": "XrEnergy", + "trustlines": 4788, + "placeInTop": null + }, + { + "id": 18423, + "code": "XWARS", + "issuer": "r9WCKRUG6Cp4tTTLNs3jEFKBUvrbikZ2yr", + "title": "XWars NFT | XRPL", + "trustlines": 313, + "placeInTop": null + }, + { + "id": 83, + "code": "Drizakeo", + "issuer": "rMrCjb6kBtXSFKx8ABMAtCo9H9ChDZE7WK", + "title": "XRP_Drizakeo", + "trustlines": 2512, + "placeInTop": null + }, + { + "id": 92, + "code": "xGBTC", + "issuer": "r4ZXk448HRwZN57H4X8GXXkr93hJQojgWQ", + "title": "RKIDZ_official (Former xGBTC)", + "trustlines": 5915, + "placeInTop": null + }, + { + "id": 282, + "code": "GOD", + "issuer": "rsBd5xoQN4GEpdqtCf4CUHycQYvPF31Fvn", + "trustlines": 431, + "placeInTop": null + }, + { + "id": 16977, + "code": "Xmicron", + "issuer": "rHULkU1xNzhaycHcHeNWFJQ2Jgpamuy4zQ", + "title": "Xmicron", + "trustlines": 556, + "placeInTop": null + }, + { + "id": 17013, + "code": "RSS", + "issuer": "rBKMemZWVwfwm3TfeD3Nt7FpwRTrDzvhh8", + "title": "RSS token / Captivatoken", + "trustlines": 1750, + "placeInTop": null + }, + { + "id": 18806, + "code": "YUM", + "issuer": "rMg4ZumKpM2BekKrFDUCRULBSvuiMqbYUM", + "trustlines": 7192, + "placeInTop": null + }, + { + "id": 133, + "code": "THCoin", + "issuer": "rs1x5ZqpE6SUMfpnvjZhQyQCbLhtYu2sKx", + "title": "$THCoin", + "trustlines": 1450, + "placeInTop": null + }, + { + "id": 287, + "code": "XStorm", + "issuer": "rGe6YD14SMJXSasbTw6fNHnK9ZdLh2PMUw", + "trustlines": 830, + "placeInTop": null + }, + { + "id": 18566, + "code": "AUD", + "issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc", + "title": "Coinex", + "trustlines": 1226, + "placeInTop": null + }, + { + "id": 17017, + "code": "xQuotient", + "issuer": "rpjYnDqU3sFrA8R4AeYqXtJiDEHmy9a33A", + "trustlines": 1536, + "placeInTop": null + }, + { + "id": 17292, + "code": "MOON", + "issuer": "rGQtGHrgN4FK1RcEn83q4t8aK6BobzDEMK", + "title": "GORILLA$Gold XRP", + "trustlines": 1304, + "placeInTop": null + }, + { + "id": 18196, + "code": "APPOLIS", + "issuer": "rpw7nNCNQWj9hjV1fonjgox7q4Mb2TkqtU", + "title": "CRYPTAPPOLIS", + "trustlines": 5477, + "placeInTop": null + }, + { + "id": 18681, + "code": "XDistrict", + "issuer": "rwtPQV4gu83aLagD74rM8mjYNknigXAKL4", + "title": "XDistrict", + "trustlines": 699, + "placeInTop": null + }, + { + "id": 56, + "code": "XRDogeGF", + "issuer": "rEMhjbzTbtGN451Ckw39m82PJg2kM7n23b", + "title": "xdogegf", + "trustlines": 2208, + "placeInTop": null + }, + { + "id": 88, + "code": "HTL", + "issuer": "rhH3hVt8kbbqLVNwT52AhfJYBN7aRXsSgX", + "title": "Hotel Coin", + "trustlines": 5835, + "placeInTop": null + }, + { + "id": 108, + "code": "XNARWHAL", + "issuer": "rwav87wr2c3DPNXneidEm3dGUgY6EBf8mo", + "title": "XNARWHAL XRPL NFT", + "trustlines": 2948, + "placeInTop": null + }, + { + "id": 17475, + "code": "RONIN", + "issuer": "rGk8tKECA9fxh38ikDAJYn1NSdDcYdY6XF", + "title": "MAYABLUE.X", + "trustlines": 132, + "placeInTop": null + }, + { + "id": 18367, + "code": "FOREST", + "issuer": "rE9zfbno2FE9hJNfpuBzWL4AtEyRNtNJLX", + "title": "Love Monster \uea00", + "trustlines": 544, + "placeInTop": null + }, + { + "id": 18872, + "code": "XDPC", + "issuer": "rByyTXWjLnYxKRVD39XbvV6YZjDYRGLbyW", + "title": "Dripped Tech", + "trustlines": 7990, + "placeInTop": null + }, + { + "id": 122, + "code": "Robot", + "issuer": "rEiYPtVB19fgasmonT9WUjX4cmC8YjSDqv", + "title": "RoboCoin", + "trustlines": 2982, + "placeInTop": null + }, + { + "id": 17000, + "code": "DRIFTTOKEN", + "issuer": "r3LMUktBCGYteqri1b35ifcRdkZ9uFiEWE", + "title": "DRIFTTOKEN", + "trustlines": 2191, + "placeInTop": null + }, + { + "id": 17195, + "code": "ANOYA", + "issuer": "rHzp9zuLb9b7pRwskKN259NsyzTRmMVJBR", + "title": "ANOYA", + "trustlines": 567, + "placeInTop": null + }, + { + "id": 19086, + "code": "BambooSwap", + "issuer": "rnptNDiFdEZoM67XpR8WQu7zQLSwsuXwjv", + "title": "BambooSwap", + "trustlines": 206, + "placeInTop": null + }, + { + "id": 17544, + "code": "XCC", + "issuer": "rUFcQnmGYEuvJVJnZy8QBc9ouKJbpvK7QU", + "title": "DigitalCurrency-ALL", + "trustlines": 129, + "placeInTop": null + }, + { + "id": 17844, + "code": "XRTIGER", + "issuer": "r4bLPg11nvq23XcGamtdDUWQ29chA5UhYo", + "title": "xrTiger", + "trustlines": 1139, + "placeInTop": null + }, + { + "id": 17845, + "code": "XWGL", + "issuer": "rhMz9DtWqjvG7L3MGqFo46UiYnACQoExXq", + "title": "XWiggle", + "trustlines": 1971, + "placeInTop": null + }, + { + "id": 18097, + "code": "METAVERSE", + "issuer": "rGeGwsRy6mqcgJ7cUN39tmWwzCMhJE91VL", + "title": "METAVERSE", + "trustlines": 903, + "placeInTop": null + }, + { + "id": 18951, + "code": "COCONUTS", + "issuer": "rU5mbWvY2ZuUHnX9kv4TMVR8ctns32DXwR", + "trustlines": 181, + "placeInTop": null + }, + { + "id": 101, + "code": "xTulips", + "issuer": "rUZNppbpNCvvsaBt2XxRhsV3TEtotir4Ps", + "title": "XRPL - xTulips", + "trustlines": 2045, + "placeInTop": null + }, + { + "id": 17168, + "code": "MYBALLS", + "issuer": "rf6R7t11MubRLUcR17ngkLvoQbuRbmHvHV", + "trustlines": 868, + "placeInTop": null + }, + { + "id": 18745, + "code": "GBB", + "issuer": "rNy5WCqUGi3qWiQumYrcCqz4zyBVfKv1Sz", + "trustlines": 210, + "placeInTop": null + }, + { + "id": 107, + "code": "CRYSTAL", + "issuer": "rJ9QRoE5tmuwwpX5LJwefAxao2odPhKUkC", + "title": "CRYSTAL", + "trustlines": 2271, + "placeInTop": null + }, + { + "id": 279, + "code": "Squid", + "issuer": "rJQZQyG8JbxdDFzWR7iKjWfq68rTd9bRY7", + "trustlines": 438, + "placeInTop": null + }, + { + "id": 18593, + "code": "CHIWEENIE", + "issuer": "rpkKss6Dndbs17xkPgAMob4mzQURshh3Rd", + "title": "Chiweenie Gang \ud83d\udc36 XRPL NFT", + "trustlines": 1799, + "placeInTop": null + }, + { + "id": 206, + "code": "Xowl", + "issuer": "rPUJshc2EqJkAw3Yj8NDXHVg3gsuS25ywD", + "title": "Xowl", + "trustlines": 2857, + "placeInTop": null + }, + { + "id": 224, + "code": "XSlime", + "issuer": "rNiPyJBQRpnGTzXtX9NCHUxK89wAPtYXg4", + "title": "XSlime", + "trustlines": 2430, + "placeInTop": null + }, + { + "id": 17264, + "code": "Sand", + "issuer": "r3V7BTuupF5mACHg2vpDRotDqGbMPh5GMv", + "trustlines": 934, + "placeInTop": null + }, + { + "id": 32, + "code": "Bears", + "issuer": "rEiX8SjACtfq1Hhyjb6RGywuqpJx5S7ivT", + "title": "Brave Bears", + "trustlines": 1637, + "placeInTop": null + }, + { + "id": 43, + "code": "MOIN", + "issuer": "rwQUNK99CsdQGv1D159YbcCFde9SvfZ5Lc", + "title": "MOIN", + "trustlines": 2289, + "placeInTop": null + }, + { + "id": 53, + "code": "hellz", + "issuer": "rK6AXD3rh1PZviVNUvYVtagjGn1W5Y9qwv", + "title": "Rocketfella DeMoon", + "trustlines": 2248, + "placeInTop": null + }, + { + "id": 17555, + "code": "XID", + "issuer": "rHNcPKr7kyKc2YMgC77wX9QBGZZJmPX3Sq", + "title": "Garaxia", + "trustlines": 508, + "placeInTop": null + }, + { + "id": 8, + "code": "BabyXRP", + "issuer": "rB4uA81SR2pTtdPRX1A67jD8kxya6cmTnj", + "title": "BabyXRP", + "trustlines": 1871, + "placeInTop": null + }, + { + "id": 17154, + "code": "XMEME*PROPERTY*Pool", + "issuer": "rwrARYbfw3XBQfM19qyJBWupMGvxFyfKpE", + "trustlines": 716, + "placeInTop": null + }, + { + "id": 16753, + "code": "ASTRO", + "issuer": "r3ntcD5rB42dhJRKb3bKKbkci9vCKMSGe4", + "title": "ASTRO", + "trustlines": 3431, + "placeInTop": null + }, + { + "id": 16873, + "code": "FLY", + "issuer": "rRv7cJEoKwQpBC3RoJBjy5LpbpKYDJq2n", + "trustlines": 1829, + "placeInTop": null + }, + { + "id": 16877, + "code": "LGB", + "issuer": "rPxpxtvnettNyuD7daQd36mURiLRC5kX1k", + "trustlines": 1747, + "placeInTop": null + }, + { + "id": 16950, + "code": "SHIT", + "issuer": "rsWMZk95Y116pRDdgowiCskA9k2gsNDWvC", + "title": "xShit", + "trustlines": 919, + "placeInTop": null + }, + { + "id": 18744, + "code": "DQS", + "issuer": "rNy5WCqUGi3qWiQumYrcCqz4zyBVfKv1Sz", + "trustlines": 361, + "placeInTop": null + }, + { + "id": 19085, + "code": "CaRRots", + "issuer": "rafgLy8LPU3mqMGCRbkJcaGWuDwoUCAuwQ", + "title": "XRDonkey", + "trustlines": 229, + "placeInTop": null + }, + { + "id": 19, + "code": "MetaMind", + "issuer": "rsXreKYGtmSzJi8ZnSdBTMgQnqaQ5V8H7S", + "title": "MetaMind", + "trustlines": 2124, + "placeInTop": null + }, + { + "id": 17850, + "code": "DuskCity", + "issuer": "rLHrC9UbWkEPKscwTzXGZVvZsQmfZst3fq", + "title": "DuskCity", + "trustlines": 1862, + "placeInTop": null + }, + { + "id": 18121, + "code": "LTC", + "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n", + "trustlines": 1370, + "placeInTop": null + }, + { + "id": 18377, + "code": "XMetaShiba", + "issuer": "rHze1SoTdFsfzEEoBRktSfBfbvBuWYTcVX", + "title": "XMetaShiba", + "trustlines": 4557, + "placeInTop": null + }, + { + "id": 18736, + "code": "ALT", + "issuer": "rabAY9tuU3T5zbYbyo7JcTevtwpjS83Ypc", + "title": "ALTseason", + "trustlines": 894, + "placeInTop": null + }, + { + "id": 111, + "code": "Aquaverse", + "issuer": "rLHB5uY44TyrMiY6jss35GC5uqFq1aZvL3", + "title": "Aquaverse", + "trustlines": 2492, + "placeInTop": null + }, + { + "id": 192, + "code": "GOONS", + "issuer": "r9qzEdXC2haAkCqE61rBwLW9VnWnCxSmFE", + "title": "XRP GOONS NFT / XRP DOODLE PUNKS NFT", + "trustlines": 3273, + "placeInTop": null + }, + { + "id": 210, + "code": "RUBY", + "issuer": "rQhCaHR3FHwAFSibHAzZCRBMtfLMczUhMK", + "title": "Ruby Play Network", + "trustlines": 1966, + "placeInTop": null + }, + { + "id": 16764, + "code": "STAR", + "issuer": "ratQpkkji1EwuX3b71xAnvssVTdDQkdeCv", + "trustlines": 509, + "placeInTop": null + }, + { + "id": 16872, + "code": "XIB", + "issuer": "rhoqaYdqnjEp4RmZuUCpLiYRsMTTMoJ37r", + "trustlines": 1047, + "placeInTop": null + }, + { + "id": 18826, + "code": "LOW", + "issuer": "rrzQdKukvET4tE7ZmUSxJrAmXAquQnMFG", + "title": "Legends of Warriors", + "trustlines": 823, + "placeInTop": null + }, + { + "id": 18915, + "code": "YXRP", + "issuer": "rEzPNsT6RzKj6ocQw3TjMBjBP2w5M2RiAS", + "trustlines": 106, + "placeInTop": null + }, + { + "id": 20, + "code": "MAA", + "issuer": "rHVCcQYcEuQ7z5bDZXFXe8devDK7Qs2ZAW", + "title": "Xmonkeyz", + "trustlines": 1825, + "placeInTop": null + }, + { + "id": 16680, + "code": "Spidey", + "issuer": "rsK5Hq8eGCkjmjPjYo8vSzfDwYnrqH3yC5", + "title": "Spidey", + "trustlines": 1548, + "placeInTop": null + }, + { + "id": 16683, + "code": "MAB", + "issuer": "rHVCcQYcEuQ7z5bDZXFXe8devDK7Qs2ZAW", + "title": "Xmonkeyz", + "trustlines": 1584, + "placeInTop": null + }, + { + "id": 17169, + "code": "ElonCumV2", + "issuer": "rsJ2rPXQhomvas42UKqDUWKXtMj3JoYLom", + "trustlines": 1138, + "placeInTop": null + }, + { + "id": 17972, + "code": "XRR", + "issuer": "rbkkBEKbToXLdqC8vPdCRgh4wf3kVD9zd", + "trustlines": 140, + "placeInTop": null + }, + { + "id": 296, + "code": "VOTE", + "issuer": "rsHukuxaGdUPUQgAc5568Avb9V6sk15R2w", + "title": "VOTE", + "trustlines": 757, + "placeInTop": null + }, + { + "id": 17044, + "code": "hellzANGELS", + "issuer": "rfya4QvfJEM7rUKUp9BLTGnUoUYaKDRUTa", + "title": "Rocketfella DeMoon", + "trustlines": 1388, + "placeInTop": null + }, + { + "id": 18417, + "code": "VCT", + "issuer": "rUwbSpFZeiJ5fNb1PjhFwyMrcQdj5URtNE", + "title": "VIRTUAL CITY TOKEN", + "trustlines": 1861, + "placeInTop": null + }, + { + "id": 152, + "code": "XAnimalier", + "issuer": "rJmuQJGXSkaoru4SeoCTUiYFf5wwaqARoc", + "title": "XAnimalier_NFT_Token", + "trustlines": 2302, + "placeInTop": null + }, + { + "id": 16743, + "code": "PoopyGary", + "issuer": "rp8XtYFuqneAztu9rue9L6EF774oLFD7YB", + "title": "GTFO GARY", + "trustlines": 692, + "placeInTop": null + }, + { + "id": 17104, + "code": "XRPLGOLD", + "issuer": "rJpjxzativvm31tEh9frBBBvuqhCssom4D", + "title": "XRPL.Gold", + "trustlines": 704, + "placeInTop": null + }, + { + "id": 17157, + "code": "CO2", + "issuer": "rQas7oYeAPR1tN7dzPq9xrYqE4yfrmvHky", + "title": "CO2ious", + "trustlines": 1473, + "placeInTop": null + }, + { + "id": 17177, + "code": "XRPORSHA", + "issuer": "r9WUWJrCJ87MRGmNhVNDd1XwLXm9A6dH6c", + "title": "X.R.PETE", + "trustlines": 753, + "placeInTop": null + }, + { + "id": 17369, + "code": "VVIIIIXDIAMONDHANDS", + "issuer": "rNmrNAmQwzKWUTcoW69s6fZ23iUpKJZEnK", + "title": "Diamond", + "trustlines": 147, + "placeInTop": null + }, + { + "id": 17881, + "code": "BEER", + "issuer": "rMyLn261pBVKtgMbzFpbAsFFBL7ksbFvvM", + "title": "CUTE Token XRPL", + "trustlines": 823, + "placeInTop": null + }, + { + "id": 18277, + "code": "OWL", + "issuer": "rKE5db4MR3Y6Hf7rMFMW8s8PvUQkqmJoy8", + "title": "CryptoOwl", + "trustlines": 1617, + "placeInTop": null + }, + { + "id": 16625, + "code": "MAC", + "issuer": "rHVCcQYcEuQ7z5bDZXFXe8devDK7Qs2ZAW", + "title": "Xmonkeyz", + "trustlines": 422, + "placeInTop": null + }, + { + "id": 16939, + "code": "BUN", + "issuer": "rG7YrUpRQUq7wsr9m44aZTvXUrMe9zjTG5", + "title": "$BUN/$EGG", + "trustlines": 2695, + "placeInTop": null + }, + { + "id": 17118, + "code": "XPIMPIN", + "issuer": "rUmwhpkg6L9yENtY8ovrVekBhAfkEdnwPr", + "trustlines": 330, + "placeInTop": null + }, + { + "id": 18418, + "code": "XCFN", + "issuer": "rnGBXMR8qeEfcjZAbM9cu7xXPBcBXMiJBv", + "title": "xCFN | Crypto Financial Network", + "trustlines": 1065, + "placeInTop": null + }, + { + "id": 18526, + "code": "Xcrb", + "issuer": "rG6dVRn7dNkyFgJX3RvNkUoTqrVLMe5i1i", + "title": "XMetacribz", + "trustlines": 636, + "placeInTop": null + }, + { + "id": 13, + "code": "TBE", + "issuer": "rP9szzcmZp2PrJEhrCskiRT3ESM1kcmYLL", + "title": "TerraMammal", + "trustlines": 802, + "placeInTop": null + }, + { + "id": 78, + "code": "xRabbit", + "issuer": "rKXHVEekLubmBZQj6fTSGwk9yfaoC7nPGk", + "title": "xRabbit", + "trustlines": 3525, + "placeInTop": null + }, + { + "id": 115, + "code": "EVA", + "issuer": "rf1xyzhgedeJUegPfVP69fEjqpzGusLP9Q", + "title": "EVA", + "trustlines": 1964, + "placeInTop": null + }, + { + "id": 17371, + "code": "KRYPTON", + "issuer": "rN2FLTp3TY3skA5axWFDBZJ1PFQMjaQ6V8", + "title": "MAYABLUE.X", + "trustlines": 110, + "placeInTop": null + }, + { + "id": 17442, + "code": "COFFEE", + "issuer": "rLwxtcVyuU3EVJZQhN9h3ZtbMBVNm9sh9r", + "trustlines": 131, + "placeInTop": null + }, + { + "id": 18369, + "code": "JNKZ", + "issuer": "rhc6NxfCW74z4zJKTPPc2kNg1sh8EoX3H5", + "title": "Whitelist Junkiez", + "trustlines": 1649, + "placeInTop": null + }, + { + "id": 18403, + "code": "BRBR", + "issuer": "rEzHW6nqgU6fquF3jeV37H8jS9jnGJwwAu", + "title": "Barber Coin", + "trustlines": 615, + "placeInTop": null + }, + { + "id": 18407, + "code": "XMonke", + "issuer": "rUzHZZ74Yain1Pu7ZsLH4VsF6WaQ1fUkL6", + "title": "xMonke | XRPL - Pre Sale live now!", + "trustlines": 803, + "placeInTop": null + }, + { + "id": 18552, + "code": "xrAPE", + "issuer": "rnxUn5EosoE9BhWHvCkFHWrwAEgmx5ogJq", + "title": "xrAPE", + "trustlines": 681, + "placeInTop": null + }, + { + "id": 18992, + "code": "xWing", + "issuer": "r4VyMvq1FyPJ8h27hVQYgKZxD3jTDmE4C1", + "title": "xWINGS", + "trustlines": 11409, + "placeInTop": null + }, + { + "id": 61, + "code": "RFL", + "issuer": "ra34sTcYZAEScDwp7zKug43yL4XwDtg6qH", + "title": "Rufalo", + "trustlines": 2999, + "placeInTop": null + }, + { + "id": 102, + "code": "MonkeyBucks", + "issuer": "rwK7r9CcfCXRLWxM2Sfc4DN2M9NnekYG22", + "trustlines": 2160, + "placeInTop": null + }, + { + "id": 17171, + "code": "Aqua", + "issuer": "r36n9PBrv7vNuok4Bok3dj1Hn87CuzMeTY", + "title": "Aqua", + "trustlines": 370, + "placeInTop": null + }, + { + "id": 18399, + "code": "XSUP", + "issuer": "rJQt4a313p5W32W2UY2BEBTDBgyikd3PGS", + "title": "X- SUP", + "trustlines": 2690, + "placeInTop": null + }, + { + "id": 18449, + "code": "XMND", + "issuer": "rEMAJB3rVeufmZuTqA9oAafwcp3HrXJTS5", + "title": "M\u2200NDRILL", + "trustlines": 1462, + "placeInTop": null + }, + { + "id": 16940, + "code": "CATCOIN", + "issuer": "rKxReH5riwRFzhJdZULT4SyybTZ8EffW4p", + "title": "CAT COIN", + "trustlines": 1392, + "placeInTop": null + }, + { + "id": 17382, + "code": "Concepts", + "issuer": "rKBWxeTF2TsURqYdgMCDv1s5fhogUp9XdG", + "title": "Digital Concepts", + "trustlines": 672, + "placeInTop": null + }, + { + "id": 17677, + "code": "SYS", + "issuer": "rJr7aKTWthoXgMS65W86fzC5Ccu38ShhXv", + "title": "SYSTEMAXRPL", + "trustlines": 4187, + "placeInTop": null + }, + { + "id": 18510, + "code": "SSTEAM", + "issuer": "rMkxC2pFKCfBdjF9LAJyzneHAhDssaafri", + "title": "Shiba Spaceship Team", + "trustlines": 4517, + "placeInTop": null + }, + { + "id": 16880, + "code": "Kiss", + "issuer": "rnHQqnwWGJ4RXs515LTvdGTNXhWtpYseY3", + "title": "SendMeKiss", + "trustlines": 1777, + "placeInTop": null + }, + { + "id": 17860, + "code": "Zerolimits", + "issuer": "rnES5iow3n1PzUa24fi61DYusxWPDNkD2P", + "title": "ZeroLimits", + "trustlines": 2620, + "placeInTop": null + }, + { + "id": 18525, + "code": "xVIP", + "issuer": "rE152quC47yC128sGJKYfJYN7vjx7of4Mq", + "trustlines": 136, + "placeInTop": null + }, + { + "id": 18923, + "code": "SPR", + "issuer": "rNqTJRgWeWaf75qaTQKg2TNXVpysuR1N9M", + "trustlines": 351, + "placeInTop": null + }, + { + "id": 18991, + "code": "XL2", + "issuer": "rBKtqxDNFdPavMPTnWAdoiokF5mJ3dGMYZ", + "title": "Lineage II XRP", + "trustlines": 670, + "placeInTop": null + }, + { + "id": 16851, + "code": "MOLE", + "issuer": "rEPU1qNvPkYC9hJtrD3rVuCJt8NV8DyEck", + "trustlines": 1405, + "placeInTop": null + }, + { + "id": 16888, + "code": "LUV", + "issuer": "r3nCM71dq1fS2tUNXoJfJgqkXUV2tsE4S4", + "title": "1111Love", + "trustlines": 765, + "placeInTop": null + }, + { + "id": 16911, + "code": "MoonParty", + "issuer": "rf4yfV3MmKKhsEUVAvoVeg6kGS5fR9vvuH", + "trustlines": 1654, + "placeInTop": null + }, + { + "id": 17308, + "code": "HBX", + "issuer": "rpTUkUBRXzc9qxSemyRGFn1igGgb22Mpgm", + "trustlines": 448, + "placeInTop": null + }, + { + "id": 17370, + "code": "XMEME*StabilityPool1", + "issuer": "rLu5fHv3Fqf6c9DBAv1LJufokVRVFhjBaW", + "title": "NFT Private Bank (Matic V2 hybrid NFTs)", + "trustlines": 134, + "placeInTop": null + }, + { + "id": 18366, + "code": "BEER", + "issuer": "rJSPpLt2tjxaVTaKVBrCPn3Lj8TWZ4y6CY", + "title": "BEER Token", + "trustlines": 945, + "placeInTop": null + }, + { + "id": 18969, + "code": "BROTHEL", + "issuer": "rfA5gMg37fKLq9wL3hsiPxi5MtoCK3ENfn", + "trustlines": 163, + "placeInTop": null + }, + { + "id": 36, + "code": "Beez", + "issuer": "rsUZwB2sgFZiHY8XooW2cVzMmCZfcwiWxJ", + "trustlines": 2340, + "placeInTop": null + }, + { + "id": 175, + "code": "TWITCHCOIN", + "issuer": "raYeuXagc8j7NfzRJRLCqyXqzPpKFLWbJU", + "title": "$Ripple\\TWITCHCOIN", + "trustlines": 1220, + "placeInTop": null + }, + { + "id": 17009, + "code": "XRPC", + "issuer": "rJXQCqcjjmBouu7x2pYF6PKKBt7waVAR9D", + "title": "x", + "trustlines": 2497, + "placeInTop": null + }, + { + "id": 17011, + "code": "XLeMi", + "issuer": "rEgLd5rcZ9CUdpDhNZ6vnkpznXQBnwTUna", + "title": "XLeMi", + "trustlines": 1008, + "placeInTop": null + }, + { + "id": 17037, + "code": "BZO", + "issuer": "rHyB8fpHCTB4NhwayEtNH9DsjLue33n1ph", + "trustlines": 973, + "placeInTop": null + }, + { + "id": 17239, + "code": "SAA", + "issuer": "rEN3MR4XsJCTNTVzYAMq82RDSdNhRU2ejb", + "title": "XSkeleton", + "trustlines": 433, + "placeInTop": null + }, + { + "id": 18383, + "code": "QUARRY", + "issuer": "rnNsTa5w5hw3KbTVF7rDQd7RWUEje5Ak5C", + "title": "Love Monster \uea00", + "trustlines": 575, + "placeInTop": null + }, + { + "id": 18476, + "code": "AYNIR", + "issuer": "rhK7Ci1QvXTBJwRVBt7qFSAPQaCjq5zonR", + "title": "AYNIR : All You Need Is Reward$$$$$", + "trustlines": 1012, + "placeInTop": null + }, + { + "id": 18782, + "code": "XZVO", + "issuer": "rQfXMb8ASNWVtpKjJYsQbANeR3WS4QWeZq", + "trustlines": 111, + "placeInTop": null + }, + { + "id": 142, + "code": "CNF", + "issuer": "raTqPZNVpWUQWWRtoG6kUMqnzLdDcfVXqJ", + "title": "ChickensNFT (XRPL)", + "trustlines": 1933, + "placeInTop": null + }, + { + "id": 156, + "code": "Xrplogistic", + "issuer": "rBmZ49VHoGKmDYVGuYsFn448FZcU8JfkGk", + "title": "XRP_Logistic", + "trustlines": 1253, + "placeInTop": null + }, + { + "id": 325, + "code": "XFRUIT", + "issuer": "rQ3HF6YLbB95K5hZRAAMG7X32a1rHE9Yc2", + "trustlines": 1693, + "placeInTop": null + }, + { + "id": 332, + "code": "FRESH", + "issuer": "rw4v5ByH4ew8FZ58NSHxv3ARbetN2TYZF5", + "trustlines": 1576, + "placeInTop": null + }, + { + "id": 16978, + "code": "BURN", + "issuer": "rBb4mVtbtxtEELncFiFULNjaZrBiNJFi3f", + "trustlines": 452, + "placeInTop": null + }, + { + "id": 17202, + "code": "XMP", + "issuer": "rKcmW712NbLw2h29BMr47cwX2HAUNEbwrt", + "title": "XMP", + "trustlines": 101, + "placeInTop": null + }, + { + "id": 17212, + "code": "MICE", + "issuer": "r4M5rrfTSbfh61SH5VANcBGqZgkworfaHQ", + "title": "Biker Mice From Moon - NFT", + "trustlines": 1325, + "placeInTop": null + }, + { + "id": 17225, + "code": "RealMadridfancoin", + "issuer": "rK4saUHTVTpbX7cqzX2zEnZovbDpXxUmk9", + "title": "RealMadridCoinXRP", + "trustlines": 1077, + "placeInTop": null + }, + { + "id": 17299, + "code": "XSwans", + "issuer": "rJYMx2ZHgjocAP3aiK1STbP3CTZASN5Sy8", + "title": "xSwans", + "trustlines": 831, + "placeInTop": null + }, + { + "id": 17411, + "code": "TAROT", + "issuer": "rMyLn261pBVKtgMbzFpbAsFFBL7ksbFvvM", + "title": "CUTE Token XRPL", + "trustlines": 186, + "placeInTop": null + }, + { + "id": 17682, + "code": "XBEAR", + "issuer": "rKXoBbYN5ZKj2Y7Tmhh5xquLMQegqpgNVA", + "trustlines": 392, + "placeInTop": null + }, + { + "id": 18720, + "code": "MTA", + "issuer": "r95bSz69js5MrCoMdhejdGHvHyPRXumLTm", + "trustlines": 664, + "placeInTop": null + }, + { + "id": 18971, + "code": "Meet", + "issuer": "raiuzYGdqxXHG5gQqWZsLdPmuFWXS6oGRF", + "title": "Meet", + "trustlines": 408, + "placeInTop": null + }, + { + "id": 160, + "code": "XAPECLUB", + "issuer": "rHLsj6TzL3jqNVjLaZQkqHissV9FSfMdH5", + "title": "X APE CLUB", + "trustlines": 1739, + "placeInTop": null + }, + { + "id": 16921, + "code": "XrGuru", + "issuer": "rU2RMypDRDod46wpyGqAKo4oHdBKmVctGw", + "title": "XrGuru", + "trustlines": 1904, + "placeInTop": null + }, + { + "id": 16998, + "code": "ProCrypto", + "issuer": "rNcnGzKCtKp6mqrMDS3Ss1T7P1XfDcNXJF", + "title": "ProCrypto Coin", + "trustlines": 458, + "placeInTop": null + }, + { + "id": 17142, + "code": "GEODIS", + "issuer": "rhqnPosMaQvXf3uX84NWbgdRi9yeE7BX8d", + "trustlines": 449, + "placeInTop": null + }, + { + "id": 17476, + "code": "NFTminer", + "issuer": "rnW1yosT7YpR7c9dxSNXNRJe7wYaj2xUzV", + "trustlines": 171, + "placeInTop": null + }, + { + "id": 17694, + "code": "BSD", + "issuer": "r9qouqdcygAWyoFGPxwMN3LoJkpHJHoCBd", + "trustlines": 295, + "placeInTop": null + }, + { + "id": 17862, + "code": "xMinecraft", + "issuer": "rwBrXocEipnM1nc4Kwaq3DQwh8tmBdRCg9", + "title": "xMinecraft", + "trustlines": 1191, + "placeInTop": null + }, + { + "id": 18256, + "code": "Metacasinocoin", + "issuer": "rMBVH1skxjcC1PguikTkx9XP9ydVNpCued", + "title": "MetaCasinoCoin", + "trustlines": 1637, + "placeInTop": null + }, + { + "id": 18493, + "code": "XARMY", + "issuer": "r9kT7eetFtMTZNG6tCaPVQ7Gsc5FaWJTkK", + "title": "XRP Army", + "trustlines": 255, + "placeInTop": null + }, + { + "id": 18620, + "code": "Earn", + "issuer": "rQB3o5oCHPCeGoFGnd9us97N6RsFmTj735", + "trustlines": 1612, + "placeInTop": null + }, + { + "id": 18989, + "code": "Btrax", + "issuer": "rzxFbGjPaW6jE2WyzqmEHKhbSQmMkkJdr", + "trustlines": 430, + "placeInTop": null + }, + { + "id": 151, + "code": "Popcorn", + "issuer": "rPYFeHg6zzD4cxyyfgToX9zAtrDwyeJwUA", + "title": "Pop Corn", + "trustlines": 1882, + "placeInTop": null + }, + { + "id": 16613, + "code": "AXOL", + "issuer": "rGkvzG2RLhCYCtdgtDxaejjaQn4BSJYoy8", + "title": "Axolotl $AXOL XRPL", + "trustlines": 4961, + "placeInTop": null + }, + { + "id": 16857, + "code": "DOP", + "issuer": "rH24cKuAFByzvHDbSx1gvghBDZNb1hb1QD", + "trustlines": 505, + "placeInTop": null + }, + { + "id": 17050, + "code": "MONKS", + "issuer": "rGQtGHrgN4FK1RcEn83q4t8aK6BobzDEMK", + "title": "GORILLA$Gold XRP", + "trustlines": 726, + "placeInTop": null + }, + { + "id": 17800, + "code": "SPUD", + "issuer": "rfzMhyP9EnSgCJ18BZGwj4uH2dLF1YtTzv", + "title": "Sinister Spuds on XRPL", + "trustlines": 930, + "placeInTop": null + }, + { + "id": 18126, + "code": "METAVISION", + "issuer": "rKuuGaBjzt3CV1sX9VgbJUGTDQ6iUSQ2Rr", + "title": "METAVISION", + "trustlines": 801, + "placeInTop": null + }, + { + "id": 18357, + "code": "XdinoKings", + "issuer": "rMxxPovLbYHdcVTpYRiQRoXe1hrke5X6wr", + "title": "Xislanders_XDNT_Xdinos", + "trustlines": 1254, + "placeInTop": null + }, + { + "id": 18619, + "code": "XLEGO", + "issuer": "rsa4uUHzFotQn3gYrW5WjPKibUtSEGXhNi", + "title": "XLEGO", + "trustlines": 355, + "placeInTop": null + }, + { + "id": 18737, + "code": "TZE", + "issuer": "r3ta6EfJVWc7W3cHsnr9srw8amPpWFWiyx", + "trustlines": 109, + "placeInTop": null + }, + { + "id": 18795, + "code": "XRPanther", + "issuer": "rJqN2Bh4e3VwRxj7c8ejVYqhJUbYscQFv1", + "trustlines": 4956, + "placeInTop": null + }, + { + "id": 18817, + "code": "AURAM", + "issuer": "rswTVUVg2kXcvUhfsHSY7a6Yt3aWbyrD9y", + "trustlines": 1077, + "placeInTop": null + }, + { + "id": 19131, + "code": "DAC", + "issuer": "rpmkKzAkJHccD4WxPA5wWimnqocDb4mYzf", + "title": "The Dirty Ape Club", + "trustlines": 432, + "placeInTop": null + }, + { + "id": 74, + "code": "XBadge", + "issuer": "rUrcmbmUADwdkXwtcKiLiFXPNmqQp3EnSE", + "title": "XBadge", + "trustlines": 3459, + "placeInTop": null + }, + { + "id": 220, + "code": "XST", + "issuer": "r9KanwHSeSYB9DXfgc3sDuoE3sRsnQv4wo", + "title": "XSTX", + "trustlines": 2594, + "placeInTop": null + }, + { + "id": 16708, + "code": "JTK", + "issuer": "rESV9W43vQoYNpFf4zJJH7AWgUzNFnHBhN", + "title": "JTK_Coin", + "trustlines": 795, + "placeInTop": null + }, + { + "id": 16860, + "code": "eBets", + "issuer": "rwjTUshHMCGzDkH7S2LPkGcsGx72GgnR2q", + "title": "eBets.crypto", + "trustlines": 1284, + "placeInTop": null + }, + { + "id": 16910, + "code": "PAV", + "issuer": "r4FJh3RK7LpW7UA5u3BJqdFpavQM3Lkwdh", + "trustlines": 1546, + "placeInTop": null + }, + { + "id": 16918, + "code": "xLottery", + "issuer": "rm1avCK5oSVe52N4iEteoFKY27owuBvny", + "trustlines": 1624, + "placeInTop": null + }, + { + "id": 16958, + "code": "XMONSTERs", + "issuer": "rNRc22evysPxh7LhefqFaYtg39YpNfinnN", + "title": "XRPL.Monster", + "trustlines": 1192, + "placeInTop": null + }, + { + "id": 17119, + "code": "Ethereum", + "issuer": "rf6FbkVyYPa9nqrSrRpMDPhBEoAL8bw9GL", + "trustlines": 146, + "placeInTop": null + }, + { + "id": 17121, + "code": "KPK", + "issuer": "rpvQdrkjcnJcimiVazstciB593xhFfxVVG", + "trustlines": 1462, + "placeInTop": null + }, + { + "id": 17218, + "code": "CoinBankShards", + "issuer": "rUhbURn9gPQWcjfqTip5YsMSstBfX8UV9h", + "trustlines": 200, + "placeInTop": null + }, + { + "id": 17373, + "code": "Amazon", + "issuer": "rf4DcrzpBMy2fU2Jdc4RtENTXzrifbdPh7", + "trustlines": 363, + "placeInTop": null + }, + { + "id": 17537, + "code": "XIA", + "issuer": "rsbgrRPMTykHoStSYst8jbqkscAnHCPWY8", + "title": "Digger", + "trustlines": 183, + "placeInTop": null + }, + { + "id": 17629, + "code": "GXZV", + "issuer": "r3u8KaU7ubXQmLMrc3jhEE7vfFpMTMLTWb", + "trustlines": 4079, + "placeInTop": null + }, + { + "id": 18000, + "code": "xWolf", + "issuer": "rKoeHxZcfcoCPsDeEnDSxypcSF5gifH9eM", + "title": "xWolves Pack", + "trustlines": 2262, + "placeInTop": null + }, + { + "id": 18012, + "code": "Scrap", + "issuer": "r3Q5nzkGA9WDz2gCZ3ZSEy2yYseSASru3m", + "title": "universal-digital-assets", + "trustlines": 182, + "placeInTop": null + }, + { + "id": 18280, + "code": "BLKN", + "issuer": "r4acpp9fdgFLcFWfBQSn7fzmXrvCTSW95v", + "trustlines": 351, + "placeInTop": null + }, + { + "id": 18297, + "code": "XDRAW", + "issuer": "rQnpjsUA9SQyPHzSm7aMo6UVQmq1RSKAqn", + "title": "XDRAW", + "trustlines": 1216, + "placeInTop": null + }, + { + "id": 18451, + "code": "MLK", + "issuer": "rHkqAyggqiSvhBdrvfbCbzqbD6nUyGy2CQ", + "title": "Doggy coin", + "trustlines": 3222, + "placeInTop": null + }, + { + "id": 18609, + "code": "Chibs", + "issuer": "rBp3bumejqA1x5uVJRJbx7Ue1viCWpf3cU", + "title": "CrossChainChibs NFT", + "trustlines": 1491, + "placeInTop": null + }, + { + "id": 18631, + "code": "HWT", + "issuer": "rGXPVoPPk8d2cSKFzvugzgTRfyro96z937", + "trustlines": 751, + "placeInTop": null + }, + { + "id": 18655, + "code": "XNFT", + "issuer": "raPmRsVDTnLibYLPSYDjCtFdQh2kNjFrNZ", + "title": "NFTSXRPL", + "trustlines": 781, + "placeInTop": null + }, + { + "id": 18889, + "code": "XLTC", + "issuer": "rLMcg5dXz4x62mTiyb8xuA3kxAKsqzpB9j", + "trustlines": 148, + "placeInTop": null + }, + { + "id": 148, + "code": "SSP", + "issuer": "rN1fgXFQsDnaqL4fpr1Ktthz2yooKkF1Pf", + "title": "SSP", + "trustlines": 1806, + "placeInTop": null + }, + { + "id": 291, + "code": "RCO", + "issuer": "rErVND2kovUvNeZEt1AY9QirN3W2TJqFdi", + "trustlines": 621, + "placeInTop": null + }, + { + "id": 341, + "code": "Multiversexrpl", + "issuer": "ra3tUVitg1Uhv2XHJNQBxP2o5SbApTVQsp", + "trustlines": 577, + "placeInTop": null + }, + { + "id": 16687, + "code": "NFG", + "issuer": "rLzhZeAWFJ8RWS7yZfF7fMZ8sv5EzP8XF9", + "title": "NFG.NFT", + "trustlines": 1511, + "placeInTop": null + }, + { + "id": 16705, + "code": "GVC", + "issuer": "rN5nHQYbpwbabamejBYELSxVbKd95g5UFQ", + "title": "XRPL Bats/Grumpy Vampire Club NFT", + "trustlines": 1057, + "placeInTop": null + }, + { + "id": 16784, + "code": "INSTAGRAM", + "issuer": "raQCAsCVEh3Ysqr5fj4MNHsp46MBjEGJhU", + "title": "INSTAGRAM COIN", + "trustlines": 454, + "placeInTop": null + }, + { + "id": 16865, + "code": "NDI", + "issuer": "rKhDUSLzPY7H7VhPLeYKrsS4tnvZxW7A9t", + "title": "XSwords", + "trustlines": 447, + "placeInTop": null + }, + { + "id": 16892, + "code": "XRPFN", + "issuer": "rLQ4tws9SJWRLuqorq7468gMuygWF3i9YN", + "trustlines": 1560, + "placeInTop": null + }, + { + "id": 17021, + "code": "MCM", + "issuer": "rLpXBZrtyHYtN5rXVYUVZvBBx5aiECQbbx", + "title": "MetaMCM", + "trustlines": 895, + "placeInTop": null + }, + { + "id": 17047, + "code": "MAD", + "issuer": "rHVCcQYcEuQ7z5bDZXFXe8devDK7Qs2ZAW", + "title": "Xmonkeyz", + "trustlines": 714, + "placeInTop": null + }, + { + "id": 17267, + "code": "xMetaCube", + "issuer": "rN3AbAi3DN7fGjR4yA6k84RbhpNEUiLCVj", + "title": "Creative xMetaCube World_XRPLtoken", + "trustlines": 930, + "placeInTop": null + }, + { + "id": 17439, + "code": "KCK", + "issuer": "ra13xSGX6pv1dos7bXi7oMKvRavaJgT3kT", + "trustlines": 216, + "placeInTop": null + }, + { + "id": 17577, + "code": "GUY", + "issuer": "rw1rvdHuTE5gFmv6Sqy7rAYVmBQuXk3Ewh", + "trustlines": 197, + "placeInTop": null + }, + { + "id": 17598, + "code": "Lil", + "issuer": "raZ87rZoVjgg8oc5LijSigyN8rbutXGdbw", + "title": "LilShib", + "trustlines": 4147, + "placeInTop": null + }, + { + "id": 18053, + "code": "USD", + "issuer": "rbwE6wsxzYat1YyGGxzAwq6wBSF5MdoAg", + "title": "R9coin", + "trustlines": 179, + "placeInTop": null + }, + { + "id": 18059, + "code": "LAV", + "issuer": "rPbghDDBotsY2V2gmxCeCddooAY97UStTM", + "title": "Lavender", + "trustlines": 390, + "placeInTop": null + }, + { + "id": 18165, + "code": "EAT", + "issuer": "rBExFc4xruwQbcDgyuVYMADf2MXoHsUZ7L", + "title": "eatozee", + "trustlines": 1237, + "placeInTop": null + }, + { + "id": 18302, + "code": "mXm", + "issuer": "rfXHsMTsLKQPrrTYWtX3fgRPtGCF2drtA2", + "title": "metaXmon", + "trustlines": 6061, + "placeInTop": null + }, + { + "id": 18486, + "code": "HHDaimondHand", + "issuer": "rQrJZnVYhqJ2DjBwQzmMLdpZb5Do8zDcpV", + "title": "HH Diamond Hand Token", + "trustlines": 3390, + "placeInTop": null + }, + { + "id": 18529, + "code": "SMR", + "issuer": "rM96qPVWy16Z5inZyP4JRPeentt63x6yiG", + "trustlines": 120, + "placeInTop": null + }, + { + "id": 18784, + "code": "EXO", + "issuer": "raUFQ3JkVvEREHpbLHmupa5aweJJvtr88v", + "trustlines": 757, + "placeInTop": null + }, + { + "id": 17070, + "code": "ZEN", + "issuer": "rE8Zn9dScGmycaYXCmNSebwwdjZxxvCxcY", + "trustlines": 1690, + "placeInTop": null + }, + { + "id": 17122, + "code": "AIR", + "issuer": "rKhrjrteasANkNRFku535yXfSwijmLLsVK", + "title": "Airdrop Master", + "trustlines": 848, + "placeInTop": null + }, + { + "id": 17201, + "code": "TRL", + "issuer": "rP9szzcmZp2PrJEhrCskiRT3ESM1kcmYLL", + "title": "TerraMammal", + "trustlines": 363, + "placeInTop": null + }, + { + "id": 17287, + "code": "Sainte", + "issuer": "rGS37CXmscR8S9eYuqAHVw9837FTx1snuw", + "trustlines": 202, + "placeInTop": null + }, + { + "id": 17843, + "code": "Three", + "issuer": "rPpXBAzmc99baYbuhSuQCwb6YuNxyTBdYV", + "title": "Three Token", + "trustlines": 960, + "placeInTop": null + }, + { + "id": 18547, + "code": "xJxS", + "issuer": "rJ18qDqXrLKLKagh2bLHLGwVu7AnHZVou7", + "trustlines": 130, + "placeInTop": null + }, + { + "id": 18601, + "code": "OXC", + "issuer": "rJK9noFthmEptcCXDGSMqkHJFwsyYf6PzT", + "title": "OnlyXPunks", + "trustlines": 1187, + "placeInTop": null + }, + { + "id": 19101, + "code": "BabyTan", + "issuer": "r3dXMYohmJxZVHryGucu7XbcwTV44euPe7", + "trustlines": 202, + "placeInTop": null + }, + { + "id": 57, + "code": "X69", + "issuer": "raWsXY8tVytuoZf9kfXXpqYHropz6kEA91", + "title": "$X69 (6,9)", + "trustlines": 2559, + "placeInTop": null + }, + { + "id": 334, + "code": "XARMY", + "issuer": "rD7exzMoMx5mTpock7Fg3kCPcMWFX8ZgPm", + "title": "xARMY", + "trustlines": 3288, + "placeInTop": null + }, + { + "id": 16717, + "code": "XWG", + "issuer": "rMJdH3KdxytqB7ibsvqK8ufCTtPDeT2GQA", + "title": "Wooden Gorilla", + "trustlines": 2150, + "placeInTop": null + }, + { + "id": 16965, + "code": "DDD", + "issuer": "rBixCrcCsbSf3ZeF9zxsLym9KMp2dQde34", + "title": "DDD Token", + "trustlines": 1133, + "placeInTop": null + }, + { + "id": 17089, + "code": "ISO20022", + "issuer": "rfoPuEFfxdskQu8nkJwyGb7HEjFHM5ijvH", + "trustlines": 102, + "placeInTop": null + }, + { + "id": 17120, + "code": "XRDraco", + "issuer": "rwH9BJ8sSVYuQjaTmuabGJLXS9B6Eo7MpT", + "trustlines": 380, + "placeInTop": null + }, + { + "id": 17268, + "code": "GEN", + "issuer": "rG9XbrVXUaBKVE2hxgUBqBNXhFtvo6XTas", + "title": "Genosix", + "trustlines": 2005, + "placeInTop": null + }, + { + "id": 18143, + "code": "Bullish", + "issuer": "rpQrqLasrD2uMUj2NRHJ9jc8d8rcT9q4oA", + "title": "The Bullish Club", + "trustlines": 841, + "placeInTop": null + }, + { + "id": 18230, + "code": "BCH", + "issuer": "rJDrynNXYLnENUSJkUoyepkq9QtAWmhWxM", + "title": "B-Ch Checker", + "trustlines": 3013, + "placeInTop": null + }, + { + "id": 18236, + "code": "XANC", + "issuer": "rHMd5mbLQafuZbN4q6tahY23kxPBnDEhYw", + "trustlines": 255, + "placeInTop": null + }, + { + "id": 18253, + "code": "WarpedAliensGame", + "issuer": "rUKxcRrVWYgCSCrMabkAD3rGBDh9LKLX3N", + "title": "\ud835\udd4e\ud835\udd52\ud835\udd63\ud835\udd61\ud835\udd56\ud835\udd55 \ud835\udd38\ud835\udd5d\ud835\udd5a\ud835\udd56\ud835\udd5f\ud835\udd64 \ud835\udd3e\ud835\udd52\ud835\udd5e\ud835\udd56 \ud835\udd60\ud835\udd5f \ud835\udd4f\u211d\u2119\ud835\udd43", + "trustlines": 1114, + "placeInTop": null + }, + { + "id": 18291, + "code": "HIW", + "issuer": "rh1db2S5sJwzWbqehfK2UXEfP3wu5svXbj", + "title": "HighIQWorld", + "trustlines": 1564, + "placeInTop": null + }, + { + "id": 18343, + "code": "Xombie", + "issuer": "rEPT3uPk7Zu2x4FdqGukrDJwtRP5tgYdw2", + "title": "Xombieverse", + "trustlines": 1004, + "placeInTop": null + }, + { + "id": 18365, + "code": "ETNL", + "issuer": "rfYcf2iZiLRuLxK94VReVPk4WaxGpRB6Fn", + "title": "Eternal Citizens", + "trustlines": 1621, + "placeInTop": null + }, + { + "id": 18381, + "code": "MYACRON", + "issuer": "rP8Fc1THdhSRRf4AYdWTxAx3UooKNsWhSz", + "title": "Love Monster \uea00", + "trustlines": 617, + "placeInTop": null + }, + { + "id": 18513, + "code": "ITEMX", + "issuer": "rfF83CBpuBwX2NkSPcTsnbn3iqwH1uPCbU", + "title": "ITEMX PROPERTIES", + "trustlines": 2558, + "placeInTop": null + }, + { + "id": 18963, + "code": "LYL", + "issuer": "rLoyaLty93xeM35DhizSQdJd8AqLXA39W5", + "trustlines": 360, + "placeInTop": null + }, + { + "id": 18995, + "code": "LOP", + "issuer": "rLoPH2gfKN63jA6AB5smFKdUHywi89s96g", + "trustlines": 2185, + "placeInTop": null + }, + { + "id": 19009, + "code": "AFRICA", + "issuer": "rfef9Ft3FagH947ggDDeNc4sbxKD9nWhPH", + "trustlines": 428, + "placeInTop": null + }, + { + "id": 87, + "code": "AGT", + "issuer": "rDPKnRGk1ceea1h2iNbxCyzVp4MQn3jdtF", + "trustlines": 2138, + "placeInTop": null + }, + { + "id": 91, + "code": "RAIN", + "issuer": "r3cBTiMpEzRdmCgPoBNV7z73CUEPmtZQsC", + "trustlines": 1970, + "placeInTop": null + }, + { + "id": 16658, + "code": "XSteam", + "issuer": "rMPEa5WFjqTtrLnBgzJJPXgyRXtVzSgZb6", + "trustlines": 1415, + "placeInTop": null + }, + { + "id": 16667, + "code": "XWITCH", + "issuer": "rDz3DMjckHrNvpbrQMkpg9uWUf56Q7fzXr", + "title": "The Witch Chibi Official", + "trustlines": 1848, + "placeInTop": null + }, + { + "id": 16902, + "code": "TYO", + "issuer": "rnFiZ5KbTxNmAd6XzvDGxtwvaSQfgwSSG6", + "title": "Tokyo", + "trustlines": 1456, + "placeInTop": null + }, + { + "id": 16927, + "code": "UNC", + "issuer": "rMwmwEFNe9Ao7DEcu4DuRWcFQiXVaCTnYh", + "trustlines": 1517, + "placeInTop": null + }, + { + "id": 16929, + "code": "TRI", + "issuer": "rGrxS9nHnExHCRyH61K8bg51dMmP4nH5hC", + "trustlines": 1156, + "placeInTop": null + }, + { + "id": 17215, + "code": "ServicesOnCall", + "issuer": "raq36mQ26mziAmucxY4tb9dPtzCWnZT7YH", + "trustlines": 1441, + "placeInTop": null + }, + { + "id": 17246, + "code": "XRBitcoin", + "issuer": "rnkUKwv2PJZrem1DecEzCHtnHUwDsEoZYi", + "title": "XRBitcoin", + "trustlines": 210, + "placeInTop": null + }, + { + "id": 17805, + "code": "FQT", + "issuer": "rHUXRWp8DqH2vZSaq9b8qUzrBXDtAwBcwr", + "title": "FooCuties", + "trustlines": 2707, + "placeInTop": null + }, + { + "id": 18066, + "code": "xETH", + "issuer": "rsBC7azvcV3BDzSsJxPeg1z8T4FRzMTAPt", + "trustlines": 657, + "placeInTop": null + }, + { + "id": 18070, + "code": "BonesNFT", + "issuer": "rwc5Uwwo8h4ByfBZ31sM5sxizQmf3FGzY1", + "title": "BonesNFT", + "trustlines": 452, + "placeInTop": null + }, + { + "id": 18128, + "code": "METADEX", + "issuer": "rPgJC4uGuM5j7vLWDwz3Sgkbs5pzP9dba7", + "title": "METADEX", + "trustlines": 456, + "placeInTop": null + }, + { + "id": 18164, + "code": "WEB3", + "issuer": "rwdbu6JD98vzkazZhA8bJi5vGk6XC31Aqq", + "title": "WEB3", + "trustlines": 422, + "placeInTop": null + }, + { + "id": 18267, + "code": "XRMeta", + "issuer": "rwHPGXBVXez6yFiqCtUogoax1TCzCDmLmu", + "trustlines": 163, + "placeInTop": null + }, + { + "id": 18413, + "code": "888", + "issuer": "rLkLGhmomU9RrYrAqaY459w8de3VcEBUG", + "trustlines": 378, + "placeInTop": null + }, + { + "id": 18416, + "code": "WBN", + "issuer": "rfCdVNjLdrGvTisnCmVNd4noP8yfZKoCBp", + "title": "Wild Bee Network", + "trustlines": 3995, + "placeInTop": null + }, + { + "id": 18467, + "code": "FRN", + "issuer": "rna7iiis1Ji6KXPRzvavwVrTNDXpxM1cTF", + "trustlines": 136, + "placeInTop": null + }, + { + "id": 18511, + "code": "FIG", + "issuer": "r4UGsoSSCG3Xfq191N3SDEfBLBzyuAo4Vx", + "trustlines": 113, + "placeInTop": null + }, + { + "id": 18712, + "code": "VFVT", + "issuer": "rnN8H9hxCBntJrJULsveK62eUdkftGP73r", + "trustlines": 236, + "placeInTop": null + }, + { + "id": 18724, + "code": "Shibaluna", + "issuer": "rs4BLVqLDujsuJST6NTZ4rpPv6NnYYjyTF", + "trustlines": 297, + "placeInTop": null + }, + { + "id": 18881, + "code": "POO", + "issuer": "rshitKoinuxBNACMhL6QLwAsfWCUDxYobm", + "trustlines": 110, + "placeInTop": null + }, + { + "id": 18948, + "code": "xBAFC", + "issuer": "rnwgiTTyapeXTD7srnFQzoQFn6P64bVETi", + "trustlines": 490, + "placeInTop": null + }, + { + "id": 18984, + "code": "FacelessWarrior", + "issuer": "r4J9VFFTqYCsdNtTUV1isqHnEsMsDYC6rJ", + "trustlines": 108, + "placeInTop": null + }, + { + "id": 19014, + "code": "Eliz", + "issuer": "rUUuaVpMwtZrD3xT1SbYH1tQVPndthxgrB", + "trustlines": 8873, + "placeInTop": null + }, + { + "id": 343, + "code": "PGS", + "issuer": "rBzmXKofJoDQuPS2bcMtNboGiz1i6EHPm8", + "trustlines": 1491, + "placeInTop": null + }, + { + "id": 344, + "code": "XRKishaInu", + "issuer": "rP5nLxe6XpNDwS9rxZWaJgv14Hbt27E32o", + "title": "XRKishaInu", + "trustlines": 742, + "placeInTop": null + }, + { + "id": 16756, + "code": "XFORTUNE", + "issuer": "rMe6YEJPaPAbRkgub1qD9nFUVJhfCmsMq", + "title": "xFortune", + "trustlines": 529, + "placeInTop": null + }, + { + "id": 16757, + "code": "MEDBED", + "issuer": "rKoJxciEmcejEaCg1gjrxHRXD4vzMg87qP", + "title": "MED BED", + "trustlines": 910, + "placeInTop": null + }, + { + "id": 16765, + "code": "Supreme", + "issuer": "rJUnK2UtHS4xrHtetEBWYTpajQzxrGKjbV", + "title": "Supreme Coin", + "trustlines": 843, + "placeInTop": null + }, + { + "id": 16788, + "code": "JEW", + "issuer": "rHd5as6pt2DJeyx7oZJjkTCZQHGY4f7yxQ", + "title": "XRPL JEWEL", + "trustlines": 893, + "placeInTop": null + }, + { + "id": 16909, + "code": "GSO", + "issuer": "rEqgJ6AyGRBpSk4vmiPUMaJD3zs9WMyKAh", + "trustlines": 1595, + "placeInTop": null + }, + { + "id": 16979, + "code": "$GM", + "issuer": "rhcMVnUV3ZUozysgbgs2Vtsco9zkzqNCMD", + "trustlines": 181, + "placeInTop": null + }, + { + "id": 17002, + "code": "XRL", + "issuer": "rsFJqThf8hZgw1VUEQK3HRpu77kKmPnYB9", + "title": "XRLunaNFT", + "trustlines": 2215, + "placeInTop": null + }, + { + "id": 17003, + "code": "Illuvium", + "issuer": "rMwmindr4En3k4UzhXDa814QXtzqd5nErc", + "trustlines": 542, + "placeInTop": null + }, + { + "id": 17064, + "code": "Xrpinu", + "issuer": "rasXLV2Qi8tc97nywVhQvHHfhHgZHvgYwf", + "trustlines": 1017, + "placeInTop": null + }, + { + "id": 17115, + "code": "GOMD", + "issuer": "rasLGGWLbSEgqm1JWycjvc2sAxUugRo8gt", + "trustlines": 296, + "placeInTop": null + }, + { + "id": 17117, + "code": "Lookey", + "issuer": "rwoVSDUwCVdCpwnkH2Ahh5o7KHfWXLQXmb", + "title": "Lookey", + "trustlines": 823, + "placeInTop": null + }, + { + "id": 17135, + "code": "XLOAN", + "issuer": "r37ueqMppLw8tL9xmu5vRouiAcqwgbtqug", + "trustlines": 1307, + "placeInTop": null + }, + { + "id": 18043, + "code": "ANGELA", + "issuer": "rHcGz1DpZ3XvVCyk6MbXvUuVSVJHqDAKVM", + "title": "Angela - The Little Girl", + "trustlines": 3225, + "placeInTop": null + }, + { + "id": 18044, + "code": "XBEATGIRL", + "issuer": "rs2gfQxFDHE73v7TqBbhqZyKLKiaFppNch", + "title": "Beatrix Girl Project", + "trustlines": 802, + "placeInTop": null + }, + { + "id": 18083, + "code": "ZNT", + "issuer": "rZntokeNtr8m9DYfyLnr2nfTzAxJDnhV9", + "trustlines": 285, + "placeInTop": null + }, + { + "id": 18468, + "code": "XRJapan", + "issuer": "rs4Rz7EiNnWXyVTbm11y3qpj94VVZPxHqm", + "title": "XRJapan", + "trustlines": 601, + "placeInTop": null + }, + { + "id": 18941, + "code": "XRPU", + "issuer": "rEjDDNL1opuSBEBs4eF3ToM4MWj1dd5M8M", + "trustlines": 120, + "placeInTop": null + }, + { + "id": 19137, + "code": "XMusic", + "issuer": "rnSmVZ9z2E9jJ3ac7PJXvobSE7XcYxGmzt", + "trustlines": 272, + "placeInTop": null + }, + { + "id": 41, + "code": "XAMMO", + "issuer": "rGoPVzsJUU4Y6NNfn1Q6tq5TKRVC5yAA44", + "trustlines": 1847, + "placeInTop": null + }, + { + "id": 54, + "code": "CRIME", + "issuer": "rfd79jYCtevdGuu1sq55pf36z6pyYioNXm", + "title": "Rocketfella DeMoon", + "trustlines": 1632, + "placeInTop": null + }, + { + "id": 229, + "code": "MAE", + "issuer": "rHVCcQYcEuQ7z5bDZXFXe8devDK7Qs2ZAW", + "title": "Xmonkeyz", + "trustlines": 159, + "placeInTop": null + }, + { + "id": 286, + "code": "RCO", + "issuer": "rLr45PV5dECH4NhNgzhG3mbDAYxuyj4u5q", + "title": "Reconcile", + "trustlines": 865, + "placeInTop": null + }, + { + "id": 16677, + "code": "XGalaxyStone", + "issuer": "rDE5vYYXxJnfLZTHbzBFo4gjAr9FhAyqqC", + "title": "XGalaxy", + "trustlines": 450, + "placeInTop": null + }, + { + "id": 16923, + "code": "Lightyear", + "issuer": "rLo5NSbWw28LvS5F81BXdYSSnv5hiBA6ES", + "trustlines": 1598, + "placeInTop": null + }, + { + "id": 16924, + "code": "NEC", + "issuer": "rNEhdhji1ByXNMMGp28oZHnwv5bHzUz9mv", + "title": "META", + "trustlines": 2882, + "placeInTop": null + }, + { + "id": 16933, + "code": "PRH", + "issuer": "rMwmwEFNe9Ao7DEcu4DuRWcFQiXVaCTnYh", + "trustlines": 1218, + "placeInTop": null + }, + { + "id": 17016, + "code": "XDO", + "issuer": "rwLLijjmG4jto3sUVPCAifKLggid5feBAZ", + "trustlines": 494, + "placeInTop": null + }, + { + "id": 17048, + "code": "XRIC", + "issuer": "radgfhTRPvEA9jM3GmYXj7gXetg6kSi26G", + "trustlines": 562, + "placeInTop": null + }, + { + "id": 17111, + "code": "ECON", + "issuer": "rpCXAqJaHaRqf4rHMjNrCpsKi1Dc2mJS8F", + "trustlines": 128, + "placeInTop": null + }, + { + "id": 17213, + "code": "EXP", + "issuer": "r9MawWuzyeeSj6GrqWbuT3BNbr6N6GPCYy", + "trustlines": 105, + "placeInTop": null + }, + { + "id": 17295, + "code": "Seby", + "issuer": "rLSG5R79BYecTmvkt5P5qxiJvxCa8p7KHA", + "trustlines": 321, + "placeInTop": null + }, + { + "id": 17315, + "code": "RogueBotNFT", + "issuer": "rDdR9FkkV61sNZZi6miRBDnQvXaNYJdiSc", + "trustlines": 224, + "placeInTop": null + }, + { + "id": 17408, + "code": "MOWGLI", + "issuer": "rfRdAtFRGCBan1E5ALXd7Hkcmdg3Xu48gF", + "title": "MOWGLI", + "trustlines": 520, + "placeInTop": null + }, + { + "id": 17951, + "code": "SNUB", + "issuer": "r9xBBqi5phd8R23omHQVHeBNjhDSrTmrMm", + "trustlines": 875, + "placeInTop": null + }, + { + "id": 18217, + "code": "XMS", + "issuer": "rasAaG4DTg1uCRJTLMAMNfdpTQgVnpM46g", + "title": "xrp multisender", + "trustlines": 1863, + "placeInTop": null + }, + { + "id": 18279, + "code": "STC", + "issuer": "rKL6wV9fP3drGHcu9eh443Q4xVERyWwWAQ", + "title": "XrpFoundationSaveTheChildren", + "trustlines": 1039, + "placeInTop": null + }, + { + "id": 18292, + "code": "Deity", + "issuer": "rGziqYkxHk2LyrUFmo4qkLmpJmK2jhk1KT", + "trustlines": 114, + "placeInTop": null + }, + { + "id": 18322, + "code": "OVEGG", + "issuer": "r9MdSTXZpykBgasdGFDjJT5p93vxuennAG", + "title": "Oviparous Collectables", + "trustlines": 670, + "placeInTop": null + }, + { + "id": 18330, + "code": "MFX", + "issuer": "r4nvnPfHr797Q6zipQKPkh8RZ3cHHLxc5D", + "trustlines": 421, + "placeInTop": null + }, + { + "id": 18341, + "code": "BRZ", + "issuer": "r4i4KNPm26RJqvqtx7HkG9Tc4u2uDBL9gg", + "trustlines": 143, + "placeInTop": null + }, + { + "id": 18380, + "code": "NEKATEST", + "issuer": "rh52WX72oNdwJj6rePE9qCpGjFReBXHoHk", + "trustlines": 133, + "placeInTop": null + }, + { + "id": 18450, + "code": "FUD", + "issuer": "rHkqAyggqiSvhBdrvfbCbzqbD6nUyGy2CQ", + "title": "Doggy coin", + "trustlines": 1584, + "placeInTop": null + }, + { + "id": 18479, + "code": "XFrogman", + "issuer": "rDmBDjjRCiMjHjHzC6m7nd11gGnz2k3Dm9", + "title": "XFrog-man", + "trustlines": 1207, + "placeInTop": null + }, + { + "id": 18568, + "code": "Maharlika", + "issuer": "rDqZqBahgSD73Pi2EwXG6ukcVmcSFtM1tT", + "title": "Maharlikan Guild", + "trustlines": 372, + "placeInTop": null + }, + { + "id": 18803, + "code": "Kidscoin", + "issuer": "rBXYEbSkQaGF3H87bjMz5s6QXAjZKwDGvd", + "trustlines": 118, + "placeInTop": null + }, + { + "id": 18903, + "code": "XPUNKFC", + "issuer": "rJH2ucwzEFcfVyrYCiLHAVwL1VkFHnA96h", + "trustlines": 328, + "placeInTop": null + }, + { + "id": 18949, + "code": "XVPWT", + "issuer": "rXvoteCZbhqMXVx11URCbJpTbkP7Jm1jd", + "trustlines": 187, + "placeInTop": null + }, + { + "id": 18996, + "code": "MUFT", + "issuer": "rw8YAeA3NvxC8usv17mY9FkmS1ei2PqKFm", + "trustlines": 6359, + "placeInTop": null + }, + { + "id": 19012, + "code": "FOOTBALL WORLD CUP", + "issuer": "rUcxXj42XfvtEgz7cXxn4LGcuDzfm7AHTm", + "trustlines": 7543, + "placeInTop": null + }, + { + "id": 129, + "code": "CROM", + "issuer": "rszWfxUVGNAM3Z5YCDQDKpndMKd8WcwK3p", + "trustlines": 643, + "placeInTop": null + }, + { + "id": 143, + "code": "Davavi", + "issuer": "raQeA6mQ5uQDEKd8YagzBQk1LgpHWzKnhD", + "trustlines": 439, + "placeInTop": null + }, + { + "id": 278, + "code": "WMP", + "issuer": "rJMdV26oyxrgZDsPKX8WUVJ55rNbSq3ynX", + "trustlines": 729, + "placeInTop": null + }, + { + "id": 16700, + "code": "KALA", + "issuer": "rfCVwJsq6L8hpsutJ1TSobfuwDJhVtGgmW", + "trustlines": 532, + "placeInTop": null + }, + { + "id": 16749, + "code": "EDUC", + "issuer": "rhRTEyx3Lq4c3RpLo5f5Xibt8YB8XtCxfv", + "title": "Education Token (EDUC on XRPL)", + "trustlines": 1744, + "placeInTop": null + }, + { + "id": 16908, + "code": "XVI", + "issuer": "rMHaa4WrVkpzyJcq9ES3kAe82ofx72DshZ", + "title": "LiveLikeKingz", + "trustlines": 1594, + "placeInTop": null + }, + { + "id": 16970, + "code": "MFC", + "issuer": "ra13xSGX6pv1dos7bXi7oMKvRavaJgT3kT", + "trustlines": 480, + "placeInTop": null + }, + { + "id": 16971, + "code": "MBT", + "issuer": "rwXySmQxaUQn7UAnBv4QpvWv5sJTB2isE1", + "trustlines": 347, + "placeInTop": null + }, + { + "id": 16995, + "code": "ChristmasCoin", + "issuer": "rP9N1D5TicAXC6qAPBJn27bprhSTJ4MEsi", + "trustlines": 1105, + "placeInTop": null + }, + { + "id": 17106, + "code": "DOBE", + "issuer": "rJyME8DgwdnJVtkUPyiEAJhA9XQHsENn9Z", + "trustlines": 539, + "placeInTop": null + }, + { + "id": 17112, + "code": "BullCuck", + "issuer": "rwGfgbLRVrDV3x2Fcu95ADDqtZMtaT4X1T", + "trustlines": 218, + "placeInTop": null + }, + { + "id": 17124, + "code": "ASSHAWK", + "issuer": "r3hCP115jHm2a6CyeXPxfBaJsWrxHMTKvV", + "trustlines": 721, + "placeInTop": null + }, + { + "id": 17145, + "code": "LAVS", + "issuer": "r42Jgmfh9vg7rkX6uyyNyMwhByBMiVuprU", + "trustlines": 269, + "placeInTop": null + }, + { + "id": 17179, + "code": "MGK", + "issuer": "r9G3u3YeVbNhNHHsKYRTsra4tApKkuxuSQ", + "trustlines": 577, + "placeInTop": null + }, + { + "id": 17217, + "code": "XRPDoggy", + "issuer": "rBR9kEXdwwDdGksDto4VaaqsgeA15qDG3q", + "title": "XRPDoggy", + "trustlines": 218, + "placeInTop": null + }, + { + "id": 17222, + "code": "GIVE", + "issuer": "rBvjBZF2qRd1o9fwWT3wbjyCMQ5GQ8KK1k", + "trustlines": 245, + "placeInTop": null + }, + { + "id": 17248, + "code": "Bitcoin", + "issuer": "r388N5HgmPCunXhM5QULAW4KMpeg7HVWkP", + "title": "Meme", + "trustlines": 244, + "placeInTop": null + }, + { + "id": 17249, + "code": "XRSaitama", + "issuer": "r9KmfUKBVVKJAJbrnQXoMUg36f9ztk74dd", + "title": "XRSaitama", + "trustlines": 122, + "placeInTop": null + }, + { + "id": 17252, + "code": "XBG", + "issuer": "r4X9JkeMrQMCW2N6dGc3XK9PFoirpW3NbS", + "trustlines": 158, + "placeInTop": null + }, + { + "id": 17276, + "code": "UFC", + "issuer": "rgfTzdr7TJVmdXBmUYyjjm2RHACAXf53c", + "trustlines": 265, + "placeInTop": null + }, + { + "id": 17281, + "code": "LGBTQ", + "issuer": "rJcDBGuShjinAbX2RU9uh1fkjhaSsBnA2Y", + "trustlines": 228, + "placeInTop": null + }, + { + "id": 17567, + "code": "XRPArmy", + "issuer": "rN8Pat8HPwshmySmwre2uKGrdiJXjSzKK7", + "trustlines": 134, + "placeInTop": null + }, + { + "id": 17681, + "code": "CONTRACTOR", + "issuer": "rKdQVsJBFdAvBrugdEEqa1MnnP7u8u4cQJ", + "title": "ContractorCoin", + "trustlines": 995, + "placeInTop": null + }, + { + "id": 17861, + "code": "XRBet", + "issuer": "rUbKCXsCsrvRyFJYUMbbWsTmnuiYtuJrdS", + "trustlines": 851, + "placeInTop": null + }, + { + "id": 17866, + "code": "Orphanage", + "issuer": "rBg9cmGA5bBnbnCxCgvcHhwypvVAwxNH7x", + "trustlines": 464, + "placeInTop": null + }, + { + "id": 17995, + "code": "Xrew", + "issuer": "r4mneNcxKRSpVGn7GZi1BdngAkSqMAeQmz", + "trustlines": 637, + "placeInTop": null + }, + { + "id": 18255, + "code": "CANNACOIN", + "issuer": "rnheY8yPSVhqWZ9XfSsyRjdUmzmeMqrUD2", + "trustlines": 186, + "placeInTop": null + }, + { + "id": 18276, + "code": "BabyShibainu", + "issuer": "rEKwMCi1hBdoy8MZ1nz963Gec4Tw1YbCgR", + "title": "BabyShibainu", + "trustlines": 108, + "placeInTop": null + }, + { + "id": 18355, + "code": "DIDIT", + "issuer": "rfRxsnwWSUAL6HPb8d9JZHVQWGrLqoAeUc", + "trustlines": 147, + "placeInTop": null + }, + { + "id": 18438, + "code": "CREDITS", + "issuer": "rL1UgNF8TcHw6YCSfbG1ricGArwiqReUNm", + "trustlines": 189, + "placeInTop": null + }, + { + "id": 18484, + "code": "PITx", + "issuer": "rsAHS3agHAyo671V8EFcERoEcEgTeeTW6G", + "trustlines": 208, + "placeInTop": null + }, + { + "id": 18501, + "code": "DBC", + "issuer": "rJpcfNjUXTx5RFEYeyzSerKCKuLk9FZPt", + "trustlines": 191, + "placeInTop": null + }, + { + "id": 18528, + "code": "PARA", + "issuer": "rft1ngEhJwNxTeQDmaqDy7Sg6bQG9YrQYQ", + "trustlines": 115, + "placeInTop": null + }, + { + "id": 18573, + "code": "RugPull", + "issuer": "rpiQEQA1i8LFvdgSZbeHBnNUWETppKPWC7", + "title": "Project Rug Pull", + "trustlines": 186, + "placeInTop": null + }, + { + "id": 18591, + "code": "KTY", + "issuer": "rLYHx7r6uhRaot5UFcRN3arVDMWjfCrodu", + "trustlines": 106, + "placeInTop": null + }, + { + "id": 18697, + "code": "VEND", + "issuer": "rpo8Yt5R5UeTXsxXih7qPeKwXHQj7yj8iL", + "title": "VEND", + "trustlines": 103, + "placeInTop": null + }, + { + "id": 18711, + "code": "TURD", + "issuer": "rH4yQVPK4W3MhJ7gTxotA3BatrdnZQ7YMB", + "trustlines": 114, + "placeInTop": null + }, + { + "id": 18743, + "code": "AstroXRPL", + "issuer": "rhv815JJApsRygvDrKq1t9iDgxmK4xDnPq", + "title": "Astro XRPL", + "trustlines": 654, + "placeInTop": null + }, + { + "id": 18758, + "code": "NCX", + "issuer": "rNAnrcZRxQ2PFYkT78wKTjNHzE7EFqA1E3", + "trustlines": 245, + "placeInTop": null + }, + { + "id": 18816, + "code": "PMC", + "issuer": "rfrmCTJe3zekYFbJfD8QtnTrRrsXvnPyBT", + "title": "PMCissuer", + "trustlines": 643, + "placeInTop": null + }, + { + "id": 18829, + "code": "PRN", + "issuer": "rDsvn6aJG4YMQdHnuJtP9NLrFp18JYTJUf", + "trustlines": 622, + "placeInTop": null + }, + { + "id": 18888, + "code": "Metropolis", + "issuer": "rawczbKvJYwY8TkYhLwAP9mmUhhJMWqwBk", + "trustlines": 157, + "placeInTop": null + }, + { + "id": 18925, + "code": "TDAC", + "issuer": "rpWpPUfN27A8dTz8fJZw4b4HiRX6igsKPZ", + "trustlines": 417, + "placeInTop": null + }, + { + "id": 39, + "code": "JUK", + "issuer": "rN7nWFdRLLiAp33FkQsnPvP7rB2ZMEQ5TF", + "title": "Jukebox", + "trustlines": 2033, + "placeInTop": null + }, + { + "id": 51, + "code": "POP", + "issuer": "rsWg7DAsQmpMDdawUjB95yPwsxXFRxj47H", + "trustlines": 1794, + "placeInTop": null + }, + { + "id": 256, + "code": "CriptoSaurioToken", + "issuer": "rsXdt9Sr9AwZbQtFxSLN9yYVsJ8XidJEVK", + "title": "CriptoSaurios", + "trustlines": 429, + "placeInTop": null + }, + { + "id": 16641, + "code": "WQZ", + "issuer": "r9FbhqdSKTtPhdpCF8pFzzCkHFHUpXLTLG", + "trustlines": 710, + "placeInTop": null + }, + { + "id": 16693, + "code": "Guidance", + "issuer": "r3NEThqCUvgLjA7zdrKfvxDANwsmviLMik", + "title": "Guidance", + "trustlines": 929, + "placeInTop": null + }, + { + "id": 16712, + "code": "XPLNT", + "issuer": "rUtPYqtPBzUpP88PhxN5knDEXcBQpUQbPd", + "trustlines": 410, + "placeInTop": null + }, + { + "id": 16741, + "code": "IGWT", + "issuer": "rPT5ZhM1UUzTg1kfYBJfo3WGxetM66C3pD", + "title": "IGWTrustline", + "trustlines": 1186, + "placeInTop": null + }, + { + "id": 16774, + "code": "XSILVER", + "issuer": "rUpm9huRVf4WoyDc4r18AjfuufZjT3KiTD", + "title": "DSK", + "trustlines": 1332, + "placeInTop": null + }, + { + "id": 16783, + "code": "KadushiXR", + "issuer": "rMx6J6daq9jj6Bq7YN7CGZZNQF2LYrP2y1", + "title": "KadushiXR", + "trustlines": 675, + "placeInTop": null + }, + { + "id": 16912, + "code": "ATH", + "issuer": "rr7GXFTucu3EYKprApnhin4pFDT6WdCRH", + "trustlines": 9014, + "placeInTop": null + }, + { + "id": 16916, + "code": "Axcoin", + "issuer": "ravaGccetHsptXRYojsa142yfW8TpnJAAq", + "trustlines": 1593, + "placeInTop": null + }, + { + "id": 16926, + "code": "RTS", + "issuer": "rMwmwEFNe9Ao7DEcu4DuRWcFQiXVaCTnYh", + "trustlines": 1095, + "placeInTop": null + }, + { + "id": 16935, + "code": "AHCX", + "issuer": "rLLiLAdbwvcDYNDr5gLowiu7DVS3mCaTLz", + "trustlines": 1780, + "placeInTop": null + }, + { + "id": 16993, + "code": "YWY", + "issuer": "rKioikK11rirQCUKySNsxoJHGHuiCD2ixK", + "trustlines": 1656, + "placeInTop": null + }, + { + "id": 17029, + "code": "XSB4", + "issuer": "rwf1QDbXehT5WuQ9E6hvdS5QoYyk6kgby6", + "trustlines": 645, + "placeInTop": null + }, + { + "id": 17039, + "code": "ASSCHEEKS", + "issuer": "rsAUydzTsZijrqXdmSapSQJ5cYyvxuP8f8", + "trustlines": 835, + "placeInTop": null + }, + { + "id": 17056, + "code": "CrucifixCoin", + "issuer": "rGx4j3jaG4RbqG6UabiwvznmEv7cgvkHCG", + "trustlines": 486, + "placeInTop": null + }, + { + "id": 17067, + "code": "Bluetongue", + "issuer": "rGDRWQtEhx8NWk59zQrShdv2eEizrZezNe", + "trustlines": 420, + "placeInTop": null + }, + { + "id": 17132, + "code": "LIT", + "issuer": "rKa3U3n3Zdcz2sy43iiimihYZXrfXk6aY8", + "trustlines": 284, + "placeInTop": null + }, + { + "id": 17140, + "code": "XRHasbulla", + "issuer": "r96QK5tcjNgCWjsccMMY1rhramhqYqBQhj", + "title": "XRHasbulla", + "trustlines": 181, + "placeInTop": null + }, + { + "id": 17216, + "code": "NATN", + "issuer": "rLnU3fo18hhpJG2sZvSkuyooLeB4crHRCy", + "trustlines": 390, + "placeInTop": null + }, + { + "id": 17219, + "code": "FoFF", + "issuer": "rG2mAZ5A2YH1UeXVjsQqh6UaZXBnpiLi9Y", + "title": "FoFF", + "trustlines": 329, + "placeInTop": null + }, + { + "id": 17227, + "code": "TravisScott", + "issuer": "rMwkHmHCjS9qYHUb7zzADZ2yhmmuh4Nhor", + "trustlines": 261, + "placeInTop": null + }, + { + "id": 17243, + "code": "BSG", + "issuer": "rLcoMGVSZDWz3rWheEbPpeQMq2MNfmUoLb", + "title": "JustBull", + "trustlines": 101, + "placeInTop": null + }, + { + "id": 17254, + "code": "MARVEL", + "issuer": "rhcJCDrptNXDyRip9TsPEvydEwfeHHcG1y", + "trustlines": 180, + "placeInTop": null + }, + { + "id": 17257, + "code": "TEXRP", + "issuer": "rwsbKbvqZZs8QbMfkfk5QL6f2MtfgubYud", + "trustlines": 556, + "placeInTop": null + }, + { + "id": 17282, + "code": "META", + "issuer": "rNEhdhji1ByXNMMGp28oZHnwv5bHzUz9mv", + "title": "META", + "trustlines": 866, + "placeInTop": null + }, + { + "id": 17289, + "code": "BRAVE", + "issuer": "rLWDjNnrFvm45uSASGnB9tZoJCnZc2jpMt", + "trustlines": 140, + "placeInTop": null + }, + { + "id": 17322, + "code": "APE", + "issuer": "rJ9b5v7UoW3HbprCU7ZwXfmuuGFN7DJfbf", + "trustlines": 230, + "placeInTop": null + }, + { + "id": 17367, + "code": "HEAL", + "issuer": "rUnkAepqGa9r6mGzpR3p2C1CYYsPurb8wj", + "trustlines": 150, + "placeInTop": null + }, + { + "id": 17398, + "code": "CNY", + "issuer": "rMTqE8ExnkRDuAVrrD7QJubz6JUmkr1P47", + "trustlines": 161, + "placeInTop": null + }, + { + "id": 17447, + "code": "Tree", + "issuer": "rHAhxoWNM8vNt5VENuTtS2UAF6BHpDfM7", + "title": "Center for Collaborative Economics", + "trustlines": 166, + "placeInTop": null + }, + { + "id": 17574, + "code": "MissionRicher", + "issuer": "rKDTfkQ81nRrUCmQriFtfeNAcB4penRzD5", + "trustlines": 201, + "placeInTop": null + }, + { + "id": 17597, + "code": "JJJ", + "issuer": "rwumuHoumJp4YpVzVDMRStnGfLjb89gnvp", + "trustlines": 301, + "placeInTop": null + }, + { + "id": 17622, + "code": "COVID", + "issuer": "rGniT8LXLhgCdwRzyErbKXKoEKyhKfxeur", + "trustlines": 138, + "placeInTop": null + }, + { + "id": 17660, + "code": "XSaitama", + "issuer": "rPDhUfhMLV5ZKGwRBY7oWeQfv1tqzEAcdk", + "title": "XSaitama", + "trustlines": 185, + "placeInTop": null + }, + { + "id": 17663, + "code": "CarbonKiss", + "issuer": "rfuCSriA4SxjA1Mi1aEYsftCQXyjTtq96i", + "trustlines": 153, + "placeInTop": null + }, + { + "id": 17838, + "code": "Xphunk", + "issuer": "rspymsbKNiMdvuoQsvvc2rFDDxm9a27wbq", + "trustlines": 189, + "placeInTop": null + }, + { + "id": 17916, + "code": "XCT", + "issuer": "rwrhYVch6n8Ljvh4EsUvypByCHPQQNKmT8", + "trustlines": 658, + "placeInTop": null + }, + { + "id": 18011, + "code": "JesusCoin", + "issuer": "rNTJyUiXZWDGAUYZxDJtj3k4FLsjyJeSUs", + "trustlines": 159, + "placeInTop": null + }, + { + "id": 18154, + "code": "HornyHorsy", + "issuer": "rfE2kuFdc9W89ZLiXFed4AX29eesH3t1kQ", + "title": "Horny Horsy NFT", + "trustlines": 820, + "placeInTop": null + }, + { + "id": 18158, + "code": "SHOP", + "issuer": "rH4hB2ufbMQWkPbwoZ27mAGtKaPeGdtv71", + "title": "SHOPSPOT", + "trustlines": 258, + "placeInTop": null + }, + { + "id": 18176, + "code": "SourGrapes", + "issuer": "rfS3BN2KpaC6h1jt8RJvZ74M8r5NdZAhHp", + "title": "SourGrapes", + "trustlines": 493, + "placeInTop": null + }, + { + "id": 18228, + "code": "Veteran", + "issuer": "r3Q5nzkGA9WDz2gCZ3ZSEy2yYseSASru3m", + "title": "Universal Digital Assets", + "trustlines": 193, + "placeInTop": null + }, + { + "id": 18254, + "code": "XQUEENS", + "issuer": "rE8VLqoUNiHFzCYaHiWP7KbfmvszNbQ23o", + "trustlines": 278, + "placeInTop": null + }, + { + "id": 18382, + "code": "XRPellet", + "issuer": "rw7WTwbVaxzjPdFw2SkHY3nr97DHwaAD5g", + "title": "XRPellet Official", + "trustlines": 609, + "placeInTop": null + }, + { + "id": 18396, + "code": "MFX", + "issuer": "rLHfNhaVAHXjPFvB7HxtZzY6JuuUKn9dDR", + "trustlines": 103, + "placeInTop": null + }, + { + "id": 18472, + "code": "CGC", + "issuer": "ra2wxKYJ834PVipcwboRQdx8XQ5iFiDyDY", + "trustlines": 101, + "placeInTop": null + }, + { + "id": 18559, + "code": "XDR", + "issuer": "rHkuWrDPcCX6EmZZi6YN5m6go7iDpoEdyN", + "trustlines": 109, + "placeInTop": null + }, + { + "id": 18682, + "code": "Divitiae", + "issuer": "rn6Ehu3VADTf9dK7spjfztKS4QaJg8ef7n", + "trustlines": 125, + "placeInTop": null + }, + { + "id": 18703, + "code": "XrtTrucker", + "issuer": "rpo5fZyJ3L3i5bLdjZTdWkKnPxrAgB2STk", + "trustlines": 458, + "placeInTop": null + }, + { + "id": 18814, + "code": "Kakaw", + "issuer": "rLfDcXdbcySa1L3RnvZvaX2sL111z64DkB", + "trustlines": 112, + "placeInTop": null + }, + { + "id": 18938, + "code": "DedAss", + "issuer": "rHYvhab6JEFcoipghVYjUz5LwMCB7sZmVm", + "title": "DedAss", + "trustlines": 451, + "placeInTop": null + }, + { + "id": 18954, + "code": "FANs", + "issuer": "rGQmh24opk1H44xhH4mGZczbYc7cWuwq3B", + "trustlines": 154, + "placeInTop": null + }, + { + "id": 18955, + "code": "XLunx", + "issuer": "r9Jtt239HADKXGJf61f4WEVAR76c3xoVCa", + "trustlines": 533, + "placeInTop": null + }, + { + "id": 18998, + "code": "CygnetX", + "issuer": "rJYMx2ZHgjocAP3aiK1STbP3CTZASN5Sy8", + "trustlines": 2579, + "placeInTop": null + }, + { + "id": 19136, + "code": "XKZN", + "issuer": "r9adfTxcTeTp1BJQXqtvTvsoK2kr8xYQzR", + "title": "XKaizen", + "trustlines": 18238, + "placeInTop": null + }, + { + "id": 18, + "code": "SDOGI", + "issuer": "rLdNVRKiVM4kvMbLzkZr97BtXcYeeoA19R", + "trustlines": 9090, + "placeInTop": null + }, + { + "id": 35, + "code": "ROOTS", + "issuer": "r3BvncNn56fptaXNXi3xxYZY6owrv4snpx", + "trustlines": 2013, + "placeInTop": null + }, + { + "id": 49, + "code": "SPCLUB", + "issuer": "r4Hoe49khvxSmoWZjQM2jBE8BENHXYpW7k", + "trustlines": 2101, + "placeInTop": null + }, + { + "id": 55, + "code": "COWPUNKS", + "issuer": "rJQs456Y5bhukH14zfFjfzCUoTHtgtamPp", + "trustlines": 1918, + "placeInTop": null + }, + { + "id": 62, + "code": "XSamurai", + "issuer": "raxgWzLpqyiCZbAqYDidy9uB92HU8xHm9M", + "title": "BullishMomentsNFTs", + "trustlines": 2259, + "placeInTop": null + }, + { + "id": 65, + "code": "ANIMA", + "issuer": "r3zbjkCSB4vbBmzFhkWudKhJG7J9fc2Q83", + "title": "Anima", + "trustlines": 4850, + "placeInTop": null + }, + { + "id": 68, + "code": "CLOUD3", + "issuer": "rD7ouXraLQj74kxrHy1aXfjHyMprYf4TCs", + "title": "CLOUD3", + "trustlines": 1813, + "placeInTop": null + }, + { + "id": 73, + "code": "crimeGAME", + "issuer": "rPHS3C24Zmdqn7cf2iKm9XYE4JuJwzmqKr", + "title": "Rocketfella DeMoon", + "trustlines": 1925, + "placeInTop": null + }, + { + "id": 76, + "code": "EXT", + "issuer": "rDL5AvJAQMP9ZvzUjfySoFzNoRM5ctWafD", + "title": "LegacyNFT", + "trustlines": 1517, + "placeInTop": null + }, + { + "id": 128, + "code": "STEM", + "issuer": "rMqnqCtwLfsgmHXi4wUmJd2ad1SK7S6KhE", + "title": "STEM", + "trustlines": 3865, + "placeInTop": null + }, + { + "id": 159, + "code": "XROBOCROT", + "issuer": "rhagSQvDqPsD1nTN4CjKX1KfnzK3h8wEEa", + "title": "xrobocrot", + "trustlines": 1088, + "placeInTop": null + }, + { + "id": 164, + "code": "589XRP", + "issuer": "rhzvvrBSJh8BMpMGzy4us3VkUKGjzD34cb", + "trustlines": 439, + "placeInTop": null + }, + { + "id": 188, + "code": "XShaolin", + "issuer": "raxgWzLpqyiCZbAqYDidy9uB92HU8xHm9M", + "title": "BullishMomentsNFTs", + "trustlines": 458, + "placeInTop": null + }, + { + "id": 189, + "code": "XWomen", + "issuer": "rEK6feXWGNwD34xjXfetggyrdxyr78YaYY", + "trustlines": 489, + "placeInTop": null + }, + { + "id": 190, + "code": "Xpotion", + "issuer": "rJDWouEkBDUncQAYjfNb34Q6fSmy6QVjAm", + "trustlines": 948, + "placeInTop": null + }, + { + "id": 200, + "code": "XMaori", + "issuer": "raxgWzLpqyiCZbAqYDidy9uB92HU8xHm9M", + "title": "BullishMomentsNFTs", + "trustlines": 934, + "placeInTop": null + }, + { + "id": 202, + "code": "XFighter", + "issuer": "raxgWzLpqyiCZbAqYDidy9uB92HU8xHm9M", + "title": "BullishMomentsNFTs", + "trustlines": 482, + "placeInTop": null + }, + { + "id": 205, + "code": "XBRAIN", + "issuer": "rEYz7xucLcUQYLdFzakHodSbuFwTV95f3T", + "title": "OFFICIAL XBRAINS XRPL ", + "trustlines": 3093, + "placeInTop": null + }, + { + "id": 207, + "code": "JennaX", + "issuer": "rHYTL5dyJFGFNCXoLBo4Yhuw2o4HAhRUiP", + "title": "#xPUNK Queen", + "trustlines": 3264, + "placeInTop": null + }, + { + "id": 226, + "code": "Redu", + "issuer": "r4hgK937DndYtxAKTT3pFQDebssMxyR3rJ", + "title": "ReducedLunch", + "trustlines": 1181, + "placeInTop": null + }, + { + "id": 262, + "code": "MSNGR3", + "issuer": "rHAhxoWNM8vNt5VENuTtS2UAF6BHpDfM7", + "title": "Center for Collaborative Economics", + "trustlines": 1122, + "placeInTop": null + }, + { + "id": 268, + "code": "Jaguar", + "issuer": "rURPhsxs1E38R2nmZkbfQtATgm955Apbwh", + "trustlines": 509, + "placeInTop": null + }, + { + "id": 272, + "code": "XFI", + "issuer": "rf38TgVezFfowhzAbmyT6etXUyg8UpjA4g", + "title": "xFinance", + "trustlines": 849, + "placeInTop": null + }, + { + "id": 274, + "code": "XRPBee", + "issuer": "rfvAg9pyBt2DzqRMLtTAfJtGEmWV4bWSAH", + "trustlines": 520, + "placeInTop": null + }, + { + "id": 280, + "code": "BSM", + "issuer": "rM8kLDwknWLBqGfiEHfqkR7QPVJMQn2Fn3", + "trustlines": 765, + "placeInTop": null + }, + { + "id": 303, + "code": "MIRI", + "issuer": "rnnGyUPdxurQL8zrNNaimZ1xA8y1yyNTCT", + "trustlines": 599, + "placeInTop": null + }, + { + "id": 304, + "code": "GuildCoin", + "issuer": "rG5CupSkB3RS8FPqZm2f38DwgGVyweMfYb", + "title": "GuildTower", + "trustlines": 1238, + "placeInTop": null + }, + { + "id": 305, + "code": "XSIC", + "issuer": "rncTRYJNaaYsCVdLYvReQsMgkigjpNuHA6", + "trustlines": 897, + "placeInTop": null + }, + { + "id": 307, + "code": "Opossum", + "issuer": "rsGJhb6aGZ86AAwJj3z45XcYs1MsRL5cDH", + "trustlines": 549, + "placeInTop": null + }, + { + "id": 308, + "code": "CryptoLand", + "issuer": "rBdz9ovrGstn3oToQawBSgVdo1TuuiNqEz", + "trustlines": 3286, + "placeInTop": null + }, + { + "id": 311, + "code": "CREATE", + "issuer": "rP6Df42wRkct2BJBNTgTSVEpjzVqCrTXBM", + "trustlines": 1735, + "placeInTop": null + }, + { + "id": 312, + "code": "ART", + "issuer": "rENp871DKiJjJrswC5Mz4fRXdEnqHj4Qny", + "trustlines": 1823, + "placeInTop": null + }, + { + "id": 316, + "code": "EDCOLLECT", + "issuer": "rJdeSa9Z8hp4YPBSu4EPRszZKAyY3MWFUS", + "trustlines": 1447, + "placeInTop": null + }, + { + "id": 322, + "code": "XBK", + "issuer": "rw2C1dawoWgJhDf3TxrT4GpLrq1NXmzfRb", + "trustlines": 593, + "placeInTop": null + }, + { + "id": 323, + "code": "RHAD", + "issuer": "rhkQRnmE9L6JgSjGGApJ6mntZCuVsfshHT", + "title": "D.P.MonksFinance LTD", + "trustlines": 3302, + "placeInTop": null + }, + { + "id": 327, + "code": "COLLECT", + "issuer": "rwNM54zKJDC8sTfcFYAmNLAoPVGEJkE2iA", + "trustlines": 1300, + "placeInTop": null + }, + { + "id": 331, + "code": "SMART", + "issuer": "r3JhTE5sSShKg84Z5ZW6WEGtEQSuXuMXqv", + "trustlines": 1723, + "placeInTop": null + }, + { + "id": 352, + "code": "FCXVoteYES", + "issuer": "rfF5kbub1f87oSdc9Bi26z9chZJhJMpEXJ", + "trustlines": 197, + "placeInTop": null + }, + { + "id": 362, + "code": "2Cents", + "issuer": "rErA5dLpi3dgHwCD8DJGeeCNs896cnsJQz", + "title": "2CentsCoin", + "trustlines": 424, + "placeInTop": null + }, + { + "id": 16626, + "code": "XUSD", + "issuer": "rfxkJ3eN9oT21thV8MxqFRG71x5xqvzywR", + "trustlines": 1374, + "placeInTop": null + }, + { + "id": 16629, + "code": "XMONSTER", + "issuer": "rPvTZzYC8uJiDsjahWqAuGKBY3TrQKCTYH", + "trustlines": 632, + "placeInTop": null + }, + { + "id": 16634, + "code": "FLR", + "issuer": "rcxJwVnftZzXqyH9YheB8TgeiZUhNo1Eu", + "title": "GateHub FLR", + "trustlines": 22643, + "placeInTop": null + }, + { + "id": 16647, + "code": "XPcoin", + "issuer": "rfpzfcK67GNnptw9Z8P7cjx5B7zhu1zv1e", + "title": "xpmarket", + "trustlines": 12447, + "placeInTop": null + }, + { + "id": 16689, + "code": "RST", + "issuer": "rBLbjkrenS6x369ifpxxj3bMvgWNp7kUCv", + "trustlines": 232, + "placeInTop": null + }, + { + "id": 16690, + "code": "QST", + "issuer": "rKTCrDyfH5dwbndZcvBRqG5UVQgD6mbTHT", + "trustlines": 374, + "placeInTop": null + }, + { + "id": 16691, + "code": "XURB", + "issuer": "rUszxyeKQhqr3KMYYgmpY2vinJB6hZbpTz", + "trustlines": 571, + "placeInTop": null + }, + { + "id": 16694, + "code": "LiFi", + "issuer": "rLWnYnn1hcr8mX113oMD87iv4ugJ6unnJB", + "trustlines": 428, + "placeInTop": null + }, + { + "id": 16696, + "code": "RKIDZ", + "issuer": "rMqyDimznTdwE1DY1V7TrwXvR7iUQZGLF5", + "title": "RKIDZ_official (Former xGBTC)", + "trustlines": 1446, + "placeInTop": null + }, + { + "id": 16697, + "code": "BACT", + "issuer": "rGrUmXd7R8BUqgNFvGqF3JXudh8YuXBFVR", + "trustlines": 534, + "placeInTop": null + }, + { + "id": 16701, + "code": "MHR", + "issuer": "rw1a6Ey51sasTgug64yN9owSjBV8JuZQeH", + "trustlines": 624, + "placeInTop": null + }, + { + "id": 16702, + "code": "XSumo", + "issuer": "raxgWzLpqyiCZbAqYDidy9uB92HU8xHm9M", + "title": "BullishMomentsNFTs", + "trustlines": 922, + "placeInTop": null + }, + { + "id": 16719, + "code": "LWTNFT", + "issuer": "rNyS4GdVJXyZV1sWufsKSAaLZwfnuJSBnf", + "trustlines": 688, + "placeInTop": null + }, + { + "id": 16738, + "code": "XLU", + "issuer": "rGa5AVLKXmn2o1XpEzswXrcMvYzjqEPHKt", + "trustlines": 635, + "placeInTop": null + }, + { + "id": 16758, + "code": "iPictorial", + "issuer": "r3CqqV1oChF4S6fsRhqZe93Ycn1ujgsJpZ", + "trustlines": 303, + "placeInTop": null + }, + { + "id": 16759, + "code": "MPT", + "issuer": "rUaNggdMiSwbytLUMecVxXKutsT3VDRV1g", + "title": "MONEY PRINTER TOKEN", + "trustlines": 1750, + "placeInTop": null + }, + { + "id": 16767, + "code": "XrpMoonParty", + "issuer": "rBx5m4QEFbGeBCRpNcT2P8YdXZNeTJsa4A", + "title": "XRP MoonParty", + "trustlines": 1507, + "placeInTop": null + }, + { + "id": 16772, + "code": "MHX", + "issuer": "rMGgjBz8BLvqNdrWtQKtsCe9ssdWn8KA5A", + "title": "Mhxproject", + "trustlines": 2930, + "placeInTop": null + }, + { + "id": 16777, + "code": "Xgen", + "issuer": "rKw6No7RYJJPRYTqnfcexG2B2Ng4jiivCJ", + "title": "GrowYourOwnNFT", + "trustlines": 979, + "placeInTop": null + }, + { + "id": 16782, + "code": "XRInjuredPunks", + "issuer": "rB9894majqFpciVNcN7HFZ5uTpukDfjfcp", + "title": "XR_InjuredPunks", + "trustlines": 1181, + "placeInTop": null + }, + { + "id": 16806, + "code": "WYM", + "issuer": "rGh6VWMaLEkY8zTTNgT1gXbBY7CEEwhQLH", + "title": "Sonar Muse", + "trustlines": 8076, + "placeInTop": null + }, + { + "id": 16854, + "code": "AGUILA", + "issuer": "rEL1KXDjqmPSXX9DFHcegsF9M3du9vFskK", + "trustlines": 1325, + "placeInTop": null + }, + { + "id": 16855, + "code": "CRYPTO", + "issuer": "rRbiKwcueo6MchUpMFDce9XpDwHhRLPFo", + "trustlines": 1448, + "placeInTop": null + }, + { + "id": 16862, + "code": "XCOLLAB", + "issuer": "ranTVywEvZEJnMqUxifTydbXZDNWx2zM7i", + "trustlines": 1593, + "placeInTop": null + }, + { + "id": 16863, + "code": "XLEARN", + "issuer": "rMofCVmt7Raz4RAPn9uE5jxxN4mFnFHG5g", + "trustlines": 681, + "placeInTop": null + }, + { + "id": 16866, + "code": "XPORN", + "issuer": "raee1iBjzvC43ntPwLkR2G7cLZn1yont3d", + "trustlines": 624, + "placeInTop": null + }, + { + "id": 16870, + "code": "XNAUT", + "issuer": "rnWgBhdddyEw8VURoXLPXWhvuqZzCLdcES", + "trustlines": 1009, + "placeInTop": null + }, + { + "id": 16871, + "code": "XChess", + "issuer": "rhrme3Dt99E8Zpzf6xqy9mV8ntfrioo1Cw", + "trustlines": 1301, + "placeInTop": null + }, + { + "id": 16874, + "code": "GVBK", + "issuer": "r3LATMBogx3Y1r4FcWC1p7MEKPMUzQmpSj", + "trustlines": 2062, + "placeInTop": null + }, + { + "id": 16883, + "code": "XFR", + "issuer": "ra7tqyvUjEeqeZ1DASguxPaz2A5trVyh9g", + "trustlines": 2144, + "placeInTop": null + }, + { + "id": 16895, + "code": "XWhale", + "issuer": "rLNkSRESSC7eihdWYysGLPY77WkmWnwk3s", + "title": "XWhales Official", + "trustlines": 1602, + "placeInTop": null + }, + { + "id": 16896, + "code": "XMC", + "issuer": "rKXXAvKSDV5SgGCc41XfdKcFTFgpsY5jKz", + "trustlines": 790, + "placeInTop": null + }, + { + "id": 16897, + "code": "PNP", + "issuer": "rhZrzqDBvRBTbUYwz1bQcGgZE3PNoMh938", + "trustlines": 1354, + "placeInTop": null + }, + { + "id": 16901, + "code": "XRV", + "issuer": "rBQKSqCmW63fcyvxSthPqvQrgiw3zn9keP", + "title": "HRM AVATAR-TIC CLUB ", + "trustlines": 829, + "placeInTop": null + }, + { + "id": 16914, + "code": "NIXY", + "issuer": "rp3pUDg84qArgBU9mwGUph6NoQeKzeBtbo", + "trustlines": 1578, + "placeInTop": null + }, + { + "id": 16915, + "code": "BCHX", + "issuer": "r4L9Q861brng8jTDhk73VCDUgPfKxPcXwT", + "title": "BCHX", + "trustlines": 1246, + "placeInTop": null + }, + { + "id": 16919, + "code": "BeerToken", + "issuer": "r9RuvgPQPDndJDtLBrf6pAxKSmYxkbsgjd", + "trustlines": 347, + "placeInTop": null + }, + { + "id": 16932, + "code": "ALTv", + "issuer": "rUTG9Xb4Zo38rXNxRffKBjZ3Tw7nu3XJKs", + "trustlines": 1148, + "placeInTop": null + }, + { + "id": 16934, + "code": "RSA", + "issuer": "rMwmwEFNe9Ao7DEcu4DuRWcFQiXVaCTnYh", + "trustlines": 1531, + "placeInTop": null + }, + { + "id": 16936, + "code": "POM", + "issuer": "r9q2WGafALdAGXS2ercbjsB8Fs93z5khWB", + "trustlines": 1926, + "placeInTop": null + }, + { + "id": 16937, + "code": "DONG", + "issuer": "rfL4Sci2ag5hhkpDuqtWYov6j3mshVWLgU", + "title": "Limited Currency", + "trustlines": 1805, + "placeInTop": null + }, + { + "id": 16938, + "code": "XSPACE", + "issuer": "rDGCeU9CjeM2KGdjqtrUPYeku7ByvHycUP", + "title": "XspaceNft", + "trustlines": 3204, + "placeInTop": null + }, + { + "id": 16944, + "code": "EliteXash", + "issuer": "rBxgRFM6SAeZN2FuvvQEpsPdFewoj2AesG", + "title": "EliteXash", + "trustlines": 1279, + "placeInTop": null + }, + { + "id": 16946, + "code": "SPC", + "issuer": "rfxkJ3eN9oT21thV8MxqFRG71x5xqvzywR", + "trustlines": 1501, + "placeInTop": null + }, + { + "id": 16947, + "code": "SSS", + "issuer": "rUDk3xTdHoVruu41mr9Jh5NyRg4svHpbnT", + "trustlines": 1759, + "placeInTop": null + }, + { + "id": 16951, + "code": "XBHT", + "issuer": "rQGwFj7F1C5G2xLpM5D9cRPf79dR4hutpc", + "title": "XBE | XBee Mekaverse NFT", + "trustlines": 2845, + "placeInTop": null + }, + { + "id": 16967, + "code": "Reciprocity", + "issuer": "rw5f98uNdDgu9uR6zhCRrku84b34aiJ3tj", + "trustlines": 202, + "placeInTop": null + }, + { + "id": 16969, + "code": "VacationCoin", + "issuer": "rDNf5DaPfVQfRNJhCYozgNtTUYSBwjqJMo", + "title": "VacationCoin", + "trustlines": 1157, + "placeInTop": null + }, + { + "id": 16972, + "code": "CONSCIENCE", + "issuer": "r9xA4QFhBrK96f7bgpEaT7hACY1Eynt9RL", + "trustlines": 387, + "placeInTop": null + }, + { + "id": 16973, + "code": "UnitOne", + "issuer": "r4dfajeq79mzxusPtvQXvEgt8uPJL7NKuD", + "trustlines": 458, + "placeInTop": null + }, + { + "id": 16974, + "code": "ARC", + "issuer": "rHXyFWcZDMcic25fQvAomQhZiu71sHroom", + "trustlines": 951, + "placeInTop": null + }, + { + "id": 16980, + "code": "LANDX", + "issuer": "rwsf7M8HuPLvozQhKHn4wSwLMtezVCS6sF", + "trustlines": 1063, + "placeInTop": null + }, + { + "id": 16990, + "code": "TOX", + "issuer": "r3EqmKHKr2VrdCsQcMxbPcCzMXjzdCd67P", + "trustlines": 2361, + "placeInTop": null + }, + { + "id": 16996, + "code": "Checkone", + "issuer": "rHZbWwtDYtvv47o2J1m9er8iM3FBRQfm6n", + "trustlines": 1846, + "placeInTop": null + }, + { + "id": 16997, + "code": "PokemonTCG", + "issuer": "rQUqp1zizdKLLMQZLBZtDDXvGDZqvCUMiy", + "title": "Gotta Catch\u2019em All! Nft Master!", + "trustlines": 1963, + "placeInTop": null + }, + { + "id": 17010, + "code": "BikiniBotton", + "issuer": "rEEzEsszXMM69ZmXpEboich1b5BJJAymoz", + "title": "BikiniBotton", + "trustlines": 560, + "placeInTop": null + }, + { + "id": 17014, + "code": "INFTM", + "issuer": "rGzaBTzsYjp7xahHQoqbE1tvKeRoNzeY6n", + "trustlines": 575, + "placeInTop": null + }, + { + "id": 17025, + "code": "XCE", + "issuer": "r9UEc2Xf2Y2TWrvBM5m7H9d7AyfZLc8wtB", + "trustlines": 336, + "placeInTop": null + }, + { + "id": 17027, + "code": "XDev", + "issuer": "rn45ynjsuP5NMefYQBYMHqbF5CSjxG2y9n", + "trustlines": 338, + "placeInTop": null + }, + { + "id": 17030, + "code": "GOLDEGG", + "issuer": "rBmUo4jaGtXm63QRCKA2RvSkXavrqsjuiH", + "trustlines": 1177, + "placeInTop": null + }, + { + "id": 17031, + "code": "GCX", + "issuer": "rNuW7ohW2iztvASjftFnBqEbdkz1xge2Bk", + "title": "GCX", + "trustlines": 414, + "placeInTop": null + }, + { + "id": 17033, + "code": "SHIA", + "issuer": "rKyiVTgYvcyorY1VzSLVmQWNZbFQGVwn1D", + "trustlines": 1092, + "placeInTop": null + }, + { + "id": 17036, + "code": "OREX", + "issuer": "rLcB1vwd9ANrkXcznNfrHF3ohJi2dB4Dka", + "trustlines": 1242, + "placeInTop": null + }, + { + "id": 17038, + "code": "KAA", + "issuer": "rECnFHk4WLGS9AmFUGYnkmmn5SNrBFp6o4", + "trustlines": 792, + "placeInTop": null + }, + { + "id": 17042, + "code": "UNC", + "issuer": "rwo1fD9XWRt6sswqTomC96sENkEG7wn1jS", + "trustlines": 1177, + "placeInTop": null + }, + { + "id": 17059, + "code": "Aether", + "issuer": "rHAhxoWNM8vNt5VENuTtS2UAF6BHpDfM7", + "title": "Center for Collaborative Economics", + "trustlines": 833, + "placeInTop": null + }, + { + "id": 17068, + "code": "PROJECT", + "issuer": "rJkMidTCfjFgp345EmwS4NWuZjjvAoWvyx", + "trustlines": 1476, + "placeInTop": null + }, + { + "id": 17075, + "code": "XRBIGPP", + "issuer": "rMKbvDuJK9gviR1z4LQMqF6VjEvhJMNvci", + "trustlines": 1828, + "placeInTop": null + }, + { + "id": 17079, + "code": "SOLOC", + "issuer": "r3JjKDmVpeWBMNu9kUMrPR2Vr26bAYDr14", + "trustlines": 1778, + "placeInTop": null + }, + { + "id": 17083, + "code": "XRPLDOGE", + "issuer": "rwCnxiJaGhsQTrCPNcZ7VoLEc6ZYfC1wVC", + "title": "XRPLDOGE", + "trustlines": 2245, + "placeInTop": null + }, + { + "id": 17105, + "code": "COBB", + "issuer": "rfKibygzNr84tfFTV8A4fU11kSyA53Pt7s", + "trustlines": 604, + "placeInTop": null + }, + { + "id": 17113, + "code": "TMC", + "issuer": "rJ212MuKJ2uvh1vTqqEkJK1V11FPob7Zrk", + "trustlines": 270, + "placeInTop": null + }, + { + "id": 17116, + "code": "MM2", + "issuer": "rrap7mxsSDGCAzKmtR6ttQmJEMUsMBrFC5", + "trustlines": 217, + "placeInTop": null + }, + { + "id": 17123, + "code": "KMBT", + "issuer": "ratNgPXEX4kASLZ3cwpU7uSMLDYFBJFCU9", + "trustlines": 184, + "placeInTop": null + }, + { + "id": 17125, + "code": "AuCoin", + "issuer": "rDAePhvSKX4gKSrjEAw8kA8xmPNhyx9Fiu", + "trustlines": 439, + "placeInTop": null + }, + { + "id": 17126, + "code": "CHAD", + "issuer": "r9nPB99QH8nNEjTtN5xfqnbDUpiduBZjdG", + "trustlines": 1026, + "placeInTop": null + }, + { + "id": 17127, + "code": "SDOGE", + "issuer": "rhCLRdLc2MCxvtoumcKZFNfxMEhQLfxdTq", + "trustlines": 1731, + "placeInTop": null + }, + { + "id": 17130, + "code": "XVikings", + "issuer": "raxgWzLpqyiCZbAqYDidy9uB92HU8xHm9M", + "title": "BullishMomentsNFTs", + "trustlines": 287, + "placeInTop": null + }, + { + "id": 17133, + "code": "xMAGA", + "issuer": "rQfCaTEkahVPNfKYbu2Bw3KT6fTFepf2kw", + "trustlines": 1275, + "placeInTop": null + }, + { + "id": 17138, + "code": "CX3", + "issuer": "rDxdjF5bECFfM4TgnnNue9o5ovNDNR2hec", + "trustlines": 1872, + "placeInTop": null + }, + { + "id": 17143, + "code": "EXQ", + "issuer": "raL4mET7PsaEj7bYujSNH1i1CzpuJ9Fw4W", + "trustlines": 471, + "placeInTop": null + }, + { + "id": 17144, + "code": "Xbud", + "issuer": "rKw6No7RYJJPRYTqnfcexG2B2Ng4jiivCJ", + "title": "GrowYourOwnNFT", + "trustlines": 494, + "placeInTop": null + }, + { + "id": 17147, + "code": "BRLZ", + "issuer": "rEghiMbiboaWGJi7gaYPM6tFGt4xKGQhgm", + "trustlines": 483, + "placeInTop": null + }, + { + "id": 17161, + "code": "XAP", + "issuer": "rwBiSxAQx7KC1WV6Zua1YVvcfJQdLPkG33", + "trustlines": 683, + "placeInTop": null + }, + { + "id": 17163, + "code": "LGT", + "issuer": "rBnXtGYjH28RHkcUkuv2R21hHuKWHmnieC", + "trustlines": 799, + "placeInTop": null + }, + { + "id": 17164, + "code": "Xgrow", + "issuer": "rKw6No7RYJJPRYTqnfcexG2B2Ng4jiivCJ", + "title": "GrowYourOwnNFT", + "trustlines": 303, + "placeInTop": null + }, + { + "id": 17167, + "code": "KYC", + "issuer": "rKTCrDyfH5dwbndZcvBRqG5UVQgD6mbTHT", + "trustlines": 249, + "placeInTop": null + }, + { + "id": 17173, + "code": "AXS", + "issuer": "rKU59Uaj3HVzSFAbSMXcdvrRsDR5CMC9Ez", + "trustlines": 105, + "placeInTop": null + }, + { + "id": 17176, + "code": "MOSS", + "issuer": "rNvoCBsP2HcFhBh2r1JncHEs3YtYGdiZoy", + "trustlines": 180, + "placeInTop": null + }, + { + "id": 17198, + "code": "XRI", + "issuer": "rPaAiDAo5uWeqhaAWZw6Sgbv1RBZzkeKmw", + "trustlines": 832, + "placeInTop": null + }, + { + "id": 17203, + "code": "SCAMx", + "issuer": "rU1KpfQhCprYH6rGBVVPie4faUb9E2vZQB", + "title": "SCAMx", + "trustlines": 395, + "placeInTop": null + }, + { + "id": 17204, + "code": "VRP", + "issuer": "rJk9sq1a3jhBr7mqx9SXmkLgpNNXwV7SDj", + "trustlines": 338, + "placeInTop": null + }, + { + "id": 17206, + "code": "XCOLLECT", + "issuer": "r314X1hJQRPWHMx7exD2xonYR8owWRYYEm", + "trustlines": 645, + "placeInTop": null + }, + { + "id": 17220, + "code": "CRYPTOPIUM", + "issuer": "rMNcmHJ2FnSze3wMZAuacESE4G2Y5G78dV", + "trustlines": 224, + "placeInTop": null + }, + { + "id": 17224, + "code": "VIAL", + "issuer": "r9VgcESgJeT7E5c7ENkhEQ2t3Y97a7i7Kh", + "trustlines": 517, + "placeInTop": null + }, + { + "id": 17233, + "code": "LAND", + "issuer": "rHAhxoWNM8vNt5VENuTtS2UAF6BHpDfM7", + "title": "Center for Collaborative Economics", + "trustlines": 262, + "placeInTop": null + }, + { + "id": 17241, + "code": "XRPP", + "issuer": "rnTAZgihNyG12FeNwXBtHQz7sfULraxRPP", + "trustlines": 432, + "placeInTop": null + }, + { + "id": 17244, + "code": "XCumRocket", + "issuer": "r4PG82JC4ApzSB9cmu1bbfVnJrkUY8j9yB", + "title": "XCumRocket", + "trustlines": 334, + "placeInTop": null + }, + { + "id": 17253, + "code": "GOLD", + "issuer": "raqT4Q5sz4t66bbooaCRLMPoiKyLRDNX8Y", + "trustlines": 168, + "placeInTop": null + }, + { + "id": 17258, + "code": "EXPC", + "issuer": "rLb593mo6Nerw7eWvC19gR1fLggeWX6u6s", + "title": "Tuition DeFi Network", + "trustlines": 228, + "placeInTop": null + }, + { + "id": 17260, + "code": "UnvaxxedSperm", + "issuer": "rLZn9qH5ivDCpMrSiW6EPutxwDZinXuYK7", + "trustlines": 150, + "placeInTop": null + }, + { + "id": 17262, + "code": "FRLZ", + "issuer": "rLT9fAFcChvKVkZpiV3H2NYabfnZzsLkii", + "trustlines": 335, + "placeInTop": null + }, + { + "id": 17275, + "code": "BINANCE", + "issuer": "rU2VgeFkmYKxiUdizrc6KC9ru72892UgFc", + "trustlines": 402, + "placeInTop": null + }, + { + "id": 17303, + "code": "EPWC", + "issuer": "rLb593mo6Nerw7eWvC19gR1fLggeWX6u6s", + "title": "Tuition DeFi Network", + "trustlines": 199, + "placeInTop": null + }, + { + "id": 17310, + "code": "DEF", + "issuer": "rBXoMDvgeWB12djrk2ye87qLZaxvtRzRP", + "trustlines": 164, + "placeInTop": null + }, + { + "id": 17313, + "code": "NKY", + "issuer": "rhZrzqDBvRBTbUYwz1bQcGgZE3PNoMh938", + "trustlines": 177, + "placeInTop": null + }, + { + "id": 17320, + "code": "Check", + "issuer": "rHZbWwtDYtvv47o2J1m9er8iM3FBRQfm6n", + "trustlines": 169, + "placeInTop": null + }, + { + "id": 17323, + "code": "Fegx", + "issuer": "rsiNNJppE3WjQVHALGZtVP9efHLVTUmdnS", + "trustlines": 203, + "placeInTop": null + }, + { + "id": 17324, + "code": "LimitedCoin", + "issuer": "rQpFGJfvyZBJFG9tq2rf6x7vuVgLdEURzY", + "trustlines": 182, + "placeInTop": null + }, + { + "id": 17325, + "code": "AzteX", + "issuer": "raxgWzLpqyiCZbAqYDidy9uB92HU8xHm9M", + "title": "BullishMomentsNFTs", + "trustlines": 179, + "placeInTop": null + }, + { + "id": 17326, + "code": "BALLS", + "issuer": "rKHpPgZwQ8EwQzsdrA6b4kavQNvWgkawj1", + "trustlines": 140, + "placeInTop": null + }, + { + "id": 17335, + "code": "XRPARMY", + "issuer": "rwTUoSbmTFoaJhQops4RzQ9vBDq5DKggc6", + "trustlines": 689, + "placeInTop": null + }, + { + "id": 17343, + "code": "618X", + "issuer": "r9ubsFeHjxfDuSMCKyPP6RBajctTTKU7zR", + "trustlines": 272, + "placeInTop": null + }, + { + "id": 17347, + "code": "Mikey", + "issuer": "rHZbWwtDYtvv47o2J1m9er8iM3FBRQfm6n", + "trustlines": 232, + "placeInTop": null + }, + { + "id": 17348, + "code": "SNX", + "issuer": "rNaKJ8p83WqNqGqpxP5E2yvPxqa122Jtfw", + "title": "Sk8Nation", + "trustlines": 178, + "placeInTop": null + }, + { + "id": 17366, + "code": "CRYPTOPIANS", + "issuer": "rR7zH1VECwtBWapGwrKWWwYTrjohyXRid", + "trustlines": 727, + "placeInTop": null + }, + { + "id": 17386, + "code": "HNT", + "issuer": "raP6XZypcFR1WkDzaKjv91FtYRCURvePWW", + "title": "InGameCredit", + "trustlines": 165, + "placeInTop": null + }, + { + "id": 17397, + "code": "USD", + "issuer": "rMTqE8ExnkRDuAVrrD7QJubz6JUmkr1P47", + "trustlines": 180, + "placeInTop": null + }, + { + "id": 17399, + "code": "KUR", + "issuer": "rG5tu1u5CZMMho6gXPivwe37qxd6H8r5gm", + "trustlines": 195, + "placeInTop": null + }, + { + "id": 17420, + "code": "GENXRPVNKS", + "issuer": "rawUvd4DMGrqnB1vfrMF9RFQ79m3b8tCDz", + "trustlines": 163, + "placeInTop": null + }, + { + "id": 17424, + "code": "VIIIIXDIAMONDHANDS", + "issuer": "r3LMqrZrBmzruZwz6CVJWkKZVo1u3nF9xK", + "trustlines": 339, + "placeInTop": null + }, + { + "id": 17437, + "code": "PDT", + "issuer": "rhd96ShDBo73iNTKb4Q57T9RUwryvM7oFB", + "title": "Physical Digital", + "trustlines": 1998, + "placeInTop": null + }, + { + "id": 17438, + "code": "ALI", + "issuer": "rppZmZ1iyXZicAqesDjsxP8rSg29H78auh", + "trustlines": 156, + "placeInTop": null + }, + { + "id": 17440, + "code": "Airborne", + "issuer": "rfYxu5QSjvhV3ZF1xokR9ka6dhoBhCCuwf", + "trustlines": 143, + "placeInTop": null + }, + { + "id": 17450, + "code": "GNT", + "issuer": "rfPUTMdYiWTMzfNaHwJNtTyZLAM4S65emU", + "trustlines": 153, + "placeInTop": null + }, + { + "id": 17469, + "code": "ABC", + "issuer": "r3dudPWiCWiT7rnwocRMSS1LWftzvzLFgN", + "trustlines": 131, + "placeInTop": null + }, + { + "id": 17589, + "code": "HKD", + "issuer": "rPT74sUcTBTQhkHVD54WGncoqXEAMYbmH7", + "title": "RippleQK", + "trustlines": 2612, + "placeInTop": null + }, + { + "id": 17630, + "code": "Loviocoin", + "issuer": "rDQgnkSVndqgWHW2ETERfcWKpLJyxCua9X", + "trustlines": 164, + "placeInTop": null + }, + { + "id": 17912, + "code": "AmericaFirstCoin", + "issuer": "rL2R4f9wJY1aaKZncrW7wiDdqncze9N22w", + "trustlines": 758, + "placeInTop": null + }, + { + "id": 17915, + "code": "Xvex", + "issuer": "r3f7jVpnLa7YrQw5yPfMnjjAcUtAbeL4xU", + "trustlines": 876, + "placeInTop": null + }, + { + "id": 17917, + "code": "NPK", + "issuer": "rV64miFA53xbWS3x9A6nRnzxMqLHN1t2h", + "title": "NATIONAL PRESTO INDUSTRIES INCs", + "trustlines": 1508, + "placeInTop": null + }, + { + "id": 17958, + "code": "BET", + "issuer": "rUeKohnBUioHMPV3Xptem3Sfi6XFjXWCHx", + "trustlines": 291, + "placeInTop": null + }, + { + "id": 17965, + "code": "GLF", + "issuer": "rLcj6P5tWAxQo7hqKtvqH7BCh6BpSu4fGj", + "trustlines": 423, + "placeInTop": null + }, + { + "id": 17971, + "code": "MonedaX", + "issuer": "rsLx8UkM9rC5aPF9xYY53TJrUbXHBLBeoq", + "trustlines": 952, + "placeInTop": null + }, + { + "id": 17979, + "code": "Nolacoinz", + "issuer": "raoYTMEoNJuL4MNDAN8Gf9C1W6wXQgvvNq", + "trustlines": 736, + "placeInTop": null + }, + { + "id": 17998, + "code": "XFLEX", + "issuer": "rwMYX4MC2PRffAxa7opuGCS899kZNYcwQV", + "trustlines": 319, + "placeInTop": null + }, + { + "id": 18003, + "code": "xWolfpack", + "issuer": "rpizJeQexuFTEbKP6fHSAVyHLBS9gNQ7H2", + "title": "xWolves Pack", + "trustlines": 648, + "placeInTop": null + }, + { + "id": 18006, + "code": "TBone", + "issuer": "rHMfdnZ65NsYpih5EmLSUTkD2tKVjE8Kne", + "trustlines": 240, + "placeInTop": null + }, + { + "id": 18062, + "code": "XrHero", + "issuer": "rwy4a3sbk9myeE1yKjghLKWceP33TRkVEg", + "title": "XrHero", + "trustlines": 1757, + "placeInTop": null + }, + { + "id": 18064, + "code": "KAWAII", + "issuer": "rKAWAaScBH7S865y1sw8ME78prx8T9jRxd", + "trustlines": 191, + "placeInTop": null + }, + { + "id": 18069, + "code": "XTLM", + "issuer": "r9TEnt2kANJGjVaGQ16iQXQ6DkUD6vWeUJ", + "trustlines": 487, + "placeInTop": null + }, + { + "id": 18087, + "code": "Coincoction", + "issuer": "rNZ4MP4mjFefyDEg6riBv6dwZqnB8RHsqh", + "trustlines": 232, + "placeInTop": null + }, + { + "id": 18094, + "code": "LedgerPunks", + "issuer": "rLpunkscgfzS8so59bUCJBVqZ3eHZue64r", + "title": "Ledger Punks", + "trustlines": 1926, + "placeInTop": null + }, + { + "id": 18112, + "code": "OMUA", + "issuer": "rfxCJNw1U4QGxChPN26jSyQA1pCzWBXgHG", + "trustlines": 364, + "placeInTop": null + }, + { + "id": 18124, + "code": "REMetaNFT", + "issuer": "r3iijjcY2qFoSWX1XerumKTkA6VYDhKSmF", + "trustlines": 203, + "placeInTop": null + }, + { + "id": 18131, + "code": "ENV", + "issuer": "rKpu9NjEmwk5USt1A2FBDoVQGajeqgM8na", + "title": "Project ENV", + "trustlines": 5364, + "placeInTop": null + }, + { + "id": 18140, + "code": "BYO", + "issuer": "rhrJEWRUmbUj4vtrCyrXzRRLLiNL5N3Jcg", + "trustlines": 419, + "placeInTop": null + }, + { + "id": 18155, + "code": "VISION", + "issuer": "rJHDa7TbSdfEZBht5JiauCBUCeFmAGREsL", + "title": "VISION", + "trustlines": 233, + "placeInTop": null + }, + { + "id": 18167, + "code": "JEONG", + "issuer": "rL2aPkRMabXiZ9kyhu9hJG6JvqnvLiooCM", + "title": "JEONGx", + "trustlines": 506, + "placeInTop": null + }, + { + "id": 18174, + "code": "MONSTERS", + "issuer": "rUhkwGEEZDSXxvUWDtSChcbNa83m7tUeP7", + "title": "MONSTERS", + "trustlines": 366, + "placeInTop": null + }, + { + "id": 18180, + "code": "BEAST", + "issuer": "r4S7yMBM4gr172BqQbwLwg9yCPeuke5nxt", + "title": "LIQUIDBEAST", + "trustlines": 186, + "placeInTop": null + }, + { + "id": 18194, + "code": "METAFURY", + "issuer": "rwTHewW7HoFdWPwSrV8gqEbQ8SSaUcdU9x", + "trustlines": 466, + "placeInTop": null + }, + { + "id": 18197, + "code": "XRPortals", + "issuer": "rpok532CPqiHzfz38Bg1rE3j5UURaRUMbE", + "title": "XRPortals", + "trustlines": 2019, + "placeInTop": null + }, + { + "id": 18198, + "code": "JeepLifeCoin", + "issuer": "r9aZ1iWLPK8KX9HnNpwnXCVqfumPnyrd8f", + "trustlines": 408, + "placeInTop": null + }, + { + "id": 18204, + "code": "RZ1", + "issuer": "rsg27AT1bfzuZnpRdk9xjJqUvhHsGDFBQm", + "trustlines": 204, + "placeInTop": null + }, + { + "id": 18222, + "code": "XLegendz", + "issuer": "rrUJjso8ZouDQ9gwE1BhaAcL7kfzDxukF", + "trustlines": 267, + "placeInTop": null + }, + { + "id": 18229, + "code": "REALX", + "issuer": "rsuK9Qk7T21Mp2STr3VV2kCwMqydS4ARXU", + "trustlines": 342, + "placeInTop": null + }, + { + "id": 18242, + "code": "KRYPTONITE", + "issuer": "rHJRMuxGiEBAxfwwwpuhSx8FLXqdZmpmQ2", + "title": "KRYPTONITE", + "trustlines": 202, + "placeInTop": null + }, + { + "id": 18243, + "code": "XPAND", + "issuer": "rMHoU2HGVuobGb9EiW7XZrcyXmKTPqYqG8", + "trustlines": 545, + "placeInTop": null + }, + { + "id": 18244, + "code": "Xfight", + "issuer": "rnffCSePCGuGhFA7wHTHHQLGv356ktTTbs", + "trustlines": 251, + "placeInTop": null + }, + { + "id": 18265, + "code": "Holoatomos", + "issuer": "rhoLoAtom8J9hrGFWdkm7PrGRB5qv9D8SN", + "trustlines": 179, + "placeInTop": null + }, + { + "id": 18285, + "code": "ShivaX", + "issuer": "rE1RjM1nthauTbbYurioK4BrSViECHyDdG", + "title": "ShivaX", + "trustlines": 612, + "placeInTop": null + }, + { + "id": 18287, + "code": "WHALE", + "issuer": "rsArLYPERuJmpN3Nc64K3hQgQdPw1ftHRc", + "title": "Whalealert", + "trustlines": 126, + "placeInTop": null + }, + { + "id": 18289, + "code": "Playcoin", + "issuer": "rPLAYcoiNxikbojBES2k48WvpzNCy6DdvL", + "trustlines": 158, + "placeInTop": null + }, + { + "id": 18290, + "code": "XCO", + "issuer": "raHTCKfgrtvSZthnUAVctDj87McDAmKkY2", + "title": "TheOfficialCryptoOpinionsXRPL", + "trustlines": 973, + "placeInTop": null + }, + { + "id": 18319, + "code": "LTRY", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 304, + "placeInTop": null + }, + { + "id": 18324, + "code": "LAUD", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 222, + "placeInTop": null + }, + { + "id": 18328, + "code": "LNGN", + "issuer": "rnGvMu8P6evJEYY4cPyzcy4N1m5gcYRjaH", + "title": "Limited USD (LUSD)", + "trustlines": 196, + "placeInTop": null + }, + { + "id": 18329, + "code": "Shinsekai", + "issuer": "rftChNukekxjRqXhKXEie3ArMUXvGvuS1R", + "trustlines": 359, + "placeInTop": null + }, + { + "id": 18354, + "code": "XMART", + "issuer": "r3wfBuKopdM37gsHvCwhJBDaw4avnxTJCV", + "title": "Xmart", + "trustlines": 948, + "placeInTop": null + }, + { + "id": 18376, + "code": "BRIX", + "issuer": "rUi3o9XGQqVrXQtQftGx5CrJsbQ4ezu2PP", + "trustlines": 261, + "placeInTop": null + }, + { + "id": 18379, + "code": "NutritionalPoints", + "issuer": "rBtqm3b3wHoA2De5JGCHrvZoZfo5ZaWQvz", + "trustlines": 168, + "placeInTop": null + }, + { + "id": 18386, + "code": "XAF", + "issuer": "rBZ6uZVjvjYQSdMcXVjkDTYExYLz3g2JGZ", + "trustlines": 132, + "placeInTop": null + }, + { + "id": 18387, + "code": "VREP", + "issuer": "rEQ3rAEUerdR1wvsdM3ZiLchsWrFqrD3mp", + "trustlines": 174, + "placeInTop": null + }, + { + "id": 18388, + "code": "VERDICT", + "issuer": "rDSq68YvnMhGqcPdGEkwvfACidcgVb729b", + "trustlines": 167, + "placeInTop": null + }, + { + "id": 18404, + "code": "N8token", + "issuer": "rDPsTswPnub3WKgB2Gk3YVF4Enb3YKSSNK", + "trustlines": 161, + "placeInTop": null + }, + { + "id": 18409, + "code": "EKO", + "issuer": "rELGFk3TqoPZpFSn7jX8mjhryX7ghB36Nn", + "trustlines": 110, + "placeInTop": null + }, + { + "id": 18410, + "code": "ECO", + "issuer": "rMeLaNib2qQzALSCKbRMPYid9VerBeQf93", + "title": "Eyes Charity", + "trustlines": 1740, + "placeInTop": null + }, + { + "id": 18412, + "code": "SMC", + "issuer": "rMaZAY3Wvi2gvKWrK8sMFRKzMwqC6jvmJU", + "title": "Smart casino 77", + "trustlines": 1345, + "placeInTop": null + }, + { + "id": 18426, + "code": "CLC", + "issuer": "rBLrMbqmMq9zD8X5LF1veDYEFuDXco8wLZ", + "trustlines": 168, + "placeInTop": null + }, + { + "id": 18435, + "code": "HackleToken", + "issuer": "r7VbtrZGPa1MVwNSascBqYRDxSWWb4TkX", + "trustlines": 150, + "placeInTop": null + }, + { + "id": 18436, + "code": "SQUIRT", + "issuer": "r3rXHvHwxzG9kEmZPpxJWTSS9gX9Ph6zqz", + "title": "Squirt NFT", + "trustlines": 294, + "placeInTop": null + }, + { + "id": 18440, + "code": "Hog", + "issuer": "rwM9XzFHdCxnpNFjanc4Sd3N96cz8ip5pQ", + "title": "HOG X", + "trustlines": 4618, + "placeInTop": null + }, + { + "id": 18442, + "code": "CANNA", + "issuer": "rs7xQ3XUadLvsqE441qDRkmKVVc2Uk6dJ6", + "title": "CANNA", + "trustlines": 139, + "placeInTop": null + }, + { + "id": 18443, + "code": "THCC", + "issuer": "rKJ7N8BJ7QboaoR5Esdp87xjcdJyQ5gFmD", + "trustlines": 198, + "placeInTop": null + }, + { + "id": 18444, + "code": "LCC", + "issuer": "rhFGZnmDBFJ3zdFKnUcpJhXUZzRP2qdEWd", + "trustlines": 1783, + "placeInTop": null + }, + { + "id": 18461, + "code": "Zarm", + "issuer": "raRpmZorhdF7okc8VGSBL29MgJKZmhB6pt", + "trustlines": 171, + "placeInTop": null + }, + { + "id": 18462, + "code": "MNFTC", + "issuer": "rwJZA6eZDxPtgzhG2BCPYe7hdDEDH5wXKc", + "title": "NFT sport cards XRP", + "trustlines": 1417, + "placeInTop": null + }, + { + "id": 18463, + "code": "GRT", + "issuer": "rEYYEdggF5yuNRMrVfCYSVjxYYhyA4NJLs", + "trustlines": 153, + "placeInTop": null + }, + { + "id": 18466, + "code": "PureX", + "issuer": "rD4xUGE8KJG78erSEZrAERT1LPLdHn4UUF", + "trustlines": 124, + "placeInTop": null + }, + { + "id": 18473, + "code": "DogeCh", + "issuer": "r4QVHaB8xz626d6uXm7BNdnoqyYHL82mJ2", + "title": "Doge Chief", + "trustlines": 742, + "placeInTop": null + }, + { + "id": 18482, + "code": "XRPBOTS", + "issuer": "rBRUZL91cJdnDwLHrQvutfHcJqNo1mtuQk", + "trustlines": 193, + "placeInTop": null + }, + { + "id": 18495, + "code": "Bolt", + "issuer": "rEWW6c7dqdr2sbAj9YMkZedoxeADdYTLry", + "trustlines": 119, + "placeInTop": null + }, + { + "id": 18500, + "code": "XBOT", + "issuer": "rfQ2G4gjbR2zAtq6xPppwJsAdsEX8Pc3Rd", + "trustlines": 244, + "placeInTop": null + }, + { + "id": 18502, + "code": "SIPS", + "issuer": "rUJxe8tyLgz6yLXLG8hggUqEnhPG3rtZzF", + "trustlines": 125, + "placeInTop": null + }, + { + "id": 18508, + "code": "CGT", + "issuer": "rp4KDV51A5dW6aySXgQkRUKnHVjFF2PQwr", + "title": "PwntNub", + "trustlines": 175, + "placeInTop": null + }, + { + "id": 18514, + "code": "xRATIONS", + "issuer": "rPArqPnUr39t8uui6THzj7VZSbDXgTNEVp", + "trustlines": 148, + "placeInTop": null + }, + { + "id": 18515, + "code": "xSOLDIERZ", + "issuer": "rwZXCQryUWczJPHrQzx5paK24yRC3J27HA", + "trustlines": 577, + "placeInTop": null + }, + { + "id": 18530, + "code": "CryptoLand1", + "issuer": "rPUTfxRRQx6bA1JVPDyjVyo4p1QvwTBiAi", + "trustlines": 187, + "placeInTop": null + }, + { + "id": 18531, + "code": "Animal", + "issuer": "r4VgKR5eeiERTrWP7wJ1QWXAJkFtCkbZa8", + "title": "Animal XRPL", + "trustlines": 612, + "placeInTop": null + }, + { + "id": 18557, + "code": "XRPACK", + "issuer": "rnzddNSJjunBR6D1hCK4pXb5nHAc6g7v2q", + "trustlines": 235, + "placeInTop": null + }, + { + "id": 18565, + "code": "UACoin", + "issuer": "rGwLmsvsDw7Q24cykGg4GjiX2fEorL9RBf", + "title": "UACoin", + "trustlines": 862, + "placeInTop": null + }, + { + "id": 18647, + "code": "Xsnowman", + "issuer": "rMFUMfjPp9tpBCCbNiYQouF8rL7Se7DTcA", + "trustlines": 694, + "placeInTop": null + }, + { + "id": 18674, + "code": "EBC", + "issuer": "rPwTbNH5P7u7yKiJcCCCaMqaBc5K9B3VQ1", + "trustlines": 120, + "placeInTop": null + }, + { + "id": 18677, + "code": "EBCGLD", + "issuer": "rUpMe5mhxjRkcGMDoH1gCx5ZNGaupiouTP", + "trustlines": 168, + "placeInTop": null + }, + { + "id": 18689, + "code": "FHS", + "issuer": "rNjTh2qW9YT4TdjrwRWYg9qvFBn1PxfZbR", + "trustlines": 1335, + "placeInTop": null + }, + { + "id": 18739, + "code": "xRStake", + "issuer": "r33VJfqaEJScqTEfzgcyRyMezU3kbeqkPF", + "trustlines": 161, + "placeInTop": null + }, + { + "id": 18746, + "code": "ECMM", + "issuer": "rUgQc7xDJJXKhgNW2UjtPkmS2bJro61Fw6", + "trustlines": 117, + "placeInTop": null + }, + { + "id": 18779, + "code": "xMF", + "issuer": "rU4RTVw3ZnqfgAeZtgLPb8prMEdX7VKFmw", + "trustlines": 393, + "placeInTop": null + }, + { + "id": 18812, + "code": "SBT", + "issuer": "rhdHkYbFfB4CoXmStFGmue9G86U5JvPUyS", + "trustlines": 105, + "placeInTop": null + }, + { + "id": 18813, + "code": "xKai", + "issuer": "rDuyXNdzmuPRJd1PoscMDFosEAGccV3Utf", + "trustlines": 549, + "placeInTop": null + }, + { + "id": 18831, + "code": "PCT", + "issuer": "rUbmmkH6VV4ZuWMNynCasKRKViZDP94VDD", + "trustlines": 117, + "placeInTop": null + }, + { + "id": 18884, + "code": "CSE", + "issuer": "rURjz2DgcnZnJitnrZTWp3BDP2VSUcnoCm", + "title": "CSE", + "trustlines": 1465, + "placeInTop": null + }, + { + "id": 18892, + "code": "SAPLING", + "issuer": "rDh9fTkJRCDn6fwDYfogcTsbLmn24hoxMr", + "trustlines": 107, + "placeInTop": null + }, + { + "id": 18899, + "code": "RUN", + "issuer": "rBAtmcnMpVc9tRwzEZgEoZ4wCJCHphSZQ3", + "title": "V-Runners", + "trustlines": 895, + "placeInTop": null + }, + { + "id": 18929, + "code": "UNI", + "issuer": "rw7L8CVYMYD7bFSRCUYTtaaLdneGzi7o9i", + "trustlines": 116, + "placeInTop": null + }, + { + "id": 18940, + "code": "LCT", + "issuer": "r3FSvMsMnMAXXJ9KwN3p6rNrTnKQGDaSiE", + "trustlines": 488, + "placeInTop": null + }, + { + "id": 18966, + "code": "TG$", + "issuer": "rsYKuosdXNfL2J93vaRasddqWVbKHkFzWq", + "trustlines": 1596, + "placeInTop": null + }, + { + "id": 18967, + "code": "MOSHI", + "issuer": "rDgk9RorPEsJLcuGwMBeo3NTQP4AmkGR3j", + "trustlines": 141, + "placeInTop": null + }, + { + "id": 18986, + "code": "INUPOX", + "issuer": "rpRStNPrA8Gm47v1HpD8wdePXb1EFhmTrv", + "trustlines": 127, + "placeInTop": null + }, + { + "id": 18993, + "code": "XNS", + "issuer": "rKbJX2GNtz2j14ESjWzwWj3QPC2oinyjff", + "trustlines": 103, + "placeInTop": null + }, + { + "id": 19020, + "code": "CROWN", + "issuer": "rNcqizvdrT1YRF19eLGUHQUyejNzkJKMBh", + "trustlines": 115, + "placeInTop": null + }, + { + "id": 19035, + "code": "666", + "issuer": "rJefvWQpP2tXVbND6xQRDg4fJNiMDsaoWH", + "trustlines": 107, + "placeInTop": null + }, + { + "id": 19037, + "code": "EVC", + "issuer": "rwz3y9C2q9XQmJDmBqjRU1Fn2xdkR7o59H", + "trustlines": 115, + "placeInTop": null + }, + { + "id": 19082, + "code": "CAD", + "issuer": "rL7CF4YGNBRk6a4U6f851yQDSddJJopu6v", + "trustlines": 151, + "placeInTop": null + }, + { + "id": 19088, + "code": "XKZ", + "issuer": "rDLFMcRrBJkEcRuXBSbWKYDtHum3EqDBGD", + "trustlines": 123, + "placeInTop": null + }, + { + "id": 19102, + "code": "xNVL", + "issuer": "rUKq3udJjjqfnirVCyCrTmK3kK21rfuChn", + "trustlines": 1154, + "placeInTop": null + }, + { + "id": 19114, + "code": "DAK", + "issuer": "rGTudhV2JHUegPmaozAR5F4US6o3skNxe8", + "title": "ONLINE CASINO SINGAPORE", + "trustlines": 1614, + "placeInTop": null + }, + { + "id": 19135, + "code": "SOUND", + "issuer": "rPazDXf3XHGqftSUKa7tStN33VWNHp6Nns", + "trustlines": 16530, + "placeInTop": null + }, + { + "id": 19139, + "code": "CHUB", + "issuer": "rU3DQ71saRwTuknZimDH1uJW7YFS1wocUY", + "trustlines": 4185, + "placeInTop": null + }, + { + "id": 48, + "code": "DIX", + "issuer": "rEiAdjhb552sX6q1tAZ2jHE5hQRMiCTGfy", + "title": "DIX", + "trustlines": 2578, + "placeInTop": null + }, + { + "id": 242, + "code": "IQC", + "issuer": "rGieogBAoXZGq6acVCs9xbMMZaARtGqyyK", + "title": "IQ_Network", + "trustlines": 1077, + "placeInTop": null + }, + { + "id": 17274, + "code": "GZX", + "issuer": "rPK6DvvoF8CVHGURSGNccCxKDXMnRAchHD", + "trustlines": 443, + "placeInTop": null + } +] diff --git a/src/chains/xrpl/xrpl_tokens_devnet.json b/src/chains/xrpl/xrpl_tokens_devnet.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/src/chains/xrpl/xrpl_tokens_devnet.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/chains/xrpl/xrpl_tokens_small.json b/src/chains/xrpl/xrpl_tokens_small.json new file mode 100644 index 0000000000..4d7460fd2a --- /dev/null +++ b/src/chains/xrpl/xrpl_tokens_small.json @@ -0,0 +1,162 @@ +[ + { + "id": 31, + "code": "SOLO", + "issuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "title": "Sologenic", + "trustlines": 288976, + "placeInTop": 1 + }, + { + "id": 16602, + "code": "CSC", + "issuer": "rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr", + "title": "CasinoCoin", + "trustlines": 54154, + "placeInTop": 2 + }, + { + "id": 18190, + "code": "CORE", + "issuer": "rcoreNywaoz2ZCQ8Lg2EbSLnGuRBmun6D", + "title": "Coreum", + "trustlines": 65817, + "placeInTop": 3 + }, + { + "id": 248, + "code": "ELS", + "issuer": "rHXuEaRYnnJHbDeuBH5w8yPh5uwNVh5zAg", + "title": "Elysian", + "trustlines": 109452, + "placeInTop": 4 + }, + { + "id": 16605, + "code": "Equilibrium", + "issuer": "rpakCr61Q92abPXJnVboKENmpKssWyHpwu", + "title": "Equilibrium", + "trustlines": 34827, + "placeInTop": 5 + }, + { + "id": 17073, + "code": "BTC", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 18187, + "placeInTop": 6 + }, + { + "id": 16603, + "code": "USD", + "issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "title": "Gatehub", + "trustlines": 110980, + "placeInTop": 7 + }, + { + "id": 17074, + "code": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "title": "Bitstamp", + "trustlines": 26696, + "placeInTop": 8 + }, + { + "id": 16818, + "code": "XPUNK", + "issuer": "rHEL3bM4RFsvF8kbQj3cya8YiDvjoEmxLq", + "title": "XRPL PUNKS", + "trustlines": 12074, + "placeInTop": 9 + }, + { + "id": 103, + "code": "VGB", + "issuer": "rhcyBrowwApgNonehKBj8Po5z4gTyRknaU", + "title": "Vagabond VGB", + "trustlines": 56661, + "placeInTop": 10 + }, + { + "id": 182, + "code": "RPR", + "issuer": "r3qWgpz2ry3BhcRJ8JE6rxM8esrfhuKp4R", + "title": "The Reaper", + "trustlines": 24140, + "placeInTop": 11 + }, + { + "id": 18542, + "code": "xSPECTAR", + "issuer": "rh5jzTCdMRCVjQ7LT6zucjezC47KATkuvv", + "title": "xSPECTAR NFT", + "trustlines": 9868, + "placeInTop": 12 + }, + { + "id": 16826, + "code": "BAY", + "issuer": "r4uq8urnYrT6LnZaadPyusyKCS68HVJtRn", + "title": "Bored Apes XRP Club", + "trustlines": 8247, + "placeInTop": 13 + }, + { + "id": 17296, + "code": "OXP", + "issuer": "rrno7Nj4RkFJLzC4nRaZiLF5aHwcTVon3d", + "title": "onXRP", + "trustlines": 9851, + "placeInTop": 14 + }, + { + "id": 16726, + "code": "ADV", + "issuer": "rPneN8WPHZJaMT9pF4Ynyyq4pZZZSeTuHu", + "title": "AdvisorBid", + "trustlines": 10454, + "placeInTop": 15 + }, + { + "id": 17363, + "code": "xBIBLx", + "issuer": "rnH9o6qdym34K293Pq3FZp5Ko7SpZxEbeG", + "title": "Bibliomp", + "trustlines": 4441, + "placeInTop": 16 + }, + { + "id": 16600, + "code": "EUR", + "issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "title": "Gatehub", + "trustlines": 85461, + "placeInTop": 17 + }, + { + "id": 225, + "code": "XRdoge", + "issuer": "rLqUC2eCPohYvJCEBJ77eCCqVL2uEiczjA", + "title": "XRdoge Labs", + "trustlines": 26611, + "placeInTop": 18 + }, + { + "id": 99, + "code": "XDX", + "issuer": "rMJAXYsbNzhwp7FfYnAsYP5ty3R9XnurPo", + "title": "D.P.MonksFinance LTD", + "trustlines": 28866, + "placeInTop": 19 + }, + { + "id": 18469, + "code": "STX", + "issuer": "rSTAYKxF2K77ZLZ8GoAwTqPGaphAqMyXV", + "title": "StaykX", + "trustlines": 12098, + "placeInTop": 20 + } +] diff --git a/src/chains/xrpl/xrpl_tokens_testnet.json b/src/chains/xrpl/xrpl_tokens_testnet.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/src/chains/xrpl/xrpl_tokens_testnet.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/connectors/connectors.routes.ts b/src/connectors/connectors.routes.ts index 178792e01e..4af4a25d0d 100644 --- a/src/connectors/connectors.routes.ts +++ b/src/connectors/connectors.routes.ts @@ -20,6 +20,7 @@ import { ConnectorsResponse } from './connectors.request'; import { DexalotCLOBConfig } from './dexalot/dexalot.clob.config'; import { TinymanConfig } from './tinyman/tinyman.config'; import { PlentyConfig } from './plenty/plenty.config'; +import { XRPLDEXConfig } from './xrpldex/xrpldex.config'; export namespace ConnectorsRoutes { export const router = Router(); @@ -157,7 +158,17 @@ export namespace ConnectorsRoutes { trading_type: PlentyConfig.config.tradingTypes, chain_type: PlentyConfig.config.chainType, available_networks: PlentyConfig.config.availableNetworks, - } + }, + { + name: 'xrpldex', + trading_type: XRPLDEXConfig.config.tradingTypes, + chain_type: XRPLDEXConfig.config.chainType, + available_networks: XRPLDEXConfig.config.availableNetworks, + additional_add_wallet_prompts: { + api_key: + 'Enter a XRPL Secret Key if you have one, otherwise hit return >>> ', + }, + }, ], }); }) diff --git a/src/connectors/xrpldex/xrpldex.config.ts b/src/connectors/xrpldex/xrpldex.config.ts new file mode 100644 index 0000000000..21db94132b --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.config.ts @@ -0,0 +1,45 @@ +// import { ConfigManagerV2 } from '../../services/config-manager-v2'; +import { AvailableNetworks } from '../../services/config-manager-types'; + +export namespace XRPLDEXConfig { + export interface Config { + availableNetworks: Array; + tradingTypes: Array; + // markets: MarketsConfig; + // tickers: TickersConfig; + } + + export interface MarketsConfig { + url: string; + blacklist: string[]; + whiteList: string[]; + } + + export interface TickersConfig { + source: string; + url: string; + } + + export const config: Config = { + tradingTypes: ['XRPLDEX_CLOB'], + // markets: { + // url: ConfigManagerV2.getInstance().get(`rippledex.markets.url`), + // blacklist: ConfigManagerV2.getInstance().get( + // `rippledex.markets.blacklist` + // ), + // whiteList: ConfigManagerV2.getInstance().get( + // `rippledex.markets.whitelist` + // ), + // }, + // tickers: { + // source: ConfigManagerV2.getInstance().get(`rippledex.tickers.source`), + // url: ConfigManagerV2.getInstance().get(`rippledex.tickers.url`), + // }, + availableNetworks: [ + { + chain: 'xrpl', + networks: ['mainnet', 'testnet'], + }, + ], + }; +} diff --git a/src/connectors/xrpldex/xrpldex.controllers.ts b/src/connectors/xrpldex/xrpldex.controllers.ts new file mode 100644 index 0000000000..05f1aab434 --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.controllers.ts @@ -0,0 +1,347 @@ +import { StatusCodes } from 'http-status-codes'; +import { XRPLish } from '../../chains/xrpl/xrpl'; +import { ResponseWrapper } from '../../services/common-interfaces'; +import { HttpException } from '../../services/error-handler'; +import { XRPLDEXish } from './xrpldex'; +import { + XRPLCancelOrdersRequest, + XRPLCancelOrdersResponse, + XRPLCreateOrdersRequest, + XRPLCreateOrdersResponse, + XRPLGetMarketsRequest, + XRPLGetMarketsResponse, + XRPLGetOpenOrdersRequest, + XRPLGetOpenOrdersResponse, + XRPLGetOrderBooksRequest, + XRPLGetOrderBooksResponse, + XRPLGetTickersRequest, + XRPLGetTickersResponse, + XRPLGetOrdersRequest, + XRPLGetOrdersResponse, +} from './xrpldex.requests'; + +import { + validateGetMarketRequest, + validateGetMarketsRequest, + validateGetTickerRequest, + validateGetTickersRequest, + validateGetOrderBookRequest, + validateGetOrderBooksRequest, +} from './xrpldex.validators'; + +import { MarketNotFoundError } from './xrpldex.types'; + +/** + * Get the mid price of a token pair + * + * @param _xrpl + * @param xrpldex + * @param request + */ +export async function getMarkets( + _xrpl: XRPLish, + xrpldex: XRPLDEXish, + request: XRPLGetMarketsRequest +): Promise> { + const response = new ResponseWrapper(); + + if ('name' in request) { + validateGetMarketRequest(request); + + try { + response.body = await xrpldex.getMarket(request.name); + response.status = StatusCodes.OK; + + return response; + } catch (exception) { + if (exception instanceof MarketNotFoundError) { + throw new HttpException(StatusCodes.NOT_FOUND, exception.message); + } else { + throw exception; + } + } + } + + if ('names' in request) { + validateGetMarketsRequest(request); + + try { + response.body = await xrpldex.getMarkets(request.names); + + response.status = StatusCodes.OK; + + return response; + } catch (exception: any) { + if (exception instanceof MarketNotFoundError) { + throw new HttpException(StatusCodes.NOT_FOUND, exception.message); + } else { + throw exception; + } + } + } + + throw new HttpException(StatusCodes.NOT_FOUND, 'No market specified.'); +} + +/** + * Get the mid price of a token pair + * + * @param _xrpl + * @param xrpldex + * @param request + */ +export async function getTickers( + _xrpl: XRPLish, + xrpldex: XRPLDEXish, + request: XRPLGetTickersRequest +): Promise> { + const response = new ResponseWrapper(); + + if ('marketName' in request) { + validateGetTickerRequest(request); + + try { + response.body = await xrpldex.getTicker(request.marketName); + response.status = StatusCodes.OK; + + return response; + } catch (exception) { + if (exception instanceof MarketNotFoundError) { + throw new HttpException(StatusCodes.NOT_FOUND, exception.message); + } else { + throw exception; + } + } + } + + if ('marketNames' in request) { + validateGetTickersRequest(request); + + try { + response.body = await xrpldex.getTickers(request.marketNames); + + response.status = StatusCodes.OK; + + return response; + } catch (exception: any) { + if (exception instanceof MarketNotFoundError) { + throw new HttpException(StatusCodes.NOT_FOUND, exception.message); + } else { + throw exception; + } + } + } + + throw new HttpException(StatusCodes.NOT_FOUND, 'No market specified.'); +} + +/** + * Get the order book of a token pair + * + * @param _xrpl + * @param xrpldex + * @param request + */ +export async function getOrderBooks( + _xrpl: XRPLish, + xrpldex: XRPLDEXish, + request: XRPLGetOrderBooksRequest +): Promise> { + const response = new ResponseWrapper(); + + if ('marketName' in request) { + validateGetOrderBookRequest(request); + + try { + response.body = await xrpldex.getOrderBook( + request.marketName, + request.limit + ); + response.status = StatusCodes.OK; + + return response; + } catch (exception) { + if (exception instanceof MarketNotFoundError) { + throw new HttpException(StatusCodes.NOT_FOUND, exception.message); + } else { + throw exception; + } + } + } + + if ('marketNames' in request) { + validateGetOrderBooksRequest(request); + + try { + response.body = await xrpldex.getOrderBooks( + request.marketNames, + request.limit + ); + + response.status = StatusCodes.OK; + + return response; + } catch (exception: any) { + if (exception instanceof MarketNotFoundError) { + throw new HttpException(StatusCodes.NOT_FOUND, exception.message); + } else { + throw exception; + } + } + } + + throw new HttpException(StatusCodes.NOT_FOUND, 'No market specified.'); +} + +/** + * Get the detail on the created order + * + * @param _xrpl + * @param xrpldex + * @param request + */ +export async function getOrders( + _xrpl: XRPLish, + xrpldex: XRPLDEXish, + request: XRPLGetOrdersRequest +): Promise> { + const response = new ResponseWrapper(); + + response.body = await xrpldex.getOrders(request.orders); + + response.status = StatusCodes.OK; + + return response; +} + +/** + * Create an order on order book + * + * @param _xrpl + * @param xrpldex + * @param request + */ +export async function createOrders( + _xrpl: XRPLish, + xrpldex: XRPLDEXish, + request: XRPLCreateOrdersRequest +): Promise> { + const response = new ResponseWrapper(); + + if ('order' in request) { + // validateCreateOrderRequest(request.order); TODO: add createOrder validator + + response.body = await xrpldex.createOrders( + [request.order], + request.waitUntilIncludedInBlock + ); + + response.status = StatusCodes.OK; + + return response; + } + + if ('orders' in request) { + // validateCreateOrdersRequest(request.orders); TODO: add createOrders validator + + response.body = await xrpldex.createOrders( + request.orders, + request.waitUntilIncludedInBlock + ); + + response.status = StatusCodes.OK; + + return response; + } + + throw new HttpException( + StatusCodes.BAD_REQUEST, + `No order(s) was/were informed.` + ); +} + +/** + * Cancel an order on order book + * + * @param _xrpl + * @param xrpldex + * @param request + */ +export async function cancelOrders( + _xrpl: XRPLish, + xrpldex: XRPLDEXish, + request: XRPLCancelOrdersRequest +): Promise> { + const response = new ResponseWrapper(); + + if ('order' in request) { + // validateCancelOrderRequest(request.order); TODO: add createOrder validator + + response.body = await xrpldex.cancelOrders( + [request.order], + request.waitUntilIncludedInBlock + ); + + response.status = StatusCodes.OK; + + return response; + } + + if ('orders' in request) { + // validateCancelOrdersRequest(request.orders); TODO: add createOrders validator + + response.body = await xrpldex.cancelOrders( + request.orders, + request.waitUntilIncludedInBlock + ); + + response.status = StatusCodes.OK; + + return response; + } + + throw new HttpException( + StatusCodes.BAD_REQUEST, + `No order(s) was/were informed.` + ); +} + +/** + * Get open orders of a token pair + * + * @param _xrpl + * @param xrpldex + * @param request + */ +export async function getOpenOrders( + _xrpl: XRPLish, + xrpldex: XRPLDEXish, + request: XRPLGetOpenOrdersRequest +): Promise> { + const response = new ResponseWrapper(); + + if ('order' in request) { + // validateOpenOrderRequest(request.order); TODO: add createOrder validator + + response.body = await xrpldex.getOpenOrders({ market: request.order }); + + response.status = StatusCodes.OK; + + return response; + } + + if ('orders' in request) { + // validateOpenOrdersRequest(request.orders); TODO: add createOrders validator + + response.body = await xrpldex.getOpenOrders({ markets: request.orders }); + + response.status = StatusCodes.OK; + + return response; + } + + throw new HttpException( + StatusCodes.BAD_REQUEST, + `No order(s) was/were informed.` + ); +} diff --git a/src/connectors/xrpldex/xrpldex.middlewares.ts b/src/connectors/xrpldex/xrpldex.middlewares.ts new file mode 100644 index 0000000000..3ae00d466a --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.middlewares.ts @@ -0,0 +1,21 @@ +import { NextFunction, Request, Response } from 'express'; +import { XRPLDEX } from './xrpldex'; +import { HttpException } from '../../services/error-handler'; + +export const verifyXRPLDEXIsAvailable = async ( + req: Request, + _res: Response, + next: NextFunction +) => { + if (!req || !req.body || !req.body.network) { + throw new HttpException(404, 'No XRPL network informed.'); + } + + const xrplDEX = XRPLDEX.getInstance(req.body.chain, req.body.network); + + if (!xrplDEX.isConnected()) { + await xrplDEX.client.connect(); + } + + return next(); +}; diff --git a/src/connectors/xrpldex/xrpldex.requests.ts b/src/connectors/xrpldex/xrpldex.requests.ts new file mode 100644 index 0000000000..e249010dfc --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.requests.ts @@ -0,0 +1,92 @@ +import { NetworkSelectionRequest } from '../../services/common-interfaces'; +import { + GetOrderBooksRequest, + GetOrderBooksResponse, + GetMarketsRequest, + GetMarketsResponse, + GetTickersRequest, + GetTickersResponse, + CreateOrdersResponse, + CancelOrderRequest, + CreateOrderRequest, + CancelOrdersResponse, + GetOpenOrderRequest, + GetOpenOrdersResponse, + GetOrdersRequest, + GetOrdersResponse, +} from './xrpldex.types'; + +// +// GET /xrpldex/markets +// +export type XRPLGetMarketsRequest = NetworkSelectionRequest & GetMarketsRequest; + +export type XRPLGetMarketsResponse = GetMarketsResponse; + +// +// GET /xrpldex/tickers +// +export type XRPLGetTickersRequest = NetworkSelectionRequest & GetTickersRequest; + +export type XRPLGetTickersResponse = GetTickersResponse; + +// +// GET /xrpldex/orders +// + +export type XRPLGetOrdersRequest = NetworkSelectionRequest & GetOrdersRequest; + +export type XRPLGetOrdersResponse = GetOrdersResponse; + +// +// GET /xrpldex/orderBooks +// + +export type XRPLGetOrderBooksRequest = NetworkSelectionRequest & + GetOrderBooksRequest; + +export type XRPLGetOrderBooksResponse = GetOrderBooksResponse; + +// +// POST /xrpldex/orders +// + +export type XRPLCreateOrdersRequest = NetworkSelectionRequest & + ( + | { order: CreateOrderRequest; waitUntilIncludedInBlock: boolean } + | { + orders: CreateOrderRequest[]; + waitUntilIncludedInBlock: boolean; + } + ); + +export type XRPLCreateOrdersResponse = CreateOrdersResponse; + +// +// DELETE /xrpldex/orders +// + +export type XRPLCancelOrdersRequest = NetworkSelectionRequest & + ( + | { order: CancelOrderRequest; waitUntilIncludedInBlock: boolean } + | { + orders: CancelOrderRequest[]; + waitUntilIncludedInBlock: boolean; + } + ); + +export type XRPLCancelOrdersResponse = CancelOrdersResponse; + +// +// GET /xrpldex/orders/open +// + +export type XRPLGetOpenOrdersRequest = NetworkSelectionRequest & + ( + | { order: GetOpenOrderRequest } + | { + orders: GetOpenOrderRequest[]; + } + ); + +export type XRPLGetOpenOrdersResponse = GetOpenOrdersResponse; diff --git a/src/connectors/xrpldex/xrpldex.routes.ts b/src/connectors/xrpldex/xrpldex.routes.ts new file mode 100644 index 0000000000..ad3289fa18 --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.routes.ts @@ -0,0 +1,193 @@ +import { Request, Response, Router } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import { XRPL } from '../../chains/xrpl/xrpl'; +import { verifyXRPLIsAvailable } from '../../chains/xrpl/xrpl-middlewares'; +import { validateXRPLAddress } from '../../chains/xrpl/xrpl.validators'; +import { asyncHandler } from '../../services/error-handler'; +import { XRPLDEX } from './xrpldex'; +import { verifyXRPLDEXIsAvailable } from './xrpldex.middlewares'; +import { + cancelOrders, + createOrders, + // getFilledOrders, + getMarkets, + getOpenOrders, + getOrderBooks, + getOrders, + getTickers, +} from './xrpldex.controllers'; +import { + XRPLGetMarketsRequest, + XRPLGetMarketsResponse, + XRPLGetOrderBooksRequest, + XRPLGetOrderBooksResponse, + XRPLGetTickersRequest, + XRPLGetTickersResponse, + XRPLCreateOrdersRequest, + XRPLCreateOrdersResponse, + XRPLCancelOrdersRequest, + XRPLCancelOrdersResponse, + XRPLGetOpenOrdersRequest, + XRPLGetOpenOrdersResponse, + XRPLGetOrdersRequest, + XRPLGetOrdersResponse, +} from './xrpldex.requests'; + +export namespace XRPLDEXRoutes { + export const router = Router(); + + export const getXRPL = async (request: Request) => + await XRPL.getInstance(request.body.network); + + export const getXRPLDEX = async (request: Request) => + await XRPLDEX.getInstance(request.body.chain, request.body.network); + + router.use( + asyncHandler(verifyXRPLIsAvailable), + asyncHandler(verifyXRPLDEXIsAvailable) + ); + + router.get( + '/', + asyncHandler( + async (request: Request, response: Response) => { + const xrplDEX = await getXRPLDEX(request); + + response.status(StatusCodes.OK).json({ + chain: xrplDEX.chain, + network: xrplDEX.network, + connector: xrplDEX.connector, + connection: xrplDEX.ready(), + timestamp: Date.now(), + }); + } + ) + ); + + router.get( + '/markets', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + const xrplDEX = await getXRPLDEX(request); + + const result = await getMarkets(xrpl, xrplDEX, request.body); + + response.status(result.status).json(result.body); + } + ) + ); + + router.get( + '/tickers', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + const xrplDEX = await getXRPLDEX(request); + + const result = await getTickers(xrpl, xrplDEX, request.body); + + response.status(result.status).json(result.body); + } + ) + ); + + router.get( + '/orderBooks', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + const xrplDEX = await getXRPLDEX(request); + + const result = await getOrderBooks(xrpl, xrplDEX, request.body); + + response.status(result.status).json(result.body); + } + ) + ); + + router.get( + '/orders', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + const xrplDEX = await getXRPLDEX(request); + + validateXRPLAddress(request.body); + + const result = await getOrders(xrpl, xrplDEX, request.body); + + response.status(result.status).json(result.body); + } + ) + ); + + router.post( + '/orders', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + const xrplDEX = await getXRPLDEX(request); + + validateXRPLAddress(request.body); + + const result = await createOrders(xrpl, xrplDEX, request.body); + + response.status(result.status).json(result.body); + } + ) + ); + + router.delete( + '/orders', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + const xrplDEX = await getXRPLDEX(request); + + validateXRPLAddress(request.body); + + const result = await cancelOrders(xrpl, xrplDEX, request.body); + + response.status(result.status).json(result.body); + } + ) + ); + + router.get( + '/orders/open', + asyncHandler( + async ( + request: Request, + response: Response + ) => { + const xrpl = await getXRPL(request); + const xrplDEX = await getXRPLDEX(request); + + validateXRPLAddress(request.body); + + const result = await getOpenOrders(xrpl, xrplDEX, request.body); + + response.status(result.status).json(result.body); + } + ) + ); +} diff --git a/src/connectors/xrpldex/xrpldex.ts b/src/connectors/xrpldex/xrpldex.ts new file mode 100644 index 0000000000..ccf2bb7bef --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.ts @@ -0,0 +1,841 @@ +import { XRPL } from '../../chains/xrpl/xrpl'; +import { + Client, + OfferCancel, + Transaction, + xrpToDrops, + AccountInfoResponse, + BookOffersResponse, + TxResponse, + TransactionMetadata, +} from 'xrpl'; +import { + Market, + MarketNotFoundError, + IMap, + GetTickerResponse, + Ticker, + GetOrderBookResponse, + Token, + CreateOrderResponse, + OrderStatus, + CreateOrderRequest, + CancelOrderRequest, + CancelOrderResponse, + GetOpenOrderRequest, + GetOpenOrdersResponse, + OrderSide, + GetOrdersResponse, + GetOrderRequest, +} from './xrpldex.types'; +import { promiseAllInBatches } from '../../chains/xrpl/xrpl.helpers'; +import { isIssuedCurrency } from 'xrpl/dist/npm/models/transactions/common'; + +export type XRPLDEXish = XRPLDEX; + +export class XRPLDEX { + private static _instances: { [name: string]: XRPLDEX }; + private readonly _client: Client; + private readonly _xrpl: XRPL; + private _ready: boolean = false; + + initializing: boolean = false; + chain: string; + network: string; + readonly connector: string = 'xrpldex'; + + /** + * Creates a new instance of xrplDEX. + * + * @param chain + * @param network + * @private + */ + private constructor(chain: string, network: string) { + this.chain = chain; + this.network = network; + + this._xrpl = XRPL.getInstance(network); + this._client = this._xrpl.client; + } + + /** + * Initialize the xrplDEX instance. + * + */ + async init() { + if (!this._ready && !this.initializing) { + this.initializing = true; + + if (!this._xrpl.ready()) { + await this._xrpl.init(); + } + + if (!this._client.isConnected()) { + await this._client.connect(); + } + + this._ready = true; + this.initializing = false; + } + } + + public static getInstance(chain: string, network: string): XRPLDEX { + if (XRPLDEX._instances === undefined) { + XRPLDEX._instances = {}; + } + if (!(network in XRPLDEX._instances)) { + XRPLDEX._instances[network] = new XRPLDEX(chain, network); + } + + return XRPLDEX._instances[network]; + } + + /** + * @param name + */ + async getMarket(name?: string): Promise { + if (!name) throw new MarketNotFoundError(`No market informed.`); + // Market name format: + // 1: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/USD.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" + // 2: "XRP/ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" + // 3: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/XRP" + let baseTickSize: number; + let baseTransferRate: number; + let quoteTickSize: number; + let quoteTransferRate: number; + const zeroTransferRate = 1000000000; + + const [base, quote] = name.split('/'); + + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + if (baseCurrency != 'XRP') { + const baseMarketResp: AccountInfoResponse = await this._client.request({ + command: 'account_info', + ledger_index: 'validated', + account: baseIssuer, + }); + + if (!baseMarketResp) + throw new MarketNotFoundError(`Market "${base}" not found.`); + + baseTickSize = baseMarketResp.result.account_data.TickSize ?? 15; + const rawTransferRate = + baseMarketResp.result.account_data.TransferRate ?? zeroTransferRate; + baseTransferRate = rawTransferRate / zeroTransferRate - 1; + } else { + baseTickSize = 6; + baseTransferRate = 0; + } + + if (quoteCurrency != 'XRP') { + const quoteMarketResp: AccountInfoResponse = await this._client.request({ + command: 'account_info', + ledger_index: 'validated', + account: quoteIssuer, + }); + + if (!quoteMarketResp) + throw new MarketNotFoundError(`Market "${quote}" not found.`); + + quoteTickSize = quoteMarketResp.result.account_data.TickSize ?? 15; + const rawTransferRate = + quoteMarketResp.result.account_data.TransferRate ?? zeroTransferRate; + quoteTransferRate = rawTransferRate / zeroTransferRate - 1; + } else { + quoteTickSize = 6; + quoteTransferRate = 0; + } + + const smallestTickSize = Math.min(baseTickSize, quoteTickSize); + const minimumOrderSize = smallestTickSize; + + const result = { + name: name, + minimumOrderSize: minimumOrderSize, + tickSize: smallestTickSize, + baseTransferRate: baseTransferRate, + quoteTransferRate: quoteTransferRate, + }; + + return result; + } + + /** + * @param names + */ + async getMarkets(names: string[]): Promise> { + const markets = IMap().asMutable(); + + const getMarket = async (name: string): Promise => { + const market = await this.getMarket(name); + + markets.set(name, market); + }; + + await promiseAllInBatches(getMarket, names, 1, 1); + + return markets; + } + + /** + * Returns the last traded prices. + */ + async getTicker(marketName: string): Promise { + const [base, quote] = marketName.split('/'); + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + const baseRequest: any = { + currency: baseCurrency, + }; + + const quoteRequest: any = { + currency: quoteCurrency, + }; + + if (baseIssuer) { + baseRequest['issuer'] = baseIssuer; + } + if (quoteIssuer) { + quoteRequest['issuer'] = quoteIssuer; + } + + const orderbook_resp_ask: any = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: baseRequest, + taker_pays: quoteRequest, + limit: 1, + }); + + const orderbook_resp_bid: any = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: quoteRequest, + taker_pays: baseRequest, + limit: 1, + }); + + const asks = orderbook_resp_ask.result.offers; + const bids = orderbook_resp_bid.result.offers; + + let topAsk = 0; + let topBid = 0; + + const askQuality = asks.length > 0 ? asks[0].quality : undefined; + const bidQuality = bids.length > 0 ? bids[0].quality : undefined; + + if (baseCurrency === 'XRP' || quoteCurrency === 'XRP') { + if (baseCurrency === 'XRP') { + topAsk = askQuality ? Number(askQuality) * 1000000 : 0; + topBid = bidQuality ? (1 / Number(bidQuality)) * 1000000 : 0; + } else { + topAsk = askQuality ? Number(askQuality) / 1000000 : 0; + topBid = bidQuality ? 1 / Number(bidQuality) / 1000000 : 0; + } + } else { + topAsk = askQuality ? Number(askQuality) : 0; + topBid = bidQuality ? 1 / Number(bidQuality) : 0; + } + + const midPrice = (topAsk + topBid) / 2; + + return { + price: midPrice, + timestamp: Date.now(), + }; + } + + async getTickers(marketNames: string[]): Promise> { + const tickers = IMap().asMutable(); + + const getTicker = async (marketName: string): Promise => { + const ticker = await this.getTicker(marketName); + + tickers.set(marketName, ticker); + }; + + await promiseAllInBatches(getTicker, marketNames, 1, 1); + + return tickers; + } + + async getOrderBook( + marketName: string, + limit: number + ): Promise { + const market = await this.getMarket(marketName); + + const [base, quote] = marketName.split('/'); + + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + const baseRequest: any = { + currency: baseCurrency, + }; + + const quoteRequest: any = { + currency: quoteCurrency, + }; + + if (baseIssuer) { + baseRequest['issuer'] = baseIssuer; + } + if (quoteIssuer) { + quoteRequest['issuer'] = quoteIssuer; + } + + const orderbook_resp_ask: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: baseRequest, + taker_pays: quoteRequest, + limit: limit, + }); + + const orderbook_resp_bid: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: quoteRequest, + taker_pays: baseRequest, + limit: limit, + }); + + const asks = orderbook_resp_ask.result.offers; + const bids = orderbook_resp_bid.result.offers; + + let topAsk = 0; + let topBid = 0; + + const askQuality = asks.length > 0 ? asks[0].quality : undefined; + const bidQuality = bids.length > 0 ? bids[0].quality : undefined; + + if (baseCurrency === 'XRP' || quoteCurrency === 'XRP') { + if (baseCurrency === 'XRP') { + topAsk = askQuality ? Number(askQuality) * 1000000 : 0; + topBid = bidQuality ? (1 / Number(bidQuality)) * 1000000 : 0; + } else { + topAsk = askQuality ? Number(askQuality) / 1000000 : 0; + topBid = bidQuality ? 1 / Number(bidQuality) / 1000000 : 0; + } + } else { + topAsk = askQuality ? Number(askQuality) : 0; + topBid = bidQuality ? 1 / Number(bidQuality) : 0; + } + + const midPrice = (topAsk + topBid) / 2; + + return { + market, + asks, + bids, + topAsk, + topBid, + midPrice, + timestamp: Date.now(), + }; + } + + async getOrderBooks( + marketNames: string[], + limit: number + ): Promise> { + const orderBooks = IMap().asMutable(); + + const getOrderBook = async (marketName: string): Promise => { + const orderBook = await this.getOrderBook(marketName, limit); + + orderBooks.set(marketName, orderBook); + }; + + await promiseAllInBatches(getOrderBook, marketNames, 1, 1); + + return orderBooks; + } + + async getOrders(orders: GetOrderRequest[]): Promise { + const queriedOrders: GetOrdersResponse = {}; + + for (const order of orders) { + const tx_resp: TxResponse = await this._client.request({ + command: 'tx', + transaction: order.signature, + binary: false, + }); + + const type = tx_resp.result.TransactionType; + if (tx_resp.result.meta) { + const meta: TransactionMetadata = tx_resp.result + .meta as TransactionMetadata; + const result = meta.TransactionResult; + const prefix = result.slice(0, 3); + + switch (prefix) { + case 'tec': + case 'tef': + case 'tel': + case 'tem': + queriedOrders[order.sequence] = { + sequence: order.sequence, + status: OrderStatus.FAILED, + signature: order.signature, + transactionResult: result, + }; + continue; + } + + if (type == 'OfferCreate') { + if (result == 'tesSUCCESS') { + queriedOrders[order.sequence] = { + sequence: order.sequence, + status: OrderStatus.OPEN, + signature: order.signature, + transactionResult: result, + }; + } else { + queriedOrders[order.sequence] = { + sequence: order.sequence, + status: OrderStatus.PENDING, + signature: order.signature, + transactionResult: result, + }; + } + } else if (type == 'OfferCancel') { + if (result == 'tesSUCCESS') { + queriedOrders[order.sequence] = { + sequence: order.sequence, + status: OrderStatus.CANCELED, + signature: order.signature, + transactionResult: result, + }; + } else { + queriedOrders[order.sequence] = { + sequence: order.sequence, + status: OrderStatus.PENDING, + signature: order.signature, + transactionResult: result, + }; + } + } else { + queriedOrders[order.sequence] = { + sequence: order.sequence, + status: OrderStatus.UNKNOWN, + signature: order.signature, + transactionResult: result, + }; + } + } else { + queriedOrders[order.sequence] = { + sequence: order.sequence, + status: OrderStatus.PENDING, + signature: order.signature, + transactionResult: 'pending', + }; + } + } + + const result = queriedOrders; + return result; + } + + async createOrder(order: CreateOrderRequest): Promise { + const [base, quote] = order.marketName.split('/'); + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + const market = await this.getMarket(order.marketName); + + const xrpl = XRPL.getInstance(this.network); + const wallet = await xrpl.getWallet(order.walletAddress); + const total = order.price * order.amount; + let fee = 0; + + let we_pay: Token = { + currency: '', + issuer: '', + value: '', + }; + let we_get: Token = { currency: '', issuer: '', value: '' }; + + if (order.side == 'BUY') { + we_pay = { + currency: quoteCurrency, + issuer: quoteIssuer, + value: Number(total.toPrecision(market.tickSize)).toString(), + }; + we_get = { + currency: baseCurrency, + issuer: baseIssuer, + value: Number(order.amount.toPrecision(market.tickSize)).toString(), + }; + + fee = market.baseTransferRate; + } else { + we_pay = { + currency: baseCurrency, + issuer: baseIssuer, + value: Number(order.amount.toPrecision(market.tickSize)).toString(), + }; + we_get = { + currency: quoteCurrency, + issuer: quoteIssuer, + value: Number(total.toPrecision(market.tickSize)).toString(), + }; + + fee = market.quoteTransferRate; + } + + if (we_pay.currency == 'XRP') { + we_pay.value = xrpToDrops(we_pay.value); + } + + if (we_get.currency == 'XRP') { + we_get.value = xrpToDrops(we_get.value); + } + + const offer: Transaction = { + TransactionType: 'OfferCreate', + Account: wallet.classicAddress, + TakerGets: we_pay.currency == 'XRP' ? we_pay.value : we_pay, + TakerPays: we_get.currency == 'XRP' ? we_get.value : we_get, + }; + + if (order.sequence != undefined) { + offer.OfferSequence = order.sequence; + } + + const prepared = await this._client.autofill(offer); + const signed = wallet.sign(prepared); + const response = await this._client.submit(signed.tx_blob); + + const orderStatus = OrderStatus.PENDING; + // const orderSequence = -1; + // const orderLedgerIndex = ''; + + // if (response.result) { + // const meta = response.result.meta; + // if (meta) { + // const affectedNodes = (meta as TransactionMetadata).AffectedNodes; + + // for (const affnode of affectedNodes) { + // if ('ModifiedNode' in affnode) { + // if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { + // // Usually a ModifiedNode of type Offer indicates a previous Offer that + // // was partially consumed by this one. + // orderStatus = OrderStatus.PARTIALLY_FILLED; + // } + // } else if ('DeletedNode' in affnode) { + // if (affnode.DeletedNode.LedgerEntryType == 'Offer') { + // // The removed Offer may have been fully consumed, or it may have been + // // found to be expired or unfunded. + // // TODO: Make a seperate method for cancelling orders + // if (offer.OfferSequence == undefined) { + // orderStatus = OrderStatus.FILLED; + // } + // } + // } else if ('CreatedNode' in affnode) { + // if (affnode.CreatedNode.LedgerEntryType == 'Offer') { + // // Created an Offer object on the Ledger + // orderStatus = OrderStatus.OPEN; + // orderSequence = response.result.Sequence ?? -1; + // orderLedgerIndex = affnode.CreatedNode.LedgerIndex; + // } + // } + // } + // } + // } + + const returnResponse: CreateOrderResponse = { + walletAddress: order.walletAddress, + marketName: order.marketName, + price: order.price, + amount: order.amount, + side: order.side, + type: order.type, + fee, + orderLedgerIndex: response.result.validated_ledger_index.toString(), + status: orderStatus, + sequence: response.result.tx_json.Sequence ?? -1, + signature: response.result.tx_json.hash, + }; + + return returnResponse; + } + + async createOrders( + orders: CreateOrderRequest[], + waitUntilIncludedInBlock: boolean + ): Promise> { + const createdOrders: Record = {}; + + if (orders.length <= 0) { + return createdOrders; + } + + const getCreatedOrders = async ( + order: CreateOrderRequest + ): Promise => { + const createdOrder = await this.createOrder(order); + + createdOrders[createdOrder.sequence] = createdOrder; + }; + + await promiseAllInBatches(getCreatedOrders, orders, 1, 1); + + if (waitUntilIncludedInBlock) { + const queriedOrders: GetOrderRequest[] = []; + let pooling = true; + let transactionStatuses: GetOrdersResponse; + + for (const key in createdOrders) { + const sequence = parseInt(key); + const signature = createdOrders[key].signature; + + if (signature != undefined) { + queriedOrders.push({ + sequence, + signature, + }); + } + } + + while (pooling) { + transactionStatuses = await this.getOrders(queriedOrders); + + for (const key in transactionStatuses) { + if (transactionStatuses[key]['status'] == OrderStatus.PENDING) { + pooling = true; + await new Promise((resolve) => setTimeout(resolve, 5000)); + break; + } + + createdOrders[key]['status'] = transactionStatuses[key]['status']; + createdOrders[key]['transactionResult'] = + transactionStatuses[key]['transactionResult']; + + pooling = false; + } + } + } + + return createdOrders; + } + + async cancelOrder(order: CancelOrderRequest): Promise { + const xrpl = XRPL.getInstance(this.network); + const wallet = await xrpl.getWallet(order.walletAddress); + const request: OfferCancel = { + TransactionType: 'OfferCancel', + Account: wallet.classicAddress, + OfferSequence: order.offerSequence, + }; + + const prepared = await this._client.autofill(request); + const signed = wallet.sign(prepared); + const response = await this._client.submit(signed.tx_blob); + + const orderStatus = OrderStatus.PENDING; + + // if (response.result) { + // const meta = response.result.meta; + // if (meta) { + // const affectedNodes = (meta as TransactionMetadata).AffectedNodes; + + // for (const affnode of affectedNodes) { + // if ('DeletedNode' in affnode) { + // if (affnode.DeletedNode.LedgerEntryType == 'Offer') { + // orderStatus = OrderStatus.CANCELED; + // } + // } + // } + // } + // } + + const returnResponse: CancelOrderResponse = { + walletAddress: order.walletAddress, + status: orderStatus, + signature: response.result.tx_json.hash, + }; + + return returnResponse; + } + + async cancelOrders( + orders: CancelOrderRequest[], + waitUntilIncludedInBlock: boolean + ): Promise> { + const cancelledOrders: Record = {}; + + if (orders.length <= 0) { + return cancelledOrders; + } + + const getCancelledOrders = async ( + order: CancelOrderRequest + ): Promise => { + const cancelledOrder = await this.cancelOrder(order); + + cancelledOrders[order.offerSequence] = cancelledOrder; + }; + + await promiseAllInBatches(getCancelledOrders, orders, 1, 1); + + if (waitUntilIncludedInBlock) { + const queriedOrders: GetOrderRequest[] = []; + let pooling = true; + let transactionStatuses: GetOrdersResponse; + + for (const key in cancelledOrders) { + const sequence = parseInt(key); + const signature = cancelledOrders[key].signature; + + if (signature != undefined) { + queriedOrders.push({ + sequence, + signature, + }); + } + } + + while (pooling) { + transactionStatuses = await this.getOrders(queriedOrders); + + for (const key in transactionStatuses) { + if (transactionStatuses[key]['status'] == OrderStatus.PENDING) { + pooling = true; + await new Promise((resolve) => setTimeout(resolve, 5000)); + break; + } + + cancelledOrders[key]['status'] = transactionStatuses[key]['status']; + cancelledOrders[key]['transactionResult'] = + transactionStatuses[key]['transactionResult']; + + pooling = false; + } + } + } + + return cancelledOrders; + } + + async getOpenOrders(params: { + market?: GetOpenOrderRequest; + markets?: GetOpenOrderRequest[]; + }): Promise { + const openOrders: any = {}; + + let marketArray: GetOpenOrderRequest[] = []; + if (params.market) marketArray.push(params.market); + if (params.markets) { + marketArray = marketArray.concat(params.markets); + } + + for (const market of marketArray) { + const [base, quote] = market.marketName.split('/'); + + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + const openOrdersInMarket: any = {}; + + const baseRequest: any = { + currency: baseCurrency, + }; + + const quoteRequest: any = { + currency: quoteCurrency, + }; + + if (baseIssuer) { + baseRequest['issuer'] = baseIssuer; + } + if (quoteIssuer) { + quoteRequest['issuer'] = quoteIssuer; + } + + const orderbook_resp_ask: BookOffersResponse = await this._client.request( + { + command: 'book_offers', + ledger_index: 'validated', + taker: market.walletAddress, + taker_gets: baseRequest, + taker_pays: quoteRequest, + } + ); + + const orderbook_resp_bid: BookOffersResponse = await this._client.request( + { + command: 'book_offers', + ledger_index: 'validated', + taker: market.walletAddress, + taker_gets: quoteRequest, + taker_pays: baseRequest, + } + ); + + let asks = orderbook_resp_ask.result.offers; + let bids = orderbook_resp_bid.result.offers; + + asks = asks.filter((ask) => ask.Account == market.walletAddress); + bids = bids.filter((bid) => bid.Account == market.walletAddress); + + for (const ask of asks) { + const price = ask.quality ?? '-1'; + let amount: string = ''; + + if (isIssuedCurrency(ask.TakerGets)) { + amount = ask.TakerGets.value; + } else { + amount = ask.TakerGets; + } + + openOrdersInMarket[String(ask.Sequence)] = { + sequence: ask.Sequence, + marketName: market.marketName, + price: price, + amount: amount, + side: OrderSide.SELL, + }; + } + + for (const bid of bids) { + const price = Math.pow(Number(bid.quality), -1).toString() ?? '-1'; + let amount: string = ''; + + if (isIssuedCurrency(bid.TakerGets)) { + amount = bid.TakerGets.value; + } else { + amount = bid.TakerGets; + } + + openOrdersInMarket[String(bid.Sequence)] = { + sequence: bid.Sequence, + marketName: market.marketName, + price: price, + amount: amount, + side: OrderSide.BUY, + }; + } + + openOrders[market.marketName] = openOrdersInMarket; + } + return openOrders; + } + + ready(): boolean { + return this._ready; + } + + isConnected(): boolean { + return this._client.isConnected(); + } + + public get client() { + return this._client; + } +} diff --git a/src/connectors/xrpldex/xrpldex.types.ts b/src/connectors/xrpldex/xrpldex.types.ts new file mode 100644 index 0000000000..de69b77fdc --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.types.ts @@ -0,0 +1,192 @@ +import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; +import { BookOffer } from 'xrpl'; + +export type IMap = ImmutableMap; +export const IMap = ImmutableMap; +export type ISet = ImmutableSet; +export const ISet = ImmutableSet; + +export enum OrderSide { + BUY = 'BUY', + SELL = 'SELL', +} + +export enum OrderStatus { + OPEN = 'OPEN', + CANCELED = 'CANCELED', + FILLED = 'FILLED', + PARTIALLY_FILLED = 'PARTIALLY_FILLED', + PENDING = 'PENDING', + FAILED = 'FAILED', + UNKNOWN = 'UNKNOWN', +} + +export enum OrderType { + LIMIT = 'LIMIT', + PASSIVE = 'PASSIVE', + IOC = 'IOC', // Immediate or Cancel + FOK = 'FOK', // Fill or Kill + SELL = 'SELL', // Sell +} + +export interface Token { + currency: string; + issuer: string; + value: string; +} + +export type GetMarketsRequest = + | Record + | { name: string } + | { names: string[] }; + +export interface GetMarketResponse { + name: string; + minimumOrderSize: number; + tickSize: number; + baseTransferRate: number; + quoteTransferRate: number; +} + +export interface Market { + name: string; + minimumOrderSize: number; + tickSize: number; + baseTransferRate: number; + quoteTransferRate: number; +} + +export type GetMarketsResponse = + | IMap + | GetMarketResponse; + +export type GetTickersRequest = + | Record + | { marketName: string } + | { marketNames: string[] }; + +export interface GetTickerResponse { + price: number; + timestamp: number; +} + +export type GetTickersResponse = + | IMap + | GetTickerResponse; + +export interface Ticker { + price: number; + timestamp: number; +} + +export interface GetOrderRequest { + sequence: number; + signature: string; +} + +export type GetOrdersRequest = + | Record + | { orders: GetOrderRequest[] }; + +export interface GetOrderResponse { + sequence: number; + status: OrderStatus; + signature: string; + transactionResult: string; +} + +export type GetOrdersResponse = Record; + +export type GetOrderBooksRequest = + | Record + | { marketName: string; limit: number } + | { marketNames: string[]; limit: number }; + +export interface GetOrderBookResponse { + market: GetMarketResponse; + topAsk: number; + topBid: number; + midPrice: number; + bids: BookOffer[]; + asks: BookOffer[]; + timestamp: number; +} + +export type GetOrderBooksResponse = + | IMap + | GetOrderBookResponse; + +export interface CreateOrderRequest { + walletAddress: string; + marketName: string; + side: OrderSide; + price: number; + amount: number; + type?: OrderType; + sequence?: number; +} + +export interface CreateOrderResponse { + walletAddress: string; + marketName: string; + price: number; + amount: number; + side: OrderSide; + status?: OrderStatus; + type?: OrderType; + fee?: number; + sequence: number; + orderLedgerIndex?: string; + signature?: string; + transactionResult?: string; +} + +export type CreateOrdersResponse = + | IMap + | CreateOrderResponse + | Record; + +export interface CancelOrderRequest { + walletAddress: string; + offerSequence: number; +} + +export type CancelOrdersRequest = + | Record + | { order: CancelOrderRequest } + | { orders: CancelOrderRequest[] }; + +export interface CancelOrderResponse { + walletAddress: string; + status?: OrderStatus; + signature?: string; + transactionResult?: string; +} + +export type CancelOrdersResponse = + | IMap + | CancelOrderResponse + | Record; + +export interface GetOpenOrderRequest { + marketName: string; + walletAddress: string; +} + +export interface GetOpenOrderResponse { + sequence: number; + marketName: string; + price: string; + amount: string; + side: OrderSide; +} + +export type GetOpenOrdersResponse = + | any + | IMap> + | IMap + | GetOpenOrderResponse; + +export class XRPLDEXishError extends Error {} + +export class MarketNotFoundError extends XRPLDEXishError {} diff --git a/src/connectors/xrpldex/xrpldex.validators.ts b/src/connectors/xrpldex/xrpldex.validators.ts new file mode 100644 index 0000000000..0ddb9b7c68 --- /dev/null +++ b/src/connectors/xrpldex/xrpldex.validators.ts @@ -0,0 +1,202 @@ +import { StatusCodes } from 'http-status-codes'; +import { HttpException } from '../../services/error-handler'; +// import { +// isBase58, +// isFloatString, +// isNaturalNumberString, +// } from '../../services/validators'; + +type Validator = ( + item: undefined | null | any | Item, + index?: number +) => { warnings: Array; errors: Array }; + +const createValidator = ( + accessor: undefined | null | string | ((target: any | Item) => any | Value), + validation: ( + item: undefined | null | any | Item, + value: undefined | null | any | Value + ) => boolean, + error: + | string + | (( + item: undefined | null | any | Item, + value: undefined | null | any | Value, + accessor: + | undefined + | null + | string + | ((target: any | Item) => any | Value), + index?: number + ) => string), + optional: boolean = false +): Validator => { + return (item: undefined | null | any | Item, index?: number) => { + const warnings: Array = []; + const errors: Array = []; + + let target: any | Value; + if (item === undefined && accessor) { + errors.push(`Request with undefined value informed when it shouldn't.`); + } else if (item === null && accessor) { + errors.push(`Request with null value informed when it shouldn't.`); + } else if (!accessor) { + target = item; + } else if (typeof accessor === 'string') { + if (!(`${accessor}` in item) && !optional) { + errors.push(`The request is missing the key/property "${accessor}".`); + } else { + target = item[accessor]; + } + } else { + target = accessor(item); + } + + if (!validation(item, target)) { + if (typeof error === 'string') { + if (optional) { + warnings.push(error); + } else { + errors.push(error); + } + } else { + if (optional) { + warnings.push(error(item, target, accessor, index)); + } else { + errors.push(error(item, target, accessor, index)); + } + } + } + + return { + warnings, + errors, + }; + }; +}; + +/** + Throw an error because the request parameter is malformed, collect all the + errors related to the request to give the most information possible + */ +export const throwIfErrorsExist = ( + errors: Array, + statusCode: number = StatusCodes.NOT_FOUND, + request: any, + headerMessage?: (request: any, errorNumber?: number) => string, + errorNumber?: number +): void => { + if (errors.length > 0) { + let message = headerMessage + ? `${headerMessage(request, errorNumber)}\n` + : ''; + message += errors.join('\n'); + + throw new HttpException(statusCode, message); + } +}; + +type RequestValidator = (item: undefined | null | any | Item) => { + warnings: Array; + errors: Array; +}; + +export const createRequestValidator = ( + validators: Array, + statusCode?: StatusCodes, + headerMessage?: (request: any) => string, + errorNumber?: number +): RequestValidator => { + return (request: undefined | null | any | Item) => { + let warnings: Array = []; + let errors: Array = []; + + for (const validator of validators) { + const result = validator(request); + warnings = [...warnings, ...result.warnings]; + errors = [...errors, ...result.errors]; + } + + throwIfErrorsExist(errors, statusCode, request, headerMessage, errorNumber); + + return { warnings, errors }; + }; +}; + +export const validateGetMarketRequest: RequestValidator = + createRequestValidator( + [ + createValidator( + null, + (request) => request.name, + `No market was informed. If you want to get a market, please inform the parameter "name".`, + false + ), + ], + StatusCodes.BAD_REQUEST + ); + +export const validateGetMarketsRequest: RequestValidator = + createRequestValidator( + [ + createValidator( + null, + (request) => request.names && request.names.length, + `No markets were informed. If you want to get all markets, please do not inform the parameter "names".`, + false + ), + ], + StatusCodes.BAD_REQUEST + ); + +export const validateGetOrderBookRequest: RequestValidator = + createRequestValidator( + [ + createValidator( + null, + (request) => request.marketName, + `No market name was informed. If you want to get an order book, please inform the parameter "marketName".`, + false + ), + ], + StatusCodes.BAD_REQUEST + ); + +export const validateGetOrderBooksRequest: RequestValidator = + createRequestValidator( + [ + createValidator( + null, + (request) => request.marketNames && request.marketNames.length, + `No market names were informed. If you want to get all order books, please do not inform the parameter "marketNames".`, + false + ), + ], + StatusCodes.BAD_REQUEST + ); + +export const validateGetTickerRequest: RequestValidator = + createRequestValidator( + [ + createValidator( + null, + (request) => request.marketName, + `No market name was informed. If you want to get a ticker, please inform the parameter "marketName".`, + false + ), + ], + StatusCodes.BAD_REQUEST + ); + +export const validateGetTickersRequest: RequestValidator = + createRequestValidator( + [ + createValidator( + null, + (request) => request.marketNames && request.marketNames.length, + `No market names were informed. If you want to get all tickers, please do not inform the parameter "marketNames".`, + false + ), + ], + StatusCodes.BAD_REQUEST + ); diff --git a/src/network/network.controllers.ts b/src/network/network.controllers.ts index 0f35a0d62b..76c269a3ee 100644 --- a/src/network/network.controllers.ts +++ b/src/network/network.controllers.ts @@ -19,6 +19,7 @@ import { getInitializedChain, UnsupportedChainException, } from '../services/connection-manager'; +import { XRPL } from '../chains/xrpl/xrpl'; export async function getStatus( req: StatusRequest @@ -102,6 +103,11 @@ export async function getStatus( connections = connections.concat( tezosConnections ? Object.values(tezosConnections) : [] ); + + const xrplConnections = XRPL.getConnectedInstances(); + connections = connections.concat( + xrplConnections ? Object.values(xrplConnections) : [] + ); } for (const connection of connections) { diff --git a/src/network/network.routes.ts b/src/network/network.routes.ts new file mode 100644 index 0000000000..b69c9234d4 --- /dev/null +++ b/src/network/network.routes.ts @@ -0,0 +1,123 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import { NextFunction, Request, Response, Router } from 'express'; +import * as ethereumControllers from '../chains/ethereum/ethereum.controllers'; +// import * as xrplControllers from '../chains/xrpl/xrpl.controllers'; +import { Ethereumish } from '../services/common-interfaces'; +import { ConfigManagerV2 } from '../services/config-manager-v2'; +import { getInitializedChain } from '../services/connection-manager'; +import { asyncHandler } from '../services/error-handler'; +import { + mkRequestValidator, + RequestValidator, + validateTxHash, +} from '../services/validators'; +import { getStatus, getTokens } from './network.controllers'; +import { + BalanceRequest, + BalanceResponse, + PollRequest, + PollResponse, + StatusRequest, + StatusResponse, + TokensRequest, + TokensResponse, +} from './network.requests'; +import { + validateBalanceRequest as validateEthereumBalanceRequest, + validateChain as validateEthereumChain, + validateNetwork as validateEthereumNetwork, +} from '../chains/ethereum/ethereum.validators'; +// import { XRPLish } from '../chains/xrpl/xrpl'; +// import { +// validateXRPLBalanceRequest, +// validateXRPLPollRequest, +// } from '../chains/xrpl/xrpl.validators'; +// import { +// XRPLBalanceResponse, +// XRPLPollRequest, +// XRPLPollResponse, +// } from '../chains/xrpl/xrpl.requests'; + +export const validatePollRequest: RequestValidator = mkRequestValidator([ + validateTxHash, +]); + +export const validateTokensRequest: RequestValidator = mkRequestValidator([ + validateEthereumChain, + validateEthereumNetwork, +]); + +export namespace NetworkRoutes { + export const router = Router(); + + router.get( + '/status', + asyncHandler( + async ( + req: Request<{}, {}, {}, StatusRequest>, + res: Response + ) => { + res.status(200).json(await getStatus(req.query)); + } + ) + ); + + router.get('/config', (_req: Request, res: Response) => { + res.status(200).json(ConfigManagerV2.getInstance().allConfigurations); + }); + + router.post( + '/balances', + asyncHandler( + async ( + req: Request<{}, {}, BalanceRequest>, + res: Response, + _next: NextFunction + ) => { + validateEthereumBalanceRequest(req.body); + const chain = await getInitializedChain( + req.body.chain, + req.body.network + ); + + // TODO: Add get xrpl balances + + res + .status(200) + .json(await ethereumControllers.balances(chain, req.body)); + } + ) + ); + + router.post( + '/poll', + asyncHandler( + async ( + req: Request<{}, {}, PollRequest>, + res: Response + ) => { + validatePollRequest(req.body); + + const chain = await getInitializedChain( + req.body.chain, + req.body.network + ); + + res.status(200).json(await ethereumControllers.poll(chain, req.body)); + } + ) + ); + + router.get( + '/tokens', + asyncHandler( + async ( + req: Request<{}, {}, {}, TokensRequest>, + res: Response + ) => { + validateTokensRequest(req.query); + res.status(200).json(await getTokens(req.query)); + } + ) + ); +} diff --git a/src/services/connection-manager.ts b/src/services/connection-manager.ts index 803f8c7c98..2d241878ee 100644 --- a/src/services/connection-manager.ts +++ b/src/services/connection-manager.ts @@ -6,6 +6,7 @@ import { Harmony } from '../chains/harmony/harmony'; import { Polygon } from '../chains/polygon/polygon'; import { Xdc } from '../chains/xdc/xdc'; import { Tezos } from '../chains/tezos/tezos'; +import { XRPL, XRPLish } from '../chains/xrpl/xrpl'; import { MadMeerkat } from '../connectors/mad_meerkat/mad_meerkat'; import { Openocean } from '../connectors/openocean/openocean'; import { Pangolin } from '../connectors/pangolin/pangolin'; @@ -18,6 +19,7 @@ import { VVSConnector } from '../connectors/vvs/vvs'; import { InjectiveCLOB } from '../connectors/injective/injective'; import { InjectiveClobPerp } from '../connectors/injective_perpetual/injective.perp'; import { Injective } from '../chains/injective/injective'; +import { XRPLDEX } from '../connectors/xrpldex/xrpldex'; import { CLOBish, Ethereumish, @@ -48,7 +50,8 @@ export type ChainUnion = | Nearish | Injective | Xdcish - | Tezosish; + | Tezosish + | XRPLish; export type Chain = T extends Algorand ? Algorand @@ -64,6 +67,8 @@ export type Chain = T extends Algorand ? Injective : T extends Tezosish ? Tezosish + : T extends XRPLish + ? XRPLish : never; export class UnsupportedChainException extends Error { @@ -125,6 +130,8 @@ export function getChainInstance( connection = Injective.getInstance(network); } else if (chain === 'tezos') { connection = Tezos.getInstance(network); + } else if (chain === 'xrpl') { + connection = XRPL.getInstance(network); } else { connection = undefined; } @@ -140,7 +147,8 @@ export type ConnectorUnion = | CLOBish | InjectiveClobPerp | Tinyman - | Plenty; + | Plenty + | XRPLDEX; export type Connector = T extends Uniswapish ? Uniswapish @@ -212,6 +220,8 @@ export async function getConnector( connectorInstance = Tinyman.getInstance(network); } else if (chain === 'tezos' && connector === 'plenty') { connectorInstance = Plenty.getInstance(network); + } else if (chain === 'xrpl' && connector === 'xrpldex') { + connectorInstance = XRPLDEX.getInstance(chain, network); } else { throw new Error('unsupported chain or connector'); } diff --git a/src/services/schema/xrpl-schema.json b/src/services/schema/xrpl-schema.json new file mode 100644 index 0000000000..6c7a9bf454 --- /dev/null +++ b/src/services/schema/xrpl-schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "networks": { + "type": "object", + "patternProperties": { + "^\\w+$": { + "type": "object", + "properties": { + "nodeURL": { "type": "string" }, + "tokenListType": { "type": "string" }, + "tokenListSource": { "type": "string" }, + "nativeCurrencySymbol": { "type": "string" } + }, + "required": [ + "nodeURL", + "tokenListType", + "tokenListSource", + "nativeCurrencySymbol" + ], + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "network": { "type": "string" }, + "requestTimeout": { "type": "integer" }, + "connectionTimeout": { "type": "integer" }, + "feeCushion": { "type": "number" }, + "maxFeeXRP": { "type": "string" }, + "retry": { + "type": "object", + "required": ["all"], + "properties": { + "all": { + "type": "object", + "required": ["maxNumberOfRetries", "delayBetweenRetries"], + "properties": { + "maxNumberOfRetries": { "type": "integer" }, + "delayBetweenRetries": { + "type": "integer" + } + } + } + } + }, + "timeout": { + "type": "object", + "required": ["all"], + "properties": { + "all": { "type": "integer" } + } + }, + "parallel": { + "type": "object", + "required": ["all"], + "properties": { + "all": { + "type": "object", + "required": ["batchSize", "delayBetweenBatches"], + "properties": { + "batchSize": { "type": "integer" }, + "delayBetweenBatches": { + "type": "integer" + } + } + } + } + } + }, + "additionalProperties": false +} diff --git a/src/services/wallet/wallet.controllers.ts b/src/services/wallet/wallet.controllers.ts index b82443cc56..d4dce61a1c 100644 --- a/src/services/wallet/wallet.controllers.ts +++ b/src/services/wallet/wallet.controllers.ts @@ -3,6 +3,7 @@ import { Xdc } from '../../chains/xdc/xdc'; import { Cosmos } from '../../chains/cosmos/cosmos'; import { Injective } from '../../chains/injective/injective'; import { Tezos } from '../../chains/tezos/tezos'; +import { XRPL } from '../../chains/xrpl/xrpl'; import { AddWalletRequest, @@ -135,9 +136,14 @@ export async function addWallet( throw new Error('Injective wallet requires a subaccount id'); } } else if (connection instanceof Tezos) { - const tezosWallet = await connection.getWalletFromPrivateKey(req.privateKey); + const tezosWallet = await connection.getWalletFromPrivateKey( + req.privateKey + ); address = await tezosWallet.signer.publicKeyHash(); - encryptedPrivateKey = connection.encrypt( + encryptedPrivateKey = connection.encrypt(req.privateKey, passphrase); + } else if (connection instanceof XRPL) { + address = connection.getWalletFromSeed(req.privateKey).classicAddress; + encryptedPrivateKey = await connection.encrypt( req.privateKey, passphrase ); @@ -170,9 +176,16 @@ export async function signMessage( if (req.chain === 'tezos') { const chain: Tezosish = await getInitializedChain(req.chain, req.network); const wallet = await chain.getWallet(req.address); - return { signature: (await wallet.signer.sign("0x03" + req.message)).sbytes.slice(4) }; + return { + signature: (await wallet.signer.sign('0x03' + req.message)).sbytes.slice( + 4 + ), + }; } else { - const chain: Ethereumish = await getInitializedChain(req.chain, req.network); + const chain: Ethereumish = await getInitializedChain( + req.chain, + req.network + ); const wallet = await chain.getWallet(req.address); return { signature: await wallet.signMessage(req.message) }; } diff --git a/src/services/wallet/wallet.validators.ts b/src/services/wallet/wallet.validators.ts index 1866191348..343b86098e 100644 --- a/src/services/wallet/wallet.validators.ts +++ b/src/services/wallet/wallet.validators.ts @@ -7,6 +7,10 @@ import { } from '../validators'; const { fromBase64 } = require('@cosmjs/encoding'); +import { + invalidXRPLPrivateKeyError, + isXRPLSeedKey, +} from '../../chains/xrpl/xrpl.validators'; export const invalidAlgorandPrivateKeyOrMnemonicError: string = 'The privateKey param is not a valid Algorand private key or mnemonic.'; @@ -59,7 +63,7 @@ export const isTezosPrivateKey = (str: string): boolean => { } catch { return false; } -} +}; // given a request, look for a key called privateKey that is an Ethereum private key export const validatePrivateKey: Validator = mkSelectingValidator( @@ -111,7 +115,6 @@ export const validatePrivateKey: Validator = mkSelectingValidator( invalidEthPrivateKeyError, (val) => typeof val === 'string' && isEthPrivateKey(val) ), - injective: mkValidator( 'privateKey', invalidEthPrivateKeyError, @@ -126,7 +129,12 @@ export const validatePrivateKey: Validator = mkSelectingValidator( 'privateKey', invalidTezosPrivateKeyError, (val) => typeof val === 'string' && isTezosPrivateKey(val) - ) + ), + xrpl: mkValidator( + 'privateKey', + invalidXRPLPrivateKeyError, + (val) => typeof val === 'string' && isXRPLSeedKey(val) + ), } ); @@ -159,8 +167,8 @@ export const validateChain: Validator = mkValidator( val === 'cosmos' || val === 'binance-smart-chain' || val === 'injective' || - val === 'tezos' - ) + val === 'tezos' || + val === 'xrpl') ); export const validateNetwork: Validator = mkValidator( diff --git a/src/templates/root.yml b/src/templates/root.yml index 6b79269053..86f6810e40 100644 --- a/src/templates/root.yml +++ b/src/templates/root.yml @@ -115,3 +115,7 @@ configurations: $namespace plenty: configurationPath: plenty.yml schemaPath: plenty-schema.json + + $namespace xrpl: + configurationPath: xrpl.yml + schemaPath: xrpl-schema.json diff --git a/src/templates/xrpl.yml b/src/templates/xrpl.yml new file mode 100644 index 0000000000..504f888b0e --- /dev/null +++ b/src/templates/xrpl.yml @@ -0,0 +1,31 @@ +networks: + mainnet: + nodeURL: wss://xrplcluster.com/ + tokenListType: 'FILE' + tokenListSource: 'src/chains/xrpl/xrpl_tokens_small.json' + nativeCurrencySymbol: 'XRP' + testnet: + nodeURL: wss://s.altnet.rippletest.net/ + tokenListType: 'FILE' + tokenListSource: 'src/chains/xrpl/xrpl_tokens_testnet.json' + nativeCurrencySymbol: 'XRP' + devnet: + nodeURL: wss://s.devnet.rippletest.net/ + tokenListType: 'FILE' + tokenListSource: 'src/chains/xrpl/xrpl_tokens_devnet.json' + nativeCurrencySymbol: 'XRP' +network: 'xrpl' +requestTimeout: 2000 +connectionTimeout: 2000 +feeCushion: 1.2 +maxFeeXRP: '2' +retry: + all: + maxNumberOfRetries: 3 # 0 means no retries + delayBetweenRetries: 1 # in milliseconds, 0 means no delay +timeout: + all: 0 # in milliseconds, 0 means no timeout. +parallel: + all: + batchSize: 1 # 0 means no batching, group all + delayBetweenBatches: 1 # in milliseconds, 0 means no delay \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d4b8424135..f1bb96ea7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6199,11 +6199,6 @@ assert@^2.0.0: object-is "^1.0.1" util "^0.12.0" -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -6463,16 +6458,6 @@ bech32@1.1.4, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bech32@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" - integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== - -big-integer@1.6.36: - version "1.6.36" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" - integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== - big-integer@^1.6.48: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -6563,12 +6548,15 @@ bip39@^2.5.0: safe-buffer "^5.0.1" unorm "^1.3.3" -bip39@^3.0.2, bip39@^3.0.3, bip39@^3.0.4: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" - integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== +bip39@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" + integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw== dependencies: - "@noble/hashes" "^1.2.0" + "@types/node" "11.11.6" + create-hash "^1.1.0" + pbkdf2 "^3.0.9" + randombytes "^2.0.1" bip66@^1.1.5: version "1.1.5" @@ -6839,14 +6827,7 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" -bs58@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" - integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== - dependencies: - base-x "^4.0.0" - -bs58check@2.1.2, bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: +bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== @@ -6916,7 +6897,15 @@ buffer@6.0.1: base64-js "^1.3.1" ieee754 "^1.2.1" -buffer@^5.0.5, buffer@^5.1.0, buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: +buffer@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + +buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -7791,11 +7780,16 @@ decimal.js-light@^2.5.0, decimal.js-light@^2.5.1: resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -decimal.js@^10.2.0, decimal.js@^10.2.1, decimal.js@^10.3.1, decimal.js@^10.4.3: +decimal.js@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +decimal.js@^10.2.1, decimal.js@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -8411,7 +8405,7 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw== -es6-promise@4.2.8, es6-promise@^4.0.3, es6-promise@^4.2.8: +es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -10281,14 +10275,6 @@ http-status-codes@^2.2.0: resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be" integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng== -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - http2-wrapper@^2.1.10: version "2.2.0" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.0.tgz#b80ad199d216b7d3680195077bd7b9060fa9d7f3" @@ -12701,6 +12687,11 @@ nan@^2.13.2, nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nan@^2.14.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" + integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== + nano-json-stream-parser@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" @@ -13007,6 +12998,14 @@ object-is@^1.0.1: call-bind "^1.0.2" define-properties "^1.1.3" +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -14462,18 +14461,18 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: hash-base "^3.0.0" inherits "^2.0.1" -ripple-address-codec@^4.1.1, ripple-address-codec@^4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.2.5.tgz#9d31b2066abd4cf1a135cd865b4e8e63269701e7" - integrity sha512-SZ96zZH+0REeyEcYVFl0vqcsGRXiFXS2RUgHupHhtVkOEk6men53vngVjJwBrSnY+oa6Cri15q1zSni3DEoxNw== +ripple-address-codec@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.2.4.tgz#a56c2168c8bb81269ea4d15ed96d6824c5a866f8" + integrity sha512-roAOjKz94+FboTItey1XRh5qynwt4xvfBLvbbcx+FiR94Yw2x3LrKLF2GVCMCSAh5I6PkcpADg6AbYsUbGN3nA== dependencies: base-x "^3.0.9" create-hash "^1.1.2" -ripple-binary-codec@^1.1.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.4.3.tgz#4737044f2aa5da496c1d57619339f26df01cd494" - integrity sha512-P4ALjAJWBJpRApTQO+dJCrHE6mZxm7ypZot9OS0a3RCKOWTReNw0pDWfdhCGh1qXh71TeQnAk4CHdMLwR/76oQ== +ripple-binary-codec@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.4.2.tgz#cdc35353e4bc7c3a704719247c82b4c4d0b57dd3" + integrity sha512-EDKIyZMa/6Ay/oNgCwjD9b9CJv0zmBreeHVQeG4BYwy+9GPnIQjNeT5e/aB6OjAnhcmpgbPeBmzwmNVwzxlt0w== dependencies: assert "^2.0.0" big-integer "^1.6.48" @@ -14482,10 +14481,10 @@ ripple-binary-codec@^1.1.3: decimal.js "^10.2.0" ripple-address-codec "^4.2.5" -ripple-keypairs@^1.0.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.1.5.tgz#eabfc371f2ef293fdc462664e18cbba32c4f5c7e" - integrity sha512-wLJXIBsMVazn2Yp/7oP4PvgA4Gd1HtuZLftdEJFNOLgraf82phqa2AnNK3t9f3XeQnApW1jAe/FcFFOY6QUn5w== +ripple-keypairs@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.1.4.tgz#4486fca703b8a2fc4f30cfd568478f3d12c1a911" + integrity sha512-PMMjTOxZmCSBOvHPj6bA+V/HGx7oFgDtGGI8VcZYuaFO2H87UX0X0jhfHy+LA2Xy31WYlD7GaDIDDt2QO+AMtw== dependencies: bn.js "^5.1.1" brorand "^1.0.5" @@ -14493,32 +14492,7 @@ ripple-keypairs@^1.0.3: hash.js "^1.0.3" ripple-address-codec "^4.2.5" -ripple-lib-transactionparser@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz#7aaad3ba1e1aeee1d5bcff32334a7a838f834dce" - integrity sha512-1teosQLjYHLyOQrKUQfYyMjDR3MAq/Ga+MJuLUfpBMypl4LZB4bEoMcmG99/+WVTEiZOezJmH9iCSvm/MyxD+g== - dependencies: - bignumber.js "^9.0.0" - lodash "^4.17.15" - -ripple-lib@^1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/ripple-lib/-/ripple-lib-1.10.1.tgz#9c353702792b25465cdb269265d6f5bb27b1471b" - integrity sha512-OQk+Syl2JfxKxV2KuF/kBMtnh012I5tNnziP3G4WDGCGSIAgeqkOgkR59IQ0YDNrs1YW8GbApxrdMSRi/QClcA== - dependencies: - "@types/lodash" "^4.14.136" - "@types/ws" "^7.2.0" - bignumber.js "^9.0.0" - https-proxy-agent "^5.0.0" - jsonschema "1.2.2" - lodash "^4.17.4" - ripple-address-codec "^4.1.1" - ripple-binary-codec "^1.1.3" - ripple-keypairs "^1.0.3" - ripple-lib-transactionparser "0.8.2" - ws "^7.2.0" - -rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4: +rlp@^2.2.3, rlp@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== @@ -15735,6 +15709,17 @@ tiny-secp256k1@^1.1.3, tiny-secp256k1@^1.1.6: elliptic "^6.4.0" nan "^2.13.2" +tiny-secp256k1@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c" + integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA== + dependencies: + bindings "^1.3.0" + bn.js "^4.11.8" + create-hmac "^1.1.7" + elliptic "^6.4.0" + nan "^2.13.2" + tiny-typed-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" @@ -17064,6 +17049,13 @@ wif@^2.0.6: dependencies: bs58check "<3.0.0" +wif@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== + dependencies: + bs58check "<3.0.0" + winston-daily-rotate-file@^4.5.5: version "4.7.1" resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz#f60a643af87f8867f23170d8cd87dbe3603a625f" @@ -17192,6 +17184,16 @@ ws@~8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +ws@^8.2.2: + version "8.12.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" + integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + integrity sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ== + xhr-request-promise@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" @@ -17275,6 +17277,21 @@ xmlhttprequest@1.8.0: resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== +xrpl@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/xrpl/-/xrpl-2.6.0.tgz#cfe9bc012e85721213f7f0e373ce5ff1d27e38c4" + integrity sha512-++rKtgO1j65TMm//mird3aGFGFYUn7VP9TjxwJkTUQZOdWkMUfiG2cd2o3tZ/zE07Ev8YVD09KgMZ9HzynJN9Q== + dependencies: + bignumber.js "^9.0.0" + bip32 "^2.0.6" + bip39 "^3.0.4" + https-proxy-agent "^5.0.0" + lodash "^4.17.4" + ripple-address-codec "^4.2.4" + ripple-binary-codec "^1.4.2" + ripple-keypairs "^1.1.4" + ws "^8.2.2" + xss@^1.0.8: version "1.0.14" resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.14.tgz#4f3efbde75ad0d82e9921cc3c95e6590dd336694" From 8ed27ad4c65e0df46edef715d52d377e0872228f Mon Sep 17 00:00:00 2001 From: La Hoang Date: Tue, 28 Feb 2023 01:17:00 +0700 Subject: [PATCH 03/39] add unified clob swagger schema --- docs/swagger/clob-beta.yml | 1410 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1410 insertions(+) create mode 100644 docs/swagger/clob-beta.yml diff --git a/docs/swagger/clob-beta.yml b/docs/swagger/clob-beta.yml new file mode 100644 index 0000000000..c9f9861767 --- /dev/null +++ b/docs/swagger/clob-beta.yml @@ -0,0 +1,1410 @@ +swagger: '2.0' +# TODO: Please verify if all clob-beta routes are compatible with Serum, if not please mark them and propose new changes +# TODO: Once the schema is final, move paths to clob-routes.yml and Merge definitions to definitions.yml + +info: + description: 'Gateway allows clients to interoperate with blockchains and DeFi protocols via a REST API. This allows for a language agnostic way to use official SDKs for blockchains.' + version: '1.0.0' + title: 'gateway' + contact: + email: 'dev@hummingbot.io' + license: + name: 'Apache 2.0' + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' + +host: 'localhost:15888' + +tags: + - name: 'clob-beta' + description: 'Interact with CLOB decentralized exchanges (BETA)' + +schemes: + - 'http' + +externalDocs: + description: 'Find out more about gateway' + url: 'https://github.com/hummingbot/hummingbot' + + +paths: + /clob-beta: + get: + tags: + - 'clob-beta' + summary: 'Verify Clob Routes Status' + description: 'Verify if the Clob routes are ready to use and show some other useful information. ' + operationId: 'clob.root' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBRootRequest' + + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBRootResponse' + + /clob-beta/markets: + get: + tags: + - 'clob-beta' + summary: 'Get One or More Markets' + description: 'Get the information of one, several or all available markets. Depending on the connector kind, response body might be different.' + operationId: 'clob.markets' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBGetMarketsRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBGetMarketsResponse' + '404': + description: 'Not found response.' + + /clob-beta/orderBooks: + get: + tags: + - 'clob-beta' + summary: 'Get One or More Order Books' + description: 'Get the information of one, several or all available order books. Depending on the connector kind, response body might be different.' + operationId: 'clob.orderBooks' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBGetOrderBooksRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBGetOrderBooksResponse' + '404': + description: 'Not found response.' + + /clob-beta/tickers: + get: + tags: + - 'clob-beta' + summary: 'Get middle price ticker for requested market' + operationId: 'tickers' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBGetTickersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBGetTickersResponse' + '404': + description: 'Not found response.' + + /clob-beta/orders: + get: + tags: + - 'clob-beta' + summary: 'Get status details of one or more orders' + operationId: 'getOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBGetOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBGetOrdersResponse' + '404': + description: 'Not found response.' + post: + tags: + - 'clob-beta' + summary: 'Create one or more orders' + description: 'Create one or more orders.' + operationId: 'createOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBPostCreateOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBPostCreateOrdersResponse' + '400': + description: 'Bad request response.' + delete: + tags: + - 'clob-beta' + summary: 'Cancel one or more orders' + description: 'Cancel one or more orders.' + operationId: 'cancelOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBDeleteOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBDeleteOrdersResponse' + '400': + description: 'Bad request response.' + + /clob-beta/orders/open: + get: + tags: + - 'clob-beta' + summary: 'Get One Or More Open Orders' + description: 'Get the information of one, several or all open orders.' + operationId: 'clob.getOpenOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBGetOpenOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBGetOpenOrdersResponse' + '400': + description: 'Bad request response.' + '404': + description: 'Not found response.' + + /clob-beta/orders/filled: + get: + tags: + - 'clob-beta' + summary: 'Get One Or More Filled Orders' + description: 'Get the information of one, several or all filled orders.' + operationId: 'clob.getFilledOrders' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBGetFilledOrdersRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBGetFilledOrdersResponse' + '400': + description: 'Bad request response.' + '404': + description: 'Not found response.' + + /clob-beta/settleFunds: + get: + tags: + - 'clob-beta' + summary: 'Settle Funds of One or More Markets' + description: 'Settle funds of one, several or all markets.' + operationId: 'clob.settleFunds' + consumes: + - 'application/json' + produces: + - 'application/json' + parameters: + - in: 'body' + name: 'body' + description: 'Request body.' + required: true + schema: + $ref: '#/definitions/CLOBPostSettleFundsRequest' + responses: + '200': + description: 'Successful response.' + schema: + $ref: '#/definitions/CLOBPostSettleFundsResponse' + '400': + description: 'Bad request response.' + '404': + description: 'Not found response.' + + +definitions: + CLOBRootRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + CLOBRootResponse: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + - 'connection' + - 'timestamp' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + connection: + type: boolean + example: true + timestamp: + type: number + example: 1652304454740 + + CLOBGetMarketsRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + name: + type: string + example: 'SOL/USDT' + names: + type: array + items: + type: string + example: + - 'SOL/USDT' + - 'SOL/USDC' + CLOBGetMarketsResponseItemCommon: + type: object + properties: + name: + type: string + example: SOL/USDT + deprecated: + type: boolean + example: false + minimumOrderSize: + type: number + example: 0.1 + tickSize: + type: number + example: 0.001 + minimumBaseIncrement: + type: string + example: 100000000 + CLOBGetMarketsResponseItemSerum: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBGetMarketsResponseItemCommon' + properties: + address: + type: string + example: HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1 + programId: + type: string + example: 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin + CLOBGetMarketrResponseItemXRPLDEX: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBGetMarketsResponseItemCommon' + properties: + name: + type: string + example: USD.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx/VND.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx + baseTransferRate: + type: number + example: 0.1 + quoteTransferRate: + type: number + example: 0.1 + CLOBGetMarketsResponse: + type: object + properties: + SOL/USDC: + $ref: '#/definitions/CLOBGetMarketsResponseItemSerum' + "USD.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx/VND.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx": + $ref: '#/definitions/CLOBGetMarketrResponseItemXRPLDEX' + + CLOBGetOrderBooksRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + marketName: + type: string + example: 'SOL/USDT' + marketNames: + type: array + items: + type: string + example: + - 'SOL/USDT' + - 'SOL/USDC' + CLOBGetOrderBooksResponseItemCommon: + type: object + properties: + market: + type: object + asks: + type: array + bids: + type: array + CLOBGetOrderBooksResponseItemSerum: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBGetOrderBooksResponseItemCommon' + properties: + market: + type: object + $ref: '#/definitions/CLOBGetMarketsResponseItemSerum' + bids: + type: array + items: + type: object + $ref: '#/definitions/CLOBGetOrderBooksResponseOrderBidItemCommon' + asks: + type: array + items: + type: object + $ref: '#/definitions/CLOBGetOrderBooksResponseOrderAskItemCommon' + CLOBGetOrderBooksResponseItemXRPLDEX: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBGetOrderBooksResponseItemCommon' + properties: + market: + type: object + $ref: '#/definitions/CLOBGetMarketrResponseItemXRPLDEX' + bids: + type: array + items: + type: object + $ref: '#/definitions/CLOBGetOrderBooksResponseOrderBidItemCommon' + asks: + type: array + items: + type: object + $ref: '#/definitions/CLOBGetOrderBooksResponseOrderAskItemCommon' + topAsk: + type: number + example: 25000 + topBid: + type: number + example: 24000 + midPrice: + type: number + example: 24500 + timestamp: + type: number + example: 1676296168012 + CLOBGetOrderBooksResponseOrderBidItemCommon: + type: object + properties: + id: + type: string + example: 35332426 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + price: + type: number + example: 250 + amount: + type: number + example: 0.1 + side: + type: string + example: BUY + status: + type: string + example: OPEN + 'type': + type: string + example: LIMIT + CLOBGetOrderBooksResponseOrderAskItemCommon: + type: object + properties: + id: + type: string + example: 35332426 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + price: + type: number + example: 250 + amount: + type: number + example: 0.1 + side: + type: string + example: SELL + status: + type: string + example: OPEN + 'type': + type: string + example: LIMIT + CLOBGetOrderBooksResponse: + type: object + properties: + SOL/USDC: + $ref: '#/definitions/CLOBGetOrderBooksResponseItemSerum' + "USD.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx/VND.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx": + $ref: '#/definitions/CLOBGetOrderBooksResponseItemXRPLDEX' + + CLOBGetTickersRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + marketName: + type: string + example: 'SOL/USDT' + marketNames: + type: array + items: + type: string + example: + - 'SOL/USDT' + - 'SOL/USDC' + CLOBGetTickersResponseItem: + type: object + properties: + price: + type: number + example: 43.37523838 + timestamp: + type: number + example: 1652363040000 + CLOBGetTickersResponse: + type: object + properties: + SOL/USDT: + $ref: '#/definitions/CLOBGetTickersResponseItem' + SOL/USDC: + $ref: '#/definitions/CLOBGetTickersResponseItem' + + CLOBGetOrdersRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + order: + type: object + required: + - 'ownerAddress' + properties: + id: + type: string + example: 123456789 + exchangeId: + type: string + example: 184467256269654779094895731 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + orders: + type: array + items: + type: object + required: + - 'ownerAddress' + properties: + ids: + type: array + items: + type: string + exchangeIds: + type: array + items: + type: string + marketName: + type: string + ownerAddress: + type: string + example: + - ids: ["123456789"] + exchangeIds: ["184467256269654779094895731"] + marketName: SOL/USDT + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["123456789"] + exchangeIds: ["184467256269654779094895731"] + marketName: SOL/USDC + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["1112234"] + exchangeIds: ["33485803"] + marketName: XRP/USDT + ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV + CLOBGetOrdersResponseItemCommon: + type: 'object' + properties: + id: + type: string + exchangeId: + type: string + marketName: + type: string + ownderAddress: + type: string + side: + type: string + price: + type: number + amount: + type: number + type: + type: string + status: + type: string + fee: + type: number + example: + id: "123456789" + exchangeId: "184467256269654779094895731" + marketName: "SOL/USDT" + ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "OPEN" + fee: 0.1 + CLOBGetOrdersResponseItemXRPLDEX: + type: 'object' + allOf: + - type: object + - $ref: '#/definitions/CLOBGetOrdersResponseItemCommon' + properties: + signature: + type: string + transactionResult: + type: string + orderLedgerIndex: + type: string + example: + id: "1112234" + exchangeId: "33485803" + marketName: "XRP/USDT" + ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "OPEN" + signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" + transactionResult: "tesSUCCESS" + orderLedgerIndex: "35357471" + CLOBGetOrdersResponse: + type: object + properties: + SOL/USDC: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBGetOrdersResponseItemCommon' + + SOL/USDT: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBGetOrdersResponseItemCommon' + + XRP/USDT: + type: object + properties: + "1112234": + $ref: '#/definitions/CLOBGetOrdersResponseItemXRPLDEX' + + CLOBPostCreateOrdersRequestCommon: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + order: + type: object + required: + - 'id' + - 'ownerAddress' + - 'marketName' + - 'side' + - 'price' + - 'amount' + - 'type' + properties: + id: + type: string + example: 123456789 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + side: + type: string + example: BUY + price: + type: number + example: 9999.9 + amount: + type: number + example: 0.1 + type: + type: string + example: LIMIT + orders: + type: array + items: + type: object + required: + - 'id' + - 'ownerAddress' + - 'marketName' + - 'side' + - 'price' + - 'amount' + - 'type' + properties: + id: + type: string + example: 123456789 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + side: + type: string + example: BUY + price: + type: number + example: 9999.9 + amount: + type: number + example: 0.1 + type: + type: string + example: LIMIT + CLOBPostCreateOrdersRequestSerum: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBPostCreateOrdersRequestCommon' + properties: + order: + type: object + properties: + payerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + orders: + type: array + items: + type: object + properties: + payerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + CLOBPostCreateOrdersRequestXRPLDEX: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBPostCreateOrdersRequestCommon' + properties: + waitUntilIncludedInBlock: + type: boolean + example: true + CLOBPostCreateOrdersRequest: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBPostCreateOrdersRequestSerum' + - $ref: '#/definitions/CLOBPostCreateOrdersRequestXRPLDEX' + CLOBPostCreateOrdersResponseItemCommon: + type: object + properties: + id: + type: string + example: 123456789 + exchangeId: + type: string + example: 184467256269654779094895731 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + price: + type: number + example: 250 + amount: + type: number + example: 0.1 + side: + type: string + example: BUY + status: + type: string + example: OPEN + type: + type: string + example: LIMIT + fee: + type: number + example: 0.1 + CLOBPostCreateOrdersResponseItemXRPLDEX: + type: object + allOf: + - type: object + - $ref: '#/definitions/CLOBPostCreateOrdersResponseItemCommon' + properties: + signature: + type: string + example: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" + transactionResult: + type: string + example: "tesSUCCESS" + orderLedgerIndex: + type: string + example: "35357471" + CLOBPostCreateOrdersResponse: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBPostCreateOrdersResponseItemCommon' + "1112234": + type: object + allOf: + - $ref: '#/definitions/CLOBPostCreateOrdersResponseItemXRPLDEX' + example: + id: "1112234" + exchangeId: "33485803" + marketName: "XRP/USDT" + ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "OPEN" + signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" + transactionResult: "tesSUCCESS" + orderLedgerIndex: "35357471" + + CLOBDeleteOrdersRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + order: + type: object + required: + - 'ownerAddress' + properties: + id: + type: string + example: 123456789 + exchangeId: + type: string + example: 184467256269654779094895731 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + orders: + type: array + items: + type: object + required: + - 'ownerAddress' + properties: + ids: + type: array + items: + type: string + exchangeIds: + type: array + items: + type: string + marketName: + type: string + ownerAddress: + type: string + example: + - ids: ["123456789", "123456789"] + exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] + marketName: SOL/USDT + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["123456789", "123456789"] + exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] + marketName: SOL/USDC + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["1112234"] + exchangeIds: ["33485803"] + marketName: XRP/USDT + ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV + CLOBDeleteOrdersResponseItemCommon: + type: 'object' + properties: + id: + type: string + exchangeId: + type: string + marketName: + type: string + ownderAddress: + type: string + side: + type: string + price: + type: number + amount: + type: number + type: + type: string + status: + type: string + fee: + type: number + example: + id: "123456789" + exchangeId: "184467256269654779094895731" + marketName: "SOL/USDT" + ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "CANCELED" + fee: 0.1 + CLOBDeleteOrdersResponseItemXRPLDEX: + type: 'object' + allOf: + - type: object + - $ref: '#/definitions/CLOBDeleteOrdersResponseItemCommon' + properties: + signature: + type: string + transactionResult: + type: string + orderLedgerIndex: + type: string + example: + id: "1112234" + exchangeId: "33485803" + marketName: "XRP/USDT" + ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "CANCELED" + signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" + transactionResult: "tesSUCCESS" + orderLedgerIndex: "35357471" + CLOBDeleteOrdersResponse: + type: object + properties: + SOL/USDC: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBDeleteOrdersResponseItemCommon' + + SOL/USDT: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBDeleteOrdersResponseItemCommon' + + XRP/USDT: + type: object + properties: + "33485803": + $ref: '#/definitions/CLOBDeleteOrdersResponseItemXRPLDEX' + + CLOBGetOpenOrdersRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + order: + type: object + required: + - 'ownerAddress' + properties: + id: + type: string + example: 123456789 + exchangeId: + type: string + example: 184467256269654779094895731 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + orders: + type: array + items: + type: object + required: + - 'ownerAddress' + properties: + ids: + type: array + items: + type: string + exchangeIds: + type: array + items: + type: string + marketName: + type: string + ownerAddress: + type: string + example: + - ids: ["123456789", "987654321"] + exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] + marketName: SOL/USDT + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["123456789", "987654321"] + exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] + marketName: SOL/USDC + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["1112234", "6783991"] + exchangeIds: ["33485803", "33485000"] + marketName: XRP/USDT + ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV + CLOBGetOpenOrdersResponseItemCommon: + type: 'object' + properties: + id: + type: string + exchangeId: + type: string + marketName: + type: string + ownderAddress: + type: string + side: + type: string + price: + type: number + amount: + type: number + type: + type: string + status: + type: string + fee: + type: number + example: + id: "123456789" + exchangeId: "184467256269654779094895731" + marketName: "SOL/USDT" + ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "OPEN" + fee: 0.1 + CLOBGetOpenOrdersResponseItemXRPLDEX: + type: 'object' + allOf: + - type: object + - $ref: '#/definitions/CLOBGetOpenOrdersResponseItemCommon' + properties: + signature: + type: string + transactionResult: + type: string + orderLedgerIndex: + type: string + example: + id: "1112234" + exchangeId: "33485803" + marketName: "XRP/USDT" + ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "OPEN" + signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" + transactionResult: "tesSUCCESS" + orderLedgerIndex: "35357471" + CLOBGetOpenOrdersResponse: + type: object + properties: + SOL/USDC: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBGetOpenOrdersResponseItemCommon' + + SOL/USDT: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBGetOpenOrdersResponseItemCommon' + + XRP/USDT: + type: object + properties: + "1112234": + $ref: '#/definitions/CLOBGetOpenOrdersResponseItemXRPLDEX' + + CLOBGetFilledOrdersRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + order: + type: object + required: + - 'ownerAddress' + properties: + id: + type: string + example: 123456789 + exchangeId: + type: string + example: 184467256269654779094895731 + marketName: + type: string + example: SOL/USDT + ownerAddress: + type: string + example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + orders: + type: array + items: + type: object + required: + - 'ownerAddress' + properties: + ids: + type: array + items: + type: string + exchangeIds: + type: array + items: + type: string + marketName: + type: string + ownerAddress: + type: string + example: + - ids: ["123456789", "987654321"] + exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] + marketName: SOL/USDT + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["123456789", "987654321"] + exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] + marketName: SOL/USDC + ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa + - ids: ["1112234", "6783991"] + exchangeIds: ["33485803", "33485000"] + marketName: XRP/USDT + ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV + CLOBGetFilledOrdersResponseItemCommon: + type: 'object' + properties: + id: + type: string + exchangeId: + type: string + marketName: + type: string + ownderAddress: + type: string + side: + type: string + price: + type: number + amount: + type: number + type: + type: string + status: + type: string + fee: + type: number + example: + id: "123456789" + exchangeId: "184467256269654779094895731" + marketName: "SOL/USDT" + ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "FILLED" + fee: 0.1 + CLOBGetFilledOrdersResponseItemXRPLDEX: + type: 'object' + allOf: + - type: object + - $ref: '#/definitions/CLOBGetFilledOrdersResponseItemCommon' + properties: + signature: + type: string + transactionResult: + type: string + orderLedgerIndex: + type: string + example: + id: "1112234" + exchangeId: "33485803" + marketName: "XRP/USDT" + ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" + side: "BUY" + price: 999.99 + amount: 0.1 + type: "LIMIT" + status: "FILLED" + signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" + transactionResult: "tesSUCCESS" + orderLedgerIndex: "35357471" + CLOBGetFilledOrdersResponse: + type: object + properties: + SOL/USDC: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBGetFilledOrdersResponseItemCommon' + + SOL/USDT: + type: object + properties: + "123456789": + $ref: '#/definitions/CLOBGetFilledOrdersResponseItemCommon' + + XRP/USDT: + type: object + properties: + "1112234": + $ref: '#/definitions/CLOBGetFilledOrdersResponseItemXRPLDEX' + + CLOBPostSettleFundsRequest: + type: 'object' + required: + - 'chain' + - 'network' + - 'connector' + - 'ownerAddress' + properties: + chain: + type: string + example: solana + network: + type: string + example: mainnet-beta + connector: + type: string + example: serum + marketName: + type: string + example: SOL/USDT + marketNames: + type: array + items: + type: string + example: + - SOL/USDT + - BTC/USDT + CLOBPostSettleFundsResponse: + type: object + properties: + SOL/USDT: + type: array + items: + type: string + example: + - 4vAstoT7dgJ3LaexYRQbJ4HwbV8vPaMEUWVP3o89oq4vJcn9E11LJHyEpoc3sZ4dxmAtZGU7YyRS1uR36wuUEQMK + BTC/USDT: + type: array + items: + type: string + example: + - 4vAstoT7dgJ3LaexYRQbJ4HwbV8vPaMEUWVP3o89oq4vJcn9E11LJHyEpoc3sZ4dxmAtZGU7YyRS1uR36wuUEQMK From f01cf296eaa934c95b9fd3c5d3ee1209f7e4bc10 Mon Sep 17 00:00:00 2001 From: La Hoang Date: Wed, 1 Mar 2023 22:44:31 +0700 Subject: [PATCH 04/39] Fix swagger missing field error for get orderbook --- docs/swagger/clob-beta.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/swagger/clob-beta.yml b/docs/swagger/clob-beta.yml index c9f9861767..ded2ed7a35 100644 --- a/docs/swagger/clob-beta.yml +++ b/docs/swagger/clob-beta.yml @@ -441,8 +441,14 @@ definitions: type: object asks: type: array + items: + type: object + $ref: '#/definitions/CLOBGetOrderBooksResponseOrderAskItemCommon' bids: type: array + items: + type: object + $ref: '#/definitions/CLOBGetOrderBooksResponseOrderBidItemCommon' CLOBGetOrderBooksResponseItemSerum: type: object allOf: From 14d0e874938ab48d58dd33dafe3ce07cd984fc17 Mon Sep 17 00:00:00 2001 From: mlguys Date: Fri, 24 Mar 2023 19:30:58 +0700 Subject: [PATCH 05/39] update yarn lock --- yarn.lock | 131 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 44 deletions(-) diff --git a/yarn.lock b/yarn.lock index f1bb96ea7c..403f81e100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6199,6 +6199,11 @@ assert@^2.0.0: object-is "^1.0.1" util "^0.12.0" +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -6458,6 +6463,16 @@ bech32@1.1.4, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + +big-integer@1.6.36: + version "1.6.36" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" + integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== + big-integer@^1.6.48: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -6548,6 +6563,13 @@ bip39@^2.5.0: safe-buffer "^5.0.1" unorm "^1.3.3" +bip39@^3.0.2, bip39@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" + integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + dependencies: + "@noble/hashes" "^1.2.0" + bip39@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" @@ -6827,7 +6849,14 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" -bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + +bs58check@2.1.2, bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== @@ -6897,15 +6926,7 @@ buffer@6.0.1: base64-js "^1.3.1" ieee754 "^1.2.1" -buffer@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: +buffer@^5.0.5, buffer@^5.1.0, buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -7780,7 +7801,7 @@ decimal.js-light@^2.5.0, decimal.js-light@^2.5.1: resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -decimal.js@^10.2.0: +decimal.js@^10.2.0, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -8405,7 +8426,7 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw== -es6-promise@^4.2.8: +es6-promise@4.2.8, es6-promise@^4.0.3, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -10275,6 +10296,14 @@ http-status-codes@^2.2.0: resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be" integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng== +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + http2-wrapper@^2.1.10: version "2.2.0" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.0.tgz#b80ad199d216b7d3680195077bd7b9060fa9d7f3" @@ -12998,14 +13027,6 @@ object-is@^1.0.1: call-bind "^1.0.2" define-properties "^1.1.3" -object-is@^1.0.1: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -14461,6 +14482,14 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: hash-base "^3.0.0" inherits "^2.0.1" +ripple-address-codec@^4.1.1, ripple-address-codec@^4.2.5: + version "4.2.5" + resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.2.5.tgz#9d31b2066abd4cf1a135cd865b4e8e63269701e7" + integrity sha512-SZ96zZH+0REeyEcYVFl0vqcsGRXiFXS2RUgHupHhtVkOEk6men53vngVjJwBrSnY+oa6Cri15q1zSni3DEoxNw== + dependencies: + base-x "^3.0.9" + create-hash "^1.1.2" + ripple-address-codec@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.2.4.tgz#a56c2168c8bb81269ea4d15ed96d6824c5a866f8" @@ -14469,6 +14498,18 @@ ripple-address-codec@^4.2.4: base-x "^3.0.9" create-hash "^1.1.2" +ripple-binary-codec@^1.1.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.4.3.tgz#4737044f2aa5da496c1d57619339f26df01cd494" + integrity sha512-P4ALjAJWBJpRApTQO+dJCrHE6mZxm7ypZot9OS0a3RCKOWTReNw0pDWfdhCGh1qXh71TeQnAk4CHdMLwR/76oQ== + dependencies: + assert "^2.0.0" + big-integer "^1.6.48" + buffer "5.6.0" + create-hash "^1.2.0" + decimal.js "^10.2.0" + ripple-address-codec "^4.2.5" + ripple-binary-codec@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.4.2.tgz#cdc35353e4bc7c3a704719247c82b4c4d0b57dd3" @@ -14492,7 +14533,32 @@ ripple-keypairs@^1.1.4: hash.js "^1.0.3" ripple-address-codec "^4.2.5" -rlp@^2.2.3, rlp@^2.2.4: +ripple-lib-transactionparser@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz#7aaad3ba1e1aeee1d5bcff32334a7a838f834dce" + integrity sha512-1teosQLjYHLyOQrKUQfYyMjDR3MAq/Ga+MJuLUfpBMypl4LZB4bEoMcmG99/+WVTEiZOezJmH9iCSvm/MyxD+g== + dependencies: + bignumber.js "^9.0.0" + lodash "^4.17.15" + +ripple-lib@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/ripple-lib/-/ripple-lib-1.10.1.tgz#9c353702792b25465cdb269265d6f5bb27b1471b" + integrity sha512-OQk+Syl2JfxKxV2KuF/kBMtnh012I5tNnziP3G4WDGCGSIAgeqkOgkR59IQ0YDNrs1YW8GbApxrdMSRi/QClcA== + dependencies: + "@types/lodash" "^4.14.136" + "@types/ws" "^7.2.0" + bignumber.js "^9.0.0" + https-proxy-agent "^5.0.0" + jsonschema "1.2.2" + lodash "^4.17.4" + ripple-address-codec "^4.1.1" + ripple-binary-codec "^1.1.3" + ripple-keypairs "^1.0.3" + ripple-lib-transactionparser "0.8.2" + ws "^7.2.0" + +rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== @@ -15709,17 +15775,6 @@ tiny-secp256k1@^1.1.3, tiny-secp256k1@^1.1.6: elliptic "^6.4.0" nan "^2.13.2" -tiny-secp256k1@^1.1.3: - version "1.1.6" - resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c" - integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA== - dependencies: - bindings "^1.3.0" - bn.js "^4.11.8" - create-hmac "^1.1.7" - elliptic "^6.4.0" - nan "^2.13.2" - tiny-typed-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" @@ -17049,13 +17104,6 @@ wif@^2.0.6: dependencies: bs58check "<3.0.0" -wif@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" - integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== - dependencies: - bs58check "<3.0.0" - winston-daily-rotate-file@^4.5.5: version "4.7.1" resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz#f60a643af87f8867f23170d8cd87dbe3603a625f" @@ -17184,11 +17232,6 @@ ws@~8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== -ws@^8.2.2: - version "8.12.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" - integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== - xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" From b0a0bc105be0bff0faa33f13dc39fb5f151bf8b9 Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 1 Jun 2023 16:46:50 +0700 Subject: [PATCH 06/39] wip --- src/chains/xrpl/xrpl.config.ts | 6 + src/chains/xrpl/xrpl.order-storage.ts | 84 ++++ src/chains/xrpl/xrpl.ts | 57 +++ src/connectors/xrpl/xrpl.clob.config.ts | 0 src/connectors/xrpl/xrpl.order-tracker.ts | 91 ++++ src/connectors/xrpl/xrpl.ts | 537 ++++++++++++++++++++++ src/connectors/xrpl/xrpl.types.ts | 219 +++++++++ 7 files changed, 994 insertions(+) create mode 100644 src/chains/xrpl/xrpl.order-storage.ts create mode 100644 src/connectors/xrpl/xrpl.clob.config.ts create mode 100644 src/connectors/xrpl/xrpl.order-tracker.ts create mode 100644 src/connectors/xrpl/xrpl.ts create mode 100644 src/connectors/xrpl/xrpl.types.ts diff --git a/src/chains/xrpl/xrpl.config.ts b/src/chains/xrpl/xrpl.config.ts index 42c4f83ab9..0ef86b030e 100644 --- a/src/chains/xrpl/xrpl.config.ts +++ b/src/chains/xrpl/xrpl.config.ts @@ -6,6 +6,7 @@ export interface NetworkConfig { tokenListType: string; // default: FILE tokenListSource: string; // default: src/chains/xrpl/xrpl_tokens.json nativeCurrencySymbol: string; // XRP + maxLRUCacheInstances: number; } export interface Config { @@ -15,6 +16,7 @@ export interface Config { connectionTimeout: number; // default: 5 feeCushion: number; // default: 1.2 maxFeeXRP: string; // default: 2 + orderDbPath: string; } // @todo: find out which configs are required @@ -35,6 +37,9 @@ export function getXRPLConfig(chainName: string, networkName: string): Config { nativeCurrencySymbol: ConfigManagerV2.getInstance().get( chainName + '.networks.' + networkName + '.nativeCurrencySymbol' ), + maxLRUCacheInstances: ConfigManagerV2.getInstance().get( + chainName + '.maxLRUCacheInstances' + ), }, requestTimeout: ConfigManagerV2.getInstance().get( chainName + '.requestTimeout' @@ -44,5 +49,6 @@ export function getXRPLConfig(chainName: string, networkName: string): Config { ), feeCushion: ConfigManagerV2.getInstance().get(chainName + '.feeCushion'), maxFeeXRP: ConfigManagerV2.getInstance().get(chainName + '.maxFeeXRP'), + orderDbPath: ConfigManagerV2.getInstance().get(chainName + '.orderDbPath'), }; } diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts new file mode 100644 index 0000000000..a1eadec795 --- /dev/null +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -0,0 +1,84 @@ +import { LocalStorage } from '../../services/local-storage'; +import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; +import { Order } from '../../connectors/xrpl/xrpl.types'; +import { OrderStatus } from '../../connectors/xrpl/xrpl.types'; + +// store the order for when a transaction was initiated +// this will be used to monitor the order status when +// the order is included in the orderbook on XRP Ledger +export class XRPLOrderStorage extends ReferenceCountingCloseable { + readonly localStorage: LocalStorage; + + protected constructor(dbPath: string) { + super(dbPath); + this.localStorage = LocalStorage.getInstance(dbPath, this.handle); + } + + public async init(): Promise { + await this.localStorage.init(); + } + + public async saveOrder( + chain: string, + chainId: string, + order: Order + ): Promise { + return this.localStorage.save( + chain + '/' + chainId + '/' + order.hash, + JSON.stringify(order) + ); + } + + public async deleteOrder( + chain: string, + chainId: string, + order: Order + ): Promise { + return this.localStorage.del(chain + '/' + chainId + '/' + order.hash); + } + + public async getOrders( + chain: string, + chainId: string + ): Promise> { + return this.localStorage.get((key: string, value: string) => { + const splitKey = key.split('/'); + if ( + splitKey.length === 3 && + splitKey[0] === chain && + splitKey[1] === chainId + ) { + return [splitKey[2], JSON.parse(value)]; + } + return; + }); + } + + public async getOrdersByState( + chain: string, + chainId: string, + state: OrderStatus + ): Promise> { + return this.localStorage.get((key: string, value: string) => { + const splitKey = key.split('/'); + if ( + splitKey.length === 3 && + splitKey[0] === chain && + splitKey[1] === chainId + ) { + const order: Order = JSON.parse(value); + if (order.state === state) { + return [splitKey[2], order]; + } + } + return; + }); + } + + public async close(handle: string): Promise { + await super.close(handle); + if (this.refCount < 1) { + await this.localStorage.close(this.handle); + } + } +} diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 137442aa1b..ed520a142c 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -14,11 +14,15 @@ import axios from 'axios'; import { promises as fs } from 'fs'; import crypto from 'crypto'; import fse from 'fs-extra'; +import path from 'path'; +import { rootPath } from '../../paths'; import { TokenListType, walletPath } from '../../services/base'; import { ConfigManagerCertPassphrase } from '../../services/config-manager-cert-passphrase'; import { getXRPLConfig } from './xrpl.config'; import { logger } from '../../services/logger'; import { TransactionResponseStatusCode } from './xrpl.requests'; +import { XRPLOrderStorage } from './xrpl.order-storage'; +import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; export type TrustlineInfo = { id: number; @@ -35,9 +39,17 @@ export type TokenBalance = { value: string; }; +export type Fee = { + base: string; + median: string; + minimum: string; + openLedger: string; +}; + export class XRPL implements XRPLish { private static _instances: { [name: string]: XRPL }; public rpcUrl; + public fee: Fee; protected tokenList: TrustlineInfo[] = []; private _tokenMap: Record = {}; @@ -54,6 +66,9 @@ export class XRPL implements XRPLish { private _ready: boolean = false; private initializing: boolean = false; + private readonly _refCountingHandle: string; + private readonly _orderStorage: XRPLOrderStorage; + private constructor(network: string) { const config = getXRPLConfig('xrpl', network); @@ -71,6 +86,12 @@ export class XRPL implements XRPLish { maxFeeXRP: config.maxFeeXRP, }); + this.fee = { + base: '0', + median: '0', + minimum: '0', + openLedger: '0', + }; // this._client.connect(); this._requestCount = 0; @@ -78,6 +99,13 @@ export class XRPL implements XRPLish { this.onValidationReceived(this.requestCounter.bind(this)); setInterval(this.metricLogger.bind(this), this.metricsLogInterval); + + this._refCountingHandle = ReferenceCountingCloseable.createHandle(); + this._orderStorage = XRPLOrderStorage.getInstance( + this.resolveDBPath(config.orderDbPath), + this._refCountingHandle + ); + this._orderStorage.declareOwnership(this._refCountingHandle); } public static getInstance(network: string): XRPL { @@ -95,6 +123,13 @@ export class XRPL implements XRPLish { return XRPL._instances; } + public resolveDBPath(oldPath: string): string { + if (oldPath.charAt(0) === '/') return oldPath; + const dbDir: string = path.join(rootPath(), 'db/'); + fse.mkdirSync(dbDir, { recursive: true }); + return path.join(dbDir, oldPath); + } + public get client() { return this._client; } @@ -142,6 +177,7 @@ export class XRPL implements XRPLish { this.initializing = true; await this._client.connect(); await this.loadTokens(this._tokenListSource, this._tokenListType); + await this.getFee(); this._ready = true; this.initializing = false; } @@ -360,9 +396,30 @@ export class XRPL implements XRPLish { async close() { if (this._network in XRPL._instances) { + await this._orderStorage.close(this._refCountingHandle); delete XRPL._instances[this._network]; } } + + async getFee() { + await this.ensureConnection(); + const tx_resp = await this._client.request({ + command: 'fee', + }); + + this.fee = { + base: tx_resp.result.drops.base_fee, + median: tx_resp.result.drops.median_fee, + minimum: tx_resp.result.drops.minimum_fee, + openLedger: tx_resp.result.drops.open_ledger_fee, + }; + + return this.fee; + } + + public get orderStorage(): XRPLOrderStorage { + return this._orderStorage; + } } export type XRPLish = XRPL; diff --git a/src/connectors/xrpl/xrpl.clob.config.ts b/src/connectors/xrpl/xrpl.clob.config.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/connectors/xrpl/xrpl.order-tracker.ts b/src/connectors/xrpl/xrpl.order-tracker.ts new file mode 100644 index 0000000000..db9cf85760 --- /dev/null +++ b/src/connectors/xrpl/xrpl.order-tracker.ts @@ -0,0 +1,91 @@ +import { Client } from 'xrpl'; +import { XRPL } from '../../chains/xrpl/xrpl'; +import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; +import { OrderStatus, TradeType, Order } from './xrpl.types'; +import LRUCache from 'lru-cache'; +import { XRPLOrderStorage } from '../../chains/xrpl/xrpl.order-storage'; + +// This class should: +// 1. Track orders that are created by xrpl.postOrder +// 2. Track orders that are deleted by xrpl.deleteOrder +// 3. Pool the XRP Ledger to update the inflight orders status +// 4. Provide interface to get order by states + +export class OrderTracker { + private static _instances: LRUCache; + private readonly _xrpl: XRPL; + private readonly _orderStorage: XRPLOrderStorage; + private _trackingId: any; + + public chain: string; + public network: string; + + private constructor(chain: string, network: string) { + this.chain = chain; + this.network = network; + + this._xrpl = XRPL.getInstance(network); + this._orderStorage = this._xrpl.orderStorage; + this._trackingId = undefined; + } + + public static getInstance(chain: string, network: string): OrderTracker { + if (OrderTracker._instances === undefined) { + const config = getXRPLConfig(chain, network); + OrderTracker._instances = new LRUCache({ + max: config.network.maxLRUCacheInstances, + }); + } + const instanceKey = chain + network; + if (!OrderTracker._instances.has(instanceKey)) { + OrderTracker._instances.set( + instanceKey, + new OrderTracker(chain, network) + ); + } + + return OrderTracker._instances.get(instanceKey) as OrderTracker; + } + + public async saveOrder(order: Order): Promise { + this._orderStorage.saveOrder(this.chain, this.network, order); + } + + public async deleteOrder(order: Order): Promise { + this._orderStorage.deleteOrder(this.chain, this.network, order); + } + + public async getOrdersByState( + state: OrderStatus + ): Promise> { + return this._orderStorage.getOrdersByState(this.chain, this.network, state); + } + + startTracking(): void { + if (this._trackingId) { + return; + } + + this._trackingId = setInterval(() => { + this._trackOrders(); + }, 1000); + } + + stopTracking(): void { + clearInterval(this._trackingId); + } + + private async _trackOrders(): Promise { + return; + } + + private isInflightOrder(order: Order): boolean { + return ( + order.state === OrderStatus.OPEN || + order.state === OrderStatus.PARTIALLY_FILLED || + order.state === OrderStatus.PENDING_OPEN || + order.state === OrderStatus.PENDING_CANCEL + ); + } + +} diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts new file mode 100644 index 0000000000..f2473abb1d --- /dev/null +++ b/src/connectors/xrpl/xrpl.ts @@ -0,0 +1,537 @@ +import { XRPL } from '../../chains/xrpl/xrpl'; +import { + Client, + OfferCancel, + Transaction, + xrpToDrops, + AccountInfoResponse, + BookOffersResponse, +} from 'xrpl'; +import { + Market, + MarketNotFoundError, + Token, + OrderStatus, + TradeType, + Orderbook, + PriceLevel, + Order, +} from './xrpl.types'; +import { + ClobMarketsRequest, + ClobOrderbookRequest, + ClobTickerRequest, + ClobGetOrderRequest, + ClobPostOrderRequest, + ClobDeleteOrderRequest, + CLOBMarkets, + ClobGetOrderResponse, +} from '../../clob/clob.requests'; +import { promiseAllInBatches } from '../../chains/xrpl/xrpl.helpers'; +import { isIssuedCurrency } from 'xrpl/dist/npm/models/transactions/common'; +import { + CLOBish, + NetworkSelectionRequest, +} from '../../services/common-interfaces'; +import LRUCache from 'lru-cache'; +import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; +import { isUndefined } from 'mathjs'; +import { OrderType } from './xrpl.types'; + +const XRP_FACTOR = 1000000; +const ORDERBOOK_LIMIT = 10; + +export class XRPLCLOB implements CLOBish { + private static _instances: LRUCache; + private readonly _client: Client; + private readonly _xrpl: XRPL; + + private _ready: boolean = false; + + public parsedMarkets: CLOBMarkets = {}; + public chain: string; + public network: string; + + private constructor(chain: string, network: string) { + this.chain = chain; + this.network = network; + + this._xrpl = XRPL.getInstance(network); + this._client = this._xrpl.client; + } + + public static getInstance(chain: string, network: string): XRPLCLOB { + if (XRPLCLOB._instances === undefined) { + const config = getXRPLConfig(chain, network); + XRPLCLOB._instances = new LRUCache({ + max: config.network.maxLRUCacheInstances, + }); + } + const instanceKey = chain + network; + if (!XRPLCLOB._instances.has(instanceKey)) { + XRPLCLOB._instances.set(instanceKey, new XRPLCLOB(chain, network)); + } + + return XRPLCLOB._instances.get(instanceKey) as XRPLCLOB; + } + + public async loadMarkets() { + const rawMarkets = await this.fetchMarkets(); + for (const market of rawMarkets) { + this.parsedMarkets[market.marketId.replace('/', '-')] = market; + } + } + + public async init() { + if (!this._xrpl.ready() || Object.keys(this.parsedMarkets).length === 0) { + await this._xrpl.init(); + await this.loadMarkets(); + this._ready = true; + } + } + + public ready(): boolean { + return this._ready; + } + + // CLOB methods: + // TODO: Find and correct the required market info in client + public async markets( + req: ClobMarketsRequest + ): Promise<{ markets: CLOBMarkets }> { + if (req.market && req.market.split('-').length === 2) { + const resp: CLOBMarkets = {}; + resp[req.market] = this.parsedMarkets[req.market]; + return { markets: resp }; + } + return { markets: this.parsedMarkets }; + } + + public async orderBook(req: ClobOrderbookRequest): Promise { + return await this.getOrderBook(this.parsedMarkets[req.market].marketId); + } + + // Utility methods: + async fetchMarkets(): Promise { + const loadedMarkets: Market[] = []; + // TODO: Move marketsToLoad to config file + const marketsToLoad = [ + 'ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/USD.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h', + 'XRP/ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h', + 'ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/XRP', + ]; + + const getMarket = async (marketId: string): Promise => { + const market = await this.getMarket(marketId); + + loadedMarkets.concat(market); + }; + + await promiseAllInBatches(getMarket, marketsToLoad, 1, 1); + + return loadedMarkets; + } + + async getMarket(marketId?: string): Promise { + if (!marketId) throw new MarketNotFoundError(`No market informed.`); + // Market marketId format: + // 1: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/USD.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" + // 2: "XRP/ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" + // 3: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/XRP" + let baseTickSize: number; + let baseTransferRate: number; + let quoteTickSize: number; + let quoteTransferRate: number; + const zeroTransferRate = 1000000000; + + const [base, quote] = marketId.split('/'); + + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + if (baseCurrency != 'XRP') { + const baseMarketResp: AccountInfoResponse = await this._client.request({ + command: 'account_info', + ledger_index: 'validated', + account: baseIssuer, + }); + + if (!baseMarketResp) + throw new MarketNotFoundError(`Market "${base}" not found.`); + + baseTickSize = baseMarketResp.result.account_data.TickSize ?? 15; + const rawTransferRate = + baseMarketResp.result.account_data.TransferRate ?? zeroTransferRate; + baseTransferRate = rawTransferRate / zeroTransferRate - 1; + } else { + baseTickSize = 6; + baseTransferRate = 0; + } + + if (quoteCurrency != 'XRP') { + const quoteMarketResp: AccountInfoResponse = await this._client.request({ + command: 'account_info', + ledger_index: 'validated', + account: quoteIssuer, + }); + + if (!quoteMarketResp) + throw new MarketNotFoundError(`Market "${quote}" not found.`); + + quoteTickSize = quoteMarketResp.result.account_data.TickSize ?? 15; + const rawTransferRate = + quoteMarketResp.result.account_data.TransferRate ?? zeroTransferRate; + quoteTransferRate = rawTransferRate / zeroTransferRate - 1; + } else { + quoteTickSize = 6; + quoteTransferRate = 0; + } + + const smallestTickSize = Math.min(baseTickSize, quoteTickSize); + const minimumOrderSize = smallestTickSize; + + // TODO: conform to this type: + // export interface ClobMarketResponse { + // network: string; + // timestamp: number; + // latency: number; + // markets: CLOBMarkets; + // } + + const result = { + marketId: marketId, + minimumOrderSize: minimumOrderSize, + tickSize: smallestTickSize, + baseTransferRate: baseTransferRate, + quoteTransferRate: quoteTransferRate, + }; + + return result; + } + + async getOrderBook(marketId: string): Promise { + const [base, quote] = marketId.split('/'); + + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + const baseRequest: any = { + currency: baseCurrency, + }; + + const quoteRequest: any = { + currency: quoteCurrency, + }; + + if (baseIssuer) { + baseRequest['issuer'] = baseIssuer; + } + if (quoteIssuer) { + quoteRequest['issuer'] = quoteIssuer; + } + + const orderbook_resp_ask: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: baseRequest, + taker_pays: quoteRequest, + limit: ORDERBOOK_LIMIT, + }); + + const orderbook_resp_bid: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: quoteRequest, + taker_pays: baseRequest, + limit: ORDERBOOK_LIMIT, + }); + + const asks = orderbook_resp_ask.result.offers; + const bids = orderbook_resp_bid.result.offers; + + const buys: PriceLevel[] = []; + const sells: PriceLevel[] = []; + + bids.forEach((bid) => { + if (isUndefined(bid.quality)) return; + + let price, quantity: string; + + if (typeof bid.taker_gets_funded === 'string') { + price = (Math.pow(parseFloat(bid.quality), -1) / XRP_FACTOR).toString(); + quantity = (parseFloat(bid.taker_gets_funded) * XRP_FACTOR).toString(); + } else if (typeof bid.taker_pays_funded === 'string') { + if (isUndefined(bid.taker_gets_funded)) return; + if (isUndefined(bid.taker_gets_funded?.value)) return; + + price = (Math.pow(parseFloat(bid.quality), -1) * XRP_FACTOR).toString(); + quantity = bid.taker_gets_funded.value; + } else { + if (isUndefined(bid.taker_gets_funded)) return; + if (isUndefined(bid.taker_gets_funded?.value)) return; + + price = Math.pow(parseFloat(bid.quality), -1).toString(); + quantity = bid.taker_gets_funded.value; + } + + buys.concat({ + price, + quantity, + timestamp: Date.now(), + }); + }); + + asks.forEach((ask) => { + if (isUndefined(ask.quality)) return; + + let price, quantity: string; + + if (typeof ask.taker_gets_funded === 'string') { + price = (parseFloat(ask.quality) * XRP_FACTOR).toString(); + quantity = (parseFloat(ask.taker_gets_funded) * XRP_FACTOR).toString(); + } else if (typeof ask.taker_pays_funded === 'string') { + if (isUndefined(ask.taker_gets_funded)) return; + if (isUndefined(ask.taker_gets_funded?.value)) return; + + price = (parseFloat(ask.quality) / XRP_FACTOR).toString(); + quantity = ask.taker_gets_funded.value; + } else { + if (isUndefined(ask.taker_gets_funded)) return; + if (isUndefined(ask.taker_gets_funded?.value)) return; + + price = ask.quality; + quantity = ask.taker_gets_funded.value; + } + + sells.concat({ + price, + quantity, + timestamp: Date.now(), + }); + }); + + return { + buys, + sells, + }; + } + + public async ticker( + req: ClobTickerRequest + ): Promise<{ markets: CLOBMarkets }> { + return await this.markets(req); + } + + public async orders( + req: ClobGetOrderRequest + ): Promise<{ orders: ClobGetOrderResponse['orders'] }> { + if (!req.market) return { orders: [] }; + const marketId: string = this.parsedMarkets[req.market].marketId; + + const [base, quote] = marketId.split('/'); + + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + const orders: Order[] = []; + + const baseRequest: any = { + currency: baseCurrency, + }; + + const quoteRequest: any = { + currency: quoteCurrency, + }; + + if (baseIssuer) { + baseRequest['issuer'] = baseIssuer; + } + if (quoteIssuer) { + quoteRequest['issuer'] = quoteIssuer; + } + + const orderbook_resp_ask: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker: req.address, + taker_gets: baseRequest, + taker_pays: quoteRequest, + }); + + const orderbook_resp_bid: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker: req.address, + taker_gets: quoteRequest, + taker_pays: baseRequest, + }); + + let asks = orderbook_resp_ask.result.offers; + let bids = orderbook_resp_bid.result.offers; + + asks = asks.filter((ask) => ask.Account == req.address); + bids = bids.filter((bid) => bid.Account == req.address); + + for (const ask of asks) { + const price = ask.quality ?? '-1'; + let amount: string = ''; + + if (isIssuedCurrency(ask.TakerGets)) { + amount = ask.TakerGets.value; + } else { + amount = ask.TakerGets; + } + + orders.push({ + hash: ask.Sequence, + marketId: marketId, + price: price, + amount: amount, + state: OrderStatus.OPEN, // TODO: create middle class to track this property + tradeType: TradeType.SELL, + orderType: OrderType.LIMIT, + createdAt: Date.now(), // TODO: create middle class to track this property + updatedAt: Date.now(), // TODO: create middle class to track this property + }); + } + + for (const bid of bids) { + const price = Math.pow(Number(bid.quality), -1).toString() ?? '-1'; + let amount: string = ''; + + if (isIssuedCurrency(bid.TakerGets)) { + amount = bid.TakerGets.value; + } else { + amount = bid.TakerGets; + } + + orders.push({ + hash: bid.Sequence, + marketId: marketId, + price: price, + amount: amount, + state: OrderStatus.OPEN, // TODO: create middle class to track this property + tradeType: TradeType.BUY, + orderType: OrderType.LIMIT, + createdAt: Date.now(), // TODO: create middle class to track this property + updatedAt: Date.now(), // TODO: create middle class to track this property + }); + } + + return { orders } as ClobGetOrderResponse; + } + + public async postOrder( + req: ClobPostOrderRequest + ): Promise<{ txHash: string }> { + const marketId: string = this.parsedMarkets[req.market].marketId; + + const [base, quote] = marketId.split('/'); + const [baseCurrency, baseIssuer] = base.split('.'); + const [quoteCurrency, quoteIssuer] = quote.split('.'); + + const market = await this.getMarket(marketId); + + const xrpl = this._xrpl; + const wallet = await xrpl.getWallet(req.address); + const total = parseFloat(req.price) * parseFloat(req.amount); + + let we_pay: Token = { + currency: '', + issuer: '', + value: '', + }; + let we_get: Token = { currency: '', issuer: '', value: '' }; + + if (req.side == TradeType.SELL) { + we_pay = { + currency: quoteCurrency, + issuer: quoteIssuer, + value: Number(total.toPrecision(market.tickSize)).toString(), + }; + we_get = { + currency: baseCurrency, + issuer: baseIssuer, + value: Number( + parseFloat(req.amount).toPrecision(market.tickSize) + ).toString(), + }; + } else { + we_pay = { + currency: baseCurrency, + issuer: baseIssuer, + value: Number( + parseFloat(req.amount).toPrecision(market.tickSize) + ).toString(), + }; + we_get = { + currency: quoteCurrency, + issuer: quoteIssuer, + value: Number(total.toPrecision(market.tickSize)).toString(), + }; + } + + if (we_pay.currency == 'XRP') { + we_pay.value = xrpToDrops(we_pay.value); + } + + if (we_get.currency == 'XRP') { + we_get.value = xrpToDrops(we_get.value); + } + + const offer: Transaction = { + TransactionType: 'OfferCreate', + Account: wallet.classicAddress, + TakerGets: we_pay.currency == 'XRP' ? we_pay.value : we_pay, + TakerPays: we_get.currency == 'XRP' ? we_get.value : we_get, + }; + + const prepared = await this._client.autofill(offer); + const signed = wallet.sign(prepared); + const response = await this._client.submit(signed.tx_blob); + + const txHash = response.result.tx_json.hash + ? response.result.tx_json.hash + : ''; + + return { txHash }; + } + + public async deleteOrder( + req: ClobDeleteOrderRequest + ): Promise<{ txHash: string }> { + const xrpl = this._xrpl; + const wallet = await xrpl.getWallet(req.address); + const request: OfferCancel = { + TransactionType: 'OfferCancel', + Account: wallet.classicAddress, + OfferSequence: parseInt(req.orderId), + }; + + const prepared = await this._client.autofill(request); + const signed = wallet.sign(prepared); + const response = await this._client.submit(signed.tx_blob); + + const txHash = response.result.tx_json.hash + ? response.result.tx_json.hash + : ''; + + return { txHash }; + } + + public estimateGas(_req: NetworkSelectionRequest): { + gasPrice: number; + gasPriceToken: string; + gasLimit: number; + gasCost: number; + } { + const fee_estimate = this._xrpl.fee; + + return { + gasPrice: parseFloat(fee_estimate.median), + gasPriceToken: this._xrpl.nativeTokenSymbol, + gasLimit: parseFloat(this._client.maxFeeXRP), + gasCost: parseFloat(fee_estimate.median) * this._client.feeCushion, + }; + } +} diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts new file mode 100644 index 0000000000..420af04154 --- /dev/null +++ b/src/connectors/xrpl/xrpl.types.ts @@ -0,0 +1,219 @@ +import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; +import { BookOffer } from 'xrpl'; + +export type IMap = ImmutableMap; +export const IMap = ImmutableMap; +export type ISet = ImmutableSet; +export const ISet = ImmutableSet; + +export enum TradeType { + BUY = 'BUY', + SELL = 'SELL', +} + +export enum OrderStatus { + OPEN = 'OPEN', + CANCELED = 'CANCELED', + FILLED = 'FILLED', + PARTIALLY_FILLED = 'PARTIALLY_FILLED', + PENDING_OPEN = 'PENDING_OPEN', + PENDING_CANCEL = 'PENDING_CANCEL', + FAILED = 'FAILED', + UNKNOWN = 'UNKNOWN', +} + +export enum OrderType { + LIMIT = 'LIMIT', + PASSIVE = 'PASSIVE', + IOC = 'IOC', // Immediate or Cancel + FOK = 'FOK', // Fill or Kill + SELL = 'SELL', // Sell +} + +export interface Token { + currency: string; + issuer: string; + value: string; +} + +export type GetMarketsRequest = + | Record + | { name: string } + | { names: string[] }; + +export interface GetMarketResponse { + name: string; + minimumOrderSize: number; + tickSize: number; + baseTransferRate: number; + quoteTransferRate: number; +} + +export interface Market { + marketId: string; + minimumOrderSize: number; + tickSize: number; + baseTransferRate: number; + quoteTransferRate: number; +} + +export type GetMarketsResponse = + | IMap + | GetMarketResponse; + +export type GetTickersRequest = + | Record + | { marketName: string } + | { marketNames: string[] }; + +export interface GetTickerResponse { + price: number; + timestamp: number; +} + +export type GetTickersResponse = + | IMap + | GetTickerResponse; + +export interface Ticker { + price: number; + timestamp: number; +} + +export interface GetOrderRequest { + sequence: number; + signature: string; +} + +export type GetOrdersRequest = + | Record + | { orders: GetOrderRequest[] }; + +export interface GetOrderResponse { + sequence: number; + status: OrderStatus; + signature: string; + transactionResult: string; +} + +export type GetOrdersResponse = Record; + +export type GetOrderBooksRequest = + | Record + | { marketName: string; limit: number } + | { marketNames: string[]; limit: number }; + +export interface GetOrderBookResponse { + market: GetMarketResponse; + topAsk: number; + topBid: number; + midPrice: number; + bids: BookOffer[]; + asks: BookOffer[]; + timestamp: number; +} + +export type GetOrderBooksResponse = + | IMap + | GetOrderBookResponse; + +export interface CreateOrderRequest { + walletAddress: string; + marketName: string; + side: TradeType; + price: number; + amount: number; + type?: OrderType; + sequence?: number; +} + +export interface CreateOrderResponse { + walletAddress: string; + marketName: string; + price: number; + amount: number; + side: TradeType; + status?: OrderStatus; + type?: OrderType; + fee?: number; + sequence: number; + orderLedgerIndex?: string; + signature?: string; + transactionResult?: string; +} + +export type CreateOrdersResponse = + | IMap + | CreateOrderResponse + | Record; + +export interface CancelOrderRequest { + walletAddress: string; + offerSequence: number; +} + +export type CancelOrdersRequest = + | Record + | { order: CancelOrderRequest } + | { orders: CancelOrderRequest[] }; + +export interface CancelOrderResponse { + walletAddress: string; + status?: OrderStatus; + signature?: string; + transactionResult?: string; +} + +export type CancelOrdersResponse = + | IMap + | CancelOrderResponse + | Record; + +export interface GetOpenOrderRequest { + marketName: string; + walletAddress: string; +} + +export interface GetOpenOrderResponse { + sequence: number; + marketName: string; + price: string; + amount: string; + side: TradeType; +} + +export type GetOpenOrdersResponse = + | any + | IMap> + | IMap + | GetOpenOrderResponse; + +export class XRPLDEXishError extends Error {} + +export class MarketNotFoundError extends XRPLDEXishError {} + +export interface PriceLevel { + price: string; + quantity: string; + timestamp: number; +} +export interface Orderbook { + buys: PriceLevel[]; + sells: PriceLevel[]; +} + +export interface Order { + hash: number; + marketId: string; + price: string; + amount: string; + filledAmount: string; + state: string; + tradeType: string; + orderType: string; + createdAt: number; + createdAtLedgerIndex: number; + updatedAt: number; + updatedAtLedgerIndex: number; + associatedTxns: string[]; +} From 22b585c74414a66039c55d338303f6ff622caa11 Mon Sep 17 00:00:00 2001 From: mlguys Date: Mon, 5 Jun 2023 00:08:05 +0700 Subject: [PATCH 07/39] WIP --- src/chains/xrpl/xrpl.config.ts | 8 ++ src/chains/xrpl/xrpl.order-storage.ts | 30 +++++--- src/chains/xrpl/xrpl.ts | 66 ++++++++++++++-- src/chains/xrpl/xrpl_markets.json | 50 ++++++++++++ src/chains/xrpl/xrpl_markets_devnet.json | 50 ++++++++++++ src/chains/xrpl/xrpl_markets_testnet.json | 50 ++++++++++++ src/chains/xrpl/xrpl_tokens.json | 8 ++ src/chains/xrpl/xrpl_tokens_small.json | 8 ++ src/connectors/xrpl/xrpl.clob.config.ts | 21 +++++ src/connectors/xrpl/xrpl.ts | 94 +++++++++++------------ src/connectors/xrpl/xrpl.types.ts | 4 + src/services/base.ts | 3 + src/templates/xrpl.yml | 8 +- 13 files changed, 330 insertions(+), 70 deletions(-) create mode 100644 src/chains/xrpl/xrpl_markets.json create mode 100644 src/chains/xrpl/xrpl_markets_devnet.json create mode 100644 src/chains/xrpl/xrpl_markets_testnet.json diff --git a/src/chains/xrpl/xrpl.config.ts b/src/chains/xrpl/xrpl.config.ts index 0ef86b030e..f08d3b18f1 100644 --- a/src/chains/xrpl/xrpl.config.ts +++ b/src/chains/xrpl/xrpl.config.ts @@ -5,6 +5,8 @@ export interface NetworkConfig { nodeUrl: string; // example: wss://xrplcluster.com/ tokenListType: string; // default: FILE tokenListSource: string; // default: src/chains/xrpl/xrpl_tokens.json + marketListType: string; // default: FILE + marketListSource: string; // default: src/chains/xrpl/xrpl_markets.json nativeCurrencySymbol: string; // XRP maxLRUCacheInstances: number; } @@ -34,6 +36,12 @@ export function getXRPLConfig(chainName: string, networkName: string): Config { tokenListSource: ConfigManagerV2.getInstance().get( chainName + '.networks.' + networkName + '.tokenListSource' ), + marketListType: ConfigManagerV2.getInstance().get( + chainName + '.networks.' + networkName + '.marketListType' + ), + marketListSource: ConfigManagerV2.getInstance().get( + chainName + '.networks.' + networkName + '.marketListSource' + ), nativeCurrencySymbol: ConfigManagerV2.getInstance().get( chainName + '.networks.' + networkName + '.nativeCurrencySymbol' ), diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index a1eadec795..e033a3607d 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -21,10 +21,11 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async saveOrder( chain: string, chainId: string, - order: Order + order: Order, + walletAddress: string ): Promise { return this.localStorage.save( - chain + '/' + chainId + '/' + order.hash, + chain + '/' + chainId + '/' + walletAddress + '/' + order.hash, JSON.stringify(order) ); } @@ -32,23 +33,28 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async deleteOrder( chain: string, chainId: string, - order: Order + order: Order, + walletAddress: string ): Promise { - return this.localStorage.del(chain + '/' + chainId + '/' + order.hash); + return this.localStorage.del( + chain + '/' + chainId + '/' + walletAddress + '/' + order.hash + ); } public async getOrders( chain: string, - chainId: string + chainId: string, + walletAddress: string ): Promise> { return this.localStorage.get((key: string, value: string) => { const splitKey = key.split('/'); if ( - splitKey.length === 3 && + splitKey.length === 4 && splitKey[0] === chain && - splitKey[1] === chainId + splitKey[1] === chainId && + splitKey[2] === walletAddress ) { - return [splitKey[2], JSON.parse(value)]; + return [splitKey[3], JSON.parse(value)]; } return; }); @@ -57,18 +63,20 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async getOrdersByState( chain: string, chainId: string, + walletAddress: string, state: OrderStatus ): Promise> { return this.localStorage.get((key: string, value: string) => { const splitKey = key.split('/'); if ( - splitKey.length === 3 && + splitKey.length === 4 && splitKey[0] === chain && - splitKey[1] === chainId + splitKey[1] === chainId && + splitKey[2] === walletAddress ) { const order: Order = JSON.parse(value); if (order.state === state) { - return [splitKey[2], order]; + return [splitKey[3], order]; } } return; diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index ed520a142c..d8e6c1041a 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -16,7 +16,7 @@ import crypto from 'crypto'; import fse from 'fs-extra'; import path from 'path'; import { rootPath } from '../../paths'; -import { TokenListType, walletPath } from '../../services/base'; +import { TokenListType, walletPath, MarketListType } from '../../services/base'; import { ConfigManagerCertPassphrase } from '../../services/config-manager-cert-passphrase'; import { getXRPLConfig } from './xrpl.config'; import { logger } from '../../services/logger'; @@ -24,7 +24,7 @@ import { TransactionResponseStatusCode } from './xrpl.requests'; import { XRPLOrderStorage } from './xrpl.order-storage'; import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; -export type TrustlineInfo = { +export type TokenInfo = { id: number; code: string; issuer: string; @@ -33,6 +33,15 @@ export type TrustlineInfo = { placeInTop: null; }; +export type MarketInfo = { + id: number; + code: string; + baseIssuer: string; + quoteIssuer: string; + baseTokenID: number; + quoteTokenID: number; +}; + export type TokenBalance = { currency: string; issuer?: string; @@ -51,8 +60,10 @@ export class XRPL implements XRPLish { public rpcUrl; public fee: Fee; - protected tokenList: TrustlineInfo[] = []; - private _tokenMap: Record = {}; + protected tokenList: TokenInfo[] = []; + protected marketList: MarketInfo[] = []; + private _tokenMap: Record = {}; + private _marketMap: Record = {}; private _client: Client; private _nativeTokenSymbol: string; @@ -61,7 +72,9 @@ export class XRPL implements XRPLish { private _requestCount: number; private _metricsLogInterval: number; private _tokenListSource: string; + private _marketListSource: string; private _tokenListType: TokenListType; + private _marketListType: MarketListType; private _ready: boolean = false; private initializing: boolean = false; @@ -78,6 +91,8 @@ export class XRPL implements XRPLish { this._nativeTokenSymbol = config.network.nativeCurrencySymbol; this._tokenListSource = config.network.tokenListSource; this._tokenListType = config.network.tokenListType; + this._marketListSource = config.network.marketListSource; + this._marketListType = config.network.marketListType; this._client = new Client(this.rpcUrl, { timeout: config.requestTimeout, @@ -177,6 +192,7 @@ export class XRPL implements XRPLish { this.initializing = true; await this._client.connect(); await this.loadTokens(this._tokenListSource, this._tokenListType); + await this.loadMarkets(this._marketListSource, this._marketListType); await this.getFee(); this._ready = true; this.initializing = false; @@ -189,16 +205,31 @@ export class XRPL implements XRPLish { ): Promise { this.tokenList = await this.getTokenList(tokenListSource, tokenListType); if (this.tokenList) { - this.tokenList.forEach((token: TrustlineInfo) => + this.tokenList.forEach((token: TokenInfo) => this._tokenMap[token.code].push(token) ); } } + async loadMarkets( + marketListSource: string, + marketListType: MarketListType + ): Promise { + this.marketList = await this.getMarketList( + marketListSource, + marketListType + ); + if (this.marketList) { + this.marketList.forEach((market: MarketInfo) => + this._marketMap[market.code].push(market) + ); + } + } + async getTokenList( tokenListSource: string, tokenListType: TokenListType - ): Promise { + ): Promise { let tokens; if (tokenListType === 'URL') { ({ @@ -210,11 +241,30 @@ export class XRPL implements XRPLish { return tokens; } - public get storedTokenList(): TrustlineInfo[] { + async getMarketList( + marketListSource: string, + marketListType: TokenListType + ): Promise { + let tokens; + if (marketListType === 'URL') { + ({ + data: { tokens }, + } = await axios.get(marketListSource)); + } else { + ({ tokens } = JSON.parse(await fs.readFile(marketListSource, 'utf8'))); + } + return tokens; + } + + public get storedTokenList(): TokenInfo[] { return this.tokenList; } - public getTokenForSymbol(code: string): TrustlineInfo[] | null { + public get storedMarketList(): MarketInfo[] { + return this.marketList; + } + + public getTokenForSymbol(code: string): TokenInfo[] | null { return this._tokenMap[code] ? this._tokenMap[code] : null; } diff --git a/src/chains/xrpl/xrpl_markets.json b/src/chains/xrpl/xrpl_markets.json new file mode 100644 index 0000000000..20e065106d --- /dev/null +++ b/src/chains/xrpl/xrpl_markets.json @@ -0,0 +1,50 @@ +[ + { + "id": 1, + "code": "SOLO/XRP", + "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "quoteIssuer": "XRP", + "baseTokenID": 31, + "quoteTokenID": 0 + }, + { + "id": 2, + "code": "SOLO/USDC", + "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "quoteIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "baseTokenID": 31, + "quoteTokenID": 18465 + }, + { + "id": 3, + "code": "USDC/XRP", + "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "quoteIssuer": "XRP", + "baseTokenID": 18465, + "quoteTokenID": 0 + }, + { + "id": 4, + "code": "USDC/USD", + "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "baseTokenID": 18465, + "quoteTokenID": 16603 + }, + { + "id": 5, + "code": "BTC/XRP", + "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", + "quoteIssuer": "XRP", + "baseTokenID": 1381, + "quoteTokenID": 0 + }, + { + "id": 6, + "code": "BTC/USD", + "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", + "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "baseTokenID": 1381, + "quoteTokenID": 16603 + } +] \ No newline at end of file diff --git a/src/chains/xrpl/xrpl_markets_devnet.json b/src/chains/xrpl/xrpl_markets_devnet.json new file mode 100644 index 0000000000..20e065106d --- /dev/null +++ b/src/chains/xrpl/xrpl_markets_devnet.json @@ -0,0 +1,50 @@ +[ + { + "id": 1, + "code": "SOLO/XRP", + "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "quoteIssuer": "XRP", + "baseTokenID": 31, + "quoteTokenID": 0 + }, + { + "id": 2, + "code": "SOLO/USDC", + "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "quoteIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "baseTokenID": 31, + "quoteTokenID": 18465 + }, + { + "id": 3, + "code": "USDC/XRP", + "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "quoteIssuer": "XRP", + "baseTokenID": 18465, + "quoteTokenID": 0 + }, + { + "id": 4, + "code": "USDC/USD", + "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "baseTokenID": 18465, + "quoteTokenID": 16603 + }, + { + "id": 5, + "code": "BTC/XRP", + "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", + "quoteIssuer": "XRP", + "baseTokenID": 1381, + "quoteTokenID": 0 + }, + { + "id": 6, + "code": "BTC/USD", + "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", + "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "baseTokenID": 1381, + "quoteTokenID": 16603 + } +] \ No newline at end of file diff --git a/src/chains/xrpl/xrpl_markets_testnet.json b/src/chains/xrpl/xrpl_markets_testnet.json new file mode 100644 index 0000000000..20e065106d --- /dev/null +++ b/src/chains/xrpl/xrpl_markets_testnet.json @@ -0,0 +1,50 @@ +[ + { + "id": 1, + "code": "SOLO/XRP", + "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "quoteIssuer": "XRP", + "baseTokenID": 31, + "quoteTokenID": 0 + }, + { + "id": 2, + "code": "SOLO/USDC", + "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", + "quoteIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "baseTokenID": 31, + "quoteTokenID": 18465 + }, + { + "id": 3, + "code": "USDC/XRP", + "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "quoteIssuer": "XRP", + "baseTokenID": 18465, + "quoteTokenID": 0 + }, + { + "id": 4, + "code": "USDC/USD", + "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", + "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "baseTokenID": 18465, + "quoteTokenID": 16603 + }, + { + "id": 5, + "code": "BTC/XRP", + "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", + "quoteIssuer": "XRP", + "baseTokenID": 1381, + "quoteTokenID": 0 + }, + { + "id": 6, + "code": "BTC/USD", + "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", + "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", + "baseTokenID": 1381, + "quoteTokenID": 16603 + } +] \ No newline at end of file diff --git a/src/chains/xrpl/xrpl_tokens.json b/src/chains/xrpl/xrpl_tokens.json index 54072bac2e..2cf5cadba0 100644 --- a/src/chains/xrpl/xrpl_tokens.json +++ b/src/chains/xrpl/xrpl_tokens.json @@ -1,4 +1,12 @@ [ + { + "id": 0, + "code": "XRP", + "issuer": "", + "title": "XRP", + "trustlines": -1, + "placeInTop": 0 + }, { "id": 31, "code": "SOLO", diff --git a/src/chains/xrpl/xrpl_tokens_small.json b/src/chains/xrpl/xrpl_tokens_small.json index 4d7460fd2a..0ad91ad7ac 100644 --- a/src/chains/xrpl/xrpl_tokens_small.json +++ b/src/chains/xrpl/xrpl_tokens_small.json @@ -1,4 +1,12 @@ [ + { + "id": 0, + "code": "XRP", + "issuer": "", + "title": "XRP", + "trustlines": -1, + "placeInTop": 0 + }, { "id": 31, "code": "SOLO", diff --git a/src/connectors/xrpl/xrpl.clob.config.ts b/src/connectors/xrpl/xrpl.clob.config.ts index e69de29bb2..786c0ddd66 100644 --- a/src/connectors/xrpl/xrpl.clob.config.ts +++ b/src/connectors/xrpl/xrpl.clob.config.ts @@ -0,0 +1,21 @@ +import { AvailableNetworks } from '../../services/config-manager-types'; +// import { ConfigManagerV2 } from '../../services/config-manager-v2'; + +export namespace XRPLCLOBConfig { + export interface NetworkConfig { + tradingTypes: Array; + chainType: string; + availableNetworks: Array; + } + + export const config: NetworkConfig = { + tradingTypes: ['CLOB_SPOT'], + chainType: 'XRPL', + availableNetworks: [ + { + chain: 'xrpl', + networks: ['mainnet', 'testnet'], + }, + ], + }; +} diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index f2473abb1d..da441dbb1b 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -1,4 +1,4 @@ -import { XRPL } from '../../chains/xrpl/xrpl'; +import { MarketInfo, XRPL } from '../../chains/xrpl/xrpl'; import { Client, OfferCancel, @@ -114,26 +114,21 @@ export class XRPLCLOB implements CLOBish { // Utility methods: async fetchMarkets(): Promise { const loadedMarkets: Market[] = []; - // TODO: Move marketsToLoad to config file - const marketsToLoad = [ - 'ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/USD.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h', - 'XRP/ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h', - 'ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/XRP', - ]; + const markets = this._xrpl.storedMarketList; - const getMarket = async (marketId: string): Promise => { - const market = await this.getMarket(marketId); + const getMarket = async (market: MarketInfo): Promise => { + const processedMarket = await this.getMarket(market); - loadedMarkets.concat(market); + loadedMarkets.push(processedMarket); }; - await promiseAllInBatches(getMarket, marketsToLoad, 1, 1); + await promiseAllInBatches(getMarket, markets, 1, 1); return loadedMarkets; } - async getMarket(marketId?: string): Promise { - if (!marketId) throw new MarketNotFoundError(`No market informed.`); + async getMarket(market: MarketInfo): Promise { + if (!market) throw new MarketNotFoundError(`No market informed.`); // Market marketId format: // 1: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/USD.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" // 2: "XRP/ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" @@ -144,10 +139,9 @@ export class XRPLCLOB implements CLOBish { let quoteTransferRate: number; const zeroTransferRate = 1000000000; - const [base, quote] = marketId.split('/'); - - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); + const [baseCurrency, quoteCurrency] = market.code.split('/'); + const baseIssuer = market.baseIssuer; + const quoteIssuer = market.quoteIssuer; if (baseCurrency != 'XRP') { const baseMarketResp: AccountInfoResponse = await this._client.request({ @@ -157,7 +151,9 @@ export class XRPLCLOB implements CLOBish { }); if (!baseMarketResp) - throw new MarketNotFoundError(`Market "${base}" not found.`); + throw new MarketNotFoundError( + `Market "${baseCurrency}.${baseIssuer}" not found.` + ); baseTickSize = baseMarketResp.result.account_data.TickSize ?? 15; const rawTransferRate = @@ -176,7 +172,9 @@ export class XRPLCLOB implements CLOBish { }); if (!quoteMarketResp) - throw new MarketNotFoundError(`Market "${quote}" not found.`); + throw new MarketNotFoundError( + `Market "${quoteCurrency}.${quoteIssuer}" not found.` + ); quoteTickSize = quoteMarketResp.result.account_data.TickSize ?? 15; const rawTransferRate = @@ -190,20 +188,16 @@ export class XRPLCLOB implements CLOBish { const smallestTickSize = Math.min(baseTickSize, quoteTickSize); const minimumOrderSize = smallestTickSize; - // TODO: conform to this type: - // export interface ClobMarketResponse { - // network: string; - // timestamp: number; - // latency: number; - // markets: CLOBMarkets; - // } - const result = { - marketId: marketId, + marketId: market.code, minimumOrderSize: minimumOrderSize, tickSize: smallestTickSize, baseTransferRate: baseTransferRate, quoteTransferRate: quoteTransferRate, + baseIssuer: baseIssuer, + quoteIssuer: quoteIssuer, + baseCurrency: baseCurrency, + quoteCurrency: quoteCurrency, }; return result; @@ -382,17 +376,17 @@ export class XRPLCLOB implements CLOBish { amount = ask.TakerGets; } - orders.push({ - hash: ask.Sequence, - marketId: marketId, - price: price, - amount: amount, - state: OrderStatus.OPEN, // TODO: create middle class to track this property - tradeType: TradeType.SELL, - orderType: OrderType.LIMIT, - createdAt: Date.now(), // TODO: create middle class to track this property - updatedAt: Date.now(), // TODO: create middle class to track this property - }); + // orders.push({ + // hash: ask.Sequence, + // marketId: marketId, + // price: price, + // amount: amount, + // state: OrderStatus.OPEN, // TODO: create middle class to track this property + // tradeType: TradeType.SELL, + // orderType: OrderType.LIMIT, + // createdAt: Date.now(), // TODO: create middle class to track this property + // updatedAt: Date.now(), // TODO: create middle class to track this property + // }); } for (const bid of bids) { @@ -405,17 +399,17 @@ export class XRPLCLOB implements CLOBish { amount = bid.TakerGets; } - orders.push({ - hash: bid.Sequence, - marketId: marketId, - price: price, - amount: amount, - state: OrderStatus.OPEN, // TODO: create middle class to track this property - tradeType: TradeType.BUY, - orderType: OrderType.LIMIT, - createdAt: Date.now(), // TODO: create middle class to track this property - updatedAt: Date.now(), // TODO: create middle class to track this property - }); + // orders.push({ + // hash: bid.Sequence, + // marketId: marketId, + // price: price, + // amount: amount, + // state: OrderStatus.OPEN, // TODO: create middle class to track this property + // tradeType: TradeType.BUY, + // orderType: OrderType.LIMIT, + // createdAt: Date.now(), // TODO: create middle class to track this property + // updatedAt: Date.now(), // TODO: create middle class to track this property + // }); } return { orders } as ClobGetOrderResponse; diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index 420af04154..e388d701c2 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -55,6 +55,10 @@ export interface Market { tickSize: number; baseTransferRate: number; quoteTransferRate: number; + baseIssuer: string; + quoteIssuer: string; + baseCurrency: string; + quoteCurrency: string; } export type GetMarketsResponse = diff --git a/src/services/base.ts b/src/services/base.ts index 7d152a7b50..b2049651c3 100644 --- a/src/services/base.ts +++ b/src/services/base.ts @@ -5,6 +5,9 @@ import { isFractionString, isFloatString } from './validators'; // the type of information source for tokens export type TokenListType = 'FILE' | 'URL'; +// the type of information source for markets +export type MarketListType = 'FILE' | 'URL'; + // represent a token any chain, it may require some work arounds export interface TokenInfo { address: string; diff --git a/src/templates/xrpl.yml b/src/templates/xrpl.yml index 504f888b0e..9532909d32 100644 --- a/src/templates/xrpl.yml +++ b/src/templates/xrpl.yml @@ -2,17 +2,23 @@ networks: mainnet: nodeURL: wss://xrplcluster.com/ tokenListType: 'FILE' - tokenListSource: 'src/chains/xrpl/xrpl_tokens_small.json' + tokenListSource: 'src/chains/xrpl/xrpl_tokens.json' + marketListType: 'FILE' + marketListSource: 'src/chains/xrpl/xrpl_markets.json' nativeCurrencySymbol: 'XRP' testnet: nodeURL: wss://s.altnet.rippletest.net/ tokenListType: 'FILE' tokenListSource: 'src/chains/xrpl/xrpl_tokens_testnet.json' + marketListType: 'FILE' + marketListSource: 'src/chains/xrpl/xrpl_markets_testnet.json' nativeCurrencySymbol: 'XRP' devnet: nodeURL: wss://s.devnet.rippletest.net/ tokenListType: 'FILE' tokenListSource: 'src/chains/xrpl/xrpl_tokens_devnet.json' + marketListType: 'FILE' + marketListSource: 'src/chains/xrpl/xrpl_markets_devnet.json' nativeCurrencySymbol: 'XRP' network: 'xrpl' requestTimeout: 2000 From a820ae3338b89eec0ba48013902aa2c7a0e2ea36 Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 6 Jun 2023 14:38:28 +0700 Subject: [PATCH 08/39] WIP --- src/chains/xrpl/xrpl.order-storage.ts | 8 +- src/chains/xrpl/xrpl_markets.json | 2 +- src/connectors/xrpl/xrpl.order-tracker.ts | 42 ++-- src/connectors/xrpl/xrpl.ts | 223 ++++++++++------------ src/connectors/xrpl/xrpl.types.ts | 4 + src/connectors/xrpldex/xrpldex.config.ts | 2 + src/services/connection-manager.ts | 2 + yarn.lock | 19 +- 8 files changed, 165 insertions(+), 137 deletions(-) diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index e033a3607d..298ebda956 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -21,8 +21,8 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async saveOrder( chain: string, chainId: string, - order: Order, - walletAddress: string + walletAddress: string, + order: Order ): Promise { return this.localStorage.save( chain + '/' + chainId + '/' + walletAddress + '/' + order.hash, @@ -33,8 +33,8 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async deleteOrder( chain: string, chainId: string, - order: Order, - walletAddress: string + walletAddress: string, + order: Order ): Promise { return this.localStorage.del( chain + '/' + chainId + '/' + walletAddress + '/' + order.hash diff --git a/src/chains/xrpl/xrpl_markets.json b/src/chains/xrpl/xrpl_markets.json index 20e065106d..648a9e189b 100644 --- a/src/chains/xrpl/xrpl_markets.json +++ b/src/chains/xrpl/xrpl_markets.json @@ -47,4 +47,4 @@ "baseTokenID": 1381, "quoteTokenID": 16603 } -] \ No newline at end of file +] diff --git a/src/connectors/xrpl/xrpl.order-tracker.ts b/src/connectors/xrpl/xrpl.order-tracker.ts index db9cf85760..e238b550c6 100644 --- a/src/connectors/xrpl/xrpl.order-tracker.ts +++ b/src/connectors/xrpl/xrpl.order-tracker.ts @@ -1,4 +1,4 @@ -import { Client } from 'xrpl'; +import { Client, Wallet } from 'xrpl'; import { XRPL } from '../../chains/xrpl/xrpl'; import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; import { OrderStatus, TradeType, Order } from './xrpl.types'; @@ -15,32 +15,36 @@ export class OrderTracker { private static _instances: LRUCache; private readonly _xrpl: XRPL; private readonly _orderStorage: XRPLOrderStorage; - private _trackingId: any; + private _wallet: Wallet; public chain: string; public network: string; - private constructor(chain: string, network: string) { + private constructor(chain: string, network: string, wallet: Wallet) { this.chain = chain; this.network = network; this._xrpl = XRPL.getInstance(network); this._orderStorage = this._xrpl.orderStorage; - this._trackingId = undefined; + this._wallet = wallet; } - public static getInstance(chain: string, network: string): OrderTracker { + public static getInstance( + chain: string, + network: string, + wallet: Wallet + ): OrderTracker { if (OrderTracker._instances === undefined) { const config = getXRPLConfig(chain, network); OrderTracker._instances = new LRUCache({ max: config.network.maxLRUCacheInstances, }); } - const instanceKey = chain + network; + const instanceKey = chain + network + wallet.classicAddress; if (!OrderTracker._instances.has(instanceKey)) { OrderTracker._instances.set( instanceKey, - new OrderTracker(chain, network) + new OrderTracker(chain, network, wallet) ); } @@ -48,19 +52,36 @@ export class OrderTracker { } public async saveOrder(order: Order): Promise { - this._orderStorage.saveOrder(this.chain, this.network, order); + this._orderStorage.saveOrder( + this.chain, + this.network, + this._wallet.classicAddress, + order + ); } public async deleteOrder(order: Order): Promise { - this._orderStorage.deleteOrder(this.chain, this.network, order); + this._orderStorage.deleteOrder( + this.chain, + this.network, + this._wallet.classicAddress, + order + ); } public async getOrdersByState( state: OrderStatus ): Promise> { - return this._orderStorage.getOrdersByState(this.chain, this.network, state); + return this._orderStorage.getOrdersByState( + this.chain, + this.network, + this._wallet.classicAddress, + state + ); } + // TODO: Start implementing order tracker! + startTracking(): void { if (this._trackingId) { return; @@ -87,5 +108,4 @@ export class OrderTracker { order.state === OrderStatus.PENDING_CANCEL ); } - } diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index da441dbb1b..dc357915bf 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -11,11 +11,11 @@ import { Market, MarketNotFoundError, Token, - OrderStatus, + // OrderStatus, TradeType, Orderbook, PriceLevel, - Order, + // Order, } from './xrpl.types'; import { ClobMarketsRequest, @@ -28,7 +28,7 @@ import { ClobGetOrderResponse, } from '../../clob/clob.requests'; import { promiseAllInBatches } from '../../chains/xrpl/xrpl.helpers'; -import { isIssuedCurrency } from 'xrpl/dist/npm/models/transactions/common'; +// import { isIssuedCurrency } from 'xrpl/dist/npm/models/transactions/common'; import { CLOBish, NetworkSelectionRequest, @@ -36,7 +36,7 @@ import { import LRUCache from 'lru-cache'; import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; import { isUndefined } from 'mathjs'; -import { OrderType } from './xrpl.types'; +// import { OrderType } from './xrpl.types'; const XRP_FACTOR = 1000000; const ORDERBOOK_LIMIT = 10; @@ -129,14 +129,10 @@ export class XRPLCLOB implements CLOBish { async getMarket(market: MarketInfo): Promise { if (!market) throw new MarketNotFoundError(`No market informed.`); - // Market marketId format: - // 1: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/USD.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" - // 2: "XRP/ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" - // 3: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/XRP" - let baseTickSize: number; - let baseTransferRate: number; - let quoteTickSize: number; - let quoteTransferRate: number; + let baseTickSize, + baseTransferRate, + quoteTickSize, + quoteTransferRate: number; const zeroTransferRate = 1000000000; const [baseCurrency, quoteCurrency] = market.code.split('/'); @@ -203,11 +199,10 @@ export class XRPLCLOB implements CLOBish { return result; } - async getOrderBook(marketId: string): Promise { - const [base, quote] = marketId.split('/'); - - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); + async getOrderBook(market: MarketInfo): Promise { + const [baseCurrency, quoteCurrency] = market.code.split('/'); + const baseIssuer = market.baseIssuer; + const quoteIssuer = market.quoteIssuer; const baseRequest: any = { currency: baseCurrency, @@ -320,111 +315,105 @@ export class XRPLCLOB implements CLOBish { req: ClobGetOrderRequest ): Promise<{ orders: ClobGetOrderResponse['orders'] }> { if (!req.market) return { orders: [] }; - const marketId: string = this.parsedMarkets[req.market].marketId; - - const [base, quote] = marketId.split('/'); - - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); - - const orders: Order[] = []; - - const baseRequest: any = { - currency: baseCurrency, - }; - - const quoteRequest: any = { - currency: quoteCurrency, - }; - - if (baseIssuer) { - baseRequest['issuer'] = baseIssuer; - } - if (quoteIssuer) { - quoteRequest['issuer'] = quoteIssuer; - } - - const orderbook_resp_ask: BookOffersResponse = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker: req.address, - taker_gets: baseRequest, - taker_pays: quoteRequest, - }); - - const orderbook_resp_bid: BookOffersResponse = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker: req.address, - taker_gets: quoteRequest, - taker_pays: baseRequest, - }); - - let asks = orderbook_resp_ask.result.offers; - let bids = orderbook_resp_bid.result.offers; - - asks = asks.filter((ask) => ask.Account == req.address); - bids = bids.filter((bid) => bid.Account == req.address); - - for (const ask of asks) { - const price = ask.quality ?? '-1'; - let amount: string = ''; - - if (isIssuedCurrency(ask.TakerGets)) { - amount = ask.TakerGets.value; - } else { - amount = ask.TakerGets; - } - - // orders.push({ - // hash: ask.Sequence, - // marketId: marketId, - // price: price, - // amount: amount, - // state: OrderStatus.OPEN, // TODO: create middle class to track this property - // tradeType: TradeType.SELL, - // orderType: OrderType.LIMIT, - // createdAt: Date.now(), // TODO: create middle class to track this property - // updatedAt: Date.now(), // TODO: create middle class to track this property - // }); - } - - for (const bid of bids) { - const price = Math.pow(Number(bid.quality), -1).toString() ?? '-1'; - let amount: string = ''; - - if (isIssuedCurrency(bid.TakerGets)) { - amount = bid.TakerGets.value; - } else { - amount = bid.TakerGets; - } - - // orders.push({ - // hash: bid.Sequence, - // marketId: marketId, - // price: price, - // amount: amount, - // state: OrderStatus.OPEN, // TODO: create middle class to track this property - // tradeType: TradeType.BUY, - // orderType: OrderType.LIMIT, - // createdAt: Date.now(), // TODO: create middle class to track this property - // updatedAt: Date.now(), // TODO: create middle class to track this property - // }); - } - - return { orders } as ClobGetOrderResponse; + // const market = this.parsedMarkets[req.market] as Market; + // const [baseCurrency, quoteCurrency] = market.marketId.split('/'); + // const baseIssuer = market.baseIssuer; + // const quoteIssuer = market.quoteIssuer; + // const orders: Order[] = []; + + // const baseRequest: any = { + // currency: baseCurrency, + // }; + + // const quoteRequest: any = { + // currency: quoteCurrency, + // }; + + // if (baseIssuer) { + // baseRequest['issuer'] = baseIssuer; + // } + // if (quoteIssuer) { + // quoteRequest['issuer'] = quoteIssuer; + // } + + // const orderbook_resp_ask: BookOffersResponse = await this._client.request({ + // command: 'book_offers', + // ledger_index: 'validated', + // taker: req.address, + // taker_gets: baseRequest, + // taker_pays: quoteRequest, + // }); + + // const orderbook_resp_bid: BookOffersResponse = await this._client.request({ + // command: 'book_offers', + // ledger_index: 'validated', + // taker: req.address, + // taker_gets: quoteRequest, + // taker_pays: baseRequest, + // }); + + // let asks = orderbook_resp_ask.result.offers; + // let bids = orderbook_resp_bid.result.offers; + + // asks = asks.filter((ask) => ask.Account == req.address); + // bids = bids.filter((bid) => bid.Account == req.address); + + // for (const ask of asks) { + // const price = ask.quality ?? '-1'; + // let amount: string = ''; + + // if (isIssuedCurrency(ask.TakerGets)) { + // amount = ask.TakerGets.value; + // } else { + // amount = ask.TakerGets; + // } + + // // orders.push({ + // // hash: ask.Sequence, + // // marketId: marketId, + // // price: price, + // // amount: amount, + // // state: OrderStatus.OPEN, // TODO: create middle class to track this property + // // tradeType: TradeType.SELL, + // // orderType: OrderType.LIMIT, + // // createdAt: Date.now(), // TODO: create middle class to track this property + // // updatedAt: Date.now(), // TODO: create middle class to track this property + // // }); + // } + + // for (const bid of bids) { + // const price = Math.pow(Number(bid.quality), -1).toString() ?? '-1'; + // let amount: string = ''; + + // if (isIssuedCurrency(bid.TakerGets)) { + // amount = bid.TakerGets.value; + // } else { + // amount = bid.TakerGets; + // } + + // // orders.push({ + // // hash: bid.Sequence, + // // marketId: marketId, + // // price: price, + // // amount: amount, + // // state: OrderStatus.OPEN, // TODO: create middle class to track this property + // // tradeType: TradeType.BUY, + // // orderType: OrderType.LIMIT, + // // createdAt: Date.now(), // TODO: create middle class to track this property + // // updatedAt: Date.now(), // TODO: create middle class to track this property + // // }); + // } + + return { orders: [] } as ClobGetOrderResponse; } public async postOrder( req: ClobPostOrderRequest ): Promise<{ txHash: string }> { - const marketId: string = this.parsedMarkets[req.market].marketId; - - const [base, quote] = marketId.split('/'); - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); - - const market = await this.getMarket(marketId); + const market = this.parsedMarkets[req.market] as Market; + const [baseCurrency, quoteCurrency] = market.marketId.split('/'); + const baseIssuer = market.baseIssuer; + const quoteIssuer = market.quoteIssuer; const xrpl = this._xrpl; const wallet = await xrpl.getWallet(req.address); diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index e388d701c2..c81a74def6 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -221,3 +221,7 @@ export interface Order { updatedAtLedgerIndex: number; associatedTxns: string[]; } + +export interface InflightOrders { + [hash: number]: Order; +} diff --git a/src/connectors/xrpldex/xrpldex.config.ts b/src/connectors/xrpldex/xrpldex.config.ts index 21db94132b..f1726174e5 100644 --- a/src/connectors/xrpldex/xrpldex.config.ts +++ b/src/connectors/xrpldex/xrpldex.config.ts @@ -4,6 +4,7 @@ import { AvailableNetworks } from '../../services/config-manager-types'; export namespace XRPLDEXConfig { export interface Config { availableNetworks: Array; + chainType: string; tradingTypes: Array; // markets: MarketsConfig; // tickers: TickersConfig; @@ -35,6 +36,7 @@ export namespace XRPLDEXConfig { // source: ConfigManagerV2.getInstance().get(`rippledex.tickers.source`), // url: ConfigManagerV2.getInstance().get(`rippledex.tickers.url`), // }, + chainType: 'XRPL', availableNetworks: [ { chain: 'xrpl', diff --git a/src/services/connection-manager.ts b/src/services/connection-manager.ts index 2d241878ee..2a2e256e25 100644 --- a/src/services/connection-manager.ts +++ b/src/services/connection-manager.ts @@ -166,6 +166,8 @@ export type Connector = T extends Uniswapish ? Tinyman : T extends Plenty ? Plenty + : T extends XRPLish + ? XRPLDEX : never; export async function getConnector( diff --git a/yarn.lock b/yarn.lock index 403f81e100..9d59e7c7d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12711,7 +12711,7 @@ nan@2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== -nan@^2.13.2, nan@^2.14.0: +nan@^2.13.2: version "2.17.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== @@ -14495,7 +14495,7 @@ ripple-address-codec@^4.2.4: resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.2.4.tgz#a56c2168c8bb81269ea4d15ed96d6824c5a866f8" integrity sha512-roAOjKz94+FboTItey1XRh5qynwt4xvfBLvbbcx+FiR94Yw2x3LrKLF2GVCMCSAh5I6PkcpADg6AbYsUbGN3nA== dependencies: - base-x "^3.0.9" + base-x "3.0.9" create-hash "^1.1.2" ripple-binary-codec@^1.1.3: @@ -14520,6 +14520,17 @@ ripple-binary-codec@^1.4.2: buffer "5.6.0" create-hash "^1.2.0" decimal.js "^10.2.0" + ripple-address-codec "^4.2.4" + +ripple-keypairs@^1.0.3: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.1.5.tgz#eabfc371f2ef293fdc462664e18cbba32c4f5c7e" + integrity sha512-wLJXIBsMVazn2Yp/7oP4PvgA4Gd1HtuZLftdEJFNOLgraf82phqa2AnNK3t9f3XeQnApW1jAe/FcFFOY6QUn5w== + dependencies: + bn.js "^5.1.1" + brorand "^1.0.5" + elliptic "^6.5.4" + hash.js "^1.0.3" ripple-address-codec "^4.2.5" ripple-keypairs@^1.1.4: @@ -14531,7 +14542,7 @@ ripple-keypairs@^1.1.4: brorand "^1.0.5" elliptic "^6.5.4" hash.js "^1.0.3" - ripple-address-codec "^4.2.5" + ripple-address-codec "^4.2.4" ripple-lib-transactionparser@0.8.2: version "0.8.2" @@ -17222,7 +17233,7 @@ ws@^3.0.0: safe-buffer "~5.1.0" ultron "~1.1.0" -ws@^8.5.0: +ws@^8.2.2, ws@^8.5.0: version "8.13.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== From f2a2bbc6a97f8703b62db2c70d818d63d291fb95 Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 6 Jun 2023 19:05:35 +0700 Subject: [PATCH 09/39] Add order tracker logic wip --- package.json | 2 +- src/connectors/xrpl/xrpl.order-tracker.ts | 509 +++++++++++++++++++++- src/connectors/xrpl/xrpl.types.ts | 41 ++ src/connectors/xrpl/xrpl.util.ts | 0 yarn.lock | 49 +-- 5 files changed, 541 insertions(+), 60 deletions(-) create mode 100644 src/connectors/xrpl/xrpl.util.ts diff --git a/package.json b/package.json index 9baa971ba5..f6752f1766 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "winston": "^3.3.3", "winston-daily-rotate-file": "^4.5.5", "xsswap-sdk": "^1.0.1", - "xrpl": "^2.5.0", + "xrpl": "^2.7.0", "yarn": "^1.22.17" }, "devDependencies": { diff --git a/src/connectors/xrpl/xrpl.order-tracker.ts b/src/connectors/xrpl/xrpl.order-tracker.ts index e238b550c6..9f8ec2ac4e 100644 --- a/src/connectors/xrpl/xrpl.order-tracker.ts +++ b/src/connectors/xrpl/xrpl.order-tracker.ts @@ -1,7 +1,32 @@ -import { Client, Wallet } from 'xrpl'; +import { + Client, + Wallet, + TxResponse, + AccountTxResponse, + rippleTimeToUnixTime, + TransactionStream, + TransactionMetadata, + Transaction, +} from 'xrpl'; import { XRPL } from '../../chains/xrpl/xrpl'; import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; -import { OrderStatus, TradeType, Order } from './xrpl.types'; +import { + OrderStatus, + // TradeType, + Order, + InflightOrders, + OrderLocks, + TransaformedAccountTransaction, + TransactionIntentType, + TransactionIntent, + AccountTransaction, + ResponseOnlyTxInfo, +} from './xrpl.types'; +import { + isModifiedNode, + isDeletedNode, +} from 'xrpl/dist/npm/models/transactions/metadata'; + import LRUCache from 'lru-cache'; import { XRPLOrderStorage } from '../../chains/xrpl/xrpl.order-storage'; @@ -82,30 +107,476 @@ export class OrderTracker { // TODO: Start implementing order tracker! - startTracking(): void { - if (this._trackingId) { - return; + // startTracking(): void { + // if (this._trackingId) { + // return; + // } + + // this._trackingId = setInterval(() => { + // this._trackOrders(); + // }, 1000); + // } + + // stopTracking(): void { + // clearInterval(this._trackingId); + // } + + // private async _trackOrders(): Promise { + // return; + // } + + async checkPendingOrder( + client: Client, + order: Order, + omm: OrderMutexManager + ): Promise { + // Check if order is canceled + // Check if order is open + let result: Order = order; + const currentLedgerIndex = await client.getLedgerIndex(); + + // If order state is not pending open or pending cancel, then return + if ( + order.state !== OrderStatus.PENDING_OPEN && + order.state !== OrderStatus.PENDING_CANCEL + ) { + return order; } - this._trackingId = setInterval(() => { - this._trackOrders(); - }, 1000); - } + // If order currentLedgerIndex is less than or equal than order createdAtLedgerIndex, then return + if (currentLedgerIndex <= order.updatedAtLedgerIndex) { + return order; + } + + // Wait until the order is not locked + while (omm.isLocked(order.hash)) { + console.log('Order is locked! Wait for 300ms'); + await new Promise((resolve) => setTimeout(resolve, 300)); + } + + // Lock the order + omm.lock(order.hash); + + // If order state is pending open, check if order is open + if (order.state === OrderStatus.PENDING_OPEN) { + const latestTxnResp = await this.getTransaction( + client, + order.associatedTxns[0] || '' + ); + if (typeof latestTxnResp?.result.meta === 'string') { + result = order; + } else if ( + latestTxnResp?.result.meta?.TransactionResult === 'tesSUCCESS' + ) { + result = { + ...order, + state: OrderStatus.OPEN, + updatedAt: latestTxnResp?.result.date + ? rippleTimeToUnixTime(latestTxnResp?.result.date) + : 0, + updatedAtLedgerIndex: latestTxnResp?.result.ledger_index ?? 0, + }; + console.log('Order opened!'); + } else { + // Handle other cases here + // https://xrpl.org/transaction-results.html + result = order; + } + } - stopTracking(): void { - clearInterval(this._trackingId); + // If order state is pending cancel, check if order is canceled + if (order.state === OrderStatus.PENDING_CANCEL) { + const latestTxnResp = await this.getTransaction( + client, + order.associatedTxns[order.associatedTxns.length - 1] || '' + ); + if (typeof latestTxnResp?.result.meta === 'string') { + result = order; + } else if ( + latestTxnResp?.result.meta?.TransactionResult === 'tesSUCCESS' + ) { + result = { + ...order, + state: OrderStatus.CANCELED, + updatedAt: latestTxnResp?.result.date + ? rippleTimeToUnixTime(latestTxnResp?.result.date) + : 0, + updatedAtLedgerIndex: latestTxnResp?.result.ledger_index ?? 0, + }; + console.log('Order canceled!'); + } else { + // Handle other cases here + // https://xrpl.org/transaction-results.html + result = order; + } + } + + // Release the lock + omm.release(order.hash); + + return result; } - private async _trackOrders(): Promise { - return; + async checkOpenOrder( + inflightOrders: InflightOrders, + account: string, + client: Client, + omm: OrderMutexManager + ) { + // TODO + // 1. Get the minLedgerIndex from the inflightOrders + // 2. Get the transactions stack based on minLedgerIndex and account id + // 3. Process the transactions stack + // 4. Update the inflightOrders + + // 1. Get the minLedgerIndex from the inflightOrders + const hashes = Object.keys(inflightOrders); + let minLedgerIndex = 0; + for (const hash of hashes) { + if ( + inflightOrders[parseInt(hash)].updatedAtLedgerIndex > minLedgerIndex + ) { + minLedgerIndex = inflightOrders[parseInt(hash)].updatedAtLedgerIndex; + } + } + + // 2. Get the transactions stack based on minLedgerIndex and account id + const txStack = await this.getTransactionsStack( + client, + account, + minLedgerIndex + ); + + // 3. Process the transactions stack + if (txStack === null) { + return inflightOrders; + } + + if (txStack.result?.transactions === undefined) { + return inflightOrders; + } + + for (const tx of txStack.result.transactions) { + const transformedTx = this.transformAccountTransaction(tx); + + if (transformedTx === null) { + continue; + } + + const updatedOrders = await this.processTransactionStream( + inflightOrders, + transformedTx, + omm + ); + + // merge updateOrders with inflightOrders + Object.keys(updatedOrders).forEach((hash) => { + inflightOrders[parseInt(hash)] = updatedOrders[parseInt(hash)]; + }); + } } - private isInflightOrder(order: Order): boolean { - return ( - order.state === OrderStatus.OPEN || - order.state === OrderStatus.PARTIALLY_FILLED || - order.state === OrderStatus.PENDING_OPEN || - order.state === OrderStatus.PENDING_CANCEL + async processTransactionStream( + inflightOrders: InflightOrders, + transaction: TransactionStream | TransaformedAccountTransaction, + omm: OrderMutexManager + ): Promise { + const transactionIntent = await this.getTransactionIntentFromStream( + transaction ); + // console.log('Transaction intent: '); + // console.log(inspect(transactionIntent, { colors: true, depth: null })); + + if (transactionIntent.sequence === undefined) { + console.log('No sequence found!'); + return inflightOrders; + } + + const matchOrder = inflightOrders[transactionIntent.sequence]; + + if (matchOrder === undefined) { + console.log('No match order found!'); + return inflightOrders; + } + + // Wait until the order is not locked + while (omm.isLocked(matchOrder.hash)) { + console.log('Order is locked! Wait for 300ms'); + await new Promise((resolve) => setTimeout(resolve, 300)); + } + + // Lock the order + omm.lock(matchOrder.hash); + + matchOrder.updatedAt = transaction.transaction.date + ? rippleTimeToUnixTime(transaction.transaction.date) + : 0; + matchOrder.updatedAtLedgerIndex = transaction.ledger_index ?? 0; + + // Find if transaction.transaction.hash already in associatedTxns, if not, then push it + const foundIndex = matchOrder.associatedTxns.findIndex((hash) => { + return hash === transaction.transaction.hash; + }); + + if (foundIndex === -1) { + matchOrder.associatedTxns.push(transaction.transaction.hash ?? 'UNKNOWN'); + } else { + console.log('Transaction already found!'); + } + + switch (transactionIntent.type) { + case TransactionIntentType.OFFER_CREATE_FINALIZED: + console.log('Offer create finalized!'); + matchOrder.state = OrderStatus.OPEN; + break; + + case TransactionIntentType.OFFER_CANCEL_FINALIZED: + console.log('Offer cancel finalized!'); + matchOrder.state = OrderStatus.CANCELED; + break; + + case TransactionIntentType.OFFER_PARTIAL_FILL: + console.log('Offer partial fill!'); + matchOrder.state = OrderStatus.PARTIALLY_FILLED; + + if (transaction.meta === undefined) { + console.log('No meta found!'); + break; + } + + for (const affnode of transaction.meta.AffectedNodes) { + if (isModifiedNode(affnode)) { + console.log('Modified node found!'); + if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { + // Usually a ModifiedNode of type Offer indicates a previous Offer that + // was partially consumed by this one. + if (affnode.ModifiedNode.FinalFields === undefined) { + console.log('No final fields found!'); + break; + } + + const finalFields = affnode.ModifiedNode.FinalFields as any; + let filledAmount = 0.0; + + // Update filled amount + if (matchOrder.tradeType === 'SELL') { + if (typeof finalFields.TakerGets === 'string') { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(finalFields.TakerGets as string); + } else { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(finalFields.TakerGets.value as string); + } + } + + if (matchOrder.tradeType === 'BUY') { + if (typeof finalFields.TakerPays === 'string') { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(finalFields.TakerPays as string); + } else { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(finalFields.TakerPays.value as string); + } + } + + console.log('Filled amount: ', filledAmount); + matchOrder.filledAmount = filledAmount.toString(); + break; + } + } + } + break; + + case TransactionIntentType.OFFER_FILL: + matchOrder.state = OrderStatus.FILLED; + matchOrder.filledAmount = matchOrder.amount; + break; + + case TransactionIntentType.UNKNOWN: + matchOrder.state = OrderStatus.UNKNOWN; + break; + } + + // Check matchOrder value + // console.log('Updated matchOrder: '); + // console.log(inspect(matchOrder, { colors: true, depth: null })); + + // Update inflightOrders + inflightOrders[matchOrder.hash] = matchOrder; + + // Release the lock + omm.release(matchOrder.hash); + + return inflightOrders; + } + + // Utility methods + async getTransactionIntentFromStream( + transaction: TransactionStream | TransaformedAccountTransaction + ): Promise { + const transactionType = transaction.transaction.TransactionType; + + if (transaction.transaction.Sequence === undefined) { + return { type: TransactionIntentType.UNKNOWN }; + } + + if (transaction.meta === undefined) { + return { type: TransactionIntentType.UNKNOWN }; + } + + switch (transactionType) { + case 'OfferCreate': + // return { type: TransactionIntentType.OFFER_CREATE_FINALIZED, hash: transaction.transaction.Sequence }; + for (const affnode of transaction.meta.AffectedNodes) { + if (isModifiedNode(affnode)) { + if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { + // Usually a ModifiedNode of type Offer indicates a previous Offer that + // was partially consumed by this one. + if (affnode.ModifiedNode.FinalFields === undefined) { + return { type: TransactionIntentType.UNKNOWN }; + } + + return { + type: TransactionIntentType.OFFER_PARTIAL_FILL, + sequence: affnode.ModifiedNode.FinalFields.Sequence as number, + }; + } + } else if (isDeletedNode(affnode)) { + if (affnode.DeletedNode.LedgerEntryType == 'Offer') { + // The removed Offer may have been fully consumed, or it may have been + // found to be expired or unfunded. + if (affnode.DeletedNode.FinalFields === undefined) { + return { type: TransactionIntentType.UNKNOWN }; + } + + return { + type: TransactionIntentType.OFFER_FILL, + sequence: affnode.DeletedNode.FinalFields.Sequence as number, + }; + } + } else { + return { + type: TransactionIntentType.OFFER_CREATE_FINALIZED, + sequence: transaction.transaction.Sequence, + }; + } + } + break; + + case 'OfferCancel': + return { + type: TransactionIntentType.OFFER_CANCEL_FINALIZED, + sequence: transaction.transaction.OfferSequence, + }; + } + + return { type: TransactionIntentType.UNKNOWN }; + } + + async getTransaction( + client: Client, + txHash: string + ): Promise { + if (txHash === '') { + return null; + } + + const tx_resp = await client.request({ + command: 'tx', + transaction: txHash, + binary: false, + }); + + const result = tx_resp; + + return result; + } + + async getTransactionsStack( + client: Client, + account: string, + minLedgerIndex: number + ): Promise { + if (account === '') { + return null; + } + + const tx_resp: AccountTxResponse = await client.request({ + command: 'account_tx', + account: account, + ledger_index_min: minLedgerIndex, + binary: false, + }); + + const result = tx_resp; + + return result; + } + + transformAccountTransaction( + transaction: AccountTransaction + ): TransaformedAccountTransaction | null { + if (typeof transaction.meta === 'string') { + return null; + } + + if (transaction.tx === undefined) { + return null; + } + + const transformedTx: TransaformedAccountTransaction = { + ledger_index: transaction.ledger_index, + meta: transaction.meta as TransactionMetadata, + transaction: transaction.tx as Transaction & ResponseOnlyTxInfo, + tx_blob: transaction.tx_blob, + validated: transaction.validated, + }; + + return transformedTx; + } +} + +class OrderMutexManager { + private locks: OrderLocks = {}; + private orders: InflightOrders = {}; // orders to manage + + constructor(ordersToManage: InflightOrders) { + this.orders = ordersToManage; + + Object.keys(this.orders).forEach((hash) => { + this.locks[parseInt(hash)] = false; + }); + } + + // lock an order by hash + lock(hash: number) { + this.locks[hash] = true; + } + + // release an order by hash + release(hash: number) { + this.locks[hash] = false; + } + + // reset all locks + reset() { + Object.keys(this.orders).forEach((hash) => { + this.locks[parseInt(hash)] = false; + }); + } + + // update orders list and locks + updateOrders(orders: InflightOrders) { + this.orders = orders; + this.reset(); + } + + // get lock satus of an order by hash + isLocked(hash: number) { + return this.locks[hash]; } } diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index c81a74def6..3d65f99342 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -1,5 +1,6 @@ import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; import { BookOffer } from 'xrpl'; +import { TransactionMetadata, Transaction } from 'xrpl'; export type IMap = ImmutableMap; export const IMap = ImmutableMap; @@ -30,6 +31,14 @@ export enum OrderType { SELL = 'SELL', // Sell } +export enum TransactionIntentType { + OFFER_CREATE_FINALIZED = 'OfferCreateFinalized', + OFFER_CANCEL_FINALIZED = 'OfferCancelFinalized', + OFFER_PARTIAL_FILL = 'OfferPartialFill', + OFFER_FILL = 'OfferFill', + UNKNOWN = 'UNKNOWN', +} + export interface Token { currency: string; issuer: string; @@ -225,3 +234,35 @@ export interface Order { export interface InflightOrders { [hash: number]: Order; } + +export interface OrderLocks { + [key: number]: boolean; +} + +export interface AccountTransaction { + ledger_index: number; + meta: string | TransactionMetadata; + tx?: Transaction & ResponseOnlyTxInfo; + tx_blob?: string; + validated: boolean; +} + +export interface TransaformedAccountTransaction { + ledger_index: number; + meta: TransactionMetadata; + transaction: Transaction & ResponseOnlyTxInfo; + tx_blob?: string; + validated: boolean; +} + +export interface ResponseOnlyTxInfo { + date?: number; + hash?: string; + ledger_index?: number; + inLedger?: number; +} + +export interface TransactionIntent { + type: TransactionIntentType; + sequence?: number; +} diff --git a/src/connectors/xrpl/xrpl.util.ts b/src/connectors/xrpl/xrpl.util.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/yarn.lock b/yarn.lock index 9d59e7c7d8..6b5fa422b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14490,15 +14490,7 @@ ripple-address-codec@^4.1.1, ripple-address-codec@^4.2.5: base-x "^3.0.9" create-hash "^1.1.2" -ripple-address-codec@^4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.2.4.tgz#a56c2168c8bb81269ea4d15ed96d6824c5a866f8" - integrity sha512-roAOjKz94+FboTItey1XRh5qynwt4xvfBLvbbcx+FiR94Yw2x3LrKLF2GVCMCSAh5I6PkcpADg6AbYsUbGN3nA== - dependencies: - base-x "3.0.9" - create-hash "^1.1.2" - -ripple-binary-codec@^1.1.3: +ripple-binary-codec@^1.1.3, ripple-binary-codec@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.4.3.tgz#4737044f2aa5da496c1d57619339f26df01cd494" integrity sha512-P4ALjAJWBJpRApTQO+dJCrHE6mZxm7ypZot9OS0a3RCKOWTReNw0pDWfdhCGh1qXh71TeQnAk4CHdMLwR/76oQ== @@ -14510,19 +14502,7 @@ ripple-binary-codec@^1.1.3: decimal.js "^10.2.0" ripple-address-codec "^4.2.5" -ripple-binary-codec@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.4.2.tgz#cdc35353e4bc7c3a704719247c82b4c4d0b57dd3" - integrity sha512-EDKIyZMa/6Ay/oNgCwjD9b9CJv0zmBreeHVQeG4BYwy+9GPnIQjNeT5e/aB6OjAnhcmpgbPeBmzwmNVwzxlt0w== - dependencies: - assert "^2.0.0" - big-integer "^1.6.48" - buffer "5.6.0" - create-hash "^1.2.0" - decimal.js "^10.2.0" - ripple-address-codec "^4.2.4" - -ripple-keypairs@^1.0.3: +ripple-keypairs@^1.0.3, ripple-keypairs@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.1.5.tgz#eabfc371f2ef293fdc462664e18cbba32c4f5c7e" integrity sha512-wLJXIBsMVazn2Yp/7oP4PvgA4Gd1HtuZLftdEJFNOLgraf82phqa2AnNK3t9f3XeQnApW1jAe/FcFFOY6QUn5w== @@ -14533,17 +14513,6 @@ ripple-keypairs@^1.0.3: hash.js "^1.0.3" ripple-address-codec "^4.2.5" -ripple-keypairs@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.1.4.tgz#4486fca703b8a2fc4f30cfd568478f3d12c1a911" - integrity sha512-PMMjTOxZmCSBOvHPj6bA+V/HGx7oFgDtGGI8VcZYuaFO2H87UX0X0jhfHy+LA2Xy31WYlD7GaDIDDt2QO+AMtw== - dependencies: - bn.js "^5.1.1" - brorand "^1.0.5" - elliptic "^6.5.4" - hash.js "^1.0.3" - ripple-address-codec "^4.2.4" - ripple-lib-transactionparser@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz#7aaad3ba1e1aeee1d5bcff32334a7a838f834dce" @@ -17331,19 +17300,19 @@ xmlhttprequest@1.8.0: resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== -xrpl@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/xrpl/-/xrpl-2.6.0.tgz#cfe9bc012e85721213f7f0e373ce5ff1d27e38c4" - integrity sha512-++rKtgO1j65TMm//mird3aGFGFYUn7VP9TjxwJkTUQZOdWkMUfiG2cd2o3tZ/zE07Ev8YVD09KgMZ9HzynJN9Q== +xrpl@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/xrpl/-/xrpl-2.7.0.tgz#3f0d04f1bf79cce34e711b666a6172bd5fd19348" + integrity sha512-P4M/Myxn2U7wl1avAG2Y/JuJMlKw2boLNx0f9woYQJLrS68sICmAfGOYKqPSzwRPc9P7kmydNrk+737nmFW5Vw== dependencies: bignumber.js "^9.0.0" bip32 "^2.0.6" bip39 "^3.0.4" https-proxy-agent "^5.0.0" lodash "^4.17.4" - ripple-address-codec "^4.2.4" - ripple-binary-codec "^1.4.2" - ripple-keypairs "^1.1.4" + ripple-address-codec "^4.2.5" + ripple-binary-codec "^1.4.3" + ripple-keypairs "^1.1.5" ws "^8.2.2" xss@^1.0.8: From b1411d7a7df772edc2b119343fff17534e6948dc Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 8 Jun 2023 20:55:33 +0700 Subject: [PATCH 10/39] Finish connecter and add routes test --- src/chains/xrpl/xrpl.order-storage.ts | 112 +++- src/chains/xrpl/xrpl.ts | 31 +- src/chains/xrpl/xrpl_markets.json | 12 +- src/chains/xrpl/xrpl_markets_devnet.json | 51 +- src/chains/xrpl/xrpl_markets_testnet.json | 51 +- src/clob/clob.validators.ts | 7 +- src/connectors/xrpl/xrpl.order-tracker.ts | 267 ++++++---- src/connectors/xrpl/xrpl.ts | 420 ++++++++------- src/connectors/xrpl/xrpl.types.ts | 1 + src/connectors/xrpl/xrpl.util.ts | 0 src/connectors/xrpl/xrpl.utils.ts | 58 +++ src/services/connection-manager.ts | 12 +- src/services/schema/xrpl-schema.json | 4 + src/templates/xrpl.yml | 38 +- test/connectors/xrpl/xrpl.routes.test.ts | 593 ++++++++++++++++++++++ 15 files changed, 1234 insertions(+), 423 deletions(-) delete mode 100644 src/connectors/xrpl/xrpl.util.ts create mode 100644 src/connectors/xrpl/xrpl.utils.ts create mode 100644 test/connectors/xrpl/xrpl.routes.test.ts diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index 298ebda956..9a011ec133 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -20,30 +20,30 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async saveOrder( chain: string, - chainId: string, + network: string, walletAddress: string, order: Order ): Promise { return this.localStorage.save( - chain + '/' + chainId + '/' + walletAddress + '/' + order.hash, + chain + '/' + network + '/' + walletAddress + '/' + order.hash, JSON.stringify(order) ); } public async deleteOrder( chain: string, - chainId: string, + network: string, walletAddress: string, order: Order ): Promise { return this.localStorage.del( - chain + '/' + chainId + '/' + walletAddress + '/' + order.hash + chain + '/' + network + '/' + walletAddress + '/' + order.hash ); } public async getOrders( chain: string, - chainId: string, + network: string, walletAddress: string ): Promise> { return this.localStorage.get((key: string, value: string) => { @@ -51,7 +51,7 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { if ( splitKey.length === 4 && splitKey[0] === chain && - splitKey[1] === chainId && + splitKey[1] === network && splitKey[2] === walletAddress ) { return [splitKey[3], JSON.parse(value)]; @@ -62,7 +62,7 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async getOrdersByState( chain: string, - chainId: string, + network: string, walletAddress: string, state: OrderStatus ): Promise> { @@ -71,7 +71,7 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { if ( splitKey.length === 4 && splitKey[0] === chain && - splitKey[1] === chainId && + splitKey[1] === network && splitKey[2] === walletAddress ) { const order: Order = JSON.parse(value); @@ -83,6 +83,102 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { }); } + public async getOrdersByMarket( + chain: string, + network: string, + walletAddress: string, + marketId: string + ): Promise> { + return this.localStorage.get((key: string, value: string) => { + const splitKey = key.split('/'); + if ( + splitKey.length === 4 && + splitKey[0] === chain && + splitKey[1] === network && + splitKey[2] === walletAddress + ) { + const order: Order = JSON.parse(value); + if (order.marketId === marketId) { + return [splitKey[3], order]; + } + } + return; + }); + } + + public async getOrderByMarketAndHash( + chain: string, + network: string, + walletAddress: string, + marketId: string, + hash: string + ): Promise> { + return this.localStorage.get((key: string, value: string) => { + const splitKey = key.split('/'); + if ( + splitKey.length === 4 && + splitKey[0] === chain && + splitKey[1] === network && + splitKey[2] === walletAddress + ) { + const order: Order = JSON.parse(value); + if (order.marketId === marketId && order.hash === parseInt(hash)) { + return [splitKey[3], order]; + } + } + return; + }); + } + + public async getOrdersByHash( + chain: string, + network: string, + walletAddress: string, + hash: string + ): Promise> { + return this.localStorage.get((key: string, value: string) => { + const splitKey = key.split('/'); + if ( + splitKey.length === 4 && + splitKey[0] === chain && + splitKey[1] === network && + splitKey[2] === walletAddress && + splitKey[3] === hash + ) { + const order: Order = JSON.parse(value); + return [splitKey[3], order]; + } + return; + }); + } + + public async getInflightOrders( + chain: string, + network: string, + walletAddress: string + ): Promise> { + return this.localStorage.get((key: string, value: string) => { + const splitKey = key.split('/'); + if ( + splitKey.length === 4 && + splitKey[0] === chain && + splitKey[1] === network && + splitKey[2] === walletAddress + ) { + const order: Order = JSON.parse(value); + if ( + order.state === OrderStatus.OPEN || + order.state === OrderStatus.PENDING_OPEN || + order.state === OrderStatus.PENDING_CANCEL || + order.state === OrderStatus.PARTIALLY_FILLED + ) { + return [splitKey[3], order]; + } + } + return; + }); + } + public async close(handle: string): Promise { await super.close(handle); if (this.refCount < 1) { diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index d8e6c1041a..4126119985 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -19,9 +19,10 @@ import { rootPath } from '../../paths'; import { TokenListType, walletPath, MarketListType } from '../../services/base'; import { ConfigManagerCertPassphrase } from '../../services/config-manager-cert-passphrase'; import { getXRPLConfig } from './xrpl.config'; -import { logger } from '../../services/logger'; +// import { logger } from '../../services/logger'; import { TransactionResponseStatusCode } from './xrpl.requests'; import { XRPLOrderStorage } from './xrpl.order-storage'; +import { OrderTracker } from '../../connectors/xrpl/xrpl.order-tracker'; import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; export type TokenInfo = { @@ -35,7 +36,7 @@ export type TokenInfo = { export type MarketInfo = { id: number; - code: string; + marketId: string; baseIssuer: string; quoteIssuer: string; baseTokenID: number; @@ -107,13 +108,12 @@ export class XRPL implements XRPLish { minimum: '0', openLedger: '0', }; - // this._client.connect(); this._requestCount = 0; this._metricsLogInterval = 300000; // 5 minutes this.onValidationReceived(this.requestCounter.bind(this)); - setInterval(this.metricLogger.bind(this), this.metricsLogInterval); + // setInterval(this.metricLogger.bind(this), this.metricsLogInterval); this._refCountingHandle = ReferenceCountingCloseable.createHandle(); this._orderStorage = XRPLOrderStorage.getInstance( @@ -190,7 +190,7 @@ export class XRPL implements XRPLish { async init(): Promise { if (!this.ready() && !this.initializing) { this.initializing = true; - await this._client.connect(); + await this.ensureConnection(); await this.loadTokens(this._tokenListSource, this._tokenListType); await this.loadMarkets(this._marketListSource, this._marketListType); await this.getFee(); @@ -221,7 +221,7 @@ export class XRPL implements XRPLish { ); if (this.marketList) { this.marketList.forEach((market: MarketInfo) => - this._marketMap[market.code].push(market) + this._marketMap[market.marketId].push(market) ); } } @@ -381,15 +381,15 @@ export class XRPL implements XRPLish { this._requestCount += 1; } - public metricLogger(): void { - logger.info( - this.requestCount + - ' request(s) sent in last ' + - this.metricsLogInterval / 1000 + - ' seconds.' - ); - this._requestCount = 0; // reset - } + // public metricLogger(): void { + // logger.info( + // this.requestCount + + // ' request(s) sent in last ' + + // this.metricsLogInterval / 1000 + + // ' seconds.' + // ); + // this._requestCount = 0; // reset + // } public get requestCount(): number { return this._requestCount; @@ -446,6 +446,7 @@ export class XRPL implements XRPLish { async close() { if (this._network in XRPL._instances) { + await OrderTracker.stopTrackingOnAllInstancesForNetwork(this._network); await this._orderStorage.close(this._refCountingHandle); delete XRPL._instances[this._network]; } diff --git a/src/chains/xrpl/xrpl_markets.json b/src/chains/xrpl/xrpl_markets.json index 648a9e189b..7cb44f0a81 100644 --- a/src/chains/xrpl/xrpl_markets.json +++ b/src/chains/xrpl/xrpl_markets.json @@ -1,7 +1,7 @@ [ { "id": 1, - "code": "SOLO/XRP", + "marketId": "SOLO-XRP", "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", "quoteIssuer": "XRP", "baseTokenID": 31, @@ -9,7 +9,7 @@ }, { "id": 2, - "code": "SOLO/USDC", + "marketId": "SOLO-USDC", "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", "quoteIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", "baseTokenID": 31, @@ -17,7 +17,7 @@ }, { "id": 3, - "code": "USDC/XRP", + "marketId": "USDC-XRP", "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", "quoteIssuer": "XRP", "baseTokenID": 18465, @@ -25,7 +25,7 @@ }, { "id": 4, - "code": "USDC/USD", + "marketId": "USDC-USD", "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", "baseTokenID": 18465, @@ -33,7 +33,7 @@ }, { "id": 5, - "code": "BTC/XRP", + "marketId": "BTC-XRP", "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", "quoteIssuer": "XRP", "baseTokenID": 1381, @@ -41,7 +41,7 @@ }, { "id": 6, - "code": "BTC/USD", + "marketId": "BTC-USD", "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", "baseTokenID": 1381, diff --git a/src/chains/xrpl/xrpl_markets_devnet.json b/src/chains/xrpl/xrpl_markets_devnet.json index 20e065106d..0637a088a0 100644 --- a/src/chains/xrpl/xrpl_markets_devnet.json +++ b/src/chains/xrpl/xrpl_markets_devnet.json @@ -1,50 +1 @@ -[ - { - "id": 1, - "code": "SOLO/XRP", - "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", - "quoteIssuer": "XRP", - "baseTokenID": 31, - "quoteTokenID": 0 - }, - { - "id": 2, - "code": "SOLO/USDC", - "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", - "quoteIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", - "baseTokenID": 31, - "quoteTokenID": 18465 - }, - { - "id": 3, - "code": "USDC/XRP", - "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", - "quoteIssuer": "XRP", - "baseTokenID": 18465, - "quoteTokenID": 0 - }, - { - "id": 4, - "code": "USDC/USD", - "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", - "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", - "baseTokenID": 18465, - "quoteTokenID": 16603 - }, - { - "id": 5, - "code": "BTC/XRP", - "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", - "quoteIssuer": "XRP", - "baseTokenID": 1381, - "quoteTokenID": 0 - }, - { - "id": 6, - "code": "BTC/USD", - "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", - "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", - "baseTokenID": 1381, - "quoteTokenID": 16603 - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/src/chains/xrpl/xrpl_markets_testnet.json b/src/chains/xrpl/xrpl_markets_testnet.json index 20e065106d..fe51488c70 100644 --- a/src/chains/xrpl/xrpl_markets_testnet.json +++ b/src/chains/xrpl/xrpl_markets_testnet.json @@ -1,50 +1 @@ -[ - { - "id": 1, - "code": "SOLO/XRP", - "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", - "quoteIssuer": "XRP", - "baseTokenID": 31, - "quoteTokenID": 0 - }, - { - "id": 2, - "code": "SOLO/USDC", - "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", - "quoteIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", - "baseTokenID": 31, - "quoteTokenID": 18465 - }, - { - "id": 3, - "code": "USDC/XRP", - "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", - "quoteIssuer": "XRP", - "baseTokenID": 18465, - "quoteTokenID": 0 - }, - { - "id": 4, - "code": "USDC/USD", - "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", - "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", - "baseTokenID": 18465, - "quoteTokenID": 16603 - }, - { - "id": 5, - "code": "BTC/XRP", - "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", - "quoteIssuer": "XRP", - "baseTokenID": 1381, - "quoteTokenID": 0 - }, - { - "id": 6, - "code": "BTC/USD", - "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", - "quoteIssuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", - "baseTokenID": 1381, - "quoteTokenID": 16603 - } -] \ No newline at end of file +[] diff --git a/src/clob/clob.validators.ts b/src/clob/clob.validators.ts index 84d60b5158..bca44e6d00 100644 --- a/src/clob/clob.validators.ts +++ b/src/clob/clob.validators.ts @@ -12,6 +12,8 @@ import { validateNetwork, } from '../chains/ethereum/ethereum.validators'; +import { isXRPLAddress } from '../chains/xrpl/xrpl.validators'; + import { validateConnector, validateAmount, @@ -95,7 +97,10 @@ export const validateWallet: Validator = mkValidator( 'address', invalidWalletError, (val) => { - return typeof val === 'string' && isAddress(val.slice(0, 42)); + return ( + typeof val === 'string' && + (isAddress(val.slice(0, 42)) || isXRPLAddress(val)) + ); } ); diff --git a/src/connectors/xrpl/xrpl.order-tracker.ts b/src/connectors/xrpl/xrpl.order-tracker.ts index 9f8ec2ac4e..2e2e4a3611 100644 --- a/src/connectors/xrpl/xrpl.order-tracker.ts +++ b/src/connectors/xrpl/xrpl.order-tracker.ts @@ -12,16 +12,15 @@ import { XRPL } from '../../chains/xrpl/xrpl'; import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; import { OrderStatus, - // TradeType, Order, InflightOrders, - OrderLocks, TransaformedAccountTransaction, TransactionIntentType, TransactionIntent, AccountTransaction, ResponseOnlyTxInfo, } from './xrpl.types'; +import { OrderMutexManager } from './xrpl.utils'; import { isModifiedNode, isDeletedNode, @@ -41,6 +40,10 @@ export class OrderTracker { private readonly _xrpl: XRPL; private readonly _orderStorage: XRPLOrderStorage; private _wallet: Wallet; + private _orderMutexManager: OrderMutexManager; + private _inflightOrders: InflightOrders; + private _isTracking: boolean; + private _orderTrackingInterval: number; public chain: string; public network: string; @@ -52,6 +55,11 @@ export class OrderTracker { this._xrpl = XRPL.getInstance(network); this._orderStorage = this._xrpl.orderStorage; this._wallet = wallet; + this._inflightOrders = {}; + this._orderMutexManager = new OrderMutexManager(this._inflightOrders); + this._isTracking = false; + // set tracking interval to 10 seconds + this._orderTrackingInterval = 10000; } public static getInstance( @@ -76,54 +84,162 @@ export class OrderTracker { return OrderTracker._instances.get(instanceKey) as OrderTracker; } - public async saveOrder(order: Order): Promise { - this._orderStorage.saveOrder( - this.chain, - this.network, - this._wallet.classicAddress, - order - ); + public get wallet(): Wallet { + return this._wallet; } - public async deleteOrder(order: Order): Promise { - this._orderStorage.deleteOrder( - this.chain, - this.network, - this._wallet.classicAddress, - order + public get isTracking(): boolean { + return this._isTracking; + } + + public addOrder(order: Order): void { + this._inflightOrders[order.hash] = order; + } + + public getOrder(hash: number): Order | undefined { + return this._inflightOrders[hash]; + } + + async saveInflightOrdersToDB(): Promise { + await Promise.all( + Object.keys(this._inflightOrders).map(async (hash) => { + await this._orderStorage.saveOrder( + this.chain, + this.network, + this._wallet.classicAddress, + this._inflightOrders[parseInt(hash)] + ); + }) ); } - public async getOrdersByState( - state: OrderStatus - ): Promise> { - return this._orderStorage.getOrdersByState( + public async loadInflightOrders(): Promise { + const orders = await this._orderStorage.getInflightOrders( this.chain, this.network, - this._wallet.classicAddress, - state + this._wallet.classicAddress ); + + Object.keys(orders).forEach((hash) => { + this._inflightOrders[parseInt(hash)] = orders[parseInt(hash)]; + }); + this._orderMutexManager.updateOrders(this._inflightOrders); + } + + public async startTracking(): Promise { + if (this._isTracking) { + return; + } + + this._isTracking = true; + + await this._xrpl.ensureConnection(); + await this.loadInflightOrders(); + + const client = this._xrpl.client; + + client.on('transaction', async (event) => { + const updatedOrders = await this.processTransactionStream( + this._inflightOrders, + event, + this._orderMutexManager + ); + + // merge updateOrders with inflightOrders + Object.keys(updatedOrders).forEach((hash) => { + this._inflightOrders[parseInt(hash)] = updatedOrders[parseInt(hash)]; + }); + + this._orderMutexManager.updateOrders(this._inflightOrders); + }); + + await client.request({ + command: 'subscribe', + accounts: [this._wallet.classicAddress], + }); + + while (this._isTracking) { + // Make sure connection is good + await this._xrpl.ensureConnection(); + + // Check pending inflightOrders + const hashes = Object.keys(this._inflightOrders); + for (const hash of hashes) { + this._inflightOrders[parseInt(hash)] = await this.checkPendingOrder( + client, + this._inflightOrders[parseInt(hash)], + this._orderMutexManager + ); + } + + // Check open inflightOrders + const updatedOrders = await this.checkOpenOrders( + this._inflightOrders, + this._wallet.classicAddress, + client, + this._orderMutexManager + ); + + // merge updateOrders with inflightOrders + Object.keys(updatedOrders).forEach((hash) => { + this._inflightOrders[parseInt(hash)] = updatedOrders[parseInt(hash)]; + }); + + // Update mutex manager + this._orderMutexManager.updateOrders(this._inflightOrders); + + // Save inflightOrders to DB + await this.saveInflightOrdersToDB(); + + // Wait for next interval + await new Promise((resolve) => + setTimeout(resolve, this._orderTrackingInterval) + ); + } } - // TODO: Start implementing order tracker! + public async stopTracking(): Promise { + this._isTracking = false; + const client = this._xrpl.client; + client.removeAllListeners('transaction'); - // startTracking(): void { - // if (this._trackingId) { - // return; - // } + if (client.isConnected()) { + await client.request({ + command: 'unsubscribe', + accounts: [this._wallet.classicAddress], + }); + } + } - // this._trackingId = setInterval(() => { - // this._trackOrders(); - // }, 1000); - // } + public static async stopTrackingOnAllInstances(): Promise { + const instances = OrderTracker._instances; + if (instances === undefined) { + return; + } - // stopTracking(): void { - // clearInterval(this._trackingId); - // } + await Promise.all( + Array.from(instances.values()).map(async (instance) => { + await instance.stopTracking(); + }) + ); + } - // private async _trackOrders(): Promise { - // return; - // } + public static async stopTrackingOnAllInstancesForNetwork( + network: string + ): Promise { + const instances = OrderTracker._instances; + if (instances === undefined) { + return; + } + + await Promise.all( + Array.from(instances.values()).map(async (instance) => { + if (instance.network === network) { + await instance.stopTracking(); + } + }) + ); + } async checkPendingOrder( client: Client, @@ -217,8 +333,8 @@ export class OrderTracker { return result; } - async checkOpenOrder( - inflightOrders: InflightOrders, + async checkOpenOrders( + openOrders: InflightOrders, account: string, client: Client, omm: OrderMutexManager @@ -228,15 +344,14 @@ export class OrderTracker { // 2. Get the transactions stack based on minLedgerIndex and account id // 3. Process the transactions stack // 4. Update the inflightOrders + const ordersToCheck: InflightOrders = openOrders; // 1. Get the minLedgerIndex from the inflightOrders - const hashes = Object.keys(inflightOrders); + const hashes = Object.keys(ordersToCheck); let minLedgerIndex = 0; for (const hash of hashes) { - if ( - inflightOrders[parseInt(hash)].updatedAtLedgerIndex > minLedgerIndex - ) { - minLedgerIndex = inflightOrders[parseInt(hash)].updatedAtLedgerIndex; + if (ordersToCheck[parseInt(hash)].updatedAtLedgerIndex > minLedgerIndex) { + minLedgerIndex = ordersToCheck[parseInt(hash)].updatedAtLedgerIndex; } } @@ -249,11 +364,11 @@ export class OrderTracker { // 3. Process the transactions stack if (txStack === null) { - return inflightOrders; + return ordersToCheck; } if (txStack.result?.transactions === undefined) { - return inflightOrders; + return ordersToCheck; } for (const tx of txStack.result.transactions) { @@ -264,16 +379,18 @@ export class OrderTracker { } const updatedOrders = await this.processTransactionStream( - inflightOrders, + ordersToCheck, transformedTx, omm ); - // merge updateOrders with inflightOrders + // merge updateOrders to ordersToCheck Object.keys(updatedOrders).forEach((hash) => { - inflightOrders[parseInt(hash)] = updatedOrders[parseInt(hash)]; + ordersToCheck[parseInt(hash)] = updatedOrders[parseInt(hash)]; }); } + + return ordersToCheck; } async processTransactionStream( @@ -284,19 +401,20 @@ export class OrderTracker { const transactionIntent = await this.getTransactionIntentFromStream( transaction ); + const ordersToCheck = inflightOrders; // console.log('Transaction intent: '); // console.log(inspect(transactionIntent, { colors: true, depth: null })); if (transactionIntent.sequence === undefined) { console.log('No sequence found!'); - return inflightOrders; + return ordersToCheck; } - const matchOrder = inflightOrders[transactionIntent.sequence]; + const matchOrder = ordersToCheck[transactionIntent.sequence]; if (matchOrder === undefined) { console.log('No match order found!'); - return inflightOrders; + return ordersToCheck; } // Wait until the order is not locked @@ -405,13 +523,13 @@ export class OrderTracker { // console.log('Updated matchOrder: '); // console.log(inspect(matchOrder, { colors: true, depth: null })); - // Update inflightOrders - inflightOrders[matchOrder.hash] = matchOrder; + // Update ordersToCheck + ordersToCheck[matchOrder.hash] = matchOrder; // Release the lock omm.release(matchOrder.hash); - return inflightOrders; + return ordersToCheck; } // Utility methods @@ -539,44 +657,3 @@ export class OrderTracker { return transformedTx; } } - -class OrderMutexManager { - private locks: OrderLocks = {}; - private orders: InflightOrders = {}; // orders to manage - - constructor(ordersToManage: InflightOrders) { - this.orders = ordersToManage; - - Object.keys(this.orders).forEach((hash) => { - this.locks[parseInt(hash)] = false; - }); - } - - // lock an order by hash - lock(hash: number) { - this.locks[hash] = true; - } - - // release an order by hash - release(hash: number) { - this.locks[hash] = false; - } - - // reset all locks - reset() { - Object.keys(this.orders).forEach((hash) => { - this.locks[parseInt(hash)] = false; - }); - } - - // update orders list and locks - updateOrders(orders: InflightOrders) { - this.orders = orders; - this.reset(); - } - - // get lock satus of an order by hash - isLocked(hash: number) { - return this.locks[hash]; - } -} diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index dc357915bf..e1e7dc1ffc 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -1,21 +1,22 @@ import { MarketInfo, XRPL } from '../../chains/xrpl/xrpl'; +import { XRPLOrderStorage } from '../../chains/xrpl/xrpl.order-storage'; import { Client, - OfferCancel, + Wallet, Transaction, xrpToDrops, AccountInfoResponse, BookOffersResponse, } from 'xrpl'; +import { OrderTracker } from './xrpl.order-tracker'; import { Market, MarketNotFoundError, Token, - // OrderStatus, TradeType, Orderbook, PriceLevel, - // Order, + Order, } from './xrpl.types'; import { ClobMarketsRequest, @@ -28,7 +29,6 @@ import { ClobGetOrderResponse, } from '../../clob/clob.requests'; import { promiseAllInBatches } from '../../chains/xrpl/xrpl.helpers'; -// import { isIssuedCurrency } from 'xrpl/dist/npm/models/transactions/common'; import { CLOBish, NetworkSelectionRequest, @@ -36,7 +36,6 @@ import { import LRUCache from 'lru-cache'; import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; import { isUndefined } from 'mathjs'; -// import { OrderType } from './xrpl.types'; const XRP_FACTOR = 1000000; const ORDERBOOK_LIMIT = 10; @@ -45,6 +44,7 @@ export class XRPLCLOB implements CLOBish { private static _instances: LRUCache; private readonly _client: Client; private readonly _xrpl: XRPL; + private readonly _orderStorage: XRPLOrderStorage; private _ready: boolean = false; @@ -58,6 +58,7 @@ export class XRPLCLOB implements CLOBish { this._xrpl = XRPL.getInstance(network); this._client = this._xrpl.client; + this._orderStorage = this._xrpl.orderStorage; } public static getInstance(chain: string, network: string): XRPLCLOB { @@ -78,7 +79,7 @@ export class XRPLCLOB implements CLOBish { public async loadMarkets() { const rawMarkets = await this.fetchMarkets(); for (const market of rawMarkets) { - this.parsedMarkets[market.marketId.replace('/', '-')] = market; + this.parsedMarkets[market.marketId] = market; } } @@ -99,16 +100,13 @@ export class XRPLCLOB implements CLOBish { public async markets( req: ClobMarketsRequest ): Promise<{ markets: CLOBMarkets }> { - if (req.market && req.market.split('-').length === 2) { - const resp: CLOBMarkets = {}; - resp[req.market] = this.parsedMarkets[req.market]; - return { markets: resp }; - } - return { markets: this.parsedMarkets }; + if (req.market && req.market in this.parsedMarkets) + return { markets: this.parsedMarkets[req.market] }; + return { markets: Object.values(this.parsedMarkets) }; } public async orderBook(req: ClobOrderbookRequest): Promise { - return await this.getOrderBook(this.parsedMarkets[req.market].marketId); + return await this.getOrderBook(this.parsedMarkets[req.market]); } // Utility methods: @@ -135,7 +133,7 @@ export class XRPLCLOB implements CLOBish { quoteTransferRate: number; const zeroTransferRate = 1000000000; - const [baseCurrency, quoteCurrency] = market.code.split('/'); + const [baseCurrency, quoteCurrency] = market.marketId.split('-'); const baseIssuer = market.baseIssuer; const quoteIssuer = market.quoteIssuer; @@ -185,7 +183,7 @@ export class XRPLCLOB implements CLOBish { const minimumOrderSize = smallestTickSize; const result = { - marketId: market.code, + marketId: market.marketId, minimumOrderSize: minimumOrderSize, tickSize: smallestTickSize, baseTransferRate: baseTransferRate, @@ -200,7 +198,7 @@ export class XRPLCLOB implements CLOBish { } async getOrderBook(market: MarketInfo): Promise { - const [baseCurrency, quoteCurrency] = market.code.split('/'); + const [baseCurrency, quoteCurrency] = market.marketId.split('-'); const baseIssuer = market.baseIssuer; const quoteIssuer = market.quoteIssuer; @@ -219,24 +217,10 @@ export class XRPLCLOB implements CLOBish { quoteRequest['issuer'] = quoteIssuer; } - const orderbook_resp_ask: BookOffersResponse = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker_gets: baseRequest, - taker_pays: quoteRequest, - limit: ORDERBOOK_LIMIT, - }); - - const orderbook_resp_bid: BookOffersResponse = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker_gets: quoteRequest, - taker_pays: baseRequest, - limit: ORDERBOOK_LIMIT, - }); - - const asks = orderbook_resp_ask.result.offers; - const bids = orderbook_resp_bid.result.offers; + const { bids, asks } = await this.getOrderBookFromXRPL( + baseRequest, + quoteRequest + ); const buys: PriceLevel[] = []; const sells: PriceLevel[] = []; @@ -246,24 +230,56 @@ export class XRPLCLOB implements CLOBish { let price, quantity: string; - if (typeof bid.taker_gets_funded === 'string') { - price = (Math.pow(parseFloat(bid.quality), -1) / XRP_FACTOR).toString(); - quantity = (parseFloat(bid.taker_gets_funded) * XRP_FACTOR).toString(); - } else if (typeof bid.taker_pays_funded === 'string') { - if (isUndefined(bid.taker_gets_funded)) return; - if (isUndefined(bid.taker_gets_funded?.value)) return; - - price = (Math.pow(parseFloat(bid.quality), -1) * XRP_FACTOR).toString(); - quantity = bid.taker_gets_funded.value; + if ( + isUndefined(bid.taker_gets_funded) && + isUndefined(bid.taker_pays_funded) + ) { + if (typeof bid.TakerGets === 'string') { + price = ( + Math.pow(parseFloat(bid.quality), -1) / XRP_FACTOR + ).toString(); + quantity = (parseFloat(bid.TakerGets) * XRP_FACTOR).toString(); + } else if (typeof bid.TakerPays === 'string') { + if (isUndefined(bid.TakerGets)) return; + if (isUndefined(bid.TakerGets?.value)) return; + + price = ( + Math.pow(parseFloat(bid.quality), -1) * XRP_FACTOR + ).toString(); + quantity = bid.TakerGets.value; + } else { + if (isUndefined(bid.TakerGets)) return; + if (isUndefined(bid.TakerGets?.value)) return; + + price = Math.pow(parseFloat(bid.quality), -1).toString(); + quantity = bid.TakerGets.value; + } } else { - if (isUndefined(bid.taker_gets_funded)) return; - if (isUndefined(bid.taker_gets_funded?.value)) return; - - price = Math.pow(parseFloat(bid.quality), -1).toString(); - quantity = bid.taker_gets_funded.value; + if (typeof bid.taker_gets_funded === 'string') { + price = ( + Math.pow(parseFloat(bid.quality), -1) / XRP_FACTOR + ).toString(); + quantity = ( + parseFloat(bid.taker_gets_funded) * XRP_FACTOR + ).toString(); + } else if (typeof bid.taker_pays_funded === 'string') { + if (isUndefined(bid.taker_gets_funded)) return; + if (isUndefined(bid.taker_gets_funded?.value)) return; + + price = ( + Math.pow(parseFloat(bid.quality), -1) * XRP_FACTOR + ).toString(); + quantity = bid.taker_gets_funded.value; + } else { + if (isUndefined(bid.taker_gets_funded)) return; + if (isUndefined(bid.taker_gets_funded?.value)) return; + + price = Math.pow(parseFloat(bid.quality), -1).toString(); + quantity = bid.taker_gets_funded.value; + } } - buys.concat({ + buys.push({ price, quantity, timestamp: Date.now(), @@ -275,24 +291,48 @@ export class XRPLCLOB implements CLOBish { let price, quantity: string; - if (typeof ask.taker_gets_funded === 'string') { - price = (parseFloat(ask.quality) * XRP_FACTOR).toString(); - quantity = (parseFloat(ask.taker_gets_funded) * XRP_FACTOR).toString(); - } else if (typeof ask.taker_pays_funded === 'string') { - if (isUndefined(ask.taker_gets_funded)) return; - if (isUndefined(ask.taker_gets_funded?.value)) return; - - price = (parseFloat(ask.quality) / XRP_FACTOR).toString(); - quantity = ask.taker_gets_funded.value; + if ( + isUndefined(ask.taker_gets_funded) && + isUndefined(ask.taker_pays_funded) + ) { + if (typeof ask.TakerGets === 'string') { + price = (parseFloat(ask.quality) * XRP_FACTOR).toString(); + quantity = (parseFloat(ask.TakerGets) / XRP_FACTOR).toString(); + } else if (typeof ask.TakerPays === 'string') { + if (isUndefined(ask.TakerGets)) return; + if (isUndefined(ask.TakerGets?.value)) return; + + price = (parseFloat(ask.quality) / XRP_FACTOR).toString(); + quantity = ask.TakerGets.value; + } else { + if (isUndefined(ask.TakerPays)) return; + if (isUndefined(ask.TakerPays?.value)) return; + + price = ask.quality; + quantity = ask.TakerGets.value; + } } else { - if (isUndefined(ask.taker_gets_funded)) return; - if (isUndefined(ask.taker_gets_funded?.value)) return; - - price = ask.quality; - quantity = ask.taker_gets_funded.value; + if (typeof ask.taker_gets_funded === 'string') { + price = (parseFloat(ask.quality) * XRP_FACTOR).toString(); + quantity = ( + parseFloat(ask.taker_gets_funded) / XRP_FACTOR + ).toString(); + } else if (typeof ask.taker_pays_funded === 'string') { + if (isUndefined(ask.taker_gets_funded)) return; + if (isUndefined(ask.taker_gets_funded?.value)) return; + + price = (parseFloat(ask.quality) / XRP_FACTOR).toString(); + quantity = ask.taker_gets_funded.value; + } else { + if (isUndefined(ask.taker_gets_funded)) return; + if (isUndefined(ask.taker_gets_funded?.value)) return; + + price = ask.quality; + quantity = ask.taker_gets_funded.value; + } } - sells.concat({ + sells.push({ price, quantity, timestamp: Date.now(), @@ -315,108 +355,47 @@ export class XRPLCLOB implements CLOBish { req: ClobGetOrderRequest ): Promise<{ orders: ClobGetOrderResponse['orders'] }> { if (!req.market) return { orders: [] }; - // const market = this.parsedMarkets[req.market] as Market; - // const [baseCurrency, quoteCurrency] = market.marketId.split('/'); - // const baseIssuer = market.baseIssuer; - // const quoteIssuer = market.quoteIssuer; - // const orders: Order[] = []; - - // const baseRequest: any = { - // currency: baseCurrency, - // }; - - // const quoteRequest: any = { - // currency: quoteCurrency, - // }; - - // if (baseIssuer) { - // baseRequest['issuer'] = baseIssuer; - // } - // if (quoteIssuer) { - // quoteRequest['issuer'] = quoteIssuer; - // } - - // const orderbook_resp_ask: BookOffersResponse = await this._client.request({ - // command: 'book_offers', - // ledger_index: 'validated', - // taker: req.address, - // taker_gets: baseRequest, - // taker_pays: quoteRequest, - // }); - - // const orderbook_resp_bid: BookOffersResponse = await this._client.request({ - // command: 'book_offers', - // ledger_index: 'validated', - // taker: req.address, - // taker_gets: quoteRequest, - // taker_pays: baseRequest, - // }); - - // let asks = orderbook_resp_ask.result.offers; - // let bids = orderbook_resp_bid.result.offers; - - // asks = asks.filter((ask) => ask.Account == req.address); - // bids = bids.filter((bid) => bid.Account == req.address); - - // for (const ask of asks) { - // const price = ask.quality ?? '-1'; - // let amount: string = ''; - - // if (isIssuedCurrency(ask.TakerGets)) { - // amount = ask.TakerGets.value; - // } else { - // amount = ask.TakerGets; - // } - - // // orders.push({ - // // hash: ask.Sequence, - // // marketId: marketId, - // // price: price, - // // amount: amount, - // // state: OrderStatus.OPEN, // TODO: create middle class to track this property - // // tradeType: TradeType.SELL, - // // orderType: OrderType.LIMIT, - // // createdAt: Date.now(), // TODO: create middle class to track this property - // // updatedAt: Date.now(), // TODO: create middle class to track this property - // // }); - // } - - // for (const bid of bids) { - // const price = Math.pow(Number(bid.quality), -1).toString() ?? '-1'; - // let amount: string = ''; - - // if (isIssuedCurrency(bid.TakerGets)) { - // amount = bid.TakerGets.value; - // } else { - // amount = bid.TakerGets; - // } - - // // orders.push({ - // // hash: bid.Sequence, - // // marketId: marketId, - // // price: price, - // // amount: amount, - // // state: OrderStatus.OPEN, // TODO: create middle class to track this property - // // tradeType: TradeType.BUY, - // // orderType: OrderType.LIMIT, - // // createdAt: Date.now(), // TODO: create middle class to track this property - // // updatedAt: Date.now(), // TODO: create middle class to track this property - // // }); - // } - - return { orders: [] } as ClobGetOrderResponse; + if (!req.address) return { orders: [] }; + + if (!req.orderId) { + const marketId = this.parsedMarkets[req.market].marketId; + const orders = await this._orderStorage.getOrdersByMarket( + this.chain, + this.network, + req.address, + marketId + ); + + const keys = Object.keys(orders); + const ordersArray = keys.map((key) => orders[key]); + + return { orders: ordersArray } as ClobGetOrderResponse; + } else { + const marketId = this.parsedMarkets[req.market].marketId; + const orders = await this._orderStorage.getOrderByMarketAndHash( + this.chain, + this.network, + req.address, + marketId, + req.orderId + ); + + const keys = Object.keys(orders); + const ordersArray = keys.map((key) => orders[key]); + + return { orders: ordersArray } as ClobGetOrderResponse; + } } public async postOrder( req: ClobPostOrderRequest ): Promise<{ txHash: string }> { const market = this.parsedMarkets[req.market] as Market; - const [baseCurrency, quoteCurrency] = market.marketId.split('/'); + const [baseCurrency, quoteCurrency] = market.marketId.split('-'); const baseIssuer = market.baseIssuer; const quoteIssuer = market.quoteIssuer; - const xrpl = this._xrpl; - const wallet = await xrpl.getWallet(req.address); + const wallet = await this.getWallet(req.address); const total = parseFloat(req.price) * parseFloat(req.amount); let we_pay: Token = { @@ -469,37 +448,88 @@ export class XRPLCLOB implements CLOBish { TakerPays: we_get.currency == 'XRP' ? we_get.value : we_get, }; - const prepared = await this._client.autofill(offer); - const signed = wallet.sign(prepared); - const response = await this._client.submit(signed.tx_blob); + const { prepared, signed } = await this.submitTxn(offer, wallet); + + const currentTime = Date.now(); + const currentLedgerIndex = await this.getCurrentBlockNumber(); + + const order: Order = { + hash: prepared.Sequence ? prepared.Sequence : 0, + marketId: baseCurrency + '-' + quoteCurrency, + price: req.price, + amount: req.amount, + filledAmount: '0', + state: 'PENDING_OPEN', + tradeType: req.side, + orderType: 'LIMIT', + createdAt: currentTime, + createdAtLedgerIndex: currentLedgerIndex, + updatedAt: currentTime, + updatedAtLedgerIndex: currentLedgerIndex, + associatedTxns: [signed.hash], + }; + + const orderTracker = OrderTracker.getInstance( + this.chain, + this.network, + wallet + ); - const txHash = response.result.tx_json.hash - ? response.result.tx_json.hash - : ''; + orderTracker.addOrder(order); - return { txHash }; + if (orderTracker.isTracking) { + orderTracker.startTracking(); + } + + return { txHash: signed.hash }; } public async deleteOrder( req: ClobDeleteOrderRequest ): Promise<{ txHash: string }> { - const xrpl = this._xrpl; - const wallet = await xrpl.getWallet(req.address); - const request: OfferCancel = { + const wallet = await this.getWallet(req.address); + const offer: Transaction = { TransactionType: 'OfferCancel', Account: wallet.classicAddress, OfferSequence: parseInt(req.orderId), }; - const prepared = await this._client.autofill(request); - const signed = wallet.sign(prepared); - const response = await this._client.submit(signed.tx_blob); + const { signed } = await this.submitTxn(offer, wallet); - const txHash = response.result.tx_json.hash - ? response.result.tx_json.hash - : ''; + const orderTracker = OrderTracker.getInstance( + this.chain, + this.network, + wallet + ); - return { txHash }; + let order = orderTracker.getOrder(parseInt(req.orderId)); + + if (order) { + order.state = 'PENDING_CANCEL'; + order.updatedAt = Date.now(); + order.updatedAtLedgerIndex = await this.getCurrentBlockNumber(); + order.associatedTxns.push(signed.hash); + } else { + order = { + hash: parseInt(req.orderId), + marketId: '', + price: '', + amount: '', + filledAmount: '', + state: 'PENDING_CANCEL', + tradeType: TradeType.UNKNOWN, + orderType: 'LIMIT', + createdAt: Date.now(), + createdAtLedgerIndex: await this.getCurrentBlockNumber(), + updatedAt: Date.now(), + updatedAtLedgerIndex: await this.getCurrentBlockNumber(), + associatedTxns: [signed.hash], + }; + } + + orderTracker.addOrder(order); + + return { txHash: signed.hash }; } public estimateGas(_req: NetworkSelectionRequest): { @@ -508,6 +538,48 @@ export class XRPLCLOB implements CLOBish { gasLimit: number; gasCost: number; } { + return this.getFeeEstimate(); + } + + private async submitTxn(offer: Transaction, wallet: Wallet) { + const prepared = await this._client.autofill(offer); + const signed = wallet.sign(prepared); + await this._client.submit(signed.tx_blob); + return { prepared, signed }; + } + + private async getWallet(address: string) { + const wallet = await this._xrpl.getWallet(address); + return wallet; + } + + private async getOrderBookFromXRPL(baseRequest: any, quoteRequest: any) { + const orderbook_resp_ask: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: baseRequest, + taker_pays: quoteRequest, + limit: ORDERBOOK_LIMIT, + }); + + const orderbook_resp_bid: BookOffersResponse = await this._client.request({ + command: 'book_offers', + ledger_index: 'validated', + taker_gets: quoteRequest, + taker_pays: baseRequest, + limit: ORDERBOOK_LIMIT, + }); + + const asks = orderbook_resp_ask.result.offers; + const bids = orderbook_resp_bid.result.offers; + return { bids, asks }; + } + + private async getCurrentBlockNumber() { + return await this._client.getLedgerIndex(); + } + + private getFeeEstimate() { const fee_estimate = this._xrpl.fee; return { diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index 3d65f99342..2eeb2f08f7 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -10,6 +10,7 @@ export const ISet = ImmutableSet; export enum TradeType { BUY = 'BUY', SELL = 'SELL', + UNKNOWN = 'UNKNOWN', } export enum OrderStatus { diff --git a/src/connectors/xrpl/xrpl.util.ts b/src/connectors/xrpl/xrpl.util.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/connectors/xrpl/xrpl.utils.ts b/src/connectors/xrpl/xrpl.utils.ts new file mode 100644 index 0000000000..70f33ec657 --- /dev/null +++ b/src/connectors/xrpl/xrpl.utils.ts @@ -0,0 +1,58 @@ +import { InflightOrders, OrderLocks } from './xrpl.types'; + +export class OrderMutexManager { + private locks: OrderLocks = {}; + private orders: InflightOrders = {}; // orders to manage + + constructor(ordersToManage: InflightOrders) { + this.orders = ordersToManage; + + Object.keys(this.orders).forEach((hash) => { + this.locks[parseInt(hash)] = false; + }); + } + + // lock an order by hash + lock(hash: number) { + this.locks[hash] = true; + } + + // release an order by hash + release(hash: number) { + this.locks[hash] = false; + } + + // reset all locks + reset() { + Object.keys(this.orders).forEach((hash) => { + this.locks[parseInt(hash)] = false; + }); + } + + // add new orders to manage + addOrders(orders: InflightOrders) { + Object.keys(orders).forEach((hash) => { + this.orders[parseInt(hash)] = orders[parseInt(hash)]; + this.locks[parseInt(hash)] = false; + }); + } + + // remove orders from manage + removeOrders(orders: InflightOrders) { + Object.keys(orders).forEach((hash) => { + delete this.orders[parseInt(hash)]; + delete this.locks[parseInt(hash)]; + }); + } + + // update orders list and locks + updateOrders(orders: InflightOrders) { + this.orders = orders; + this.reset(); + } + + // get lock satus of an order by hash + isLocked(hash: number) { + return this.locks[hash]; + } +} \ No newline at end of file diff --git a/src/services/connection-manager.ts b/src/services/connection-manager.ts index 2a2e256e25..f4c3171bbd 100644 --- a/src/services/connection-manager.ts +++ b/src/services/connection-manager.ts @@ -19,7 +19,7 @@ import { VVSConnector } from '../connectors/vvs/vvs'; import { InjectiveCLOB } from '../connectors/injective/injective'; import { InjectiveClobPerp } from '../connectors/injective_perpetual/injective.perp'; import { Injective } from '../chains/injective/injective'; -import { XRPLDEX } from '../connectors/xrpldex/xrpldex'; +import { XRPLCLOB } from '../connectors/xrpl/xrpl'; import { CLOBish, Ethereumish, @@ -29,7 +29,7 @@ import { Uniswapish, UniswapLPish, Xdcish, - Tezosish + Tezosish, } from './common-interfaces'; import { Traderjoe } from '../connectors/traderjoe/traderjoe'; import { Sushiswap } from '../connectors/sushiswap/sushiswap'; @@ -148,7 +148,7 @@ export type ConnectorUnion = | InjectiveClobPerp | Tinyman | Plenty - | XRPLDEX; + | XRPLCLOB; export type Connector = T extends Uniswapish ? Uniswapish @@ -167,7 +167,7 @@ export type Connector = T extends Uniswapish : T extends Plenty ? Plenty : T extends XRPLish - ? XRPLDEX + ? XRPLCLOB : never; export async function getConnector( @@ -222,8 +222,8 @@ export async function getConnector( connectorInstance = Tinyman.getInstance(network); } else if (chain === 'tezos' && connector === 'plenty') { connectorInstance = Plenty.getInstance(network); - } else if (chain === 'xrpl' && connector === 'xrpldex') { - connectorInstance = XRPLDEX.getInstance(chain, network); + } else if (chain === 'xrpl' && connector === 'xrpl') { + connectorInstance = XRPLCLOB.getInstance(chain, network); } else { throw new Error('unsupported chain or connector'); } diff --git a/src/services/schema/xrpl-schema.json b/src/services/schema/xrpl-schema.json index 6c7a9bf454..5c9b88d2cc 100644 --- a/src/services/schema/xrpl-schema.json +++ b/src/services/schema/xrpl-schema.json @@ -11,6 +11,8 @@ "nodeURL": { "type": "string" }, "tokenListType": { "type": "string" }, "tokenListSource": { "type": "string" }, + "marketListType": { "type": "string" }, + "marketListSource": { "type": "string" }, "nativeCurrencySymbol": { "type": "string" } }, "required": [ @@ -29,6 +31,8 @@ "connectionTimeout": { "type": "integer" }, "feeCushion": { "type": "number" }, "maxFeeXRP": { "type": "string" }, + "orderDbPath": { "type": "string" }, + "maxLRUCacheInstances": { "type": "number" }, "retry": { "type": "object", "required": ["all"], diff --git a/src/templates/xrpl.yml b/src/templates/xrpl.yml index 9532909d32..a9cd319b78 100644 --- a/src/templates/xrpl.yml +++ b/src/templates/xrpl.yml @@ -1,30 +1,32 @@ networks: mainnet: nodeURL: wss://xrplcluster.com/ - tokenListType: 'FILE' - tokenListSource: 'src/chains/xrpl/xrpl_tokens.json' - marketListType: 'FILE' - marketListSource: 'src/chains/xrpl/xrpl_markets.json' - nativeCurrencySymbol: 'XRP' + tokenListType: "FILE" + tokenListSource: "src/chains/xrpl/xrpl_tokens.json" + marketListType: "FILE" + marketListSource: "src/chains/xrpl/xrpl_markets.json" + nativeCurrencySymbol: "XRP" testnet: nodeURL: wss://s.altnet.rippletest.net/ - tokenListType: 'FILE' - tokenListSource: 'src/chains/xrpl/xrpl_tokens_testnet.json' - marketListType: 'FILE' - marketListSource: 'src/chains/xrpl/xrpl_markets_testnet.json' - nativeCurrencySymbol: 'XRP' + tokenListType: "FILE" + tokenListSource: "src/chains/xrpl/xrpl_tokens_testnet.json" + marketListType: "FILE" + marketListSource: "src/chains/xrpl/xrpl_markets_testnet.json" + nativeCurrencySymbol: "XRP" devnet: nodeURL: wss://s.devnet.rippletest.net/ - tokenListType: 'FILE' - tokenListSource: 'src/chains/xrpl/xrpl_tokens_devnet.json' - marketListType: 'FILE' - marketListSource: 'src/chains/xrpl/xrpl_markets_devnet.json' - nativeCurrencySymbol: 'XRP' -network: 'xrpl' + tokenListType: "FILE" + tokenListSource: "src/chains/xrpl/xrpl_tokens_devnet.json" + marketListType: "FILE" + marketListSource: "src/chains/xrpl/xrpl_markets_devnet.json" + nativeCurrencySymbol: "XRP" +network: "xrpl" requestTimeout: 2000 connectionTimeout: 2000 feeCushion: 1.2 -maxFeeXRP: '2' +maxFeeXRP: "2" +orderDbPath: "/db/xrpl.level" +maxLRUCacheInstances: 10 retry: all: maxNumberOfRetries: 3 # 0 means no retries @@ -34,4 +36,4 @@ timeout: parallel: all: batchSize: 1 # 0 means no batching, group all - delayBetweenBatches: 1 # in milliseconds, 0 means no delay \ No newline at end of file + delayBetweenBatches: 1 # in milliseconds, 0 means no delay diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts new file mode 100644 index 0000000000..9127a2d0f0 --- /dev/null +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -0,0 +1,593 @@ +import request from 'supertest'; +import { gatewayApp } from '../../../src/app'; +import { XRPL } from '../../../src/chains/xrpl/xrpl'; +import { XRPLCLOB } from '../../../src/connectors/xrpl/xrpl'; +import { Order } from '../../../src/connectors/xrpl/xrpl.types'; +import { patch, unpatch } from '../../services/patch'; + +let xrpl: XRPL; +let xrplCLOB: XRPLCLOB; + +const TX_HASH = + 'ADEC6FF35C49B9FF5D06741B5D219D194568919A57876E29296AC530EB25F1CB'; // noqa: mock +const MARKET = 'USD-XRP'; + +const MARKETS = { + 'USD-XRP': { + marketId: 'USD-XRP', + minimumOrderSize: 1, + tickSize: 1, + baseTransferRate: 1, + quoteTransferRate: 1, + baseIssuer: 'r999', + quoteIssuer: '', + baseCurrency: 'USD', + quoteCurrency: 'XRP', + }, + 'BTC-XRP': { + marketId: 'BTC-XRP', + minimumOrderSize: 1, + tickSize: 1, + baseTransferRate: 1, + quoteTransferRate: 1, + baseIssuer: 'r888', + quoteIssuer: '', + baseCurrency: 'BTC', + quoteCurrency: 'XRP', + }, +}; + +const ORDER = { + hash: 1234567, + marketId: 'USD-XRP', + price: '1', + amount: '1', + filledAmount: '0', + state: 'OPEN', + tradeType: 'BUY', + orderType: 'LIMIT', + createdAt: 1234567, + createdAtLedgerIndex: 1234567, + updatedAt: 1234567, + updatedAtLedgerIndex: 1234567, + associatedTxns: [TX_HASH], +}; + +const ORDER_BOOK_1 = { + asks: [ + { + Account: 'rBTwLga3i2gz3doX6Gva3MgEV8ZCD8jjah', + BookDirectory: + 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E1282583CD33780', + BookNode: '0', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0', + PreviousTxnID: + '5F8F23607A58A936A5F1D355A7A7491474AABB115C70F5773F3510082EE9B9CB', + PreviousTxnLgrSeq: 80327788, + Sequence: 95908936, + TakerGets: '60133000000', + TakerPays: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '31328.481625431', + }, + index: 'BBA5F7052917489072A98DDBE46D3258C5A58BB00771FB784A315F6F9C27A9F2', + owner_funds: '60199074323', + quality: '0.000000520986507', + }, + { + Account: 'rBndiPPKs9k5rjBb7HsEiqXKrz8AfUnqWq', + BookDirectory: + 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E12826062F5399E', + BookNode: '0', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0', + PreviousTxnID: + 'AEA372417F1BEA783D317ADA70ECE577424F186F663AD621C2B26350784611AC', + PreviousTxnLgrSeq: 80327786, + Sequence: 2591399, + TakerGets: '2618294750', + TakerPays: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '1364.1054', + }, + index: '6F758711E36E2DBDD6806F123B0266FF7C2556D6918F068573E9B8B6F0FA44B0', + owner_funds: '2648294730', + quality: '0.0000005209900069501342', + }, + { + Account: 'r39rBggWHTUN95x31mAdxPCC7XnhuHRHor', + BookDirectory: + 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E12843D2E482F0A', + BookNode: '0', + Flags: 131072, + LedgerEntryType: 'Offer', + OwnerNode: '1024a', + PreviousTxnID: + '4C9178098581A43C88E8C42760D9CF86CCDC9B18B99DDA20E91CEA63BC156559', + PreviousTxnLgrSeq: 80327776, + Sequence: 3289832, + TakerGets: '404927402', + TakerPays: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '211.046051649151', + }, + index: '41E60EC5FE55577CC6B9DDE07EF0A6659D2B069E25F12F75BCEDCF94E2007B7B', + owner_funds: '23953272168', + quality: '0.0000005211947885145866', + }, + ], + bids: [ + { + Account: 'rhG9NsvuiG9q3acfR8YbuQd5MabMVDocpc', + BookDirectory: + '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5B06E2259ADB6200', + BookNode: '0', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '3', + PreviousTxnID: + 'F4D2C5D67C38C30E611815222D1402ECD438DE167252BF434E2CFA58BF69E804', + PreviousTxnLgrSeq: 80326053, + Sequence: 79612812, + TakerGets: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '5', + }, + TakerPays: '9687505', + index: '379709FE07C7209BF66F31255822049087E1B4977B556462E46D5F26183C2CC7', + owner_funds: '67.76153742499045', + quality: '1937501', + }, + { + Account: 'rBTwLga3i2gz3doX6Gva3MgEV8ZCD8jjah', + BookDirectory: + '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5B06E72B1C323523', + BookNode: '0', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0', + PreviousTxnID: + '79759B2B346BADF16A3E0A7CC52F7EE77AAE356FAB04178F097329CAEBD25FB5', + PreviousTxnLgrSeq: 80327788, + Sequence: 95908935, + TakerGets: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '102932.4316', + }, + TakerPays: '200000000000', + index: '2C61AF858D251FE41FDF7768B42C412C39A49FC562B063A1A40A4C175AD8C6A6', + owner_funds: '192823.71422378', + quality: '1943022.202926371', + }, + { + Account: 'r39rBggWHTUN95x31mAdxPCC7XnhuHRHor', + BookDirectory: + '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5B06E72BAB10809A', + BookNode: '0', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '1024a', + PreviousTxnID: + 'A5F18BB1F514F879E6B896F2353E84E5AD67D537B494D5C3237B7AA7B93CA9EC', + PreviousTxnLgrSeq: 80327776, + Sequence: 3289831, + TakerGets: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '14.1299883707017', + }, + TakerPays: '27454915', + index: '65D89D4C9EE529A927E4C831EE873F94BEBCDF29D3CCADD676AE77F7B1283A60', + owner_funds: '537.8134698861974', + quality: '1943024.599859354', + }, + ], +}; + +const ORDER_BOOK_2 = { + asks: [ + { + Account: 'rhWVeCa6aL7U5argwYiUpMD9Gxtd6kxkNw', + BookDirectory: + 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE491CC6E836AE4000', + BookNode: '0', + Flags: 131072, + LedgerEntryType: 'Offer', + OwnerNode: '0', + PreviousTxnID: + '44A811F5EE120D2215A7F482BDC4C0AF048C82C895C69E7E0FEC6163FCA5A48E', + PreviousTxnLgrSeq: 33839380, + Sequence: 33834963, + TakerGets: '2000000', + TakerPays: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '0.0000162', + }, + index: '772B28EBC0CB840329A49BF6B696EC454444560C1EA343EB1C29C9421889C45F', + owner_funds: '1383999940', + quality: '8100000000000000e-27', + }, + { + Account: 'rJHjA2WqqYWSh4ttCPW1b9aSyFkisfz93j', + BookDirectory: + 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE491CDAE1056DB731', + BookNode: '0', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0', + PreviousTxnID: + '5AA8D0E7FAB6E1C854F8E69C3C84A095FC9FEFF3BF29072D2D435758CA52DB9E', + PreviousTxnLgrSeq: 17154522, + Sequence: 17130191, + TakerGets: '123123000000', + TakerPays: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '1', + }, + index: 'BC91DD45090AC3C2955A1877706BB8E637E1AE9D4A5731A942A06C2887839DB0', + owner_funds: '967999808', + quality: '8121959341471537e-27', + taker_gets_funded: '967999808', + taker_pays_funded: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '0.007862055083128254', + }, + }, + { + Account: 'rMFwQW3F5EvvJ4mu9dBZ3kWnEwj9SHjoGd', + BookDirectory: + 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4D1D9B1F5D20D555', + BookNode: '0', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0', + PreviousTxnID: + 'CF1FDE17372D00219401EB4847B3281A2AF76485BEB0EDF7DF7A6D5DC387C6C6', + PreviousTxnLgrSeq: 16948251, + Sequence: 16801746, + TakerGets: '12000000', + TakerPays: { + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '1', + }, + index: '52F6B9F706D7324BBC6DC35EAFBE54E213930C99BD2432261D452E937C2817B8', + owner_funds: '175107078', + quality: '0.00000008333333333333333', + }, + ], + bids: [], +}; + +const GAS_PRICES = { + gasPrice: '500000000', + gasPriceToken: 'Token', + gasLimit: '1000', + gasCost: '100', +}; + +const INVALID_REQUEST = { + chain: 'unknown', + network: 'testnet', +}; + +beforeAll(async () => { + xrpl = XRPL.getInstance('testnet'); + patchConnect(); + xrpl.init(); + xrplCLOB = XRPLCLOB.getInstance('xrpl', 'testnet'); + await xrplCLOB.init(); +}); + +// eslint-disable-next-line @typescript-eslint/no-empty-function +beforeEach(() => { + patchConnect(); + patchFee(); + patchCurrentBlockNumber(); +}); + +afterEach(() => { + unpatch(); +}); + +afterAll(async () => { + await xrpl.close(); +}); + +const patchConnect = () => { + patch(xrpl, 'ensureConnection', () => { + return Promise.resolve(); + }); +}; + +const patchFee = () => { + patch(xrpl, 'getFee', () => { + return { + base: '1', + median: '1', + minimum: '1', + openLedger: '1', + }; + }); +}; + +const patchCurrentBlockNumber = (withError: boolean = false) => { + patch(xrplCLOB, 'getCurrentBlockNumber', () => { + return withError ? -1 : 100; + }); +}; + +const patchMarkets = () => { + patch(xrplCLOB, 'parsedMarkets', MARKETS); +}; + +const patchGetOrderBookFromXRPL = (orderbook: any) => { + patch(xrplCLOB, 'getOrderBookFromXRPL', () => { + return orderbook; + }); +}; + +const patchOrders = () => { + patch(xrplCLOB, '_orderStorage', { + async getOrderByMarketAndHash() { + const orders: Record = { [ORDER.hash]: ORDER }; + return orders; + }, + async getOrdersByMarket() { + const orders: Record = { [ORDER.hash]: ORDER }; + return orders; + }, + }); +}; + +const patchSubmitTxn = () => { + patch(xrplCLOB, 'submitTxn', () => { + return { + prepared: { + Sequence: 1234567, + }, + signed: { + hash: TX_HASH, + }, + }; + }); +}; + +const patchGetWallet = () => { + patch(xrplCLOB, 'getWallet', () => { + return { + classicAddress: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', + }; + }); +}; + +const patchGasPrices = () => { + patch(xrplCLOB, 'getFeeEstimate', () => { + return GAS_PRICES; + }); +}; + +describe('GET /clob/markets', () => { + it('should return 200 with proper request', async () => { + patchMarkets(); + await request(gatewayApp) + .get(`/clob/markets`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.markets.length).toEqual(2); + }); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/markets`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('GET /clob/orderBook', () => { + it('should return 200 with proper request with ORDER_BOOK_1', async () => { + patchMarkets(); + patchGetOrderBookFromXRPL(ORDER_BOOK_1); + await request(gatewayApp) + .get(`/clob/orderBook`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + market: MARKET, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => + expect(res.body.buys[0].price).toEqual('0.5161287658690241') + ) + .expect((res) => expect(res.body.buys[0].quantity).toEqual('5')) + .expect((res) => expect(res.body.sells[0].price).toEqual('0.520986507')) + .expect((res) => expect(res.body.sells[0].quantity).toEqual('60133')); + }); + + it('should return 200 with proper request with ORDER_BOOK_2', async () => { + patchMarkets(); + patchGetOrderBookFromXRPL(ORDER_BOOK_2); + await request(gatewayApp) + .get(`/clob/orderBook`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + market: MARKET, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.buys[0]).toBeUndefined()) + .expect((res) => expect(res.body.sells[0].price).toEqual('0.0000081')) + .expect((res) => expect(res.body.sells[0].quantity).toEqual('2')); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/orderBook`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('GET /clob/ticker', () => { + it('should return 200 with proper request', async () => { + patchMarkets(); + await request(gatewayApp) + .get(`/clob/ticker`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + market: MARKET, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.markets.baseCurrency).toEqual('USD')) + .expect((res) => expect(res.body.markets.quoteCurrency).toEqual('XRP')); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/ticker`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('GET /clob/orders', () => { + it('should return 200 with proper request', async () => { + patchMarkets(); + patchOrders(); + await request(gatewayApp) + .get(`/clob/orders`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', // noqa: mock + market: MARKET, + orderId: 1234567, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.orders.length).toEqual(1)); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/orders`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('POST /clob/orders', () => { + it('should return 200 with proper request', async () => { + patchMarkets(); + patchSubmitTxn(); + patchGetWallet(); + await request(gatewayApp) + .post(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', // noqa: mock + market: MARKET, + price: '1', + amount: '1', + side: 'BUY', + orderType: 'LIMIT', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.txHash).toEqual(TX_HASH)); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('DELETE /clob/orders', () => { + it('should return 200 with proper request', async () => { + patchSubmitTxn(); + patchGetWallet(); + await request(gatewayApp) + .delete(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', // noqa: mock + market: MARKET, + orderId: '1234567', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.txHash).toEqual(TX_HASH)); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .delete(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('GET /clob/estimateGas', () => { + it('should return 200 with proper request', async () => { + patchGasPrices(); + await request(gatewayApp) + .get(`/clob/estimateGas`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.gasPrice).toEqual(GAS_PRICES.gasPrice)); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/estimateGas`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); From ad5704aac6522ec1964c4f3636b2ba54d7ac8cc9 Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 8 Jun 2023 23:31:26 +0700 Subject: [PATCH 11/39] Remove old implementation and disable comments --- src/connectors/xrpl/xrpl.order-tracker.ts | 28 +- src/connectors/xrpldex/xrpldex.config.ts | 47 - src/connectors/xrpldex/xrpldex.controllers.ts | 347 -------- src/connectors/xrpldex/xrpldex.middlewares.ts | 21 - src/connectors/xrpldex/xrpldex.requests.ts | 92 -- src/connectors/xrpldex/xrpldex.routes.ts | 193 ---- src/connectors/xrpldex/xrpldex.ts | 841 ------------------ src/connectors/xrpldex/xrpldex.types.ts | 192 ---- src/connectors/xrpldex/xrpldex.validators.ts | 202 ----- 9 files changed, 14 insertions(+), 1949 deletions(-) delete mode 100644 src/connectors/xrpldex/xrpldex.config.ts delete mode 100644 src/connectors/xrpldex/xrpldex.controllers.ts delete mode 100644 src/connectors/xrpldex/xrpldex.middlewares.ts delete mode 100644 src/connectors/xrpldex/xrpldex.requests.ts delete mode 100644 src/connectors/xrpldex/xrpldex.routes.ts delete mode 100644 src/connectors/xrpldex/xrpldex.ts delete mode 100644 src/connectors/xrpldex/xrpldex.types.ts delete mode 100644 src/connectors/xrpldex/xrpldex.validators.ts diff --git a/src/connectors/xrpl/xrpl.order-tracker.ts b/src/connectors/xrpl/xrpl.order-tracker.ts index 2e2e4a3611..4cf9da7b10 100644 --- a/src/connectors/xrpl/xrpl.order-tracker.ts +++ b/src/connectors/xrpl/xrpl.order-tracker.ts @@ -266,7 +266,7 @@ export class OrderTracker { // Wait until the order is not locked while (omm.isLocked(order.hash)) { - console.log('Order is locked! Wait for 300ms'); + // console.log('Order is locked! Wait for 300ms'); await new Promise((resolve) => setTimeout(resolve, 300)); } @@ -292,7 +292,7 @@ export class OrderTracker { : 0, updatedAtLedgerIndex: latestTxnResp?.result.ledger_index ?? 0, }; - console.log('Order opened!'); + // console.log('Order opened!'); } else { // Handle other cases here // https://xrpl.org/transaction-results.html @@ -319,7 +319,7 @@ export class OrderTracker { : 0, updatedAtLedgerIndex: latestTxnResp?.result.ledger_index ?? 0, }; - console.log('Order canceled!'); + // console.log('Order canceled!'); } else { // Handle other cases here // https://xrpl.org/transaction-results.html @@ -406,20 +406,20 @@ export class OrderTracker { // console.log(inspect(transactionIntent, { colors: true, depth: null })); if (transactionIntent.sequence === undefined) { - console.log('No sequence found!'); + // console.log('No sequence found!'); return ordersToCheck; } const matchOrder = ordersToCheck[transactionIntent.sequence]; if (matchOrder === undefined) { - console.log('No match order found!'); + // console.log('No match order found!'); return ordersToCheck; } // Wait until the order is not locked while (omm.isLocked(matchOrder.hash)) { - console.log('Order is locked! Wait for 300ms'); + // console.log('Order is locked! Wait for 300ms'); await new Promise((resolve) => setTimeout(resolve, 300)); } @@ -439,37 +439,37 @@ export class OrderTracker { if (foundIndex === -1) { matchOrder.associatedTxns.push(transaction.transaction.hash ?? 'UNKNOWN'); } else { - console.log('Transaction already found!'); + // console.log('Transaction already found!'); } switch (transactionIntent.type) { case TransactionIntentType.OFFER_CREATE_FINALIZED: - console.log('Offer create finalized!'); + // console.log('Offer create finalized!'); matchOrder.state = OrderStatus.OPEN; break; case TransactionIntentType.OFFER_CANCEL_FINALIZED: - console.log('Offer cancel finalized!'); + // console.log('Offer cancel finalized!'); matchOrder.state = OrderStatus.CANCELED; break; case TransactionIntentType.OFFER_PARTIAL_FILL: - console.log('Offer partial fill!'); + // console.log('Offer partial fill!'); matchOrder.state = OrderStatus.PARTIALLY_FILLED; if (transaction.meta === undefined) { - console.log('No meta found!'); + // console.log('No meta found!'); break; } for (const affnode of transaction.meta.AffectedNodes) { if (isModifiedNode(affnode)) { - console.log('Modified node found!'); + // console.log('Modified node found!'); if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { // Usually a ModifiedNode of type Offer indicates a previous Offer that // was partially consumed by this one. if (affnode.ModifiedNode.FinalFields === undefined) { - console.log('No final fields found!'); + // console.log('No final fields found!'); break; } @@ -501,7 +501,7 @@ export class OrderTracker { } } - console.log('Filled amount: ', filledAmount); + // console.log('Filled amount: ', filledAmount); matchOrder.filledAmount = filledAmount.toString(); break; } diff --git a/src/connectors/xrpldex/xrpldex.config.ts b/src/connectors/xrpldex/xrpldex.config.ts deleted file mode 100644 index f1726174e5..0000000000 --- a/src/connectors/xrpldex/xrpldex.config.ts +++ /dev/null @@ -1,47 +0,0 @@ -// import { ConfigManagerV2 } from '../../services/config-manager-v2'; -import { AvailableNetworks } from '../../services/config-manager-types'; - -export namespace XRPLDEXConfig { - export interface Config { - availableNetworks: Array; - chainType: string; - tradingTypes: Array; - // markets: MarketsConfig; - // tickers: TickersConfig; - } - - export interface MarketsConfig { - url: string; - blacklist: string[]; - whiteList: string[]; - } - - export interface TickersConfig { - source: string; - url: string; - } - - export const config: Config = { - tradingTypes: ['XRPLDEX_CLOB'], - // markets: { - // url: ConfigManagerV2.getInstance().get(`rippledex.markets.url`), - // blacklist: ConfigManagerV2.getInstance().get( - // `rippledex.markets.blacklist` - // ), - // whiteList: ConfigManagerV2.getInstance().get( - // `rippledex.markets.whitelist` - // ), - // }, - // tickers: { - // source: ConfigManagerV2.getInstance().get(`rippledex.tickers.source`), - // url: ConfigManagerV2.getInstance().get(`rippledex.tickers.url`), - // }, - chainType: 'XRPL', - availableNetworks: [ - { - chain: 'xrpl', - networks: ['mainnet', 'testnet'], - }, - ], - }; -} diff --git a/src/connectors/xrpldex/xrpldex.controllers.ts b/src/connectors/xrpldex/xrpldex.controllers.ts deleted file mode 100644 index 05f1aab434..0000000000 --- a/src/connectors/xrpldex/xrpldex.controllers.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { StatusCodes } from 'http-status-codes'; -import { XRPLish } from '../../chains/xrpl/xrpl'; -import { ResponseWrapper } from '../../services/common-interfaces'; -import { HttpException } from '../../services/error-handler'; -import { XRPLDEXish } from './xrpldex'; -import { - XRPLCancelOrdersRequest, - XRPLCancelOrdersResponse, - XRPLCreateOrdersRequest, - XRPLCreateOrdersResponse, - XRPLGetMarketsRequest, - XRPLGetMarketsResponse, - XRPLGetOpenOrdersRequest, - XRPLGetOpenOrdersResponse, - XRPLGetOrderBooksRequest, - XRPLGetOrderBooksResponse, - XRPLGetTickersRequest, - XRPLGetTickersResponse, - XRPLGetOrdersRequest, - XRPLGetOrdersResponse, -} from './xrpldex.requests'; - -import { - validateGetMarketRequest, - validateGetMarketsRequest, - validateGetTickerRequest, - validateGetTickersRequest, - validateGetOrderBookRequest, - validateGetOrderBooksRequest, -} from './xrpldex.validators'; - -import { MarketNotFoundError } from './xrpldex.types'; - -/** - * Get the mid price of a token pair - * - * @param _xrpl - * @param xrpldex - * @param request - */ -export async function getMarkets( - _xrpl: XRPLish, - xrpldex: XRPLDEXish, - request: XRPLGetMarketsRequest -): Promise> { - const response = new ResponseWrapper(); - - if ('name' in request) { - validateGetMarketRequest(request); - - try { - response.body = await xrpldex.getMarket(request.name); - response.status = StatusCodes.OK; - - return response; - } catch (exception) { - if (exception instanceof MarketNotFoundError) { - throw new HttpException(StatusCodes.NOT_FOUND, exception.message); - } else { - throw exception; - } - } - } - - if ('names' in request) { - validateGetMarketsRequest(request); - - try { - response.body = await xrpldex.getMarkets(request.names); - - response.status = StatusCodes.OK; - - return response; - } catch (exception: any) { - if (exception instanceof MarketNotFoundError) { - throw new HttpException(StatusCodes.NOT_FOUND, exception.message); - } else { - throw exception; - } - } - } - - throw new HttpException(StatusCodes.NOT_FOUND, 'No market specified.'); -} - -/** - * Get the mid price of a token pair - * - * @param _xrpl - * @param xrpldex - * @param request - */ -export async function getTickers( - _xrpl: XRPLish, - xrpldex: XRPLDEXish, - request: XRPLGetTickersRequest -): Promise> { - const response = new ResponseWrapper(); - - if ('marketName' in request) { - validateGetTickerRequest(request); - - try { - response.body = await xrpldex.getTicker(request.marketName); - response.status = StatusCodes.OK; - - return response; - } catch (exception) { - if (exception instanceof MarketNotFoundError) { - throw new HttpException(StatusCodes.NOT_FOUND, exception.message); - } else { - throw exception; - } - } - } - - if ('marketNames' in request) { - validateGetTickersRequest(request); - - try { - response.body = await xrpldex.getTickers(request.marketNames); - - response.status = StatusCodes.OK; - - return response; - } catch (exception: any) { - if (exception instanceof MarketNotFoundError) { - throw new HttpException(StatusCodes.NOT_FOUND, exception.message); - } else { - throw exception; - } - } - } - - throw new HttpException(StatusCodes.NOT_FOUND, 'No market specified.'); -} - -/** - * Get the order book of a token pair - * - * @param _xrpl - * @param xrpldex - * @param request - */ -export async function getOrderBooks( - _xrpl: XRPLish, - xrpldex: XRPLDEXish, - request: XRPLGetOrderBooksRequest -): Promise> { - const response = new ResponseWrapper(); - - if ('marketName' in request) { - validateGetOrderBookRequest(request); - - try { - response.body = await xrpldex.getOrderBook( - request.marketName, - request.limit - ); - response.status = StatusCodes.OK; - - return response; - } catch (exception) { - if (exception instanceof MarketNotFoundError) { - throw new HttpException(StatusCodes.NOT_FOUND, exception.message); - } else { - throw exception; - } - } - } - - if ('marketNames' in request) { - validateGetOrderBooksRequest(request); - - try { - response.body = await xrpldex.getOrderBooks( - request.marketNames, - request.limit - ); - - response.status = StatusCodes.OK; - - return response; - } catch (exception: any) { - if (exception instanceof MarketNotFoundError) { - throw new HttpException(StatusCodes.NOT_FOUND, exception.message); - } else { - throw exception; - } - } - } - - throw new HttpException(StatusCodes.NOT_FOUND, 'No market specified.'); -} - -/** - * Get the detail on the created order - * - * @param _xrpl - * @param xrpldex - * @param request - */ -export async function getOrders( - _xrpl: XRPLish, - xrpldex: XRPLDEXish, - request: XRPLGetOrdersRequest -): Promise> { - const response = new ResponseWrapper(); - - response.body = await xrpldex.getOrders(request.orders); - - response.status = StatusCodes.OK; - - return response; -} - -/** - * Create an order on order book - * - * @param _xrpl - * @param xrpldex - * @param request - */ -export async function createOrders( - _xrpl: XRPLish, - xrpldex: XRPLDEXish, - request: XRPLCreateOrdersRequest -): Promise> { - const response = new ResponseWrapper(); - - if ('order' in request) { - // validateCreateOrderRequest(request.order); TODO: add createOrder validator - - response.body = await xrpldex.createOrders( - [request.order], - request.waitUntilIncludedInBlock - ); - - response.status = StatusCodes.OK; - - return response; - } - - if ('orders' in request) { - // validateCreateOrdersRequest(request.orders); TODO: add createOrders validator - - response.body = await xrpldex.createOrders( - request.orders, - request.waitUntilIncludedInBlock - ); - - response.status = StatusCodes.OK; - - return response; - } - - throw new HttpException( - StatusCodes.BAD_REQUEST, - `No order(s) was/were informed.` - ); -} - -/** - * Cancel an order on order book - * - * @param _xrpl - * @param xrpldex - * @param request - */ -export async function cancelOrders( - _xrpl: XRPLish, - xrpldex: XRPLDEXish, - request: XRPLCancelOrdersRequest -): Promise> { - const response = new ResponseWrapper(); - - if ('order' in request) { - // validateCancelOrderRequest(request.order); TODO: add createOrder validator - - response.body = await xrpldex.cancelOrders( - [request.order], - request.waitUntilIncludedInBlock - ); - - response.status = StatusCodes.OK; - - return response; - } - - if ('orders' in request) { - // validateCancelOrdersRequest(request.orders); TODO: add createOrders validator - - response.body = await xrpldex.cancelOrders( - request.orders, - request.waitUntilIncludedInBlock - ); - - response.status = StatusCodes.OK; - - return response; - } - - throw new HttpException( - StatusCodes.BAD_REQUEST, - `No order(s) was/were informed.` - ); -} - -/** - * Get open orders of a token pair - * - * @param _xrpl - * @param xrpldex - * @param request - */ -export async function getOpenOrders( - _xrpl: XRPLish, - xrpldex: XRPLDEXish, - request: XRPLGetOpenOrdersRequest -): Promise> { - const response = new ResponseWrapper(); - - if ('order' in request) { - // validateOpenOrderRequest(request.order); TODO: add createOrder validator - - response.body = await xrpldex.getOpenOrders({ market: request.order }); - - response.status = StatusCodes.OK; - - return response; - } - - if ('orders' in request) { - // validateOpenOrdersRequest(request.orders); TODO: add createOrders validator - - response.body = await xrpldex.getOpenOrders({ markets: request.orders }); - - response.status = StatusCodes.OK; - - return response; - } - - throw new HttpException( - StatusCodes.BAD_REQUEST, - `No order(s) was/were informed.` - ); -} diff --git a/src/connectors/xrpldex/xrpldex.middlewares.ts b/src/connectors/xrpldex/xrpldex.middlewares.ts deleted file mode 100644 index 3ae00d466a..0000000000 --- a/src/connectors/xrpldex/xrpldex.middlewares.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NextFunction, Request, Response } from 'express'; -import { XRPLDEX } from './xrpldex'; -import { HttpException } from '../../services/error-handler'; - -export const verifyXRPLDEXIsAvailable = async ( - req: Request, - _res: Response, - next: NextFunction -) => { - if (!req || !req.body || !req.body.network) { - throw new HttpException(404, 'No XRPL network informed.'); - } - - const xrplDEX = XRPLDEX.getInstance(req.body.chain, req.body.network); - - if (!xrplDEX.isConnected()) { - await xrplDEX.client.connect(); - } - - return next(); -}; diff --git a/src/connectors/xrpldex/xrpldex.requests.ts b/src/connectors/xrpldex/xrpldex.requests.ts deleted file mode 100644 index e249010dfc..0000000000 --- a/src/connectors/xrpldex/xrpldex.requests.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { NetworkSelectionRequest } from '../../services/common-interfaces'; -import { - GetOrderBooksRequest, - GetOrderBooksResponse, - GetMarketsRequest, - GetMarketsResponse, - GetTickersRequest, - GetTickersResponse, - CreateOrdersResponse, - CancelOrderRequest, - CreateOrderRequest, - CancelOrdersResponse, - GetOpenOrderRequest, - GetOpenOrdersResponse, - GetOrdersRequest, - GetOrdersResponse, -} from './xrpldex.types'; - -// -// GET /xrpldex/markets -// -export type XRPLGetMarketsRequest = NetworkSelectionRequest & GetMarketsRequest; - -export type XRPLGetMarketsResponse = GetMarketsResponse; - -// -// GET /xrpldex/tickers -// -export type XRPLGetTickersRequest = NetworkSelectionRequest & GetTickersRequest; - -export type XRPLGetTickersResponse = GetTickersResponse; - -// -// GET /xrpldex/orders -// - -export type XRPLGetOrdersRequest = NetworkSelectionRequest & GetOrdersRequest; - -export type XRPLGetOrdersResponse = GetOrdersResponse; - -// -// GET /xrpldex/orderBooks -// - -export type XRPLGetOrderBooksRequest = NetworkSelectionRequest & - GetOrderBooksRequest; - -export type XRPLGetOrderBooksResponse = GetOrderBooksResponse; - -// -// POST /xrpldex/orders -// - -export type XRPLCreateOrdersRequest = NetworkSelectionRequest & - ( - | { order: CreateOrderRequest; waitUntilIncludedInBlock: boolean } - | { - orders: CreateOrderRequest[]; - waitUntilIncludedInBlock: boolean; - } - ); - -export type XRPLCreateOrdersResponse = CreateOrdersResponse; - -// -// DELETE /xrpldex/orders -// - -export type XRPLCancelOrdersRequest = NetworkSelectionRequest & - ( - | { order: CancelOrderRequest; waitUntilIncludedInBlock: boolean } - | { - orders: CancelOrderRequest[]; - waitUntilIncludedInBlock: boolean; - } - ); - -export type XRPLCancelOrdersResponse = CancelOrdersResponse; - -// -// GET /xrpldex/orders/open -// - -export type XRPLGetOpenOrdersRequest = NetworkSelectionRequest & - ( - | { order: GetOpenOrderRequest } - | { - orders: GetOpenOrderRequest[]; - } - ); - -export type XRPLGetOpenOrdersResponse = GetOpenOrdersResponse; diff --git a/src/connectors/xrpldex/xrpldex.routes.ts b/src/connectors/xrpldex/xrpldex.routes.ts deleted file mode 100644 index ad3289fa18..0000000000 --- a/src/connectors/xrpldex/xrpldex.routes.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Request, Response, Router } from 'express'; -import { StatusCodes } from 'http-status-codes'; -import { XRPL } from '../../chains/xrpl/xrpl'; -import { verifyXRPLIsAvailable } from '../../chains/xrpl/xrpl-middlewares'; -import { validateXRPLAddress } from '../../chains/xrpl/xrpl.validators'; -import { asyncHandler } from '../../services/error-handler'; -import { XRPLDEX } from './xrpldex'; -import { verifyXRPLDEXIsAvailable } from './xrpldex.middlewares'; -import { - cancelOrders, - createOrders, - // getFilledOrders, - getMarkets, - getOpenOrders, - getOrderBooks, - getOrders, - getTickers, -} from './xrpldex.controllers'; -import { - XRPLGetMarketsRequest, - XRPLGetMarketsResponse, - XRPLGetOrderBooksRequest, - XRPLGetOrderBooksResponse, - XRPLGetTickersRequest, - XRPLGetTickersResponse, - XRPLCreateOrdersRequest, - XRPLCreateOrdersResponse, - XRPLCancelOrdersRequest, - XRPLCancelOrdersResponse, - XRPLGetOpenOrdersRequest, - XRPLGetOpenOrdersResponse, - XRPLGetOrdersRequest, - XRPLGetOrdersResponse, -} from './xrpldex.requests'; - -export namespace XRPLDEXRoutes { - export const router = Router(); - - export const getXRPL = async (request: Request) => - await XRPL.getInstance(request.body.network); - - export const getXRPLDEX = async (request: Request) => - await XRPLDEX.getInstance(request.body.chain, request.body.network); - - router.use( - asyncHandler(verifyXRPLIsAvailable), - asyncHandler(verifyXRPLDEXIsAvailable) - ); - - router.get( - '/', - asyncHandler( - async (request: Request, response: Response) => { - const xrplDEX = await getXRPLDEX(request); - - response.status(StatusCodes.OK).json({ - chain: xrplDEX.chain, - network: xrplDEX.network, - connector: xrplDEX.connector, - connection: xrplDEX.ready(), - timestamp: Date.now(), - }); - } - ) - ); - - router.get( - '/markets', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); - const xrplDEX = await getXRPLDEX(request); - - const result = await getMarkets(xrpl, xrplDEX, request.body); - - response.status(result.status).json(result.body); - } - ) - ); - - router.get( - '/tickers', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); - const xrplDEX = await getXRPLDEX(request); - - const result = await getTickers(xrpl, xrplDEX, request.body); - - response.status(result.status).json(result.body); - } - ) - ); - - router.get( - '/orderBooks', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); - const xrplDEX = await getXRPLDEX(request); - - const result = await getOrderBooks(xrpl, xrplDEX, request.body); - - response.status(result.status).json(result.body); - } - ) - ); - - router.get( - '/orders', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); - const xrplDEX = await getXRPLDEX(request); - - validateXRPLAddress(request.body); - - const result = await getOrders(xrpl, xrplDEX, request.body); - - response.status(result.status).json(result.body); - } - ) - ); - - router.post( - '/orders', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); - const xrplDEX = await getXRPLDEX(request); - - validateXRPLAddress(request.body); - - const result = await createOrders(xrpl, xrplDEX, request.body); - - response.status(result.status).json(result.body); - } - ) - ); - - router.delete( - '/orders', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); - const xrplDEX = await getXRPLDEX(request); - - validateXRPLAddress(request.body); - - const result = await cancelOrders(xrpl, xrplDEX, request.body); - - response.status(result.status).json(result.body); - } - ) - ); - - router.get( - '/orders/open', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); - const xrplDEX = await getXRPLDEX(request); - - validateXRPLAddress(request.body); - - const result = await getOpenOrders(xrpl, xrplDEX, request.body); - - response.status(result.status).json(result.body); - } - ) - ); -} diff --git a/src/connectors/xrpldex/xrpldex.ts b/src/connectors/xrpldex/xrpldex.ts deleted file mode 100644 index ccf2bb7bef..0000000000 --- a/src/connectors/xrpldex/xrpldex.ts +++ /dev/null @@ -1,841 +0,0 @@ -import { XRPL } from '../../chains/xrpl/xrpl'; -import { - Client, - OfferCancel, - Transaction, - xrpToDrops, - AccountInfoResponse, - BookOffersResponse, - TxResponse, - TransactionMetadata, -} from 'xrpl'; -import { - Market, - MarketNotFoundError, - IMap, - GetTickerResponse, - Ticker, - GetOrderBookResponse, - Token, - CreateOrderResponse, - OrderStatus, - CreateOrderRequest, - CancelOrderRequest, - CancelOrderResponse, - GetOpenOrderRequest, - GetOpenOrdersResponse, - OrderSide, - GetOrdersResponse, - GetOrderRequest, -} from './xrpldex.types'; -import { promiseAllInBatches } from '../../chains/xrpl/xrpl.helpers'; -import { isIssuedCurrency } from 'xrpl/dist/npm/models/transactions/common'; - -export type XRPLDEXish = XRPLDEX; - -export class XRPLDEX { - private static _instances: { [name: string]: XRPLDEX }; - private readonly _client: Client; - private readonly _xrpl: XRPL; - private _ready: boolean = false; - - initializing: boolean = false; - chain: string; - network: string; - readonly connector: string = 'xrpldex'; - - /** - * Creates a new instance of xrplDEX. - * - * @param chain - * @param network - * @private - */ - private constructor(chain: string, network: string) { - this.chain = chain; - this.network = network; - - this._xrpl = XRPL.getInstance(network); - this._client = this._xrpl.client; - } - - /** - * Initialize the xrplDEX instance. - * - */ - async init() { - if (!this._ready && !this.initializing) { - this.initializing = true; - - if (!this._xrpl.ready()) { - await this._xrpl.init(); - } - - if (!this._client.isConnected()) { - await this._client.connect(); - } - - this._ready = true; - this.initializing = false; - } - } - - public static getInstance(chain: string, network: string): XRPLDEX { - if (XRPLDEX._instances === undefined) { - XRPLDEX._instances = {}; - } - if (!(network in XRPLDEX._instances)) { - XRPLDEX._instances[network] = new XRPLDEX(chain, network); - } - - return XRPLDEX._instances[network]; - } - - /** - * @param name - */ - async getMarket(name?: string): Promise { - if (!name) throw new MarketNotFoundError(`No market informed.`); - // Market name format: - // 1: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/USD.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" - // 2: "XRP/ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h" - // 3: "ETH.rcA8X3TVMST1n3CJeAdGk1RdRCHii7N2h/XRP" - let baseTickSize: number; - let baseTransferRate: number; - let quoteTickSize: number; - let quoteTransferRate: number; - const zeroTransferRate = 1000000000; - - const [base, quote] = name.split('/'); - - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); - - if (baseCurrency != 'XRP') { - const baseMarketResp: AccountInfoResponse = await this._client.request({ - command: 'account_info', - ledger_index: 'validated', - account: baseIssuer, - }); - - if (!baseMarketResp) - throw new MarketNotFoundError(`Market "${base}" not found.`); - - baseTickSize = baseMarketResp.result.account_data.TickSize ?? 15; - const rawTransferRate = - baseMarketResp.result.account_data.TransferRate ?? zeroTransferRate; - baseTransferRate = rawTransferRate / zeroTransferRate - 1; - } else { - baseTickSize = 6; - baseTransferRate = 0; - } - - if (quoteCurrency != 'XRP') { - const quoteMarketResp: AccountInfoResponse = await this._client.request({ - command: 'account_info', - ledger_index: 'validated', - account: quoteIssuer, - }); - - if (!quoteMarketResp) - throw new MarketNotFoundError(`Market "${quote}" not found.`); - - quoteTickSize = quoteMarketResp.result.account_data.TickSize ?? 15; - const rawTransferRate = - quoteMarketResp.result.account_data.TransferRate ?? zeroTransferRate; - quoteTransferRate = rawTransferRate / zeroTransferRate - 1; - } else { - quoteTickSize = 6; - quoteTransferRate = 0; - } - - const smallestTickSize = Math.min(baseTickSize, quoteTickSize); - const minimumOrderSize = smallestTickSize; - - const result = { - name: name, - minimumOrderSize: minimumOrderSize, - tickSize: smallestTickSize, - baseTransferRate: baseTransferRate, - quoteTransferRate: quoteTransferRate, - }; - - return result; - } - - /** - * @param names - */ - async getMarkets(names: string[]): Promise> { - const markets = IMap().asMutable(); - - const getMarket = async (name: string): Promise => { - const market = await this.getMarket(name); - - markets.set(name, market); - }; - - await promiseAllInBatches(getMarket, names, 1, 1); - - return markets; - } - - /** - * Returns the last traded prices. - */ - async getTicker(marketName: string): Promise { - const [base, quote] = marketName.split('/'); - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); - - const baseRequest: any = { - currency: baseCurrency, - }; - - const quoteRequest: any = { - currency: quoteCurrency, - }; - - if (baseIssuer) { - baseRequest['issuer'] = baseIssuer; - } - if (quoteIssuer) { - quoteRequest['issuer'] = quoteIssuer; - } - - const orderbook_resp_ask: any = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker_gets: baseRequest, - taker_pays: quoteRequest, - limit: 1, - }); - - const orderbook_resp_bid: any = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker_gets: quoteRequest, - taker_pays: baseRequest, - limit: 1, - }); - - const asks = orderbook_resp_ask.result.offers; - const bids = orderbook_resp_bid.result.offers; - - let topAsk = 0; - let topBid = 0; - - const askQuality = asks.length > 0 ? asks[0].quality : undefined; - const bidQuality = bids.length > 0 ? bids[0].quality : undefined; - - if (baseCurrency === 'XRP' || quoteCurrency === 'XRP') { - if (baseCurrency === 'XRP') { - topAsk = askQuality ? Number(askQuality) * 1000000 : 0; - topBid = bidQuality ? (1 / Number(bidQuality)) * 1000000 : 0; - } else { - topAsk = askQuality ? Number(askQuality) / 1000000 : 0; - topBid = bidQuality ? 1 / Number(bidQuality) / 1000000 : 0; - } - } else { - topAsk = askQuality ? Number(askQuality) : 0; - topBid = bidQuality ? 1 / Number(bidQuality) : 0; - } - - const midPrice = (topAsk + topBid) / 2; - - return { - price: midPrice, - timestamp: Date.now(), - }; - } - - async getTickers(marketNames: string[]): Promise> { - const tickers = IMap().asMutable(); - - const getTicker = async (marketName: string): Promise => { - const ticker = await this.getTicker(marketName); - - tickers.set(marketName, ticker); - }; - - await promiseAllInBatches(getTicker, marketNames, 1, 1); - - return tickers; - } - - async getOrderBook( - marketName: string, - limit: number - ): Promise { - const market = await this.getMarket(marketName); - - const [base, quote] = marketName.split('/'); - - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); - - const baseRequest: any = { - currency: baseCurrency, - }; - - const quoteRequest: any = { - currency: quoteCurrency, - }; - - if (baseIssuer) { - baseRequest['issuer'] = baseIssuer; - } - if (quoteIssuer) { - quoteRequest['issuer'] = quoteIssuer; - } - - const orderbook_resp_ask: BookOffersResponse = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker_gets: baseRequest, - taker_pays: quoteRequest, - limit: limit, - }); - - const orderbook_resp_bid: BookOffersResponse = await this._client.request({ - command: 'book_offers', - ledger_index: 'validated', - taker_gets: quoteRequest, - taker_pays: baseRequest, - limit: limit, - }); - - const asks = orderbook_resp_ask.result.offers; - const bids = orderbook_resp_bid.result.offers; - - let topAsk = 0; - let topBid = 0; - - const askQuality = asks.length > 0 ? asks[0].quality : undefined; - const bidQuality = bids.length > 0 ? bids[0].quality : undefined; - - if (baseCurrency === 'XRP' || quoteCurrency === 'XRP') { - if (baseCurrency === 'XRP') { - topAsk = askQuality ? Number(askQuality) * 1000000 : 0; - topBid = bidQuality ? (1 / Number(bidQuality)) * 1000000 : 0; - } else { - topAsk = askQuality ? Number(askQuality) / 1000000 : 0; - topBid = bidQuality ? 1 / Number(bidQuality) / 1000000 : 0; - } - } else { - topAsk = askQuality ? Number(askQuality) : 0; - topBid = bidQuality ? 1 / Number(bidQuality) : 0; - } - - const midPrice = (topAsk + topBid) / 2; - - return { - market, - asks, - bids, - topAsk, - topBid, - midPrice, - timestamp: Date.now(), - }; - } - - async getOrderBooks( - marketNames: string[], - limit: number - ): Promise> { - const orderBooks = IMap().asMutable(); - - const getOrderBook = async (marketName: string): Promise => { - const orderBook = await this.getOrderBook(marketName, limit); - - orderBooks.set(marketName, orderBook); - }; - - await promiseAllInBatches(getOrderBook, marketNames, 1, 1); - - return orderBooks; - } - - async getOrders(orders: GetOrderRequest[]): Promise { - const queriedOrders: GetOrdersResponse = {}; - - for (const order of orders) { - const tx_resp: TxResponse = await this._client.request({ - command: 'tx', - transaction: order.signature, - binary: false, - }); - - const type = tx_resp.result.TransactionType; - if (tx_resp.result.meta) { - const meta: TransactionMetadata = tx_resp.result - .meta as TransactionMetadata; - const result = meta.TransactionResult; - const prefix = result.slice(0, 3); - - switch (prefix) { - case 'tec': - case 'tef': - case 'tel': - case 'tem': - queriedOrders[order.sequence] = { - sequence: order.sequence, - status: OrderStatus.FAILED, - signature: order.signature, - transactionResult: result, - }; - continue; - } - - if (type == 'OfferCreate') { - if (result == 'tesSUCCESS') { - queriedOrders[order.sequence] = { - sequence: order.sequence, - status: OrderStatus.OPEN, - signature: order.signature, - transactionResult: result, - }; - } else { - queriedOrders[order.sequence] = { - sequence: order.sequence, - status: OrderStatus.PENDING, - signature: order.signature, - transactionResult: result, - }; - } - } else if (type == 'OfferCancel') { - if (result == 'tesSUCCESS') { - queriedOrders[order.sequence] = { - sequence: order.sequence, - status: OrderStatus.CANCELED, - signature: order.signature, - transactionResult: result, - }; - } else { - queriedOrders[order.sequence] = { - sequence: order.sequence, - status: OrderStatus.PENDING, - signature: order.signature, - transactionResult: result, - }; - } - } else { - queriedOrders[order.sequence] = { - sequence: order.sequence, - status: OrderStatus.UNKNOWN, - signature: order.signature, - transactionResult: result, - }; - } - } else { - queriedOrders[order.sequence] = { - sequence: order.sequence, - status: OrderStatus.PENDING, - signature: order.signature, - transactionResult: 'pending', - }; - } - } - - const result = queriedOrders; - return result; - } - - async createOrder(order: CreateOrderRequest): Promise { - const [base, quote] = order.marketName.split('/'); - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); - - const market = await this.getMarket(order.marketName); - - const xrpl = XRPL.getInstance(this.network); - const wallet = await xrpl.getWallet(order.walletAddress); - const total = order.price * order.amount; - let fee = 0; - - let we_pay: Token = { - currency: '', - issuer: '', - value: '', - }; - let we_get: Token = { currency: '', issuer: '', value: '' }; - - if (order.side == 'BUY') { - we_pay = { - currency: quoteCurrency, - issuer: quoteIssuer, - value: Number(total.toPrecision(market.tickSize)).toString(), - }; - we_get = { - currency: baseCurrency, - issuer: baseIssuer, - value: Number(order.amount.toPrecision(market.tickSize)).toString(), - }; - - fee = market.baseTransferRate; - } else { - we_pay = { - currency: baseCurrency, - issuer: baseIssuer, - value: Number(order.amount.toPrecision(market.tickSize)).toString(), - }; - we_get = { - currency: quoteCurrency, - issuer: quoteIssuer, - value: Number(total.toPrecision(market.tickSize)).toString(), - }; - - fee = market.quoteTransferRate; - } - - if (we_pay.currency == 'XRP') { - we_pay.value = xrpToDrops(we_pay.value); - } - - if (we_get.currency == 'XRP') { - we_get.value = xrpToDrops(we_get.value); - } - - const offer: Transaction = { - TransactionType: 'OfferCreate', - Account: wallet.classicAddress, - TakerGets: we_pay.currency == 'XRP' ? we_pay.value : we_pay, - TakerPays: we_get.currency == 'XRP' ? we_get.value : we_get, - }; - - if (order.sequence != undefined) { - offer.OfferSequence = order.sequence; - } - - const prepared = await this._client.autofill(offer); - const signed = wallet.sign(prepared); - const response = await this._client.submit(signed.tx_blob); - - const orderStatus = OrderStatus.PENDING; - // const orderSequence = -1; - // const orderLedgerIndex = ''; - - // if (response.result) { - // const meta = response.result.meta; - // if (meta) { - // const affectedNodes = (meta as TransactionMetadata).AffectedNodes; - - // for (const affnode of affectedNodes) { - // if ('ModifiedNode' in affnode) { - // if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { - // // Usually a ModifiedNode of type Offer indicates a previous Offer that - // // was partially consumed by this one. - // orderStatus = OrderStatus.PARTIALLY_FILLED; - // } - // } else if ('DeletedNode' in affnode) { - // if (affnode.DeletedNode.LedgerEntryType == 'Offer') { - // // The removed Offer may have been fully consumed, or it may have been - // // found to be expired or unfunded. - // // TODO: Make a seperate method for cancelling orders - // if (offer.OfferSequence == undefined) { - // orderStatus = OrderStatus.FILLED; - // } - // } - // } else if ('CreatedNode' in affnode) { - // if (affnode.CreatedNode.LedgerEntryType == 'Offer') { - // // Created an Offer object on the Ledger - // orderStatus = OrderStatus.OPEN; - // orderSequence = response.result.Sequence ?? -1; - // orderLedgerIndex = affnode.CreatedNode.LedgerIndex; - // } - // } - // } - // } - // } - - const returnResponse: CreateOrderResponse = { - walletAddress: order.walletAddress, - marketName: order.marketName, - price: order.price, - amount: order.amount, - side: order.side, - type: order.type, - fee, - orderLedgerIndex: response.result.validated_ledger_index.toString(), - status: orderStatus, - sequence: response.result.tx_json.Sequence ?? -1, - signature: response.result.tx_json.hash, - }; - - return returnResponse; - } - - async createOrders( - orders: CreateOrderRequest[], - waitUntilIncludedInBlock: boolean - ): Promise> { - const createdOrders: Record = {}; - - if (orders.length <= 0) { - return createdOrders; - } - - const getCreatedOrders = async ( - order: CreateOrderRequest - ): Promise => { - const createdOrder = await this.createOrder(order); - - createdOrders[createdOrder.sequence] = createdOrder; - }; - - await promiseAllInBatches(getCreatedOrders, orders, 1, 1); - - if (waitUntilIncludedInBlock) { - const queriedOrders: GetOrderRequest[] = []; - let pooling = true; - let transactionStatuses: GetOrdersResponse; - - for (const key in createdOrders) { - const sequence = parseInt(key); - const signature = createdOrders[key].signature; - - if (signature != undefined) { - queriedOrders.push({ - sequence, - signature, - }); - } - } - - while (pooling) { - transactionStatuses = await this.getOrders(queriedOrders); - - for (const key in transactionStatuses) { - if (transactionStatuses[key]['status'] == OrderStatus.PENDING) { - pooling = true; - await new Promise((resolve) => setTimeout(resolve, 5000)); - break; - } - - createdOrders[key]['status'] = transactionStatuses[key]['status']; - createdOrders[key]['transactionResult'] = - transactionStatuses[key]['transactionResult']; - - pooling = false; - } - } - } - - return createdOrders; - } - - async cancelOrder(order: CancelOrderRequest): Promise { - const xrpl = XRPL.getInstance(this.network); - const wallet = await xrpl.getWallet(order.walletAddress); - const request: OfferCancel = { - TransactionType: 'OfferCancel', - Account: wallet.classicAddress, - OfferSequence: order.offerSequence, - }; - - const prepared = await this._client.autofill(request); - const signed = wallet.sign(prepared); - const response = await this._client.submit(signed.tx_blob); - - const orderStatus = OrderStatus.PENDING; - - // if (response.result) { - // const meta = response.result.meta; - // if (meta) { - // const affectedNodes = (meta as TransactionMetadata).AffectedNodes; - - // for (const affnode of affectedNodes) { - // if ('DeletedNode' in affnode) { - // if (affnode.DeletedNode.LedgerEntryType == 'Offer') { - // orderStatus = OrderStatus.CANCELED; - // } - // } - // } - // } - // } - - const returnResponse: CancelOrderResponse = { - walletAddress: order.walletAddress, - status: orderStatus, - signature: response.result.tx_json.hash, - }; - - return returnResponse; - } - - async cancelOrders( - orders: CancelOrderRequest[], - waitUntilIncludedInBlock: boolean - ): Promise> { - const cancelledOrders: Record = {}; - - if (orders.length <= 0) { - return cancelledOrders; - } - - const getCancelledOrders = async ( - order: CancelOrderRequest - ): Promise => { - const cancelledOrder = await this.cancelOrder(order); - - cancelledOrders[order.offerSequence] = cancelledOrder; - }; - - await promiseAllInBatches(getCancelledOrders, orders, 1, 1); - - if (waitUntilIncludedInBlock) { - const queriedOrders: GetOrderRequest[] = []; - let pooling = true; - let transactionStatuses: GetOrdersResponse; - - for (const key in cancelledOrders) { - const sequence = parseInt(key); - const signature = cancelledOrders[key].signature; - - if (signature != undefined) { - queriedOrders.push({ - sequence, - signature, - }); - } - } - - while (pooling) { - transactionStatuses = await this.getOrders(queriedOrders); - - for (const key in transactionStatuses) { - if (transactionStatuses[key]['status'] == OrderStatus.PENDING) { - pooling = true; - await new Promise((resolve) => setTimeout(resolve, 5000)); - break; - } - - cancelledOrders[key]['status'] = transactionStatuses[key]['status']; - cancelledOrders[key]['transactionResult'] = - transactionStatuses[key]['transactionResult']; - - pooling = false; - } - } - } - - return cancelledOrders; - } - - async getOpenOrders(params: { - market?: GetOpenOrderRequest; - markets?: GetOpenOrderRequest[]; - }): Promise { - const openOrders: any = {}; - - let marketArray: GetOpenOrderRequest[] = []; - if (params.market) marketArray.push(params.market); - if (params.markets) { - marketArray = marketArray.concat(params.markets); - } - - for (const market of marketArray) { - const [base, quote] = market.marketName.split('/'); - - const [baseCurrency, baseIssuer] = base.split('.'); - const [quoteCurrency, quoteIssuer] = quote.split('.'); - - const openOrdersInMarket: any = {}; - - const baseRequest: any = { - currency: baseCurrency, - }; - - const quoteRequest: any = { - currency: quoteCurrency, - }; - - if (baseIssuer) { - baseRequest['issuer'] = baseIssuer; - } - if (quoteIssuer) { - quoteRequest['issuer'] = quoteIssuer; - } - - const orderbook_resp_ask: BookOffersResponse = await this._client.request( - { - command: 'book_offers', - ledger_index: 'validated', - taker: market.walletAddress, - taker_gets: baseRequest, - taker_pays: quoteRequest, - } - ); - - const orderbook_resp_bid: BookOffersResponse = await this._client.request( - { - command: 'book_offers', - ledger_index: 'validated', - taker: market.walletAddress, - taker_gets: quoteRequest, - taker_pays: baseRequest, - } - ); - - let asks = orderbook_resp_ask.result.offers; - let bids = orderbook_resp_bid.result.offers; - - asks = asks.filter((ask) => ask.Account == market.walletAddress); - bids = bids.filter((bid) => bid.Account == market.walletAddress); - - for (const ask of asks) { - const price = ask.quality ?? '-1'; - let amount: string = ''; - - if (isIssuedCurrency(ask.TakerGets)) { - amount = ask.TakerGets.value; - } else { - amount = ask.TakerGets; - } - - openOrdersInMarket[String(ask.Sequence)] = { - sequence: ask.Sequence, - marketName: market.marketName, - price: price, - amount: amount, - side: OrderSide.SELL, - }; - } - - for (const bid of bids) { - const price = Math.pow(Number(bid.quality), -1).toString() ?? '-1'; - let amount: string = ''; - - if (isIssuedCurrency(bid.TakerGets)) { - amount = bid.TakerGets.value; - } else { - amount = bid.TakerGets; - } - - openOrdersInMarket[String(bid.Sequence)] = { - sequence: bid.Sequence, - marketName: market.marketName, - price: price, - amount: amount, - side: OrderSide.BUY, - }; - } - - openOrders[market.marketName] = openOrdersInMarket; - } - return openOrders; - } - - ready(): boolean { - return this._ready; - } - - isConnected(): boolean { - return this._client.isConnected(); - } - - public get client() { - return this._client; - } -} diff --git a/src/connectors/xrpldex/xrpldex.types.ts b/src/connectors/xrpldex/xrpldex.types.ts deleted file mode 100644 index de69b77fdc..0000000000 --- a/src/connectors/xrpldex/xrpldex.types.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; -import { BookOffer } from 'xrpl'; - -export type IMap = ImmutableMap; -export const IMap = ImmutableMap; -export type ISet = ImmutableSet; -export const ISet = ImmutableSet; - -export enum OrderSide { - BUY = 'BUY', - SELL = 'SELL', -} - -export enum OrderStatus { - OPEN = 'OPEN', - CANCELED = 'CANCELED', - FILLED = 'FILLED', - PARTIALLY_FILLED = 'PARTIALLY_FILLED', - PENDING = 'PENDING', - FAILED = 'FAILED', - UNKNOWN = 'UNKNOWN', -} - -export enum OrderType { - LIMIT = 'LIMIT', - PASSIVE = 'PASSIVE', - IOC = 'IOC', // Immediate or Cancel - FOK = 'FOK', // Fill or Kill - SELL = 'SELL', // Sell -} - -export interface Token { - currency: string; - issuer: string; - value: string; -} - -export type GetMarketsRequest = - | Record - | { name: string } - | { names: string[] }; - -export interface GetMarketResponse { - name: string; - minimumOrderSize: number; - tickSize: number; - baseTransferRate: number; - quoteTransferRate: number; -} - -export interface Market { - name: string; - minimumOrderSize: number; - tickSize: number; - baseTransferRate: number; - quoteTransferRate: number; -} - -export type GetMarketsResponse = - | IMap - | GetMarketResponse; - -export type GetTickersRequest = - | Record - | { marketName: string } - | { marketNames: string[] }; - -export interface GetTickerResponse { - price: number; - timestamp: number; -} - -export type GetTickersResponse = - | IMap - | GetTickerResponse; - -export interface Ticker { - price: number; - timestamp: number; -} - -export interface GetOrderRequest { - sequence: number; - signature: string; -} - -export type GetOrdersRequest = - | Record - | { orders: GetOrderRequest[] }; - -export interface GetOrderResponse { - sequence: number; - status: OrderStatus; - signature: string; - transactionResult: string; -} - -export type GetOrdersResponse = Record; - -export type GetOrderBooksRequest = - | Record - | { marketName: string; limit: number } - | { marketNames: string[]; limit: number }; - -export interface GetOrderBookResponse { - market: GetMarketResponse; - topAsk: number; - topBid: number; - midPrice: number; - bids: BookOffer[]; - asks: BookOffer[]; - timestamp: number; -} - -export type GetOrderBooksResponse = - | IMap - | GetOrderBookResponse; - -export interface CreateOrderRequest { - walletAddress: string; - marketName: string; - side: OrderSide; - price: number; - amount: number; - type?: OrderType; - sequence?: number; -} - -export interface CreateOrderResponse { - walletAddress: string; - marketName: string; - price: number; - amount: number; - side: OrderSide; - status?: OrderStatus; - type?: OrderType; - fee?: number; - sequence: number; - orderLedgerIndex?: string; - signature?: string; - transactionResult?: string; -} - -export type CreateOrdersResponse = - | IMap - | CreateOrderResponse - | Record; - -export interface CancelOrderRequest { - walletAddress: string; - offerSequence: number; -} - -export type CancelOrdersRequest = - | Record - | { order: CancelOrderRequest } - | { orders: CancelOrderRequest[] }; - -export interface CancelOrderResponse { - walletAddress: string; - status?: OrderStatus; - signature?: string; - transactionResult?: string; -} - -export type CancelOrdersResponse = - | IMap - | CancelOrderResponse - | Record; - -export interface GetOpenOrderRequest { - marketName: string; - walletAddress: string; -} - -export interface GetOpenOrderResponse { - sequence: number; - marketName: string; - price: string; - amount: string; - side: OrderSide; -} - -export type GetOpenOrdersResponse = - | any - | IMap> - | IMap - | GetOpenOrderResponse; - -export class XRPLDEXishError extends Error {} - -export class MarketNotFoundError extends XRPLDEXishError {} diff --git a/src/connectors/xrpldex/xrpldex.validators.ts b/src/connectors/xrpldex/xrpldex.validators.ts deleted file mode 100644 index 0ddb9b7c68..0000000000 --- a/src/connectors/xrpldex/xrpldex.validators.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { StatusCodes } from 'http-status-codes'; -import { HttpException } from '../../services/error-handler'; -// import { -// isBase58, -// isFloatString, -// isNaturalNumberString, -// } from '../../services/validators'; - -type Validator = ( - item: undefined | null | any | Item, - index?: number -) => { warnings: Array; errors: Array }; - -const createValidator = ( - accessor: undefined | null | string | ((target: any | Item) => any | Value), - validation: ( - item: undefined | null | any | Item, - value: undefined | null | any | Value - ) => boolean, - error: - | string - | (( - item: undefined | null | any | Item, - value: undefined | null | any | Value, - accessor: - | undefined - | null - | string - | ((target: any | Item) => any | Value), - index?: number - ) => string), - optional: boolean = false -): Validator => { - return (item: undefined | null | any | Item, index?: number) => { - const warnings: Array = []; - const errors: Array = []; - - let target: any | Value; - if (item === undefined && accessor) { - errors.push(`Request with undefined value informed when it shouldn't.`); - } else if (item === null && accessor) { - errors.push(`Request with null value informed when it shouldn't.`); - } else if (!accessor) { - target = item; - } else if (typeof accessor === 'string') { - if (!(`${accessor}` in item) && !optional) { - errors.push(`The request is missing the key/property "${accessor}".`); - } else { - target = item[accessor]; - } - } else { - target = accessor(item); - } - - if (!validation(item, target)) { - if (typeof error === 'string') { - if (optional) { - warnings.push(error); - } else { - errors.push(error); - } - } else { - if (optional) { - warnings.push(error(item, target, accessor, index)); - } else { - errors.push(error(item, target, accessor, index)); - } - } - } - - return { - warnings, - errors, - }; - }; -}; - -/** - Throw an error because the request parameter is malformed, collect all the - errors related to the request to give the most information possible - */ -export const throwIfErrorsExist = ( - errors: Array, - statusCode: number = StatusCodes.NOT_FOUND, - request: any, - headerMessage?: (request: any, errorNumber?: number) => string, - errorNumber?: number -): void => { - if (errors.length > 0) { - let message = headerMessage - ? `${headerMessage(request, errorNumber)}\n` - : ''; - message += errors.join('\n'); - - throw new HttpException(statusCode, message); - } -}; - -type RequestValidator = (item: undefined | null | any | Item) => { - warnings: Array; - errors: Array; -}; - -export const createRequestValidator = ( - validators: Array, - statusCode?: StatusCodes, - headerMessage?: (request: any) => string, - errorNumber?: number -): RequestValidator => { - return (request: undefined | null | any | Item) => { - let warnings: Array = []; - let errors: Array = []; - - for (const validator of validators) { - const result = validator(request); - warnings = [...warnings, ...result.warnings]; - errors = [...errors, ...result.errors]; - } - - throwIfErrorsExist(errors, statusCode, request, headerMessage, errorNumber); - - return { warnings, errors }; - }; -}; - -export const validateGetMarketRequest: RequestValidator = - createRequestValidator( - [ - createValidator( - null, - (request) => request.name, - `No market was informed. If you want to get a market, please inform the parameter "name".`, - false - ), - ], - StatusCodes.BAD_REQUEST - ); - -export const validateGetMarketsRequest: RequestValidator = - createRequestValidator( - [ - createValidator( - null, - (request) => request.names && request.names.length, - `No markets were informed. If you want to get all markets, please do not inform the parameter "names".`, - false - ), - ], - StatusCodes.BAD_REQUEST - ); - -export const validateGetOrderBookRequest: RequestValidator = - createRequestValidator( - [ - createValidator( - null, - (request) => request.marketName, - `No market name was informed. If you want to get an order book, please inform the parameter "marketName".`, - false - ), - ], - StatusCodes.BAD_REQUEST - ); - -export const validateGetOrderBooksRequest: RequestValidator = - createRequestValidator( - [ - createValidator( - null, - (request) => request.marketNames && request.marketNames.length, - `No market names were informed. If you want to get all order books, please do not inform the parameter "marketNames".`, - false - ), - ], - StatusCodes.BAD_REQUEST - ); - -export const validateGetTickerRequest: RequestValidator = - createRequestValidator( - [ - createValidator( - null, - (request) => request.marketName, - `No market name was informed. If you want to get a ticker, please inform the parameter "marketName".`, - false - ), - ], - StatusCodes.BAD_REQUEST - ); - -export const validateGetTickersRequest: RequestValidator = - createRequestValidator( - [ - createValidator( - null, - (request) => request.marketNames && request.marketNames.length, - `No market names were informed. If you want to get all tickers, please do not inform the parameter "marketNames".`, - false - ), - ], - StatusCodes.BAD_REQUEST - ); From 8ba3776f0e35780bcf1c741fb1ff6fe35e1940f3 Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 8 Jun 2023 23:46:32 +0700 Subject: [PATCH 12/39] Remove artifacts --- docs/swagger/clob-beta.yml | 1416 --------------------------- docs/swagger/xrpldex-routes.yml | 148 --- src/connectors/connectors.routes.ts | 14 +- src/connectors/xrpl/xrpl.types.ts | 4 +- 4 files changed, 7 insertions(+), 1575 deletions(-) delete mode 100644 docs/swagger/clob-beta.yml delete mode 100644 docs/swagger/xrpldex-routes.yml diff --git a/docs/swagger/clob-beta.yml b/docs/swagger/clob-beta.yml deleted file mode 100644 index ded2ed7a35..0000000000 --- a/docs/swagger/clob-beta.yml +++ /dev/null @@ -1,1416 +0,0 @@ -swagger: '2.0' -# TODO: Please verify if all clob-beta routes are compatible with Serum, if not please mark them and propose new changes -# TODO: Once the schema is final, move paths to clob-routes.yml and Merge definitions to definitions.yml - -info: - description: 'Gateway allows clients to interoperate with blockchains and DeFi protocols via a REST API. This allows for a language agnostic way to use official SDKs for blockchains.' - version: '1.0.0' - title: 'gateway' - contact: - email: 'dev@hummingbot.io' - license: - name: 'Apache 2.0' - url: 'http://www.apache.org/licenses/LICENSE-2.0.html' - -host: 'localhost:15888' - -tags: - - name: 'clob-beta' - description: 'Interact with CLOB decentralized exchanges (BETA)' - -schemes: - - 'http' - -externalDocs: - description: 'Find out more about gateway' - url: 'https://github.com/hummingbot/hummingbot' - - -paths: - /clob-beta: - get: - tags: - - 'clob-beta' - summary: 'Verify Clob Routes Status' - description: 'Verify if the Clob routes are ready to use and show some other useful information. ' - operationId: 'clob.root' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBRootRequest' - - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBRootResponse' - - /clob-beta/markets: - get: - tags: - - 'clob-beta' - summary: 'Get One or More Markets' - description: 'Get the information of one, several or all available markets. Depending on the connector kind, response body might be different.' - operationId: 'clob.markets' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBGetMarketsRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBGetMarketsResponse' - '404': - description: 'Not found response.' - - /clob-beta/orderBooks: - get: - tags: - - 'clob-beta' - summary: 'Get One or More Order Books' - description: 'Get the information of one, several or all available order books. Depending on the connector kind, response body might be different.' - operationId: 'clob.orderBooks' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBGetOrderBooksRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBGetOrderBooksResponse' - '404': - description: 'Not found response.' - - /clob-beta/tickers: - get: - tags: - - 'clob-beta' - summary: 'Get middle price ticker for requested market' - operationId: 'tickers' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBGetTickersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBGetTickersResponse' - '404': - description: 'Not found response.' - - /clob-beta/orders: - get: - tags: - - 'clob-beta' - summary: 'Get status details of one or more orders' - operationId: 'getOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBGetOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBGetOrdersResponse' - '404': - description: 'Not found response.' - post: - tags: - - 'clob-beta' - summary: 'Create one or more orders' - description: 'Create one or more orders.' - operationId: 'createOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBPostCreateOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBPostCreateOrdersResponse' - '400': - description: 'Bad request response.' - delete: - tags: - - 'clob-beta' - summary: 'Cancel one or more orders' - description: 'Cancel one or more orders.' - operationId: 'cancelOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBDeleteOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBDeleteOrdersResponse' - '400': - description: 'Bad request response.' - - /clob-beta/orders/open: - get: - tags: - - 'clob-beta' - summary: 'Get One Or More Open Orders' - description: 'Get the information of one, several or all open orders.' - operationId: 'clob.getOpenOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBGetOpenOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBGetOpenOrdersResponse' - '400': - description: 'Bad request response.' - '404': - description: 'Not found response.' - - /clob-beta/orders/filled: - get: - tags: - - 'clob-beta' - summary: 'Get One Or More Filled Orders' - description: 'Get the information of one, several or all filled orders.' - operationId: 'clob.getFilledOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBGetFilledOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBGetFilledOrdersResponse' - '400': - description: 'Bad request response.' - '404': - description: 'Not found response.' - - /clob-beta/settleFunds: - get: - tags: - - 'clob-beta' - summary: 'Settle Funds of One or More Markets' - description: 'Settle funds of one, several or all markets.' - operationId: 'clob.settleFunds' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/CLOBPostSettleFundsRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/CLOBPostSettleFundsResponse' - '400': - description: 'Bad request response.' - '404': - description: 'Not found response.' - - -definitions: - CLOBRootRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - CLOBRootResponse: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - - 'connection' - - 'timestamp' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - connection: - type: boolean - example: true - timestamp: - type: number - example: 1652304454740 - - CLOBGetMarketsRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - name: - type: string - example: 'SOL/USDT' - names: - type: array - items: - type: string - example: - - 'SOL/USDT' - - 'SOL/USDC' - CLOBGetMarketsResponseItemCommon: - type: object - properties: - name: - type: string - example: SOL/USDT - deprecated: - type: boolean - example: false - minimumOrderSize: - type: number - example: 0.1 - tickSize: - type: number - example: 0.001 - minimumBaseIncrement: - type: string - example: 100000000 - CLOBGetMarketsResponseItemSerum: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBGetMarketsResponseItemCommon' - properties: - address: - type: string - example: HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1 - programId: - type: string - example: 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin - CLOBGetMarketrResponseItemXRPLDEX: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBGetMarketsResponseItemCommon' - properties: - name: - type: string - example: USD.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx/VND.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx - baseTransferRate: - type: number - example: 0.1 - quoteTransferRate: - type: number - example: 0.1 - CLOBGetMarketsResponse: - type: object - properties: - SOL/USDC: - $ref: '#/definitions/CLOBGetMarketsResponseItemSerum' - "USD.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx/VND.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx": - $ref: '#/definitions/CLOBGetMarketrResponseItemXRPLDEX' - - CLOBGetOrderBooksRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - marketName: - type: string - example: 'SOL/USDT' - marketNames: - type: array - items: - type: string - example: - - 'SOL/USDT' - - 'SOL/USDC' - CLOBGetOrderBooksResponseItemCommon: - type: object - properties: - market: - type: object - asks: - type: array - items: - type: object - $ref: '#/definitions/CLOBGetOrderBooksResponseOrderAskItemCommon' - bids: - type: array - items: - type: object - $ref: '#/definitions/CLOBGetOrderBooksResponseOrderBidItemCommon' - CLOBGetOrderBooksResponseItemSerum: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBGetOrderBooksResponseItemCommon' - properties: - market: - type: object - $ref: '#/definitions/CLOBGetMarketsResponseItemSerum' - bids: - type: array - items: - type: object - $ref: '#/definitions/CLOBGetOrderBooksResponseOrderBidItemCommon' - asks: - type: array - items: - type: object - $ref: '#/definitions/CLOBGetOrderBooksResponseOrderAskItemCommon' - CLOBGetOrderBooksResponseItemXRPLDEX: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBGetOrderBooksResponseItemCommon' - properties: - market: - type: object - $ref: '#/definitions/CLOBGetMarketrResponseItemXRPLDEX' - bids: - type: array - items: - type: object - $ref: '#/definitions/CLOBGetOrderBooksResponseOrderBidItemCommon' - asks: - type: array - items: - type: object - $ref: '#/definitions/CLOBGetOrderBooksResponseOrderAskItemCommon' - topAsk: - type: number - example: 25000 - topBid: - type: number - example: 24000 - midPrice: - type: number - example: 24500 - timestamp: - type: number - example: 1676296168012 - CLOBGetOrderBooksResponseOrderBidItemCommon: - type: object - properties: - id: - type: string - example: 35332426 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - price: - type: number - example: 250 - amount: - type: number - example: 0.1 - side: - type: string - example: BUY - status: - type: string - example: OPEN - 'type': - type: string - example: LIMIT - CLOBGetOrderBooksResponseOrderAskItemCommon: - type: object - properties: - id: - type: string - example: 35332426 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - price: - type: number - example: 250 - amount: - type: number - example: 0.1 - side: - type: string - example: SELL - status: - type: string - example: OPEN - 'type': - type: string - example: LIMIT - CLOBGetOrderBooksResponse: - type: object - properties: - SOL/USDC: - $ref: '#/definitions/CLOBGetOrderBooksResponseItemSerum' - "USD.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx/VND.rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx": - $ref: '#/definitions/CLOBGetOrderBooksResponseItemXRPLDEX' - - CLOBGetTickersRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - marketName: - type: string - example: 'SOL/USDT' - marketNames: - type: array - items: - type: string - example: - - 'SOL/USDT' - - 'SOL/USDC' - CLOBGetTickersResponseItem: - type: object - properties: - price: - type: number - example: 43.37523838 - timestamp: - type: number - example: 1652363040000 - CLOBGetTickersResponse: - type: object - properties: - SOL/USDT: - $ref: '#/definitions/CLOBGetTickersResponseItem' - SOL/USDC: - $ref: '#/definitions/CLOBGetTickersResponseItem' - - CLOBGetOrdersRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - order: - type: object - required: - - 'ownerAddress' - properties: - id: - type: string - example: 123456789 - exchangeId: - type: string - example: 184467256269654779094895731 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - orders: - type: array - items: - type: object - required: - - 'ownerAddress' - properties: - ids: - type: array - items: - type: string - exchangeIds: - type: array - items: - type: string - marketName: - type: string - ownerAddress: - type: string - example: - - ids: ["123456789"] - exchangeIds: ["184467256269654779094895731"] - marketName: SOL/USDT - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["123456789"] - exchangeIds: ["184467256269654779094895731"] - marketName: SOL/USDC - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["1112234"] - exchangeIds: ["33485803"] - marketName: XRP/USDT - ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV - CLOBGetOrdersResponseItemCommon: - type: 'object' - properties: - id: - type: string - exchangeId: - type: string - marketName: - type: string - ownderAddress: - type: string - side: - type: string - price: - type: number - amount: - type: number - type: - type: string - status: - type: string - fee: - type: number - example: - id: "123456789" - exchangeId: "184467256269654779094895731" - marketName: "SOL/USDT" - ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "OPEN" - fee: 0.1 - CLOBGetOrdersResponseItemXRPLDEX: - type: 'object' - allOf: - - type: object - - $ref: '#/definitions/CLOBGetOrdersResponseItemCommon' - properties: - signature: - type: string - transactionResult: - type: string - orderLedgerIndex: - type: string - example: - id: "1112234" - exchangeId: "33485803" - marketName: "XRP/USDT" - ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "OPEN" - signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" - transactionResult: "tesSUCCESS" - orderLedgerIndex: "35357471" - CLOBGetOrdersResponse: - type: object - properties: - SOL/USDC: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBGetOrdersResponseItemCommon' - - SOL/USDT: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBGetOrdersResponseItemCommon' - - XRP/USDT: - type: object - properties: - "1112234": - $ref: '#/definitions/CLOBGetOrdersResponseItemXRPLDEX' - - CLOBPostCreateOrdersRequestCommon: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - order: - type: object - required: - - 'id' - - 'ownerAddress' - - 'marketName' - - 'side' - - 'price' - - 'amount' - - 'type' - properties: - id: - type: string - example: 123456789 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - side: - type: string - example: BUY - price: - type: number - example: 9999.9 - amount: - type: number - example: 0.1 - type: - type: string - example: LIMIT - orders: - type: array - items: - type: object - required: - - 'id' - - 'ownerAddress' - - 'marketName' - - 'side' - - 'price' - - 'amount' - - 'type' - properties: - id: - type: string - example: 123456789 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - side: - type: string - example: BUY - price: - type: number - example: 9999.9 - amount: - type: number - example: 0.1 - type: - type: string - example: LIMIT - CLOBPostCreateOrdersRequestSerum: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBPostCreateOrdersRequestCommon' - properties: - order: - type: object - properties: - payerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - orders: - type: array - items: - type: object - properties: - payerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - CLOBPostCreateOrdersRequestXRPLDEX: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBPostCreateOrdersRequestCommon' - properties: - waitUntilIncludedInBlock: - type: boolean - example: true - CLOBPostCreateOrdersRequest: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBPostCreateOrdersRequestSerum' - - $ref: '#/definitions/CLOBPostCreateOrdersRequestXRPLDEX' - CLOBPostCreateOrdersResponseItemCommon: - type: object - properties: - id: - type: string - example: 123456789 - exchangeId: - type: string - example: 184467256269654779094895731 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - price: - type: number - example: 250 - amount: - type: number - example: 0.1 - side: - type: string - example: BUY - status: - type: string - example: OPEN - type: - type: string - example: LIMIT - fee: - type: number - example: 0.1 - CLOBPostCreateOrdersResponseItemXRPLDEX: - type: object - allOf: - - type: object - - $ref: '#/definitions/CLOBPostCreateOrdersResponseItemCommon' - properties: - signature: - type: string - example: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" - transactionResult: - type: string - example: "tesSUCCESS" - orderLedgerIndex: - type: string - example: "35357471" - CLOBPostCreateOrdersResponse: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBPostCreateOrdersResponseItemCommon' - "1112234": - type: object - allOf: - - $ref: '#/definitions/CLOBPostCreateOrdersResponseItemXRPLDEX' - example: - id: "1112234" - exchangeId: "33485803" - marketName: "XRP/USDT" - ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "OPEN" - signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" - transactionResult: "tesSUCCESS" - orderLedgerIndex: "35357471" - - CLOBDeleteOrdersRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - order: - type: object - required: - - 'ownerAddress' - properties: - id: - type: string - example: 123456789 - exchangeId: - type: string - example: 184467256269654779094895731 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - orders: - type: array - items: - type: object - required: - - 'ownerAddress' - properties: - ids: - type: array - items: - type: string - exchangeIds: - type: array - items: - type: string - marketName: - type: string - ownerAddress: - type: string - example: - - ids: ["123456789", "123456789"] - exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] - marketName: SOL/USDT - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["123456789", "123456789"] - exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] - marketName: SOL/USDC - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["1112234"] - exchangeIds: ["33485803"] - marketName: XRP/USDT - ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV - CLOBDeleteOrdersResponseItemCommon: - type: 'object' - properties: - id: - type: string - exchangeId: - type: string - marketName: - type: string - ownderAddress: - type: string - side: - type: string - price: - type: number - amount: - type: number - type: - type: string - status: - type: string - fee: - type: number - example: - id: "123456789" - exchangeId: "184467256269654779094895731" - marketName: "SOL/USDT" - ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "CANCELED" - fee: 0.1 - CLOBDeleteOrdersResponseItemXRPLDEX: - type: 'object' - allOf: - - type: object - - $ref: '#/definitions/CLOBDeleteOrdersResponseItemCommon' - properties: - signature: - type: string - transactionResult: - type: string - orderLedgerIndex: - type: string - example: - id: "1112234" - exchangeId: "33485803" - marketName: "XRP/USDT" - ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "CANCELED" - signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" - transactionResult: "tesSUCCESS" - orderLedgerIndex: "35357471" - CLOBDeleteOrdersResponse: - type: object - properties: - SOL/USDC: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBDeleteOrdersResponseItemCommon' - - SOL/USDT: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBDeleteOrdersResponseItemCommon' - - XRP/USDT: - type: object - properties: - "33485803": - $ref: '#/definitions/CLOBDeleteOrdersResponseItemXRPLDEX' - - CLOBGetOpenOrdersRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - order: - type: object - required: - - 'ownerAddress' - properties: - id: - type: string - example: 123456789 - exchangeId: - type: string - example: 184467256269654779094895731 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - orders: - type: array - items: - type: object - required: - - 'ownerAddress' - properties: - ids: - type: array - items: - type: string - exchangeIds: - type: array - items: - type: string - marketName: - type: string - ownerAddress: - type: string - example: - - ids: ["123456789", "987654321"] - exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] - marketName: SOL/USDT - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["123456789", "987654321"] - exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] - marketName: SOL/USDC - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["1112234", "6783991"] - exchangeIds: ["33485803", "33485000"] - marketName: XRP/USDT - ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV - CLOBGetOpenOrdersResponseItemCommon: - type: 'object' - properties: - id: - type: string - exchangeId: - type: string - marketName: - type: string - ownderAddress: - type: string - side: - type: string - price: - type: number - amount: - type: number - type: - type: string - status: - type: string - fee: - type: number - example: - id: "123456789" - exchangeId: "184467256269654779094895731" - marketName: "SOL/USDT" - ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "OPEN" - fee: 0.1 - CLOBGetOpenOrdersResponseItemXRPLDEX: - type: 'object' - allOf: - - type: object - - $ref: '#/definitions/CLOBGetOpenOrdersResponseItemCommon' - properties: - signature: - type: string - transactionResult: - type: string - orderLedgerIndex: - type: string - example: - id: "1112234" - exchangeId: "33485803" - marketName: "XRP/USDT" - ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "OPEN" - signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" - transactionResult: "tesSUCCESS" - orderLedgerIndex: "35357471" - CLOBGetOpenOrdersResponse: - type: object - properties: - SOL/USDC: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBGetOpenOrdersResponseItemCommon' - - SOL/USDT: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBGetOpenOrdersResponseItemCommon' - - XRP/USDT: - type: object - properties: - "1112234": - $ref: '#/definitions/CLOBGetOpenOrdersResponseItemXRPLDEX' - - CLOBGetFilledOrdersRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - order: - type: object - required: - - 'ownerAddress' - properties: - id: - type: string - example: 123456789 - exchangeId: - type: string - example: 184467256269654779094895731 - marketName: - type: string - example: SOL/USDT - ownerAddress: - type: string - example: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - orders: - type: array - items: - type: object - required: - - 'ownerAddress' - properties: - ids: - type: array - items: - type: string - exchangeIds: - type: array - items: - type: string - marketName: - type: string - ownerAddress: - type: string - example: - - ids: ["123456789", "987654321"] - exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] - marketName: SOL/USDT - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["123456789", "987654321"] - exchangeIds: ["184467256269654779094895731", "284467256269654779094895723"] - marketName: SOL/USDC - ownerAddress: 2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa - - ids: ["1112234", "6783991"] - exchangeIds: ["33485803", "33485000"] - marketName: XRP/USDT - ownerAddress: r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV - CLOBGetFilledOrdersResponseItemCommon: - type: 'object' - properties: - id: - type: string - exchangeId: - type: string - marketName: - type: string - ownderAddress: - type: string - side: - type: string - price: - type: number - amount: - type: number - type: - type: string - status: - type: string - fee: - type: number - example: - id: "123456789" - exchangeId: "184467256269654779094895731" - marketName: "SOL/USDT" - ownderAddress: "2wvayrdTmrJFUAdg0yHoBtPu1eE41t0fxruxTh7ufnJa" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "FILLED" - fee: 0.1 - CLOBGetFilledOrdersResponseItemXRPLDEX: - type: 'object' - allOf: - - type: object - - $ref: '#/definitions/CLOBGetFilledOrdersResponseItemCommon' - properties: - signature: - type: string - transactionResult: - type: string - orderLedgerIndex: - type: string - example: - id: "1112234" - exchangeId: "33485803" - marketName: "XRP/USDT" - ownderAddress: "r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV" - side: "BUY" - price: 999.99 - amount: 0.1 - type: "LIMIT" - status: "FILLED" - signature: "53CCABA69887B92D91AE27B083D11262B4393D95D9A0AEFF4B07A731FA1F74E8" - transactionResult: "tesSUCCESS" - orderLedgerIndex: "35357471" - CLOBGetFilledOrdersResponse: - type: object - properties: - SOL/USDC: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBGetFilledOrdersResponseItemCommon' - - SOL/USDT: - type: object - properties: - "123456789": - $ref: '#/definitions/CLOBGetFilledOrdersResponseItemCommon' - - XRP/USDT: - type: object - properties: - "1112234": - $ref: '#/definitions/CLOBGetFilledOrdersResponseItemXRPLDEX' - - CLOBPostSettleFundsRequest: - type: 'object' - required: - - 'chain' - - 'network' - - 'connector' - - 'ownerAddress' - properties: - chain: - type: string - example: solana - network: - type: string - example: mainnet-beta - connector: - type: string - example: serum - marketName: - type: string - example: SOL/USDT - marketNames: - type: array - items: - type: string - example: - - SOL/USDT - - BTC/USDT - CLOBPostSettleFundsResponse: - type: object - properties: - SOL/USDT: - type: array - items: - type: string - example: - - 4vAstoT7dgJ3LaexYRQbJ4HwbV8vPaMEUWVP3o89oq4vJcn9E11LJHyEpoc3sZ4dxmAtZGU7YyRS1uR36wuUEQMK - BTC/USDT: - type: array - items: - type: string - example: - - 4vAstoT7dgJ3LaexYRQbJ4HwbV8vPaMEUWVP3o89oq4vJcn9E11LJHyEpoc3sZ4dxmAtZGU7YyRS1uR36wuUEQMK diff --git a/docs/swagger/xrpldex-routes.yml b/docs/swagger/xrpldex-routes.yml deleted file mode 100644 index 97a5278a4d..0000000000 --- a/docs/swagger/xrpldex-routes.yml +++ /dev/null @@ -1,148 +0,0 @@ -paths: - /xrpldex/tickers: - get: - tags: - - 'xrpldex' - summary: 'Get middle price ticker for requested market' - operationId: 'tickers' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/XRPLDEXGetTickersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/XRPLDEXGetTickersResponse' - '404': - description: 'Not found response.' - - /xrpldex/orderBooks: - get: - tags: - - 'xrpldex' - summary: 'Get Order Book Details' - operationId: 'orderBooks' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/XRPLDEXGetOrderBooksRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/XRPLDEXGetOrderBooksResponse' - '404': - description: 'Not found response.' - - /xrpldex/orders: - get: - tags: - - 'xrpldex' - summary: 'Get status details of one or more orders' - operationId: 'getOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/XRPLDEXGetOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/XRPLDEXGetOrdersResponse' - '404': - description: 'Not found response.' - post: - tags: - - 'xrpldex' - summary: 'Create one or more orders' - description: 'Create one or more orders.' - operationId: 'createOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/XRPLDEXPostCreateOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/XRPLDEXPostCreateOrdersResponse' - '400': - description: 'Bad request response.' - delete: - tags: - - 'xrpldex' - summary: 'Cancel one or more orders' - description: 'Cancel one or more orders.' - operationId: 'cancelOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/XRPLDEXDeleteCancelOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/XRPLDEXDeleteCancelOrdersResponse' - '400': - description: 'Bad request response.' - - /xrpldex/orders/open: - get: - tags: - - 'xrpldex' - summary: 'Get open orders from a wallet' - operationId: 'getOpenOrders' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Request body.' - required: true - schema: - $ref: '#/definitions/XRPLDEXGetOpenOrdersRequest' - responses: - '200': - description: 'Successful response.' - schema: - $ref: '#/definitions/XRPLDEXGetOpenOrdersResponse' - '404': - description: 'Not found response.' \ No newline at end of file diff --git a/src/connectors/connectors.routes.ts b/src/connectors/connectors.routes.ts index 4af4a25d0d..62fc3e4d9f 100644 --- a/src/connectors/connectors.routes.ts +++ b/src/connectors/connectors.routes.ts @@ -20,7 +20,7 @@ import { ConnectorsResponse } from './connectors.request'; import { DexalotCLOBConfig } from './dexalot/dexalot.clob.config'; import { TinymanConfig } from './tinyman/tinyman.config'; import { PlentyConfig } from './plenty/plenty.config'; -import { XRPLDEXConfig } from './xrpldex/xrpldex.config'; +import { XRPLCLOBConfig } from './xrpl/xrpl.clob.config'; export namespace ConnectorsRoutes { export const router = Router(); @@ -160,14 +160,10 @@ export namespace ConnectorsRoutes { available_networks: PlentyConfig.config.availableNetworks, }, { - name: 'xrpldex', - trading_type: XRPLDEXConfig.config.tradingTypes, - chain_type: XRPLDEXConfig.config.chainType, - available_networks: XRPLDEXConfig.config.availableNetworks, - additional_add_wallet_prompts: { - api_key: - 'Enter a XRPL Secret Key if you have one, otherwise hit return >>> ', - }, + name: 'xrpl', + trading_type: XRPLCLOBConfig.config.tradingTypes, + chain_type: XRPLCLOBConfig.config.chainType, + available_networks: XRPLCLOBConfig.config.availableNetworks, }, ], }); diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index 2eeb2f08f7..9b0aa67b20 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -202,9 +202,9 @@ export type GetOpenOrdersResponse = | IMap | GetOpenOrderResponse; -export class XRPLDEXishError extends Error {} +export class XRPLishError extends Error {} -export class MarketNotFoundError extends XRPLDEXishError {} +export class MarketNotFoundError extends XRPLishError {} export interface PriceLevel { price: string; From 3f49fba1aa341577a80aa47395fc5f51e7635b70 Mon Sep 17 00:00:00 2001 From: mlguys Date: Fri, 9 Jun 2023 00:16:06 +0700 Subject: [PATCH 13/39] Move order tracker to chain directory --- src/{connectors => chains}/xrpl/xrpl.order-tracker.ts | 10 +++++----- src/chains/xrpl/xrpl.ts | 2 +- src/connectors/xrpl/xrpl.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/{connectors => chains}/xrpl/xrpl.order-tracker.ts (98%) diff --git a/src/connectors/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts similarity index 98% rename from src/connectors/xrpl/xrpl.order-tracker.ts rename to src/chains/xrpl/xrpl.order-tracker.ts index 4cf9da7b10..2a991f4b0c 100644 --- a/src/connectors/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -8,8 +8,8 @@ import { TransactionMetadata, Transaction, } from 'xrpl'; -import { XRPL } from '../../chains/xrpl/xrpl'; -import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; +import { XRPL } from './xrpl'; +import { getXRPLConfig } from './xrpl.config'; import { OrderStatus, Order, @@ -19,15 +19,15 @@ import { TransactionIntent, AccountTransaction, ResponseOnlyTxInfo, -} from './xrpl.types'; -import { OrderMutexManager } from './xrpl.utils'; +} from '../../connectors/xrpl/xrpl.types'; +import { OrderMutexManager } from '../../connectors/xrpl/xrpl.utils'; import { isModifiedNode, isDeletedNode, } from 'xrpl/dist/npm/models/transactions/metadata'; import LRUCache from 'lru-cache'; -import { XRPLOrderStorage } from '../../chains/xrpl/xrpl.order-storage'; +import { XRPLOrderStorage } from './xrpl.order-storage'; // This class should: // 1. Track orders that are created by xrpl.postOrder diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 4126119985..91b832864b 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -22,7 +22,7 @@ import { getXRPLConfig } from './xrpl.config'; // import { logger } from '../../services/logger'; import { TransactionResponseStatusCode } from './xrpl.requests'; import { XRPLOrderStorage } from './xrpl.order-storage'; -import { OrderTracker } from '../../connectors/xrpl/xrpl.order-tracker'; +import { OrderTracker } from './xrpl.order-tracker'; import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; export type TokenInfo = { diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index e1e7dc1ffc..0814c573d8 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -8,7 +8,7 @@ import { AccountInfoResponse, BookOffersResponse, } from 'xrpl'; -import { OrderTracker } from './xrpl.order-tracker'; +import { OrderTracker } from '../../chains/xrpl/xrpl.order-tracker'; import { Market, MarketNotFoundError, From b2b589e1513b786799585cd2b103371684b9c4b9 Mon Sep 17 00:00:00 2001 From: mlguys Date: Fri, 9 Jun 2023 00:18:15 +0700 Subject: [PATCH 14/39] add space --- src/connectors/xrpl/xrpl.utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/connectors/xrpl/xrpl.utils.ts b/src/connectors/xrpl/xrpl.utils.ts index 70f33ec657..4356928e4a 100644 --- a/src/connectors/xrpl/xrpl.utils.ts +++ b/src/connectors/xrpl/xrpl.utils.ts @@ -1,4 +1,4 @@ -import { InflightOrders, OrderLocks } from './xrpl.types'; +import { InflightOrders, OrderLocks } from './xrpl.types'; export class OrderMutexManager { private locks: OrderLocks = {}; @@ -55,4 +55,4 @@ export class OrderMutexManager { isLocked(hash: number) { return this.locks[hash]; } -} \ No newline at end of file +} From 056e212b173d51831ca4d1debaa9910759c9d211 Mon Sep 17 00:00:00 2001 From: mlguys Date: Fri, 9 Jun 2023 10:52:42 +0700 Subject: [PATCH 15/39] WIP --- package.json | 4 +++- test/connectors/xrpl/xrpl.integration.test.ts | 0 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 test/connectors/xrpl/xrpl.integration.test.ts diff --git a/package.json b/package.json index f6752f1766..08c4d1ad7e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,9 @@ "test:debug": "node --inspect node_modules/.bin/jest --watch --runInBand", "test:unit": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --verbose ./test/", "test:cov": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/", - "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts" + "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts", + "test:xrpl": "jest -i --verbose test/connectors/xrpl/*.test.ts", + "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts" }, "dependencies": { "@cosmjs/proto-signing": "^0.28.10", diff --git a/test/connectors/xrpl/xrpl.integration.test.ts b/test/connectors/xrpl/xrpl.integration.test.ts new file mode 100644 index 0000000000..e69de29bb2 From 0af5945c043ec8ba944d1835eaec70c843d7bc8f Mon Sep 17 00:00:00 2001 From: mlguys Date: Mon, 26 Jun 2023 17:30:48 +0700 Subject: [PATCH 16/39] Add basic e2e test scripts + fix bugs --- package.json | 1 + src/chains/xrpl/xrpl.order-storage.ts | 21 +- src/chains/xrpl/xrpl.ts | 32 ++- src/chains/xrpl/xrpl_markets.json | 6 +- src/chains/xrpl/xrpl_markets_testnet.json | 35 ++- src/chains/xrpl/xrpl_tokens_testnet.json | 35 ++- src/connectors/xrpl/xrpl.ts | 195 ++++++-------- src/connectors/xrpl/xrpl.utils.ts | 56 ++++ test/connectors/xrpl/xrpl.e2e.test.ts | 251 ++++++++++++++++++ test/connectors/xrpl/xrpl.integration.test.ts | 0 test/connectors/xrpl/xrpl.routes.test.ts | 26 +- 11 files changed, 523 insertions(+), 135 deletions(-) create mode 100644 test/connectors/xrpl/xrpl.e2e.test.ts delete mode 100644 test/connectors/xrpl/xrpl.integration.test.ts diff --git a/package.json b/package.json index 08c4d1ad7e..deea219090 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test:cov": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/", "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts", "test:xrpl": "jest -i --verbose test/connectors/xrpl/*.test.ts", + "test:e2e:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.e2e.test.ts", "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts" }, "dependencies": { diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index 9a011ec133..ab153cb4d7 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -15,7 +15,18 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { } public async init(): Promise { - await this.localStorage.init(); + try { + await this.localStorage.init(); + } catch (error) { + console.log( + '🪧 -> file: xrpl.order-storage.ts:22 -> XRPLOrderStorage -> init -> error:', + error + ); + } + } + + public storageStatus(): string { + return this.localStorage.dbStatus; } public async saveOrder( @@ -46,8 +57,16 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { network: string, walletAddress: string ): Promise> { + console.log( + '🪧 -> file: xrpl.order-storage.ts:60 -> XRPLOrderStorage -> walletAddress:', + walletAddress + ); return this.localStorage.get((key: string, value: string) => { const splitKey = key.split('/'); + console.log( + '🪧 -> file: xrpl.order-storage.ts:62 -> XRPLOrderStorage -> returnthis.localStorage.get -> splitKey:', + splitKey + ); if ( splitKey.length === 4 && splitKey[0] === chain && diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 91b832864b..78802efc2b 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -194,6 +194,7 @@ export class XRPL implements XRPLish { await this.loadTokens(this._tokenListSource, this._tokenListType); await this.loadMarkets(this._marketListSource, this._marketListType); await this.getFee(); + await this._orderStorage.init(); this._ready = true; this.initializing = false; } @@ -205,9 +206,13 @@ export class XRPL implements XRPLish { ): Promise { this.tokenList = await this.getTokenList(tokenListSource, tokenListType); if (this.tokenList) { - this.tokenList.forEach((token: TokenInfo) => - this._tokenMap[token.code].push(token) - ); + this.tokenList.forEach((token: TokenInfo) => { + if (!this._tokenMap[token.code]) { + this._tokenMap[token.code] = []; + } + + this._tokenMap[token.code].push(token); + }); } } @@ -220,9 +225,13 @@ export class XRPL implements XRPLish { marketListType ); if (this.marketList) { - this.marketList.forEach((market: MarketInfo) => - this._marketMap[market.marketId].push(market) - ); + this.marketList.forEach((market: MarketInfo) => { + if (!this._marketMap[market.marketId]) { + this._marketMap[market.marketId] = []; + } + + this._marketMap[market.marketId].push(market); + }); } } @@ -247,11 +256,10 @@ export class XRPL implements XRPLish { ): Promise { let tokens; if (marketListType === 'URL') { - ({ - data: { tokens }, - } = await axios.get(marketListSource)); + const resp = await axios.get(marketListSource); + tokens = resp.data.tokens; } else { - ({ tokens } = JSON.parse(await fs.readFile(marketListSource, 'utf8'))); + tokens = JSON.parse(await fs.readFile(marketListSource, 'utf8')); } return tokens; } @@ -360,6 +368,10 @@ export class XRPL implements XRPLish { } async ensureConnection() { + console.log( + '🪧 -> file: xrpl.ts:372 -> XRPL -> ensureConnection -> isConnected:', + this.isConnected() + ); if (!this.isConnected()) { await this._client.connect(); } diff --git a/src/chains/xrpl/xrpl_markets.json b/src/chains/xrpl/xrpl_markets.json index 7cb44f0a81..b385debb72 100644 --- a/src/chains/xrpl/xrpl_markets.json +++ b/src/chains/xrpl/xrpl_markets.json @@ -3,7 +3,7 @@ "id": 1, "marketId": "SOLO-XRP", "baseIssuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", - "quoteIssuer": "XRP", + "quoteIssuer": "", "baseTokenID": 31, "quoteTokenID": 0 }, @@ -19,7 +19,7 @@ "id": 3, "marketId": "USDC-XRP", "baseIssuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu", - "quoteIssuer": "XRP", + "quoteIssuer": "", "baseTokenID": 18465, "quoteTokenID": 0 }, @@ -35,7 +35,7 @@ "id": 5, "marketId": "BTC-XRP", "baseIssuer": "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL", - "quoteIssuer": "XRP", + "quoteIssuer": "", "baseTokenID": 1381, "quoteTokenID": 0 }, diff --git a/src/chains/xrpl/xrpl_markets_testnet.json b/src/chains/xrpl/xrpl_markets_testnet.json index fe51488c70..806f37894e 100644 --- a/src/chains/xrpl/xrpl_markets_testnet.json +++ b/src/chains/xrpl/xrpl_markets_testnet.json @@ -1 +1,34 @@ -[] +[ + { + "id": 1, + "marketId": "SOLO-XRP", + "baseIssuer": "rHZwvHEs56GCmHupwjA4RY7oPA3EoAJWuN", + "quoteIssuer": "", + "baseTokenID": 3, + "quoteTokenID": 0 + }, + { + "id": 2, + "marketId": "SOLO-USD", + "baseIssuer": "rHZwvHEs56GCmHupwjA4RY7oPA3EoAJWuN", + "quoteIssuer": "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx", + "baseTokenID": 3, + "quoteTokenID": 2 + }, + { + "id": 3, + "marketId": "USD-XRP", + "baseIssuer": "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx", + "quoteIssuer": "", + "baseTokenID": 2, + "quoteTokenID": 0 + }, + { + "id": 4, + "marketId": "USD-VND", + "baseIssuer": "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx", + "quoteIssuer": "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx", + "baseTokenID": 2, + "quoteTokenID": 1 + } +] diff --git a/src/chains/xrpl/xrpl_tokens_testnet.json b/src/chains/xrpl/xrpl_tokens_testnet.json index 9e26dfeeb6..20eda9535b 100644 --- a/src/chains/xrpl/xrpl_tokens_testnet.json +++ b/src/chains/xrpl/xrpl_tokens_testnet.json @@ -1 +1,34 @@ -{} \ No newline at end of file +[ + { + "id": 0, + "code": "XRP", + "issuer": "", + "title": "XRP", + "trustlines": -1, + "placeInTop": 0 + }, + { + "id": 1, + "code": "VND", + "issuer": "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx", + "title": "Vietnam Dong", + "trustlines": 999, + "placeInTop": 1 + }, + { + "id": 2, + "code": "USD", + "issuer": "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx", + "title": "United States Dollar", + "trustlines": 999, + "placeInTop": 1 + }, + { + "id": 3, + "code": "SOLO", + "issuer": "rHZwvHEs56GCmHupwjA4RY7oPA3EoAJWuN", + "title": "Sologenic", + "trustlines": 999, + "placeInTop": 1 + } +] diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index 0814c573d8..73aee60b9d 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -18,6 +18,12 @@ import { PriceLevel, Order, } from './xrpl.types'; +import { + getTakerGetsAmount, + getTakerPaysAmount, + getTakerGetsFundedAmount, + getTakerPaysFundedAmount, +} from './xrpl.utils'; import { ClobMarketsRequest, ClobOrderbookRequest, @@ -37,7 +43,7 @@ import LRUCache from 'lru-cache'; import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; import { isUndefined } from 'mathjs'; -const XRP_FACTOR = 1000000; +// const XRP_FACTOR = 1000000; const ORDERBOOK_LIMIT = 10; export class XRPLCLOB implements CLOBish { @@ -95,6 +101,12 @@ export class XRPLCLOB implements CLOBish { return this._ready; } + public getInfo(): string { + const info = `XRPLCLOB: ${this.chain} ${this.network} | RCP URL: ${this._xrpl.rpcUrl} | XRPLCLOB ready: ${this._ready}`; + + return info; + } + // CLOB methods: // TODO: Find and correct the required market info in client public async markets( @@ -113,10 +125,8 @@ export class XRPLCLOB implements CLOBish { async fetchMarkets(): Promise { const loadedMarkets: Market[] = []; const markets = this._xrpl.storedMarketList; - const getMarket = async (market: MarketInfo): Promise => { const processedMarket = await this.getMarket(market); - loadedMarkets.push(processedMarket); }; @@ -226,57 +236,29 @@ export class XRPLCLOB implements CLOBish { const sells: PriceLevel[] = []; bids.forEach((bid) => { - if (isUndefined(bid.quality)) return; - let price, quantity: string; if ( isUndefined(bid.taker_gets_funded) && isUndefined(bid.taker_pays_funded) ) { - if (typeof bid.TakerGets === 'string') { - price = ( - Math.pow(parseFloat(bid.quality), -1) / XRP_FACTOR - ).toString(); - quantity = (parseFloat(bid.TakerGets) * XRP_FACTOR).toString(); - } else if (typeof bid.TakerPays === 'string') { - if (isUndefined(bid.TakerGets)) return; - if (isUndefined(bid.TakerGets?.value)) return; - - price = ( - Math.pow(parseFloat(bid.quality), -1) * XRP_FACTOR - ).toString(); - quantity = bid.TakerGets.value; - } else { - if (isUndefined(bid.TakerGets)) return; - if (isUndefined(bid.TakerGets?.value)) return; - - price = Math.pow(parseFloat(bid.quality), -1).toString(); - quantity = bid.TakerGets.value; - } + if (isUndefined(bid.TakerGets)) return; + if (isUndefined(bid.TakerPays)) return; + + price = ( + parseFloat(getTakerGetsAmount(bid)) / + parseFloat(getTakerPaysAmount(bid)) + ).toString(); + quantity = getTakerPaysAmount(bid); } else { - if (typeof bid.taker_gets_funded === 'string') { - price = ( - Math.pow(parseFloat(bid.quality), -1) / XRP_FACTOR - ).toString(); - quantity = ( - parseFloat(bid.taker_gets_funded) * XRP_FACTOR - ).toString(); - } else if (typeof bid.taker_pays_funded === 'string') { - if (isUndefined(bid.taker_gets_funded)) return; - if (isUndefined(bid.taker_gets_funded?.value)) return; - - price = ( - Math.pow(parseFloat(bid.quality), -1) * XRP_FACTOR - ).toString(); - quantity = bid.taker_gets_funded.value; - } else { - if (isUndefined(bid.taker_gets_funded)) return; - if (isUndefined(bid.taker_gets_funded?.value)) return; - - price = Math.pow(parseFloat(bid.quality), -1).toString(); - quantity = bid.taker_gets_funded.value; - } + if (isUndefined(bid.taker_gets_funded)) return; + if (isUndefined(bid.taker_pays_funded)) return; + + price = ( + parseFloat(getTakerGetsFundedAmount(bid)) / + parseFloat(getTakerPaysFundedAmount(bid)) + ).toString(); + quantity = getTakerPaysFundedAmount(bid); } buys.push({ @@ -287,49 +269,29 @@ export class XRPLCLOB implements CLOBish { }); asks.forEach((ask) => { - if (isUndefined(ask.quality)) return; - let price, quantity: string; if ( isUndefined(ask.taker_gets_funded) && isUndefined(ask.taker_pays_funded) ) { - if (typeof ask.TakerGets === 'string') { - price = (parseFloat(ask.quality) * XRP_FACTOR).toString(); - quantity = (parseFloat(ask.TakerGets) / XRP_FACTOR).toString(); - } else if (typeof ask.TakerPays === 'string') { - if (isUndefined(ask.TakerGets)) return; - if (isUndefined(ask.TakerGets?.value)) return; - - price = (parseFloat(ask.quality) / XRP_FACTOR).toString(); - quantity = ask.TakerGets.value; - } else { - if (isUndefined(ask.TakerPays)) return; - if (isUndefined(ask.TakerPays?.value)) return; - - price = ask.quality; - quantity = ask.TakerGets.value; - } + if (isUndefined(ask.TakerGets)) return; + if (isUndefined(ask.TakerPays)) return; + + price = ( + parseFloat(getTakerPaysAmount(ask)) / + parseFloat(getTakerGetsAmount(ask)) + ).toString(); + quantity = getTakerGetsAmount(ask); } else { - if (typeof ask.taker_gets_funded === 'string') { - price = (parseFloat(ask.quality) * XRP_FACTOR).toString(); - quantity = ( - parseFloat(ask.taker_gets_funded) / XRP_FACTOR - ).toString(); - } else if (typeof ask.taker_pays_funded === 'string') { - if (isUndefined(ask.taker_gets_funded)) return; - if (isUndefined(ask.taker_gets_funded?.value)) return; - - price = (parseFloat(ask.quality) / XRP_FACTOR).toString(); - quantity = ask.taker_gets_funded.value; - } else { - if (isUndefined(ask.taker_gets_funded)) return; - if (isUndefined(ask.taker_gets_funded?.value)) return; - - price = ask.quality; - quantity = ask.taker_gets_funded.value; - } + if (isUndefined(ask.taker_gets_funded)) return; + if (isUndefined(ask.taker_pays_funded)) return; + + price = ( + parseFloat(getTakerPaysFundedAmount(ask)) / + parseFloat(getTakerGetsFundedAmount(ask)) + ).toString(); + quantity = getTakerGetsFundedAmount(ask); } sells.push({ @@ -356,8 +318,9 @@ export class XRPLCLOB implements CLOBish { ): Promise<{ orders: ClobGetOrderResponse['orders'] }> { if (!req.market) return { orders: [] }; if (!req.address) return { orders: [] }; + if (!req.orderId) return { orders: [] }; - if (!req.orderId) { + if (req.orderId === 'all') { const marketId = this.parsedMarkets[req.market].marketId; const orders = await this._orderStorage.getOrdersByMarket( this.chain, @@ -407,30 +370,30 @@ export class XRPLCLOB implements CLOBish { if (req.side == TradeType.SELL) { we_pay = { - currency: quoteCurrency, - issuer: quoteIssuer, - value: Number(total.toPrecision(market.tickSize)).toString(), - }; - we_get = { currency: baseCurrency, issuer: baseIssuer, value: Number( parseFloat(req.amount).toPrecision(market.tickSize) ).toString(), }; + we_get = { + currency: quoteCurrency, + issuer: quoteIssuer, + value: Number(total.toPrecision(market.tickSize)).toString(), + }; } else { we_pay = { + currency: quoteCurrency, + issuer: quoteIssuer, + value: Number(total.toPrecision(market.tickSize)).toString(), + }; + we_get = { currency: baseCurrency, issuer: baseIssuer, value: Number( parseFloat(req.amount).toPrecision(market.tickSize) ).toString(), }; - we_get = { - currency: quoteCurrency, - issuer: quoteIssuer, - value: Number(total.toPrecision(market.tickSize)).toString(), - }; } if (we_pay.currency == 'XRP') { @@ -469,17 +432,7 @@ export class XRPLCLOB implements CLOBish { associatedTxns: [signed.hash], }; - const orderTracker = OrderTracker.getInstance( - this.chain, - this.network, - wallet - ); - - orderTracker.addOrder(order); - - if (orderTracker.isTracking) { - orderTracker.startTracking(); - } + this.trackOrder(wallet, order); return { txHash: signed.hash }; } @@ -495,14 +448,9 @@ export class XRPLCLOB implements CLOBish { }; const { signed } = await this.submitTxn(offer, wallet); + console.log('🪧 -> file: xrpl.ts:451 -> XRPLCLOB -> signed:', signed); - const orderTracker = OrderTracker.getInstance( - this.chain, - this.network, - wallet - ); - - let order = orderTracker.getOrder(parseInt(req.orderId)); + let order = this.getOrder(wallet, req); if (order) { order.state = 'PENDING_CANCEL'; @@ -527,7 +475,7 @@ export class XRPLCLOB implements CLOBish { }; } - orderTracker.addOrder(order); + this.trackOrder(wallet, order); return { txHash: signed.hash }; } @@ -589,4 +537,25 @@ export class XRPLCLOB implements CLOBish { gasCost: parseFloat(fee_estimate.median) * this._client.feeCushion, }; } + + private getOrder(wallet: Wallet, req: ClobDeleteOrderRequest) { + const orderTracker = OrderTracker.getInstance( + this.chain, + this.network, + wallet + ); + + return orderTracker.getOrder(parseInt(req.orderId)); + } + + private trackOrder(wallet: Wallet, order: Order) { + const orderTracker = OrderTracker.getInstance( + this.chain, + this.network, + wallet + ); + + orderTracker.addOrder(order); + orderTracker.startTracking(); + } } diff --git a/src/connectors/xrpl/xrpl.utils.ts b/src/connectors/xrpl/xrpl.utils.ts index 4356928e4a..78c5f4008a 100644 --- a/src/connectors/xrpl/xrpl.utils.ts +++ b/src/connectors/xrpl/xrpl.utils.ts @@ -1,4 +1,6 @@ import { InflightOrders, OrderLocks } from './xrpl.types'; +import { BookOffer, dropsToXrp } from 'xrpl'; +import { XRPL } from '../../chains/xrpl/xrpl'; export class OrderMutexManager { private locks: OrderLocks = {}; @@ -56,3 +58,57 @@ export class OrderMutexManager { return this.locks[hash]; } } + +export function getTakerGetsAmount(offer: BookOffer): string { + if (typeof offer.TakerGets === 'string') { + return dropsToXrp(offer.TakerGets); + } + + return offer.TakerGets.value; +} + +export function getTakerPaysAmount(offer: BookOffer): string { + if (typeof offer.TakerPays === 'string') { + return dropsToXrp(offer.TakerPays); + } + + return offer.TakerPays.value; +} + +export function getTakerGetsFundedAmount(offer: BookOffer): string { + if (typeof offer.taker_gets_funded === 'string') { + return dropsToXrp(offer.taker_gets_funded); + } + + if (!offer.taker_gets_funded) { + return '0'; + } + + return offer.taker_gets_funded.value; +} + +export function getTakerPaysFundedAmount(offer: BookOffer): string { + if (typeof offer.taker_pays_funded === 'string') { + return dropsToXrp(offer.taker_pays_funded); + } + + if (!offer.taker_pays_funded) { + return '0'; + } + + return offer.taker_pays_funded.value; +} + +export async function getsSequenceNumberFromTxn( + network: string, + TxnHash: string +): Promise { + const xrpl = XRPL.getInstance(network); + const txn = await xrpl.getTransaction(TxnHash); + + if (txn) { + return txn.result.Sequence; + } + + return undefined; +} diff --git a/test/connectors/xrpl/xrpl.e2e.test.ts b/test/connectors/xrpl/xrpl.e2e.test.ts new file mode 100644 index 0000000000..feb8c04745 --- /dev/null +++ b/test/connectors/xrpl/xrpl.e2e.test.ts @@ -0,0 +1,251 @@ +import request from 'supertest'; +import { gatewayApp } from '../../../src/app'; +import { Wallet } from 'xrpl'; +import { XRPL } from '../../../src/chains/xrpl/xrpl'; +import { XRPLCLOB } from '../../../src/connectors/xrpl/xrpl'; +import { getsSequenceNumberFromTxn } from '../../../src/connectors/xrpl/xrpl.utils'; +import { patch, unpatch } from '../../services/patch'; + +let xrpl: XRPL; +let xrplCLOB: XRPLCLOB; +const wallet = Wallet.fromSecret('sEd74fJ432TFE4f5Sy48gLyzknkdc1t'); +const MARKET = 'USD-VND'; +const postedOrderTxn: string[] = []; + +// interface Token { +// currency: string; +// issuer: string; +// value?: string; +// } + +// const base_token: Token = { +// currency: 'USD', +// issuer: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', +// value: '0', +// }; + +// const quote_token: Token = { +// currency: 'VND', +// issuer: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', +// value: '0', +// }; + +const INVALID_REQUEST = { + chain: 'unknown', + network: 'testnet', +}; + +const patchWallet = () => { + patch(xrpl, 'getWallet', () => wallet); +}; + +beforeAll(async () => { + xrpl = XRPL.getInstance('testnet'); + await xrpl.init(); + xrplCLOB = XRPLCLOB.getInstance('xrpl', 'testnet'); + await xrplCLOB.init(); + patchWallet(); +}); + +afterAll(async () => { + unpatch(); + await xrpl.close(); +}); + +// Scenario: +// 1. Add wallet to the connector +// 2. Post orders +// 3. Cancel orders +// 4. Post orders again +// 5. Use an other accounts to consume some orders +// 6. Check the balances +// 7. Check the orders status + +describe('Get estimated gas price', () => { + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .get(`/clob/estimateGas`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.gasPrice).toBeDefined()); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/estimateGas`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Get Markets List', () => { + it('should return a list of markets', async () => { + await request(gatewayApp) + .get(`/clob/markets`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.markets.length).toBeGreaterThan(0); + }); + }); +}); + +describe(`Get ticker info for ${MARKET}`, () => { + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .get(`/clob/ticker`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + market: MARKET, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.markets.baseCurrency).toEqual('USD')) + .expect((res) => expect(res.body.markets.quoteCurrency).toEqual('VND')); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/ticker`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Post order', () => { + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock + market: MARKET, + price: '23600', + amount: '1', + side: 'BUY', + orderType: 'LIMIT', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.txHash).toBeDefined(); + postedOrderTxn.push(res.body.txHash); + }); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Get posted order', () => { + it('should return 200 with proper request', async () => { + // wait for 4 second to make sure the order is posted + await new Promise((resolve) => setTimeout(resolve, 4000)); + + await request(gatewayApp) + .get(`/clob/orders`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + market: MARKET, + orderId: 'all', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/orders`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Get orderbook details', () => { + it('should return 200 with proper request with USD-VND', async () => { + await request(gatewayApp) + .get(`/clob/orderBook`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + market: MARKET, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.buys.length).toBeGreaterThan(0); + }); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/orderBook`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('DELETE /clob/orders', () => { + it('should return 200 with proper request', async () => { + // wait for 4 second to make sure the order is posted + await new Promise((resolve) => setTimeout(resolve, 4000)); + + const postedOrderSequence = await getsSequenceNumberFromTxn( + 'testnet', + postedOrderTxn[0] + ); + + expect(postedOrderSequence).toBeDefined(); + + await request(gatewayApp) + .delete(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock + market: MARKET, + orderId: postedOrderSequence?.toString(), + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.txHash).toBeDefined()); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .delete(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); + }); +}); diff --git a/test/connectors/xrpl/xrpl.integration.test.ts b/test/connectors/xrpl/xrpl.integration.test.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index 9127a2d0f0..59ab9cf412 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -285,16 +285,18 @@ const INVALID_REQUEST = { beforeAll(async () => { xrpl = XRPL.getInstance('testnet'); patchConnect(); - xrpl.init(); + // await xrpl.init(); xrplCLOB = XRPLCLOB.getInstance('xrpl', 'testnet'); - await xrplCLOB.init(); + // await xrplCLOB.init(); }); // eslint-disable-next-line @typescript-eslint/no-empty-function beforeEach(() => { patchConnect(); patchFee(); + patchOrderTracking(); patchCurrentBlockNumber(); + patchMarkets(); }); afterEach(() => { @@ -306,7 +308,7 @@ afterAll(async () => { }); const patchConnect = () => { - patch(xrpl, 'ensureConnection', () => { + patch(xrpl, 'ensureConnection', async () => { return Promise.resolve(); }); }; @@ -322,6 +324,12 @@ const patchFee = () => { }); }; +const patchOrderTracking = () => { + patch(xrplCLOB, 'trackOrder', () => { + return Promise.resolve(); + }); +}; + const patchCurrentBlockNumber = (withError: boolean = false) => { patch(xrplCLOB, 'getCurrentBlockNumber', () => { return withError ? -1 : 100; @@ -419,11 +427,17 @@ describe('GET /clob/orderBook', () => { .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) + .expect((res) => { + console.log( + '🪧 -> file: xrpl.routes.test.ts:429 -> it -> res.body.buys:', + res.body.buys + ); + expect(res.body.buys[0].price).toEqual('0.5161287658690241'); + }) + .expect((res) => expect(res.body.buys[0].quantity).toEqual('9.687505')) .expect((res) => - expect(res.body.buys[0].price).toEqual('0.5161287658690241') + expect(res.body.sells[0].price).toEqual('0.5209865069999999') ) - .expect((res) => expect(res.body.buys[0].quantity).toEqual('5')) - .expect((res) => expect(res.body.sells[0].price).toEqual('0.520986507')) .expect((res) => expect(res.body.sells[0].quantity).toEqual('60133')); }); From 6609840ded262d1d44cc9245681458c4d38ba7e0 Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 27 Jun 2023 19:47:58 +0700 Subject: [PATCH 17/39] Finish order posting and cancelling scenario test --- src/chains/xrpl/xrpl.order-tracker.ts | 8 +- src/chains/xrpl/xrpl.ts | 4 - src/connectors/xrpl/xrpl.ts | 2 +- test/connectors/xrpl/xrpl.e2e.test.ts | 199 ++++++++++++++++++++++---- 4 files changed, 177 insertions(+), 36 deletions(-) diff --git a/src/chains/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts index 2a991f4b0c..35ddb4651b 100644 --- a/src/chains/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -58,8 +58,8 @@ export class OrderTracker { this._inflightOrders = {}; this._orderMutexManager = new OrderMutexManager(this._inflightOrders); this._isTracking = false; - // set tracking interval to 10 seconds - this._orderTrackingInterval = 10000; + // set tracking interval to 1 seconds + this._orderTrackingInterval = 1000; } public static getInstance( @@ -187,7 +187,6 @@ export class OrderTracker { // Update mutex manager this._orderMutexManager.updateOrders(this._inflightOrders); - // Save inflightOrders to DB await this.saveInflightOrdersToDB(); @@ -430,7 +429,6 @@ export class OrderTracker { ? rippleTimeToUnixTime(transaction.transaction.date) : 0; matchOrder.updatedAtLedgerIndex = transaction.ledger_index ?? 0; - // Find if transaction.transaction.hash already in associatedTxns, if not, then push it const foundIndex = matchOrder.associatedTxns.findIndex((hash) => { return hash === transaction.transaction.hash; @@ -647,7 +645,7 @@ export class OrderTracker { } const transformedTx: TransaformedAccountTransaction = { - ledger_index: transaction.ledger_index, + ledger_index: transaction.tx.ledger_index ?? 0, meta: transaction.meta as TransactionMetadata, transaction: transaction.tx as Transaction & ResponseOnlyTxInfo, tx_blob: transaction.tx_blob, diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 78802efc2b..2f69e37a1a 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -368,10 +368,6 @@ export class XRPL implements XRPLish { } async ensureConnection() { - console.log( - '🪧 -> file: xrpl.ts:372 -> XRPL -> ensureConnection -> isConnected:', - this.isConnected() - ); if (!this.isConnected()) { await this._client.connect(); } diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index 73aee60b9d..bf2f1abc5a 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -44,7 +44,7 @@ import { getXRPLConfig } from '../../chains/xrpl/xrpl.config'; import { isUndefined } from 'mathjs'; // const XRP_FACTOR = 1000000; -const ORDERBOOK_LIMIT = 10; +const ORDERBOOK_LIMIT = 100; export class XRPLCLOB implements CLOBish { private static _instances: LRUCache; diff --git a/test/connectors/xrpl/xrpl.e2e.test.ts b/test/connectors/xrpl/xrpl.e2e.test.ts index feb8c04745..ddd10d4499 100644 --- a/test/connectors/xrpl/xrpl.e2e.test.ts +++ b/test/connectors/xrpl/xrpl.e2e.test.ts @@ -5,10 +5,12 @@ import { XRPL } from '../../../src/chains/xrpl/xrpl'; import { XRPLCLOB } from '../../../src/connectors/xrpl/xrpl'; import { getsSequenceNumberFromTxn } from '../../../src/connectors/xrpl/xrpl.utils'; import { patch, unpatch } from '../../services/patch'; +import { Order } from '../../../src/connectors/xrpl/xrpl.types'; let xrpl: XRPL; let xrplCLOB: XRPLCLOB; -const wallet = Wallet.fromSecret('sEd74fJ432TFE4f5Sy48gLyzknkdc1t'); +const wallet1 = Wallet.fromSecret('sEd74fJ432TFE4f5Sy48gLyzknkdc1t'); +// const wallet2 = Wallet.fromSecret('sEd7oiMn5napJBthB2z4CtN5nVi56Bd'); const MARKET = 'USD-VND'; const postedOrderTxn: string[] = []; @@ -35,16 +37,20 @@ const INVALID_REQUEST = { network: 'testnet', }; -const patchWallet = () => { - patch(xrpl, 'getWallet', () => wallet); +const patchWallet1 = () => { + patch(xrpl, 'getWallet', () => wallet1); }; +// const patchWallet2 = () => { +// patch(xrpl, 'getWallet', () => wallet2); +// }; + beforeAll(async () => { xrpl = XRPL.getInstance('testnet'); await xrpl.init(); xrplCLOB = XRPLCLOB.getInstance('xrpl', 'testnet'); await xrplCLOB.init(); - patchWallet(); + patchWallet1(); }); afterAll(async () => { @@ -52,14 +58,14 @@ afterAll(async () => { await xrpl.close(); }); -// Scenario: -// 1. Add wallet to the connector -// 2. Post orders -// 3. Cancel orders -// 4. Post orders again -// 5. Use an other accounts to consume some orders -// 6. Check the balances -// 7. Check the orders status +// 1st Scenario: +// 1. Get estimated gas price +// 2. Get markets list +// 3. Get ticker info +// 4. Get orderbook +// 5. Post an order +// 6. Get posted order details +// 7. Cancel the posted order describe('Get estimated gas price', () => { it('should return 200 with proper request', async () => { @@ -151,18 +157,58 @@ describe('Post order', () => { }); }); - it('should return 404 when parameters are invalid', async () => { + it('should return PENDING_OPEN with proper request', async () => { + // wait for 1 second to make sure the order is getting tracked + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const postedOrderSequence = await getsSequenceNumberFromTxn( + 'testnet', + postedOrderTxn[0] + ); + + if (postedOrderSequence === undefined) { + throw new Error('postedOrderSequence is undefined'); + } + await request(gatewayApp) - .post(`/clob/orders`) - .send(INVALID_REQUEST) - .expect(404); + .get(`/clob/orders`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + market: MARKET, + orderId: 'all', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) + .expect((res) => { + const orders: Order[] = res.body.orders; + const postedOrder = orders.find( + (order) => order.hash === postedOrderSequence + ); + + if (postedOrder === undefined) { + throw new Error('postedOrder is undefined'); + } + expect(postedOrder.state).toBe('PENDING_OPEN'); + }); }); -}); -describe('Get posted order', () => { - it('should return 200 with proper request', async () => { - // wait for 4 second to make sure the order is posted - await new Promise((resolve) => setTimeout(resolve, 4000)); + it('should return OPEN with proper request', async () => { + // wait for 6 second to make sure the order is posted on blockchain + await new Promise((resolve) => setTimeout(resolve, 6000)); + + const postedOrderSequence = await getsSequenceNumberFromTxn( + 'testnet', + postedOrderTxn[0] + ); + + if (postedOrderSequence === undefined) { + throw new Error('postedOrderSequence is undefined'); + } await request(gatewayApp) .get(`/clob/orders`) @@ -177,7 +223,25 @@ describe('Get posted order', () => { .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) - .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)); + .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) + .expect((res) => { + const orders: Order[] = res.body.orders; + const postedOrder = orders.find( + (order) => order.hash === postedOrderSequence + ); + + if (postedOrder === undefined) { + throw new Error('postedOrder is undefined'); + } + expect(postedOrder.state).toBe('OPEN'); + }); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); }); it('should return 404 when parameters are invalid', async () => { @@ -214,11 +278,8 @@ describe('Get orderbook details', () => { }); }); -describe('DELETE /clob/orders', () => { +describe('Delete order', () => { it('should return 200 with proper request', async () => { - // wait for 4 second to make sure the order is posted - await new Promise((resolve) => setTimeout(resolve, 4000)); - const postedOrderSequence = await getsSequenceNumberFromTxn( 'testnet', postedOrderTxn[0] @@ -242,6 +303,82 @@ describe('DELETE /clob/orders', () => { .expect((res) => expect(res.body.txHash).toBeDefined()); }); + it('should return PENDING_CANCEL with proper request', async () => { + // wait for 1 second to make sure the order is getting tracked + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const postedOrderSequence = await getsSequenceNumberFromTxn( + 'testnet', + postedOrderTxn[0] + ); + + if (postedOrderSequence === undefined) { + throw new Error('postedOrderSequence is undefined'); + } + + await request(gatewayApp) + .get(`/clob/orders`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + market: MARKET, + orderId: postedOrderSequence.toString(), + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) + .expect((res) => { + const orders: Order[] = res.body.orders; + const postedOrder = orders[0]; + + if (postedOrder === undefined) { + throw new Error('postedOrder is undefined'); + } + expect(postedOrder.state).toBe('PENDING_CANCEL'); + }); + }); + + it('should return CANCELED with proper request', async () => { + // wait for 6 second to make sure the order is posted on blockchain + await new Promise((resolve) => setTimeout(resolve, 6000)); + + const postedOrderSequence = await getsSequenceNumberFromTxn( + 'testnet', + postedOrderTxn[0] + ); + + if (postedOrderSequence === undefined) { + throw new Error('postedOrderSequence is undefined'); + } + + await request(gatewayApp) + .get(`/clob/orders`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + market: MARKET, + orderId: postedOrderSequence.toString(), + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) + .expect((res) => { + const orders: Order[] = res.body.orders; + const postedOrder = orders[0]; + + if (postedOrder === undefined) { + throw new Error('postedOrder is undefined'); + } + expect(postedOrder.state).toBe('CANCELED'); + }); + }); + it('should return 404 when parameters are invalid', async () => { await request(gatewayApp) .delete(`/clob/orders`) @@ -249,3 +386,13 @@ describe('DELETE /clob/orders', () => { .expect(404); }); }); + +// 2nd Senario: +// 1. Post an order +// 2. Get posted order details +// 3. Verify the posted order status is PENDING_OPEN +// 4. Wait for 5 seconds and then verify the posted order status is OPEN +// 5. Use 2nd wallet to create a counter order to consume the posted order partially +// 6. Verify the posted order status is PARTIALLY_FILLED +// 7. Use 2nd wallet to create a counter order to consume the posted order completely +// 8. Verify the posted order status is FILLED From 1111b273e44d14fcfc88bd7f2611631b99f4e469 Mon Sep 17 00:00:00 2001 From: mlguys Date: Sat, 1 Jul 2023 01:07:45 +0700 Subject: [PATCH 18/39] Finish fixing order tracking --- src/chains/xrpl/xrpl.order-tracker.ts | 643 ++++++++++++++++---------- src/chains/xrpl/xrpl.ts | 1 + src/connectors/xrpl/xrpl.ts | 11 +- src/connectors/xrpl/xrpl.types.ts | 48 +- test/connectors/xrpl/xrpl.e2e.test.ts | 362 ++++++++------- 5 files changed, 655 insertions(+), 410 deletions(-) diff --git a/src/chains/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts index 35ddb4651b..9220de91dc 100644 --- a/src/chains/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -7,6 +7,7 @@ import { TransactionStream, TransactionMetadata, Transaction, + dropsToXrp, } from 'xrpl'; import { XRPL } from './xrpl'; import { getXRPLConfig } from './xrpl.config'; @@ -19,11 +20,14 @@ import { TransactionIntent, AccountTransaction, ResponseOnlyTxInfo, + CreatedNode, + ModifiedNode, } from '../../connectors/xrpl/xrpl.types'; import { OrderMutexManager } from '../../connectors/xrpl/xrpl.utils'; import { isModifiedNode, isDeletedNode, + isCreatedNode, } from 'xrpl/dist/npm/models/transactions/metadata'; import LRUCache from 'lru-cache'; @@ -43,6 +47,7 @@ export class OrderTracker { private _orderMutexManager: OrderMutexManager; private _inflightOrders: InflightOrders; private _isTracking: boolean; + private _isProcessing: boolean; private _orderTrackingInterval: number; public chain: string; @@ -58,8 +63,10 @@ export class OrderTracker { this._inflightOrders = {}; this._orderMutexManager = new OrderMutexManager(this._inflightOrders); this._isTracking = false; + this._isProcessing = false; // set tracking interval to 1 seconds this._orderTrackingInterval = 1000; + this.startTracking(); } public static getInstance( @@ -92,8 +99,22 @@ export class OrderTracker { return this._isTracking; } - public addOrder(order: Order): void { + public async addOrder(order: Order): Promise { this._inflightOrders[order.hash] = order; + + // Check if isProcessing is on, if so, wait until it's done + while (this._isProcessing) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + // Mark isProcessing as true + this._isProcessing = true; + + // save inflightOrders to DB + await this.saveInflightOrdersToDB(); + + // Mark isProcessing as false + this._isProcessing = false; } public getOrder(hash: number): Order | undefined { @@ -139,6 +160,14 @@ export class OrderTracker { const client = this._xrpl.client; client.on('transaction', async (event) => { + // Check if isProcessing is on, if so, wait until it's done + while (this._isProcessing) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + // Mark isProcessing as true + this._isProcessing = true; + const updatedOrders = await this.processTransactionStream( this._inflightOrders, event, @@ -151,6 +180,12 @@ export class OrderTracker { }); this._orderMutexManager.updateOrders(this._inflightOrders); + + // Save inflightOrders to DB + await this.saveInflightOrdersToDB(); + + // Mark isProcessing as false + this._isProcessing = false; }); await client.request({ @@ -162,17 +197,15 @@ export class OrderTracker { // Make sure connection is good await this._xrpl.ensureConnection(); - // Check pending inflightOrders - const hashes = Object.keys(this._inflightOrders); - for (const hash of hashes) { - this._inflightOrders[parseInt(hash)] = await this.checkPendingOrder( - client, - this._inflightOrders[parseInt(hash)], - this._orderMutexManager - ); + // Check if isProcessing is on, if so, wait until it's done + while (this._isProcessing) { + await new Promise((resolve) => setTimeout(resolve, 100)); } - // Check open inflightOrders + // Mark isProcessing as true + this._isProcessing = true; + + // Check inflightOrders const updatedOrders = await this.checkOpenOrders( this._inflightOrders, this._wallet.classicAddress, @@ -190,6 +223,14 @@ export class OrderTracker { // Save inflightOrders to DB await this.saveInflightOrdersToDB(); + // Mark isProcessing as false + this._isProcessing = false; + + // console.log( + // '🪧 -> file: xrpl.order-tracker.ts:241 -> OrderTracker -> startTracking -> _inflightOrders:', + // this._inflightOrders + // ); + // Wait for next interval await new Promise((resolve) => setTimeout(resolve, this._orderTrackingInterval) @@ -240,98 +281,6 @@ export class OrderTracker { ); } - async checkPendingOrder( - client: Client, - order: Order, - omm: OrderMutexManager - ): Promise { - // Check if order is canceled - // Check if order is open - let result: Order = order; - const currentLedgerIndex = await client.getLedgerIndex(); - - // If order state is not pending open or pending cancel, then return - if ( - order.state !== OrderStatus.PENDING_OPEN && - order.state !== OrderStatus.PENDING_CANCEL - ) { - return order; - } - - // If order currentLedgerIndex is less than or equal than order createdAtLedgerIndex, then return - if (currentLedgerIndex <= order.updatedAtLedgerIndex) { - return order; - } - - // Wait until the order is not locked - while (omm.isLocked(order.hash)) { - // console.log('Order is locked! Wait for 300ms'); - await new Promise((resolve) => setTimeout(resolve, 300)); - } - - // Lock the order - omm.lock(order.hash); - - // If order state is pending open, check if order is open - if (order.state === OrderStatus.PENDING_OPEN) { - const latestTxnResp = await this.getTransaction( - client, - order.associatedTxns[0] || '' - ); - if (typeof latestTxnResp?.result.meta === 'string') { - result = order; - } else if ( - latestTxnResp?.result.meta?.TransactionResult === 'tesSUCCESS' - ) { - result = { - ...order, - state: OrderStatus.OPEN, - updatedAt: latestTxnResp?.result.date - ? rippleTimeToUnixTime(latestTxnResp?.result.date) - : 0, - updatedAtLedgerIndex: latestTxnResp?.result.ledger_index ?? 0, - }; - // console.log('Order opened!'); - } else { - // Handle other cases here - // https://xrpl.org/transaction-results.html - result = order; - } - } - - // If order state is pending cancel, check if order is canceled - if (order.state === OrderStatus.PENDING_CANCEL) { - const latestTxnResp = await this.getTransaction( - client, - order.associatedTxns[order.associatedTxns.length - 1] || '' - ); - if (typeof latestTxnResp?.result.meta === 'string') { - result = order; - } else if ( - latestTxnResp?.result.meta?.TransactionResult === 'tesSUCCESS' - ) { - result = { - ...order, - state: OrderStatus.CANCELED, - updatedAt: latestTxnResp?.result.date - ? rippleTimeToUnixTime(latestTxnResp?.result.date) - : 0, - updatedAtLedgerIndex: latestTxnResp?.result.ledger_index ?? 0, - }; - // console.log('Order canceled!'); - } else { - // Handle other cases here - // https://xrpl.org/transaction-results.html - result = order; - } - } - - // Release the lock - omm.release(order.hash); - - return result; - } - async checkOpenOrders( openOrders: InflightOrders, account: string, @@ -344,12 +293,16 @@ export class OrderTracker { // 3. Process the transactions stack // 4. Update the inflightOrders const ordersToCheck: InflightOrders = openOrders; + console.log( + '🪧 -> file: xrpl.order-tracker.ts:296 -> OrderTracker -> BEFORE ordersToCheck:', + ordersToCheck + ); // 1. Get the minLedgerIndex from the inflightOrders const hashes = Object.keys(ordersToCheck); - let minLedgerIndex = 0; + let minLedgerIndex = await this._xrpl.getCurrentLedgerIndex(); for (const hash of hashes) { - if (ordersToCheck[parseInt(hash)].updatedAtLedgerIndex > minLedgerIndex) { + if (ordersToCheck[parseInt(hash)].updatedAtLedgerIndex < minLedgerIndex) { minLedgerIndex = ordersToCheck[parseInt(hash)].updatedAtLedgerIndex; } } @@ -370,7 +323,10 @@ export class OrderTracker { return ordersToCheck; } - for (const tx of txStack.result.transactions) { + const transactionStack = txStack.result.transactions; + transactionStack.reverse(); + + for (const tx of transactionStack) { const transformedTx = this.transformAccountTransaction(tx); if (transformedTx === null) { @@ -389,6 +345,11 @@ export class OrderTracker { }); } + console.log( + '🪧 -> file: xrpl.order-tracker.ts:296 -> OrderTracker -> AFTER ordersToCheck:', + ordersToCheck + ); + return ordersToCheck; } @@ -397,202 +358,396 @@ export class OrderTracker { transaction: TransactionStream | TransaformedAccountTransaction, omm: OrderMutexManager ): Promise { - const transactionIntent = await this.getTransactionIntentFromStream( + // TODO: Extend this function to handle multiple intents + const transactionIntents = await this.getTransactionIntentsFromStream( + this.wallet.classicAddress, transaction ); + const ordersToCheck = inflightOrders; // console.log('Transaction intent: '); // console.log(inspect(transactionIntent, { colors: true, depth: null })); - if (transactionIntent.sequence === undefined) { - // console.log('No sequence found!'); + if (transactionIntents.length === 0) { + // console.log('No intent found!'); return ordersToCheck; } - const matchOrder = ordersToCheck[transactionIntent.sequence]; + for (const intent of transactionIntents) { + if (intent.sequence === undefined) { + continue; + } - if (matchOrder === undefined) { - // console.log('No match order found!'); - return ordersToCheck; - } + const matchOrder = ordersToCheck[intent.sequence]; - // Wait until the order is not locked - while (omm.isLocked(matchOrder.hash)) { - // console.log('Order is locked! Wait for 300ms'); - await new Promise((resolve) => setTimeout(resolve, 300)); - } + if (matchOrder === undefined) { + // console.log('No match order found!'); + continue; + } - // Lock the order - omm.lock(matchOrder.hash); + // Wait until the order is not locked + while (omm.isLocked(matchOrder.hash)) { + // console.log('Order is locked! Wait for 300ms'); + await new Promise((resolve) => setTimeout(resolve, 300)); + } - matchOrder.updatedAt = transaction.transaction.date - ? rippleTimeToUnixTime(transaction.transaction.date) - : 0; - matchOrder.updatedAtLedgerIndex = transaction.ledger_index ?? 0; - // Find if transaction.transaction.hash already in associatedTxns, if not, then push it - const foundIndex = matchOrder.associatedTxns.findIndex((hash) => { - return hash === transaction.transaction.hash; - }); + // Lock the order + omm.lock(matchOrder.hash); - if (foundIndex === -1) { - matchOrder.associatedTxns.push(transaction.transaction.hash ?? 'UNKNOWN'); - } else { - // console.log('Transaction already found!'); - } + matchOrder.updatedAt = transaction.transaction.date + ? rippleTimeToUnixTime(transaction.transaction.date) + : 0; + matchOrder.updatedAtLedgerIndex = transaction.ledger_index ?? 0; - switch (transactionIntent.type) { - case TransactionIntentType.OFFER_CREATE_FINALIZED: - // console.log('Offer create finalized!'); - matchOrder.state = OrderStatus.OPEN; - break; + // Find if transaction.transaction.hash already in associatedTxns, if not, then push it + const foundIndex = matchOrder.associatedTxns.findIndex((hash) => { + return hash === intent.tx.transaction.hash; + }); + + if (foundIndex === -1) { + matchOrder.associatedTxns.push(intent.tx.transaction.hash ?? 'UNKNOWN'); + } else { + // console.log('Transaction already found!'); + } - case TransactionIntentType.OFFER_CANCEL_FINALIZED: - // console.log('Offer cancel finalized!'); - matchOrder.state = OrderStatus.CANCELED; - break; + let filledAmount = 0.0; + let node: CreatedNode | ModifiedNode; + let fields: any; - case TransactionIntentType.OFFER_PARTIAL_FILL: - // console.log('Offer partial fill!'); - matchOrder.state = OrderStatus.PARTIALLY_FILLED; + switch (intent.type) { + case TransactionIntentType.OFFER_CREATE_FINALIZED: + // console.log('Offer create finalized!'); + if (matchOrder.state === OrderStatus.PENDING_OPEN) { + matchOrder.state = OrderStatus.OPEN; + } + break; - if (transaction.meta === undefined) { - // console.log('No meta found!'); + case TransactionIntentType.OFFER_CANCEL_FINALIZED: + // console.log('Offer cancel finalized!'); + matchOrder.state = OrderStatus.CANCELED; break; - } - for (const affnode of transaction.meta.AffectedNodes) { - if (isModifiedNode(affnode)) { - // console.log('Modified node found!'); - if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { - // Usually a ModifiedNode of type Offer indicates a previous Offer that - // was partially consumed by this one. - if (affnode.ModifiedNode.FinalFields === undefined) { - // console.log('No final fields found!'); - break; - } + case TransactionIntentType.OFFER_PARTIAL_FILL: + // console.log('Offer partial fill!'); + matchOrder.state = OrderStatus.PARTIALLY_FILLED; - const finalFields = affnode.ModifiedNode.FinalFields as any; - let filledAmount = 0.0; + if (intent.node === undefined) { + // console.log('No node found!'); + break; + } - // Update filled amount - if (matchOrder.tradeType === 'SELL') { - if (typeof finalFields.TakerGets === 'string') { - filledAmount = - parseFloat(matchOrder.amount) - - parseFloat(finalFields.TakerGets as string); - } else { - filledAmount = - parseFloat(matchOrder.amount) - - parseFloat(finalFields.TakerGets.value as string); - } - } + node = intent.node as CreatedNode | ModifiedNode; - if (matchOrder.tradeType === 'BUY') { - if (typeof finalFields.TakerPays === 'string') { - filledAmount = - parseFloat(matchOrder.amount) - - parseFloat(finalFields.TakerPays as string); - } else { - filledAmount = - parseFloat(matchOrder.amount) - - parseFloat(finalFields.TakerPays.value as string); - } - } + if (isCreatedNode(node)) { + fields = node.CreatedNode.NewFields; + } else { + fields = node.ModifiedNode.FinalFields; + } - // console.log('Filled amount: ', filledAmount); - matchOrder.filledAmount = filledAmount.toString(); - break; + // Update filled amount + if (matchOrder.tradeType === 'SELL') { + if (typeof fields.TakerGets === 'string') { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(dropsToXrp(fields.TakerGets as string)); + } else { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(fields.TakerGets.value as string); } } - } - break; - case TransactionIntentType.OFFER_FILL: - matchOrder.state = OrderStatus.FILLED; - matchOrder.filledAmount = matchOrder.amount; - break; + if (matchOrder.tradeType === 'BUY') { + if (typeof fields.TakerPays === 'string') { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(dropsToXrp(fields.TakerPays as string)); + } else { + filledAmount = + parseFloat(matchOrder.amount) - + parseFloat(fields.TakerPays.value as string); + } + } - case TransactionIntentType.UNKNOWN: - matchOrder.state = OrderStatus.UNKNOWN; - break; - } + // console.log('Filled amount: ', filledAmount); + matchOrder.filledAmount = filledAmount.toString(); + break; - // Check matchOrder value - // console.log('Updated matchOrder: '); - // console.log(inspect(matchOrder, { colors: true, depth: null })); + case TransactionIntentType.OFFER_FILL: + matchOrder.state = OrderStatus.FILLED; + matchOrder.filledAmount = matchOrder.amount; + break; - // Update ordersToCheck - ordersToCheck[matchOrder.hash] = matchOrder; + case TransactionIntentType.OFFER_EXPIRED_OR_UNFUNDED: + matchOrder.state = OrderStatus.OFFER_EXPIRED_OR_UNFUNDED; + break; - // Release the lock - omm.release(matchOrder.hash); + case TransactionIntentType.UNKNOWN: + matchOrder.state = OrderStatus.UNKNOWN; + break; + } + + // Check matchOrder value + // console.log('Updated matchOrder: '); + // console.log(inspect(matchOrder, { colors: true, depth: null })); + + // Update ordersToCheck + ordersToCheck[matchOrder.hash] = matchOrder; + + // Release the lock + omm.release(matchOrder.hash); + } return ordersToCheck; } // Utility methods - async getTransactionIntentFromStream( + async getTransactionIntentsFromStream( + walletAddress: string, transaction: TransactionStream | TransaformedAccountTransaction - ): Promise { + ): Promise { const transactionType = transaction.transaction.TransactionType; + const intents: TransactionIntent[] = []; if (transaction.transaction.Sequence === undefined) { - return { type: TransactionIntentType.UNKNOWN }; + return [ + { + type: TransactionIntentType.UNKNOWN, + sequence: 0, + tx: transaction, + }, + ]; } if (transaction.meta === undefined) { - return { type: TransactionIntentType.UNKNOWN }; + return [ + { + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.Sequence, + tx: transaction, + }, + ]; } - switch (transactionType) { - case 'OfferCreate': - // return { type: TransactionIntentType.OFFER_CREATE_FINALIZED, hash: transaction.transaction.Sequence }; - for (const affnode of transaction.meta.AffectedNodes) { - if (isModifiedNode(affnode)) { - if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { - // Usually a ModifiedNode of type Offer indicates a previous Offer that - // was partially consumed by this one. - if (affnode.ModifiedNode.FinalFields === undefined) { - return { type: TransactionIntentType.UNKNOWN }; + if (transaction.transaction.Account !== walletAddress) { + // console.log('Transaction is not from wallet!'); + switch (transactionType) { + case 'OfferCreate': + for (const affnode of transaction.meta.AffectedNodes) { + if (isModifiedNode(affnode)) { + if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { + // Usually a ModifiedNode of type Offer indicates a previous Offer that + // was partially consumed by this one. + if (affnode.ModifiedNode.FinalFields === undefined) { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.Sequence, + tx: transaction, + node: affnode, + }); + } + + const finalFields = affnode.ModifiedNode.FinalFields as any; + + intents.push({ + type: TransactionIntentType.OFFER_PARTIAL_FILL, + sequence: finalFields.Sequence as number, + tx: transaction, + node: affnode, + }); } + } else if (isDeletedNode(affnode)) { + if (affnode.DeletedNode.LedgerEntryType == 'Offer') { + // The removed Offer may have been fully consumed, or it may have been + // found to be expired or unfunded. + if (affnode.DeletedNode.FinalFields === undefined) { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.Sequence, + tx: transaction, + node: affnode, + }); + } + + const finalFields = affnode.DeletedNode.FinalFields as any; - return { - type: TransactionIntentType.OFFER_PARTIAL_FILL, - sequence: affnode.ModifiedNode.FinalFields.Sequence as number, - }; + intents.push({ + type: TransactionIntentType.OFFER_FILL, + sequence: finalFields.Sequence as number, + tx: transaction, + node: affnode, + }); + } } - } else if (isDeletedNode(affnode)) { - if (affnode.DeletedNode.LedgerEntryType == 'Offer') { - // The removed Offer may have been fully consumed, or it may have been - // found to be expired or unfunded. - if (affnode.DeletedNode.FinalFields === undefined) { - return { type: TransactionIntentType.UNKNOWN }; + } + break; + } + } else { + // console.log('Transaction is from wallet!'); + let consumedNodeCount = 0; + let createNodeCount = 0; + let deleteNodeCount = 0; + + switch (transactionType) { + case 'OfferCreate': + for (const affnode of transaction.meta.AffectedNodes) { + if (isModifiedNode(affnode)) { + if (affnode.ModifiedNode.LedgerEntryType == 'Offer') { + // Usually a ModifiedNode of type Offer indicates a previous Offer that + // was partially consumed by this one. + + if (affnode.ModifiedNode.FinalFields === undefined) { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.Sequence, + tx: transaction, + node: affnode, + }); + } + + const finalFields = affnode.ModifiedNode.FinalFields as any; + intents.push({ + type: TransactionIntentType.OFFER_PARTIAL_FILL, + sequence: finalFields.Sequence as number, + tx: transaction, + node: affnode, + }); + consumedNodeCount++; + } + } else if (isDeletedNode(affnode)) { + if (affnode.DeletedNode.LedgerEntryType == 'Offer') { + // The removed Offer may have been fully consumed, or it may have been + // found to be expired or unfunded. + if (affnode.DeletedNode.FinalFields === undefined) { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.Sequence, + tx: transaction, + node: affnode, + }); + } else { + const finalFields = affnode.DeletedNode.FinalFields as any; + + if (finalFields.Account === walletAddress) { + const replacingOfferSequnce = + transaction.transaction.OfferSequence; + + if (finalFields.Sequence !== replacingOfferSequnce) { + intents.push({ + type: TransactionIntentType.OFFER_EXPIRED_OR_UNFUNDED, + sequence: finalFields.Sequence as number, + tx: transaction, + node: affnode, + }); + } else { + intents.push({ + type: TransactionIntentType.OFFER_CANCEL_FINALIZED, + sequence: finalFields.Sequence as number, + tx: transaction, + node: affnode, + }); + } + } else { + intents.push({ + type: TransactionIntentType.OFFER_FILL, + sequence: finalFields.Sequence as number, + tx: transaction, + node: affnode, + }); + consumedNodeCount++; + } + } + } + } else if (isCreatedNode(affnode)) { + if (affnode.CreatedNode.LedgerEntryType == 'Offer') { + if (affnode.CreatedNode.NewFields === undefined) { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.Sequence, + tx: transaction, + node: affnode, + }); + } else { + const newFields = affnode.CreatedNode.NewFields as any; + if (consumedNodeCount > 0) { + intents.push({ + type: TransactionIntentType.OFFER_PARTIAL_FILL, + sequence: newFields.Sequence, + tx: transaction, + node: affnode, + }); + } else { + intents.push({ + type: TransactionIntentType.OFFER_CREATE_FINALIZED, + sequence: newFields.Sequence, + tx: transaction, + node: affnode, + }); + } + createNodeCount++; + } } + } + } - return { + if (createNodeCount === 0) { + if (consumedNodeCount > 0) { + intents.push({ type: TransactionIntentType.OFFER_FILL, - sequence: affnode.DeletedNode.FinalFields.Sequence as number, - }; + sequence: transaction.transaction.Sequence, + tx: transaction, + }); + } else { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.Sequence, + tx: transaction, + }); } - } else { - return { - type: TransactionIntentType.OFFER_CREATE_FINALIZED, - sequence: transaction.transaction.Sequence, - }; } - } - break; + break; - case 'OfferCancel': - return { - type: TransactionIntentType.OFFER_CANCEL_FINALIZED, - sequence: transaction.transaction.OfferSequence, - }; + case 'OfferCancel': + for (const affnode of transaction.meta.AffectedNodes) { + if (isDeletedNode(affnode)) { + if (affnode.DeletedNode.LedgerEntryType == 'Offer') { + if (affnode.DeletedNode.FinalFields === undefined) { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.OfferSequence, + tx: transaction, + node: affnode, + }); + } else { + const finalFields = affnode.DeletedNode.FinalFields as any; + + if (finalFields.Account === walletAddress) { + intents.push({ + type: TransactionIntentType.OFFER_CANCEL_FINALIZED, + sequence: finalFields.Sequence as number, + tx: transaction, + node: affnode, + }); + deleteNodeCount++; + } + } + } + } + } + + if (deleteNodeCount === 0) { + intents.push({ + type: TransactionIntentType.UNKNOWN, + sequence: transaction.transaction.OfferSequence, + tx: transaction, + }); + } + break; + } } - return { type: TransactionIntentType.UNKNOWN }; + return intents; } - async getTransaction( client: Client, txHash: string diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 2f69e37a1a..a3f638d216 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -414,6 +414,7 @@ export class XRPL implements XRPLish { } public async getCurrentBlockNumber(): Promise { + await this.ensureConnection(); const currentIndex = await this.getCurrentLedgerIndex(); return currentIndex; } diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index bf2f1abc5a..47fa82022b 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -432,7 +432,7 @@ export class XRPLCLOB implements CLOBish { associatedTxns: [signed.hash], }; - this.trackOrder(wallet, order); + await this.trackOrder(wallet, order); return { txHash: signed.hash }; } @@ -448,7 +448,6 @@ export class XRPLCLOB implements CLOBish { }; const { signed } = await this.submitTxn(offer, wallet); - console.log('🪧 -> file: xrpl.ts:451 -> XRPLCLOB -> signed:', signed); let order = this.getOrder(wallet, req); @@ -475,7 +474,7 @@ export class XRPLCLOB implements CLOBish { }; } - this.trackOrder(wallet, order); + await this.trackOrder(wallet, order); return { txHash: signed.hash }; } @@ -548,14 +547,14 @@ export class XRPLCLOB implements CLOBish { return orderTracker.getOrder(parseInt(req.orderId)); } - private trackOrder(wallet: Wallet, order: Order) { + private async trackOrder(wallet: Wallet, order: Order) { const orderTracker = OrderTracker.getInstance( this.chain, this.network, wallet ); - orderTracker.addOrder(order); - orderTracker.startTracking(); + await orderTracker.addOrder(order); + // orderTracker.startTracking(); } } diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index 9b0aa67b20..f4fd7b8c15 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -1,6 +1,10 @@ import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; -import { BookOffer } from 'xrpl'; -import { TransactionMetadata, Transaction } from 'xrpl'; +import { + BookOffer, + TransactionStream, + TransactionMetadata, + Transaction, +} from 'xrpl'; export type IMap = ImmutableMap; export const IMap = ImmutableMap; @@ -21,6 +25,7 @@ export enum OrderStatus { PENDING_OPEN = 'PENDING_OPEN', PENDING_CANCEL = 'PENDING_CANCEL', FAILED = 'FAILED', + OFFER_EXPIRED_OR_UNFUNDED = 'OFFER_EXPIRED_OR_UNFUNDED', UNKNOWN = 'UNKNOWN', } @@ -37,6 +42,7 @@ export enum TransactionIntentType { OFFER_CANCEL_FINALIZED = 'OfferCancelFinalized', OFFER_PARTIAL_FILL = 'OfferPartialFill', OFFER_FILL = 'OfferFill', + OFFER_EXPIRED_OR_UNFUNDED = 'OfferExpiredOrUnfunded', UNKNOWN = 'UNKNOWN', } @@ -266,4 +272,42 @@ export interface ResponseOnlyTxInfo { export interface TransactionIntent { type: TransactionIntentType; sequence?: number; + tx: TransactionStream | TransaformedAccountTransaction; + filledAmount?: string; + node?: Node; +} + +type Node = CreatedNode | ModifiedNode | DeletedNode; + +export interface CreatedNode { + CreatedNode: { + LedgerEntryType: string; + LedgerIndex: string; + NewFields: { + [field: string]: unknown; + }; + }; +} +export interface ModifiedNode { + ModifiedNode: { + LedgerEntryType: string; + LedgerIndex: string; + FinalFields?: { + [field: string]: unknown; + }; + PreviousFields?: { + [field: string]: unknown; + }; + PreviousTxnID?: string; + PreviousTxnLgrSeq?: number; + }; +} +export interface DeletedNode { + DeletedNode: { + LedgerEntryType: string; + LedgerIndex: string; + FinalFields: { + [field: string]: unknown; + }; + }; } diff --git a/test/connectors/xrpl/xrpl.e2e.test.ts b/test/connectors/xrpl/xrpl.e2e.test.ts index ddd10d4499..f1e513a09d 100644 --- a/test/connectors/xrpl/xrpl.e2e.test.ts +++ b/test/connectors/xrpl/xrpl.e2e.test.ts @@ -9,28 +9,10 @@ import { Order } from '../../../src/connectors/xrpl/xrpl.types'; let xrpl: XRPL; let xrplCLOB: XRPLCLOB; -const wallet1 = Wallet.fromSecret('sEd74fJ432TFE4f5Sy48gLyzknkdc1t'); -// const wallet2 = Wallet.fromSecret('sEd7oiMn5napJBthB2z4CtN5nVi56Bd'); +const wallet1 = Wallet.fromSecret('sEd74fJ432TFE4f5Sy48gLyzknkdc1t'); // r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16 +const wallet2 = Wallet.fromSecret('sEd7oiMn5napJBthB2z4CtN5nVi56Bd'); // r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV const MARKET = 'USD-VND'; -const postedOrderTxn: string[] = []; - -// interface Token { -// currency: string; -// issuer: string; -// value?: string; -// } - -// const base_token: Token = { -// currency: 'USD', -// issuer: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', -// value: '0', -// }; - -// const quote_token: Token = { -// currency: 'VND', -// issuer: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', -// value: '0', -// }; +let postedOrderTxn: string; const INVALID_REQUEST = { chain: 'unknown', @@ -38,12 +20,12 @@ const INVALID_REQUEST = { }; const patchWallet1 = () => { - patch(xrpl, 'getWallet', () => wallet1); -}; + patch(xrpl, 'getWallet', (walletAddress: string) => { + if (walletAddress === 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16') return wallet1; -// const patchWallet2 = () => { -// patch(xrpl, 'getWallet', () => wallet2); -// }; + return wallet2; + }); +}; beforeAll(async () => { xrpl = XRPL.getInstance('testnet'); @@ -143,8 +125,8 @@ describe('Post order', () => { connector: 'xrpl', address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock market: MARKET, - price: '23600', - amount: '1', + price: '20000', + amount: '0.1', side: 'BUY', orderType: 'LIMIT', }) @@ -153,88 +135,30 @@ describe('Post order', () => { .expect(200) .expect((res) => { expect(res.body.txHash).toBeDefined(); - postedOrderTxn.push(res.body.txHash); + postedOrderTxn = res.body.txHash; }); }); it('should return PENDING_OPEN with proper request', async () => { - // wait for 1 second to make sure the order is getting tracked - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const postedOrderSequence = await getsSequenceNumberFromTxn( - 'testnet', - postedOrderTxn[0] + await checkOrderStatus( + postedOrderTxn, + 10, + 'PENDING_OPEN', + 500, + wallet1.address, + true ); - - if (postedOrderSequence === undefined) { - throw new Error('postedOrderSequence is undefined'); - } - - await request(gatewayApp) - .get(`/clob/orders`) - .query({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', - market: MARKET, - orderId: 'all', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) - .expect((res) => { - const orders: Order[] = res.body.orders; - const postedOrder = orders.find( - (order) => order.hash === postedOrderSequence - ); - - if (postedOrder === undefined) { - throw new Error('postedOrder is undefined'); - } - expect(postedOrder.state).toBe('PENDING_OPEN'); - }); }); it('should return OPEN with proper request', async () => { - // wait for 6 second to make sure the order is posted on blockchain - await new Promise((resolve) => setTimeout(resolve, 6000)); - - const postedOrderSequence = await getsSequenceNumberFromTxn( - 'testnet', - postedOrderTxn[0] + await checkOrderStatus( + postedOrderTxn, + 9, + 'OPEN', + 1000, + wallet1.address, + true ); - - if (postedOrderSequence === undefined) { - throw new Error('postedOrderSequence is undefined'); - } - - await request(gatewayApp) - .get(`/clob/orders`) - .query({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', - market: MARKET, - orderId: 'all', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) - .expect((res) => { - const orders: Order[] = res.body.orders; - const postedOrder = orders.find( - (order) => order.hash === postedOrderSequence - ); - - if (postedOrder === undefined) { - throw new Error('postedOrder is undefined'); - } - expect(postedOrder.state).toBe('OPEN'); - }); }); it('should return 404 when parameters are invalid', async () => { @@ -282,7 +206,7 @@ describe('Delete order', () => { it('should return 200 with proper request', async () => { const postedOrderSequence = await getsSequenceNumberFromTxn( 'testnet', - postedOrderTxn[0] + postedOrderTxn ); expect(postedOrderSequence).toBeDefined(); @@ -304,95 +228,217 @@ describe('Delete order', () => { }); it('should return PENDING_CANCEL with proper request', async () => { - // wait for 1 second to make sure the order is getting tracked - await new Promise((resolve) => setTimeout(resolve, 1000)); + await checkOrderStatus( + postedOrderTxn, + 10, + 'PENDING_CANCEL', + 500, + wallet1.address, + true + ); + }); - const postedOrderSequence = await getsSequenceNumberFromTxn( - 'testnet', - postedOrderTxn[0] + it('should return CANCELED with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 9, + 'CANCELED', + 1000, + wallet1.address, + true ); + }); - if (postedOrderSequence === undefined) { - throw new Error('postedOrderSequence is undefined'); - } + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .delete(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); + }); +}); + +// 2nd Senario: +// 1. Post an order +// 2. Get posted order details +// 3. Verify the posted order status is PENDING_OPEN +// 4. Wait for 5 seconds and then verify the posted order status is OPEN +// 5. Use 2nd wallet to create a counter order to consume the posted order partially +// 6. Verify the posted order status is PARTIALLY_FILLED +// 7. Use 2nd wallet to create a counter order to consume the posted order completely +// 8. Verify the posted order status is FILLED +describe('Post order to be consumed', () => { + it('should return 200 with proper request', async () => { await request(gatewayApp) - .get(`/clob/orders`) - .query({ + .post(`/clob/orders`) + .send({ chain: 'xrpl', network: 'testnet', connector: 'xrpl', - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + address: wallet2.address, // noqa: mock market: MARKET, - orderId: postedOrderSequence.toString(), + price: '20000', + amount: '0.1', + side: 'BUY', + orderType: 'LIMIT', }) .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) - .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) .expect((res) => { - const orders: Order[] = res.body.orders; - const postedOrder = orders[0]; - - if (postedOrder === undefined) { - throw new Error('postedOrder is undefined'); - } - expect(postedOrder.state).toBe('PENDING_CANCEL'); + expect(res.body.txHash).toBeDefined(); + postedOrderTxn = res.body.txHash; }); }); - it('should return CANCELED with proper request', async () => { - // wait for 6 second to make sure the order is posted on blockchain - await new Promise((resolve) => setTimeout(resolve, 6000)); + it('should return PENDING_OPEN with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 9, + 'PENDING_OPEN', + 1000, + wallet2.address, + true + ); + }); - const postedOrderSequence = await getsSequenceNumberFromTxn( - 'testnet', - postedOrderTxn[0] + it('should return OPEN with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 9, + 'OPEN', + 1000, + wallet2.address, + true ); + }); - if (postedOrderSequence === undefined) { - throw new Error('postedOrderSequence is undefined'); - } + describe('Consume posted order', () => { + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: wallet1.classicAddress, // noqa: mock + market: MARKET, + price: '19999', + amount: '0.05', + side: 'SELL', + orderType: 'LIMIT', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.txHash).toBeDefined(); + }); + }); + + it('should return PARTIALLY_FILLED with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 9, + 'PARTIALLY_FILLED', + 1000, + wallet2.classicAddress, + true + ); + }); + + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: wallet1.address, // noqa: mock + market: MARKET, + price: '19999', + amount: '0.051', + side: 'SELL', + orderType: 'LIMIT', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.txHash).toBeDefined(); + }); + }); + + it('should return FILLED with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 9, + 'FILLED', + 1000, + wallet2.classicAddress, + true + ); + }); + }); +}); +async function checkOrderStatus( + postedOrderTxn: string, + maxCheckCount: number, + state: string, + requestFrequency: number = 1000, + walletAddress: string, + getAllOrdres: boolean = false +) { + const postedOrderSequence = await getsSequenceNumberFromTxn( + 'testnet', + postedOrderTxn + ); + + if (postedOrderSequence === undefined) { + throw new Error('postedOrderSequence is undefined'); + } + + let hasPassed = false; + let checkCount = 0; + let orderState = ''; + let orders: Order[] = []; + + while (!hasPassed && checkCount < maxCheckCount) { await request(gatewayApp) .get(`/clob/orders`) .query({ chain: 'xrpl', network: 'testnet', connector: 'xrpl', - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + address: walletAddress, market: MARKET, - orderId: postedOrderSequence.toString(), + orderId: getAllOrdres ? 'all' : postedOrderSequence, }) .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) - .expect((res) => expect(res.body.orders.length).toBeGreaterThan(0)) - .expect((res) => { - const orders: Order[] = res.body.orders; - const postedOrder = orders[0]; + .then((res) => { + orders = res.body.orders; + }); + + if (orders.length > 0) { + const postedOrder = orders.find( + (order) => order.hash === postedOrderSequence + ); - if (postedOrder === undefined) { - throw new Error('postedOrder is undefined'); + if (postedOrder !== undefined) { + orderState = postedOrder.state; + if (orderState === state) { + hasPassed = true; } - expect(postedOrder.state).toBe('CANCELED'); - }); - }); + } + } - it('should return 404 when parameters are invalid', async () => { - await request(gatewayApp) - .delete(`/clob/orders`) - .send(INVALID_REQUEST) - .expect(404); - }); -}); + checkCount++; + await new Promise((resolve) => setTimeout(resolve, requestFrequency)); + } -// 2nd Senario: -// 1. Post an order -// 2. Get posted order details -// 3. Verify the posted order status is PENDING_OPEN -// 4. Wait for 5 seconds and then verify the posted order status is OPEN -// 5. Use 2nd wallet to create a counter order to consume the posted order partially -// 6. Verify the posted order status is PARTIALLY_FILLED -// 7. Use 2nd wallet to create a counter order to consume the posted order completely -// 8. Verify the posted order status is FILLED + expect(orderState).toBe(state); + expect(hasPassed).toBe(true); +} From 4547c7e44355475340f8aa306b1def69d5dee549 Mon Sep 17 00:00:00 2001 From: mlguys Date: Fri, 14 Jul 2023 14:05:31 +0700 Subject: [PATCH 19/39] merge development + fix test cases --- package.json | 2 +- src/chains/xrpl/xrpl.order-storage.ts | 13 +------------ src/chains/xrpl/xrpl.order-tracker.ts | 14 -------------- src/connectors/xrpl/xrpl.ts | 1 - test/connectors/xrpl/xrpl.routes.test.ts | 9 +++++++++ 5 files changed, 11 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index deea219090..9801172513 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test:unit": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --verbose ./test/", "test:cov": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/", "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts", - "test:xrpl": "jest -i --verbose test/connectors/xrpl/*.test.ts", + "test:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.routes.test.ts", "test:e2e:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.e2e.test.ts", "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts" }, diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index ab153cb4d7..12d8d94ad5 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -18,10 +18,7 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { try { await this.localStorage.init(); } catch (error) { - console.log( - '🪧 -> file: xrpl.order-storage.ts:22 -> XRPLOrderStorage -> init -> error:', - error - ); + throw new Error('Failed to initialize local storage: ' + error); } } @@ -57,16 +54,8 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { network: string, walletAddress: string ): Promise> { - console.log( - '🪧 -> file: xrpl.order-storage.ts:60 -> XRPLOrderStorage -> walletAddress:', - walletAddress - ); return this.localStorage.get((key: string, value: string) => { const splitKey = key.split('/'); - console.log( - '🪧 -> file: xrpl.order-storage.ts:62 -> XRPLOrderStorage -> returnthis.localStorage.get -> splitKey:', - splitKey - ); if ( splitKey.length === 4 && splitKey[0] === chain && diff --git a/src/chains/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts index 9220de91dc..82921d2fdf 100644 --- a/src/chains/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -226,11 +226,6 @@ export class OrderTracker { // Mark isProcessing as false this._isProcessing = false; - // console.log( - // '🪧 -> file: xrpl.order-tracker.ts:241 -> OrderTracker -> startTracking -> _inflightOrders:', - // this._inflightOrders - // ); - // Wait for next interval await new Promise((resolve) => setTimeout(resolve, this._orderTrackingInterval) @@ -293,10 +288,6 @@ export class OrderTracker { // 3. Process the transactions stack // 4. Update the inflightOrders const ordersToCheck: InflightOrders = openOrders; - console.log( - '🪧 -> file: xrpl.order-tracker.ts:296 -> OrderTracker -> BEFORE ordersToCheck:', - ordersToCheck - ); // 1. Get the minLedgerIndex from the inflightOrders const hashes = Object.keys(ordersToCheck); @@ -345,11 +336,6 @@ export class OrderTracker { }); } - console.log( - '🪧 -> file: xrpl.order-tracker.ts:296 -> OrderTracker -> AFTER ordersToCheck:', - ordersToCheck - ); - return ordersToCheck; } diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index 47fa82022b..cd8746c252 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -555,6 +555,5 @@ export class XRPLCLOB implements CLOBish { ); await orderTracker.addOrder(order); - // orderTracker.startTracking(); } } diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index 59ab9cf412..78cb6499a7 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -2,6 +2,7 @@ import request from 'supertest'; import { gatewayApp } from '../../../src/app'; import { XRPL } from '../../../src/chains/xrpl/xrpl'; import { XRPLCLOB } from '../../../src/connectors/xrpl/xrpl'; +import {} from '../../../src/chains/xrpl/xrpl.order-tracker'; import { Order } from '../../../src/connectors/xrpl/xrpl.types'; import { patch, unpatch } from '../../services/patch'; @@ -330,6 +331,12 @@ const patchOrderTracking = () => { }); }; +const patchGetOrder = () => { + patch(xrplCLOB, 'getOrder', () => { + return undefined; + }); +}; + const patchCurrentBlockNumber = (withError: boolean = false) => { patch(xrplCLOB, 'getCurrentBlockNumber', () => { return withError ? -1 : 100; @@ -558,6 +565,7 @@ describe('DELETE /clob/orders', () => { it('should return 200 with proper request', async () => { patchSubmitTxn(); patchGetWallet(); + patchGetOrder(); await request(gatewayApp) .delete(`/clob/orders`) .send({ @@ -585,6 +593,7 @@ describe('DELETE /clob/orders', () => { describe('GET /clob/estimateGas', () => { it('should return 200 with proper request', async () => { patchGasPrices(); + patchGetOrder(); await request(gatewayApp) .get(`/clob/estimateGas`) .query({ From d7844c82d47b1f7664076d2fe4e599c2a6f9553b Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 27 Jul 2023 01:38:23 +0700 Subject: [PATCH 20/39] Add tracking fills --- src/chains/xrpl/xrpl.order-tracker.ts | 265 +++++++++++++++++++++++++- src/connectors/xrpl/xrpl.ts | 2 + src/connectors/xrpl/xrpl.types.ts | 14 ++ 3 files changed, 279 insertions(+), 2 deletions(-) diff --git a/src/chains/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts index 82921d2fdf..7de7e5b767 100644 --- a/src/chains/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -22,6 +22,8 @@ import { ResponseOnlyTxInfo, CreatedNode, ModifiedNode, + DeletedNode, + FillData, } from '../../connectors/xrpl/xrpl.types'; import { OrderMutexManager } from '../../connectors/xrpl/xrpl.utils'; import { @@ -397,8 +399,10 @@ export class OrderTracker { } let filledAmount = 0.0; - let node: CreatedNode | ModifiedNode; + let node: CreatedNode | ModifiedNode | DeletedNode | undefined; let fields: any; + let foundTradeIndex = 0; + let fillData: FillData; switch (intent.type) { case TransactionIntentType.OFFER_CREATE_FINALIZED: @@ -455,6 +459,42 @@ export class OrderTracker { } } + // add intent as trade date + foundTradeIndex = matchOrder.associatedFills.findIndex((fill) => { + if (node === undefined) { + return ( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + fill.id === `${intent.type}-${transaction.transaction.hash!}` + ); + } + + if (isCreatedNode(node)) { + return ( + fill.id === `${intent.type}-${node.CreatedNode.LedgerIndex}` + ); + } + + if (isModifiedNode(node)) { + return ( + fill.id === `${intent.type}-${node.ModifiedNode.LedgerIndex}` + ); + } + + return false; + }); + + if (foundTradeIndex === -1) { + fillData = this.buildFillData( + intent, + node, + matchOrder, + transaction + ); + matchOrder.associatedFills.push(fillData); + } else { + // console.log('FillData already found!'); + } + // console.log('Filled amount: ', filledAmount); matchOrder.filledAmount = filledAmount.toString(); break; @@ -462,6 +502,45 @@ export class OrderTracker { case TransactionIntentType.OFFER_FILL: matchOrder.state = OrderStatus.FILLED; matchOrder.filledAmount = matchOrder.amount; + + // add intent as trade date + node = intent.node as CreatedNode | DeletedNode | undefined; + + foundTradeIndex = matchOrder.associatedFills.findIndex((fill) => { + if (node === undefined) { + return ( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + fill.id === `${intent.type}-${transaction.transaction.hash!}` + ); + } + + if (isCreatedNode(node)) { + return ( + fill.id === `${intent.type}-${node.CreatedNode.LedgerIndex}` + ); + } + + if (isDeletedNode(node)) { + return ( + fill.id === `${intent.type}-${node.DeletedNode.LedgerIndex}` + ); + } + + return false; + }); + + if (foundTradeIndex === -1) { + fillData = this.buildFillData( + intent, + node, + matchOrder, + transaction + ); + + matchOrder.associatedFills.push(fillData); + } else { + // console.log('FillData already found!'); + } break; case TransactionIntentType.OFFER_EXPIRED_OR_UNFUNDED: @@ -614,7 +693,10 @@ export class OrderTracker { } else { const finalFields = affnode.DeletedNode.FinalFields as any; - if (finalFields.Account === walletAddress) { + if ( + transaction.transaction.Account === walletAddress && + finalFields.Account === walletAddress + ) { const replacingOfferSequnce = transaction.transaction.OfferSequence; @@ -795,4 +877,183 @@ export class OrderTracker { return transformedTx; } + + buildFillData( + intent: TransactionIntent, + node: CreatedNode | ModifiedNode | DeletedNode | undefined, + order: Order, + transaction: TransactionStream | TransaformedAccountTransaction + ): FillData { + let id: string; + + if (node === undefined) { + return { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + id: `${intent.type}-${transaction.transaction.hash!}`, + price: order.price, + quantity: order.amount, + fee_token: 'XRP', + fee: this.getFeeFromTransaction(transaction).toString(), + timestamp: transaction.transaction.date + ? rippleTimeToUnixTime(transaction.transaction.date) + : 0, + type: 'Taker', + }; + } + + if (isModifiedNode(node)) { + id = `${intent.type}-${node.ModifiedNode.LedgerIndex}`; + } else if (isDeletedNode(node)) { + id = `${intent.type}-${node.DeletedNode.LedgerIndex}`; + } else { + id = `${intent.type}-${node.CreatedNode.LedgerIndex}`; + } + + const price = order.price; + const quantity = this.getFilledAmountFromNode( + order, + node, + order.tradeType + ).toString(); + const fee_token = 'XRP'; + const fee = this.getFeeFromTransaction(transaction).toString(); + const timestamp = transaction.transaction.date + ? rippleTimeToUnixTime(transaction.transaction.date) + : 0; + const type = 'Maker'; + + return { + id, + price, + quantity, + fee_token, + fee, + timestamp, + type, + }; + } + + getFeeFromTransaction( + transaction: TransactionStream | TransaformedAccountTransaction + ): string { + let fee = '0'; + + if (transaction.meta === undefined) { + return fee; + } + + transaction.meta.AffectedNodes.forEach((node) => { + if (isModifiedNode(node)) { + if (node.ModifiedNode.LedgerEntryType === 'AccountRoot') { + try { + const previousFields = node.ModifiedNode.PreviousFields as any; + const finalFields = node.ModifiedNode.FinalFields as any; + + if ( + previousFields.Balance !== undefined && + finalFields.Balance !== undefined + ) { + fee = dropsToXrp( + parseInt(previousFields.Balance) - parseInt(finalFields.Balance) + ); + } + } catch (error) { + throw new Error( + 'Error parsing fee from transaction: ' + transaction + ); + } + } + } + }); + + return fee; + } + + getFilledAmountFromNode( + order: Order, + node: CreatedNode | ModifiedNode | DeletedNode, + tradeType: string + ): number { + let filledAmount = 0; + let ledgerEntryType = ''; + let previousFields: any, finalFields: any; + + try { + if (isModifiedNode(node)) { + ledgerEntryType = node.ModifiedNode.LedgerEntryType; + previousFields = node.ModifiedNode.PreviousFields; + finalFields = node.ModifiedNode.FinalFields; + } else if (isDeletedNode(node)) { + ledgerEntryType = node.DeletedNode.LedgerEntryType; + previousFields = node.DeletedNode.PreviousFields; + finalFields = node.DeletedNode.FinalFields; + } else { + ledgerEntryType = node.CreatedNode.LedgerEntryType; + previousFields = undefined; + finalFields = node.CreatedNode.NewFields; + } + } catch (error) { + throw new Error('Error parsing node: ' + error); + } + + if (ledgerEntryType === 'Offer' && isCreatedNode(node)) { + if (finalFields === undefined) throw new Error('Final fields undefined'); + + if (tradeType === 'SELL') { + if (typeof finalFields.TakerGets === 'string') { + filledAmount = + parseFloat(order.amount) - + parseFloat(dropsToXrp(finalFields.TakerGets as string)); + } else { + filledAmount = + parseFloat(order.amount) - + parseFloat(finalFields.TakerGets.value as string); + } + } + + if (tradeType === 'BUY') { + if (typeof finalFields.TakerPays === 'string') { + filledAmount = + parseFloat(order.amount) - + parseFloat(dropsToXrp(finalFields.TakerPays as string)); + } else { + filledAmount = + parseFloat(order.amount) - + parseFloat(finalFields.TakerPays.value as string); + } + } + } else if (ledgerEntryType === 'Offer') { + if (previousFields === undefined) + throw new Error('Previous fields undefined'); + if (finalFields === undefined) throw new Error('Final fields undefined'); + + if (tradeType === 'SELL') { + if (typeof finalFields.TakerGets === 'string') { + filledAmount = + parseFloat(dropsToXrp(previousFields.TakerGets as string)) - + parseFloat(dropsToXrp(finalFields.TakerGets as string)); + } else { + filledAmount = + parseFloat(previousFields.TakerGets.value as string) - + parseFloat(finalFields.TakerGets.value as string); + } + } + + if (tradeType === 'BUY') { + if (typeof finalFields.TakerPays === 'string') { + filledAmount = + parseFloat(dropsToXrp(previousFields.TakerPays as string)) - + parseFloat(dropsToXrp(finalFields.TakerPays as string)); + } else { + filledAmount = + parseFloat(previousFields.TakerPays.value as string) - + parseFloat(finalFields.TakerPays.value as string); + } + } + } else { + throw new Error('Invalid ledgerEntryType type'); + } + + return filledAmount; + } } diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index cd8746c252..c191d1100e 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -430,6 +430,7 @@ export class XRPLCLOB implements CLOBish { updatedAt: currentTime, updatedAtLedgerIndex: currentLedgerIndex, associatedTxns: [signed.hash], + associatedFills: [], }; await this.trackOrder(wallet, order); @@ -471,6 +472,7 @@ export class XRPLCLOB implements CLOBish { updatedAt: Date.now(), updatedAtLedgerIndex: await this.getCurrentBlockNumber(), associatedTxns: [signed.hash], + associatedFills: [], }; } diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index f4fd7b8c15..c254a69260 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -236,6 +236,7 @@ export interface Order { updatedAt: number; updatedAtLedgerIndex: number; associatedTxns: string[]; + associatedFills: FillData[]; } export interface InflightOrders { @@ -277,6 +278,16 @@ export interface TransactionIntent { node?: Node; } +export interface FillData { + id: string; + price: string; + quantity: string; + fee_token: string; + fee: string; + timestamp: number; + type: string; +} + type Node = CreatedNode | ModifiedNode | DeletedNode; export interface CreatedNode { @@ -309,5 +320,8 @@ export interface DeletedNode { FinalFields: { [field: string]: unknown; }; + PreviousFields?: { + [field: string]: unknown; + }; }; } From edc2d9cbb62affaeadcb7485a38f95e8e157e31e Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 27 Jul 2023 13:49:08 +0700 Subject: [PATCH 21/39] add orderhash to filldata --- src/chains/xrpl/xrpl.order-tracker.ts | 42 +++++++++++++++++---------- src/connectors/xrpl/xrpl.types.ts | 6 ++-- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/chains/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts index 7de7e5b767..aab404d805 100644 --- a/src/chains/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -463,20 +463,23 @@ export class OrderTracker { foundTradeIndex = matchOrder.associatedFills.findIndex((fill) => { if (node === undefined) { return ( + fill.tradeId === // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - fill.id === `${intent.type}-${transaction.transaction.hash!}` + `${intent.type}-${transaction.transaction.hash!}` ); } if (isCreatedNode(node)) { return ( - fill.id === `${intent.type}-${node.CreatedNode.LedgerIndex}` + fill.tradeId === + `${intent.type}-${node.CreatedNode.LedgerIndex}` ); } if (isModifiedNode(node)) { return ( - fill.id === `${intent.type}-${node.ModifiedNode.LedgerIndex}` + fill.tradeId === + `${intent.type}-${node.ModifiedNode.LedgerIndex}` ); } @@ -509,20 +512,23 @@ export class OrderTracker { foundTradeIndex = matchOrder.associatedFills.findIndex((fill) => { if (node === undefined) { return ( + fill.tradeId === // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - fill.id === `${intent.type}-${transaction.transaction.hash!}` + `${intent.type}-${transaction.transaction.hash!}` ); } if (isCreatedNode(node)) { return ( - fill.id === `${intent.type}-${node.CreatedNode.LedgerIndex}` + fill.tradeId === + `${intent.type}-${node.CreatedNode.LedgerIndex}` ); } if (isDeletedNode(node)) { return ( - fill.id === `${intent.type}-${node.DeletedNode.LedgerIndex}` + fill.tradeId === + `${intent.type}-${node.DeletedNode.LedgerIndex}` ); } @@ -884,15 +890,18 @@ export class OrderTracker { order: Order, transaction: TransactionStream | TransaformedAccountTransaction ): FillData { - let id: string; + let tradeId: string; + const orderHash: number = order.hash; if (node === undefined) { return { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - id: `${intent.type}-${transaction.transaction.hash!}`, + tradeId: `${intent.type}-${transaction.transaction.hash!}`, + orderHash: order.hash, price: order.price, quantity: order.amount, - fee_token: 'XRP', + feeToken: 'XRP', + side: order.tradeType, fee: this.getFeeFromTransaction(transaction).toString(), timestamp: transaction.transaction.date ? rippleTimeToUnixTime(transaction.transaction.date) @@ -902,11 +911,11 @@ export class OrderTracker { } if (isModifiedNode(node)) { - id = `${intent.type}-${node.ModifiedNode.LedgerIndex}`; + tradeId = `${intent.type}-${node.ModifiedNode.LedgerIndex}`; } else if (isDeletedNode(node)) { - id = `${intent.type}-${node.DeletedNode.LedgerIndex}`; + tradeId = `${intent.type}-${node.DeletedNode.LedgerIndex}`; } else { - id = `${intent.type}-${node.CreatedNode.LedgerIndex}`; + tradeId = `${intent.type}-${node.CreatedNode.LedgerIndex}`; } const price = order.price; @@ -915,7 +924,8 @@ export class OrderTracker { node, order.tradeType ).toString(); - const fee_token = 'XRP'; + const feeToken = 'XRP'; + const side = order.tradeType; const fee = this.getFeeFromTransaction(transaction).toString(); const timestamp = transaction.transaction.date ? rippleTimeToUnixTime(transaction.transaction.date) @@ -923,10 +933,12 @@ export class OrderTracker { const type = 'Maker'; return { - id, + tradeId, + orderHash, price, quantity, - fee_token, + feeToken, + side, fee, timestamp, type, diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index c254a69260..5b8ac427a3 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -279,10 +279,12 @@ export interface TransactionIntent { } export interface FillData { - id: string; + tradeId: string; + orderHash: number; price: string; quantity: string; - fee_token: string; + feeToken: string; + side: string; fee: string; timestamp: number; type: string; From 1737d2491d326538cbea99f0c65a2b1512fc7949 Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 1 Aug 2023 18:03:45 +0700 Subject: [PATCH 22/39] fix balances chain endpoint for xrpl --- package.json | 3 +- src/chains/xrpl/xrpl.controllers.ts | 98 +++++++------ src/chains/xrpl/xrpl.requests.ts | 1 + src/chains/xrpl/xrpl.routes.ts | 133 ++++++++--------- src/chains/xrpl/xrpl.ts | 4 + src/chains/xrpl/xrpl.validators.ts | 2 - test/chains/xrpl/xrpl.routes.test.ts | 79 ++++++++++ test/chains/xrpl/xrpl.validator.test.ts | 187 ++++++++++++++++++++++++ 8 files changed, 397 insertions(+), 110 deletions(-) create mode 100644 test/chains/xrpl/xrpl.routes.test.ts create mode 100644 test/chains/xrpl/xrpl.validator.test.ts diff --git a/package.json b/package.json index 9801172513..c052dde938 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts", "test:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.routes.test.ts", "test:e2e:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.e2e.test.ts", - "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts" + "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts", + "test:chain:xrpl": "jest -i --verbose test/chains/xrpl/*.test.ts" }, "dependencies": { "@cosmjs/proto-signing": "^0.28.10", diff --git a/src/chains/xrpl/xrpl.controllers.ts b/src/chains/xrpl/xrpl.controllers.ts index 7f33ad83ae..fdd00b00bc 100644 --- a/src/chains/xrpl/xrpl.controllers.ts +++ b/src/chains/xrpl/xrpl.controllers.ts @@ -15,51 +15,67 @@ import { XRPLPollResponse, } from './xrpl.requests'; -export async function balances( - xrplish: XRPLish, - req: XRPLBalanceRequest -): Promise { - const initTime = Date.now(); - let wallet: Wallet; +import { + validateXRPLBalanceRequest, + validateXRPLPollRequest, +} from './xrpl.validators'; - try { - wallet = await xrplish.getWallet(req.address); - } catch (err) { - throw new HttpException( - 500, - LOAD_WALLET_ERROR_MESSAGE + err, - LOAD_WALLET_ERROR_CODE - ); +export class XRPLController { + static async currentBlockNumber(xrplish: XRPLish): Promise { + return xrplish.getCurrentLedgerIndex(); } - const balances = await xrplish.getAllBalance(wallet); + static async balances( + xrplish: XRPLish, + req: XRPLBalanceRequest + ): Promise { + const initTime = Date.now(); + let wallet: Wallet; - return { - network: xrplish.network, - timestamp: initTime, - latency: latency(initTime, Date.now()), - balances, - }; -} + validateXRPLBalanceRequest(req); + + try { + wallet = await xrplish.getWallet(req.address); + } catch (err) { + throw new HttpException( + 500, + LOAD_WALLET_ERROR_MESSAGE + err, + LOAD_WALLET_ERROR_CODE + ); + } + + const balances = await xrplish.getAllBalance(wallet); -export async function poll( - xrplish: XRPLish, - req: XRPLPollRequest -): Promise { - const initTime = Date.now(); - const currentLedgerIndex = await xrplish.getCurrentLedgerIndex(); - const txData = getNotNullOrThrowError( - await xrplish.getTransaction(req.txHash) - ); - const txStatus = await xrplish.getTransactionStatusCode(txData); + return { + network: xrplish.network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + address: req.address, + balances, + }; + } + + static async poll( + xrplish: XRPLish, + req: XRPLPollRequest + ): Promise { + validateXRPLPollRequest(req); - return { - network: xrplish.network, - timestamp: initTime, - currentLedgerIndex: currentLedgerIndex, - txHash: req.txHash, - txStatus: txStatus, - txLedgerIndex: txData.result.ledger_index, - txData: getNotNullOrThrowError(txData), - }; + const initTime = Date.now(); + const currentLedgerIndex = await xrplish.getCurrentLedgerIndex(); + const txData = getNotNullOrThrowError( + await xrplish.getTransaction(req.txHash) + ); + const txStatus = await xrplish.getTransactionStatusCode(txData); + + return { + network: xrplish.network, + timestamp: initTime, + currentLedgerIndex: currentLedgerIndex, + txHash: req.txHash, + txStatus: txStatus, + txLedgerIndex: txData.result.ledger_index, + txData: getNotNullOrThrowError(txData), + }; + } } diff --git a/src/chains/xrpl/xrpl.requests.ts b/src/chains/xrpl/xrpl.requests.ts index c69112f56e..efeb5da274 100644 --- a/src/chains/xrpl/xrpl.requests.ts +++ b/src/chains/xrpl/xrpl.requests.ts @@ -10,6 +10,7 @@ export interface XRPLBalanceResponse { network: string; timestamp: number; latency: number; + address: string; balances: Record; } diff --git a/src/chains/xrpl/xrpl.routes.ts b/src/chains/xrpl/xrpl.routes.ts index e3664847fa..8ef5ecd0ab 100644 --- a/src/chains/xrpl/xrpl.routes.ts +++ b/src/chains/xrpl/xrpl.routes.ts @@ -1,77 +1,78 @@ -import { NextFunction, Request, Response, Router } from 'express'; -import { ParamsDictionary } from 'express-serve-static-core'; -import { XRPL } from './xrpl'; -import { verifyXRPLIsAvailable } from './xrpl-middlewares'; -import { asyncHandler } from '../../services/error-handler'; -import { balances, poll } from './xrpl.controllers'; -import { - XRPLBalanceRequest, - XRPLBalanceResponse, - XRPLPollRequest, - XRPLPollResponse, -} from './xrpl.requests'; -import { - validateXRPLBalanceRequest, - validateXRPLPollRequest, -} from './xrpl.validators'; +// TODO: Deprecated, remove soon +// import { NextFunction, Request, Response, Router } from 'express'; +// import { ParamsDictionary } from 'express-serve-static-core'; +// import { XRPL } from './xrpl'; +// import { verifyXRPLIsAvailable } from './xrpl-middlewares'; +// import { asyncHandler } from '../../services/error-handler'; +// import { balances, poll } from './xrpl.controllers'; +// import { +// XRPLBalanceRequest, +// XRPLBalanceResponse, +// XRPLPollRequest, +// XRPLPollResponse, +// } from './xrpl.requests'; +// import { +// validateXRPLBalanceRequest, +// validateXRPLPollRequest, +// } from './xrpl.validators'; -export namespace XRPLRoutes { - export const router = Router(); +// export namespace XRPLRoutes { +// export const router = Router(); - export const getXRPL = async (request: Request) => { - const xrpl = await XRPL.getInstance(request.body.network); - await xrpl.init(); +// export const getXRPL = async (request: Request) => { +// const xrpl = await XRPL.getInstance(request.body.network); +// await xrpl.init(); - return xrpl; - }; +// return xrpl; +// }; - router.use(asyncHandler(verifyXRPLIsAvailable)); +// router.use(asyncHandler(verifyXRPLIsAvailable)); - router.get( - '/', - asyncHandler(async (request: Request, response: Response) => { - const xrpl = await getXRPL(request); +// router.get( +// '/', +// asyncHandler(async (request: Request, response: Response) => { +// const xrpl = await getXRPL(request); - const rpcUrl = xrpl.rpcUrl; +// const rpcUrl = xrpl.rpcUrl; - response.status(200).json({ - network: xrpl.network, - rpcUrl: rpcUrl, - connection: true, - timestamp: Date.now(), - }); - }) - ); +// response.status(200).json({ +// network: xrpl.network, +// rpcUrl: rpcUrl, +// connection: true, +// timestamp: Date.now(), +// }); +// }) +// ); - router.get( - '/balances', - asyncHandler( - async ( - request: Request, - response: Response, - _next: NextFunction - ) => { - const xrpl = await getXRPL(request); +// router.get( +// '/balances', +// asyncHandler( +// async ( +// request: Request, +// response: Response, +// _next: NextFunction +// ) => { +// const xrpl = await getXRPL(request); - validateXRPLBalanceRequest(request.body); - response.status(200).json(await balances(xrpl, request.body)); - } - ) - ); +// validateXRPLBalanceRequest(request.body); +// response.status(200).json(await balances(xrpl, request.body)); +// } +// ) +// ); - // TODO: change this to GET - router.get( - '/poll', - asyncHandler( - async ( - request: Request, - response: Response - ) => { - const xrpl = await getXRPL(request); +// // TODO: change this to GET +// router.get( +// '/poll', +// asyncHandler( +// async ( +// request: Request, +// response: Response +// ) => { +// const xrpl = await getXRPL(request); - validateXRPLPollRequest(request.body); - response.status(200).json(await poll(xrpl, request.body)); - } - ) - ); -} +// validateXRPLPollRequest(request.body); +// response.status(200).json(await poll(xrpl, request.body)); +// } +// ) +// ); +// } diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index a3f638d216..91c4bb2f70 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -24,6 +24,7 @@ import { TransactionResponseStatusCode } from './xrpl.requests'; import { XRPLOrderStorage } from './xrpl.order-storage'; import { OrderTracker } from './xrpl.order-tracker'; import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; +import { XRPLController } from './xrpl.controllers'; export type TokenInfo = { id: number; @@ -83,6 +84,8 @@ export class XRPL implements XRPLish { private readonly _refCountingHandle: string; private readonly _orderStorage: XRPLOrderStorage; + public controller: typeof XRPLController; + private constructor(network: string) { const config = getXRPLConfig('xrpl', network); @@ -121,6 +124,7 @@ export class XRPL implements XRPLish { this._refCountingHandle ); this._orderStorage.declareOwnership(this._refCountingHandle); + this.controller = XRPLController; } public static getInstance(network: string): XRPL { diff --git a/src/chains/xrpl/xrpl.validators.ts b/src/chains/xrpl/xrpl.validators.ts index acefd6c257..6a0abb614a 100644 --- a/src/chains/xrpl/xrpl.validators.ts +++ b/src/chains/xrpl/xrpl.validators.ts @@ -1,5 +1,4 @@ import { - validateTokenSymbols, mkValidator, mkRequestValidator, RequestValidator, @@ -37,7 +36,6 @@ export const validateXRPLAddress: Validator = mkValidator( export const validateXRPLBalanceRequest: RequestValidator = mkRequestValidator([ validateXRPLAddress, - validateTokenSymbols, ]); export const validateXRPLPollRequest: RequestValidator = mkRequestValidator([ diff --git a/test/chains/xrpl/xrpl.routes.test.ts b/test/chains/xrpl/xrpl.routes.test.ts new file mode 100644 index 0000000000..1767efe93c --- /dev/null +++ b/test/chains/xrpl/xrpl.routes.test.ts @@ -0,0 +1,79 @@ +import request from 'supertest'; +import { XRPL } from '../../../src/chains/xrpl/xrpl'; +import { patch, unpatch } from '../../services/patch'; +import { gatewayApp } from '../../../src/app'; +import { Wallet } from 'xrpl'; + +let xrplChain: XRPL; + +const wallet1 = Wallet.fromSecret('sEd74fJ432TFE4f5Sy48gLyzknkdc1t'); // r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16 +const wallet2 = Wallet.fromSecret('sEd7oiMn5napJBthB2z4CtN5nVi56Bd'); // r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV + +const patchWallet = () => { + patch(xrplChain, 'getWallet', (walletAddress: string) => { + if (walletAddress === 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16') return wallet1; + + return wallet2; + }); +}; + +beforeAll(async () => { + xrplChain = XRPL.getInstance('testnet'); + await xrplChain.init(); +}); + +afterAll(async () => { + unpatch(); + await xrplChain.close(); +}); + +describe('POST /chain/balances', () => { + it('should return 200 with correct parameters', async () => { + patchWallet(); + await request(gatewayApp) + .post(`/chain/balances`) + .send({ + chain: 'xrpl', + network: 'testnet', + address: 'r3z4R6KQWfwRf9G15AhUZe2GN67Sj6PYNV', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body).toBeDefined(); + }); + }); + + it('should return 404 when parameters are invalid/incomplete', async () => { + unpatch(); + await request(gatewayApp) + .post(`/chain/balances`) + .send({ + chain: 'xrpl', + network: 'testnet', + }) + .expect(404); + }); +}); + +describe('POST /chain/poll', () => { + it('should return 200 with correct parameters', async () => { + const res = await request(gatewayApp).post('/chain/poll').send({ + chain: 'xrpl', + network: 'testnet', + txHash: + '61DD63760B94102E929BBC2EF0954E513EC41CCAF57619E1B079E7AA48B4F889', // noqa: mock + }); + expect(res.statusCode).toEqual(200); + }); + + it('should get unknown error with invalid txHash', async () => { + const res = await request(gatewayApp).post('/chain/poll').send({ + chain: 'xrpl', + network: 'testnet', + txHash: 123, + }); + expect(res.statusCode).toEqual(404); + }); +}); diff --git a/test/chains/xrpl/xrpl.validator.test.ts b/test/chains/xrpl/xrpl.validator.test.ts new file mode 100644 index 0000000000..f1832fc5dd --- /dev/null +++ b/test/chains/xrpl/xrpl.validator.test.ts @@ -0,0 +1,187 @@ +import { + validateXRPLAddress, + validateXRPLBalanceRequest, + validateXRPLPollRequest, + validateXRPLGetTokenRequest, + validateXRPLPostTokenRequest, + invalidXRPLAddressError, +} from '../../../src/chains/xrpl/xrpl.validators'; +import { HttpException } from '../../../src/services/error-handler'; + +import { + invalidTxHashError, + invalidTokenError, +} from '../../../src/services/validators'; + +import { missingParameter } from '../../../src/services/validators'; + +import 'jest-extended'; + +describe('validateXRPLPollRequest', () => { + it('valid when req.txHash is a txHash', () => { + expect( + validateXRPLPollRequest({ + txHash: + '92EE240C1C31E50AAA7E3C00A6280A4BE52E65B5A8A4C1B4A6FEF9E170B14D0F', // noqa: mock + }) + ).toEqual(undefined); + }); + + it('return error when req.txHash does not exist', () => { + try { + validateXRPLPollRequest({ + hello: 'world', + }); + } catch (error) { + expect((error as HttpException).message).toEqual( + missingParameter('txHash') + ); + } + }); + + it('return error when req.txHash is invalid', () => { + try { + validateXRPLPollRequest({ + txHash: 123, + }); + } catch (error) { + expect((error as HttpException).message).toEqual(invalidTxHashError); + } + }); +}); + +describe('validateAddress', () => { + it('valid when req.address is a address', () => { + expect( + validateXRPLAddress({ + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + }) + ).toEqual([]); + }); + + it('return error when req.address does not exist', () => { + expect( + validateXRPLAddress({ + hello: 'world', + }) + ).toEqual([missingParameter('address')]); + }); + + it('return error when req.address is invalid', () => { + expect( + validateXRPLAddress({ + address: 123, + }) + ).toEqual([invalidXRPLAddressError]); + }); +}); + +describe('validateXRPLBalanceRequest', () => { + it('valid when req.token is a token and address is a valid address', () => { + expect( + validateXRPLBalanceRequest({ + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + }) + ).toEqual(undefined); + }); + + it('return error when req.address is invalid', () => { + try { + validateXRPLBalanceRequest({ + address: 123, + }); + } catch (error) { + expect((error as HttpException).message).toEqual(invalidXRPLAddressError); + } + }); +}); + +describe('validateXRPLGetTokenRequest', () => { + it('valid when req.token is a token and address is a valid address', () => { + expect( + validateXRPLGetTokenRequest({ + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + token: 'XRP', + }) + ).toEqual(undefined); + }); + + it('return error when req.token and req.address does not exist', () => { + try { + validateXRPLGetTokenRequest({ + hello: 'world', + }); + } catch (error) { + expect((error as HttpException).message).toEqual( + [missingParameter('token'), missingParameter('address')].join(', ') + ); + } + }); + + it('return error when req.token is invalid', () => { + try { + validateXRPLGetTokenRequest({ + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + token: 123, + }); + } catch (error) { + expect((error as HttpException).message).toEqual(invalidTokenError); + } + }); + + it('return error when req.address is invalid', () => { + try { + validateXRPLGetTokenRequest({ + address: 123, + token: `XRP`, + }); + } catch (error) { + expect((error as HttpException).message).toEqual(invalidXRPLAddressError); + } + }); +}); + +describe('validateXRPLPostTokenRequest', () => { + it('valid when req.token is a token and address is a valid address', () => { + expect( + validateXRPLPostTokenRequest({ + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + token: 'XRP', + }) + ).toEqual(undefined); + }); + + it('return error when req.token and req.address does not exist', () => { + try { + validateXRPLPostTokenRequest({ + hello: 'world', + }); + } catch (error) { + expect((error as HttpException).message).toEqual( + [missingParameter('token'), missingParameter('address')].join(', ') + ); + } + }); + + it('return error when req.token is invalid', () => { + try { + validateXRPLPostTokenRequest({ + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', + token: 123, + }); + } catch (error) { + expect((error as HttpException).message).toEqual(invalidTokenError); + } + }); + + it('return error when req.address is invalid', () => { + try { + validateXRPLPostTokenRequest({ + address: 123, + token: `XRP`, + }); + } catch (error) { + expect((error as HttpException).message).toEqual(invalidXRPLAddressError); + } + }); +}); From 32d96ea5301186e45bd89a8a8a168070823cc3ac Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 1 Aug 2023 23:44:38 +0700 Subject: [PATCH 23/39] update getTokens endpoint --- src/chains/xrpl/xrpl.controllers.ts | 23 ++++++++- src/chains/xrpl/xrpl.requests.ts | 8 ++- src/chains/xrpl/xrpl.ts | 28 +++++------ src/chains/xrpl/xrpl.validators.ts | 7 +-- test/chains/xrpl/xrpl.validator.test.ts | 66 +++++-------------------- 5 files changed, 57 insertions(+), 75 deletions(-) diff --git a/src/chains/xrpl/xrpl.controllers.ts b/src/chains/xrpl/xrpl.controllers.ts index fdd00b00bc..46e2c5e474 100644 --- a/src/chains/xrpl/xrpl.controllers.ts +++ b/src/chains/xrpl/xrpl.controllers.ts @@ -1,5 +1,5 @@ import { Wallet, TxResponse } from 'xrpl'; -import { XRPLish } from './xrpl'; +import { TokenInfo, XRPLish } from './xrpl'; import { latency } from '../../services/base'; import { HttpException, @@ -18,7 +18,9 @@ import { import { validateXRPLBalanceRequest, validateXRPLPollRequest, + validateXRPLGetTokenRequest, } from './xrpl.validators'; +import { TokensRequest } from '../../network/network.requests'; export class XRPLController { static async currentBlockNumber(xrplish: XRPLish): Promise { @@ -78,4 +80,23 @@ export class XRPLController { txData: getNotNullOrThrowError(txData), }; } + + static async getTokens(xrplish: XRPLish, req: TokensRequest) { + validateXRPLGetTokenRequest(req); + let tokens: TokenInfo[] = []; + if (req.tokenSymbols?.length === 0) { + tokens = xrplish.storedTokenList; + } else { + for (const t of req.tokenSymbols as []) { + const arr = xrplish.getTokenForSymbol(t); + if (arr !== undefined) { + arr.forEach((token) => { + tokens.push(token); + }); + } + } + } + + return { tokens }; + } } diff --git a/src/chains/xrpl/xrpl.requests.ts b/src/chains/xrpl/xrpl.requests.ts index efeb5da274..77fc7a6a79 100644 --- a/src/chains/xrpl/xrpl.requests.ts +++ b/src/chains/xrpl/xrpl.requests.ts @@ -11,9 +11,15 @@ export interface XRPLBalanceResponse { timestamp: number; latency: number; address: string; - balances: Record; + balances: Array; } +export type TokenBalance = { + currency: string; + issuer?: string; + value: string; +}; + export interface XRPLTokenRequest extends NetworkSelectionRequest { address: string; // the user's Solana address as Base58 token: string; // the token symbol the spender will be approved for diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 91c4bb2f70..67ad5d8a46 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -20,7 +20,7 @@ import { TokenListType, walletPath, MarketListType } from '../../services/base'; import { ConfigManagerCertPassphrase } from '../../services/config-manager-cert-passphrase'; import { getXRPLConfig } from './xrpl.config'; // import { logger } from '../../services/logger'; -import { TransactionResponseStatusCode } from './xrpl.requests'; +import { TransactionResponseStatusCode, TokenBalance } from './xrpl.requests'; import { XRPLOrderStorage } from './xrpl.order-storage'; import { OrderTracker } from './xrpl.order-tracker'; import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; @@ -44,12 +44,6 @@ export type MarketInfo = { quoteTokenID: number; }; -export type TokenBalance = { - currency: string; - issuer?: string; - value: string; -}; - export type Fee = { base: string; median: string; @@ -276,8 +270,8 @@ export class XRPL implements XRPLish { return this.marketList; } - public getTokenForSymbol(code: string): TokenInfo[] | null { - return this._tokenMap[code] ? this._tokenMap[code] : null; + public getTokenForSymbol(code: string): TokenInfo[] | undefined { + return this._tokenMap[code] ? this._tokenMap[code] : undefined; } public getWalletFromSeed(seed: string): Wallet { @@ -346,17 +340,23 @@ export class XRPL implements XRPLish { return balance; } - async getAllBalance(wallet: Wallet): Promise> { + async getAllBalance(wallet: Wallet): Promise> { await this.ensureConnection(); - const balances: Record = {}; + const balances: Array = []; const respBalances = await this._client.getBalances(wallet.address); respBalances.forEach((token) => { if (token.currency === 'XRP') { - balances[token.currency] = token.value; + balances.push({ + currency: token.currency, + value: token.value, + }); } else { - const symbol = token.currency + '.' + token.issuer; - balances[symbol] = token.value; + balances.push({ + currency: token.currency, + issuer: token.issuer, + value: token.value, + }); } }); diff --git a/src/chains/xrpl/xrpl.validators.ts b/src/chains/xrpl/xrpl.validators.ts index 6a0abb614a..287ffddbc7 100644 --- a/src/chains/xrpl/xrpl.validators.ts +++ b/src/chains/xrpl/xrpl.validators.ts @@ -1,11 +1,11 @@ import { + validateTokenSymbols, mkValidator, mkRequestValidator, RequestValidator, Validator, isBase58, validateTxHash, - validateToken, } from '../../services/validators'; // invalid parameter errors @@ -43,8 +43,5 @@ export const validateXRPLPollRequest: RequestValidator = mkRequestValidator([ ]); export const validateXRPLGetTokenRequest: RequestValidator = mkRequestValidator( - [validateToken, validateXRPLAddress] + [validateTokenSymbols, validateXRPLAddress] ); - -export const validateXRPLPostTokenRequest: RequestValidator = - mkRequestValidator([validateToken, validateXRPLAddress]); diff --git a/test/chains/xrpl/xrpl.validator.test.ts b/test/chains/xrpl/xrpl.validator.test.ts index f1832fc5dd..3a00760e6b 100644 --- a/test/chains/xrpl/xrpl.validator.test.ts +++ b/test/chains/xrpl/xrpl.validator.test.ts @@ -3,14 +3,13 @@ import { validateXRPLBalanceRequest, validateXRPLPollRequest, validateXRPLGetTokenRequest, - validateXRPLPostTokenRequest, invalidXRPLAddressError, } from '../../../src/chains/xrpl/xrpl.validators'; import { HttpException } from '../../../src/services/error-handler'; import { invalidTxHashError, - invalidTokenError, + invalidTokenSymbolsError, } from '../../../src/services/validators'; import { missingParameter } from '../../../src/services/validators'; @@ -97,88 +96,47 @@ describe('validateXRPLBalanceRequest', () => { }); describe('validateXRPLGetTokenRequest', () => { - it('valid when req.token is a token and address is a valid address', () => { + it('valid when req.tokenSymbols is a token and address is a valid address', () => { expect( validateXRPLGetTokenRequest({ address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', - token: 'XRP', + tokenSymbols: ['XRP'], }) ).toEqual(undefined); }); - it('return error when req.token and req.address does not exist', () => { + it('return error when req.tokenSymbols and req.address does not exist', () => { try { validateXRPLGetTokenRequest({ hello: 'world', }); } catch (error) { expect((error as HttpException).message).toEqual( - [missingParameter('token'), missingParameter('address')].join(', ') + [missingParameter('tokenSymbols'), missingParameter('address')].join( + ', ' + ) ); } }); - it('return error when req.token is invalid', () => { + it('return error when req.tokenSymbols is invalid', () => { try { validateXRPLGetTokenRequest({ address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', - token: 123, - }); - } catch (error) { - expect((error as HttpException).message).toEqual(invalidTokenError); - } - }); - - it('return error when req.address is invalid', () => { - try { - validateXRPLGetTokenRequest({ - address: 123, - token: `XRP`, - }); - } catch (error) { - expect((error as HttpException).message).toEqual(invalidXRPLAddressError); - } - }); -}); - -describe('validateXRPLPostTokenRequest', () => { - it('valid when req.token is a token and address is a valid address', () => { - expect( - validateXRPLPostTokenRequest({ - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', - token: 'XRP', - }) - ).toEqual(undefined); - }); - - it('return error when req.token and req.address does not exist', () => { - try { - validateXRPLPostTokenRequest({ - hello: 'world', + tokenSymbols: 123, }); } catch (error) { expect((error as HttpException).message).toEqual( - [missingParameter('token'), missingParameter('address')].join(', ') + invalidTokenSymbolsError ); } }); - it('return error when req.token is invalid', () => { - try { - validateXRPLPostTokenRequest({ - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', - token: 123, - }); - } catch (error) { - expect((error as HttpException).message).toEqual(invalidTokenError); - } - }); - it('return error when req.address is invalid', () => { try { - validateXRPLPostTokenRequest({ + validateXRPLGetTokenRequest({ address: 123, - token: `XRP`, + tokenSymbols: ['XRP'], }); } catch (error) { expect((error as HttpException).message).toEqual(invalidXRPLAddressError); From 7c8147197535fcffbf5f990290de7ca8dee18768 Mon Sep 17 00:00:00 2001 From: mlguys Date: Wed, 2 Aug 2023 14:07:15 +0700 Subject: [PATCH 24/39] add get midprice for a market --- src/connectors/xrpl/xrpl.ts | 52 ++++++++++++++++++++---- test/connectors/xrpl/xrpl.routes.test.ts | 23 +++++++---- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index c191d1100e..5d720d5433 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -112,9 +112,12 @@ export class XRPLCLOB implements CLOBish { public async markets( req: ClobMarketsRequest ): Promise<{ markets: CLOBMarkets }> { - if (req.market && req.market in this.parsedMarkets) - return { markets: this.parsedMarkets[req.market] }; - return { markets: Object.values(this.parsedMarkets) }; + if (req.market && req.market.split('-').length === 2) { + const resp: CLOBMarkets = {}; + resp[req.market] = this.parsedMarkets[req.market]; + return { markets: resp }; + } + return { markets: this.parsedMarkets }; } public async orderBook(req: ClobOrderbookRequest): Promise { @@ -207,7 +210,10 @@ export class XRPLCLOB implements CLOBish { return result; } - async getOrderBook(market: MarketInfo): Promise { + async getOrderBook( + market: MarketInfo, + limit: number = ORDERBOOK_LIMIT + ): Promise { const [baseCurrency, quoteCurrency] = market.marketId.split('-'); const baseIssuer = market.baseIssuer; const quoteIssuer = market.quoteIssuer; @@ -229,7 +235,8 @@ export class XRPLCLOB implements CLOBish { const { bids, asks } = await this.getOrderBookFromXRPL( baseRequest, - quoteRequest + quoteRequest, + limit ); const buys: PriceLevel[] = []; @@ -310,7 +317,15 @@ export class XRPLCLOB implements CLOBish { public async ticker( req: ClobTickerRequest ): Promise<{ markets: CLOBMarkets }> { - return await this.markets(req); + const getMarkets = await this.markets(req); + const markets: MarketInfo[] = Object.values(getMarkets.markets); + + for (const market of markets) { + getMarkets.markets[market.marketId]['midprice'] = + await this.getMidPriceForMarket(market); + } + + return getMarkets; } public async orders( @@ -502,13 +517,17 @@ export class XRPLCLOB implements CLOBish { return wallet; } - private async getOrderBookFromXRPL(baseRequest: any, quoteRequest: any) { + private async getOrderBookFromXRPL( + baseRequest: any, + quoteRequest: any, + limit: number + ) { const orderbook_resp_ask: BookOffersResponse = await this._client.request({ command: 'book_offers', ledger_index: 'validated', taker_gets: baseRequest, taker_pays: quoteRequest, - limit: ORDERBOOK_LIMIT, + limit, }); const orderbook_resp_bid: BookOffersResponse = await this._client.request({ @@ -516,7 +535,7 @@ export class XRPLCLOB implements CLOBish { ledger_index: 'validated', taker_gets: quoteRequest, taker_pays: baseRequest, - limit: ORDERBOOK_LIMIT, + limit, }); const asks = orderbook_resp_ask.result.offers; @@ -558,4 +577,19 @@ export class XRPLCLOB implements CLOBish { await orderTracker.addOrder(order); } + + private async getMidPriceForMarket(market: MarketInfo) { + const orderbook = await this.getOrderBook(market, 1); + try { + const bestAsk = orderbook.sells[0]; + const bestBid = orderbook.buys[0]; + const midPrice = + (parseFloat(bestAsk.price) + parseFloat(bestBid.price)) / 2; + return midPrice; + } catch (error) { + // TODO: report this error + + return 0; + } + } } diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index 78cb6499a7..3a1eaa7958 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -52,6 +52,7 @@ const ORDER = { updatedAt: 1234567, updatedAtLedgerIndex: 1234567, associatedTxns: [TX_HASH], + associatedFills: [], }; const ORDER_BOOK_1 = { @@ -347,6 +348,12 @@ const patchMarkets = () => { patch(xrplCLOB, 'parsedMarkets', MARKETS); }; +const patchGetMidPriceForMarket = () => { + patch(xrplCLOB, 'getMidPriceForMarket', () => { + return 1.0; + }); +}; + const patchGetOrderBookFromXRPL = (orderbook: any) => { patch(xrplCLOB, 'getOrderBookFromXRPL', () => { return orderbook; @@ -407,7 +414,7 @@ describe('GET /clob/markets', () => { .expect('Content-Type', /json/) .expect(200) .expect((res) => { - expect(res.body.markets.length).toEqual(2); + expect(Object.values(res.body.markets).length).toEqual(2); }); }); @@ -435,10 +442,6 @@ describe('GET /clob/orderBook', () => { .expect('Content-Type', /json/) .expect(200) .expect((res) => { - console.log( - '🪧 -> file: xrpl.routes.test.ts:429 -> it -> res.body.buys:', - res.body.buys - ); expect(res.body.buys[0].price).toEqual('0.5161287658690241'); }) .expect((res) => expect(res.body.buys[0].quantity).toEqual('9.687505')) @@ -478,6 +481,7 @@ describe('GET /clob/orderBook', () => { describe('GET /clob/ticker', () => { it('should return 200 with proper request', async () => { patchMarkets(); + patchGetMidPriceForMarket(); await request(gatewayApp) .get(`/clob/ticker`) .query({ @@ -489,8 +493,13 @@ describe('GET /clob/ticker', () => { .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) - .expect((res) => expect(res.body.markets.baseCurrency).toEqual('USD')) - .expect((res) => expect(res.body.markets.quoteCurrency).toEqual('XRP')); + .expect((res) => { + expect(res.body.markets['USD-XRP'].baseCurrency).toEqual('USD'); + }) + .expect((res) => + expect(res.body.markets['USD-XRP'].quoteCurrency).toEqual('XRP') + ) + .expect((res) => expect(res.body.markets['USD-XRP'].midprice).toEqual(1)); }); it('should return 404 when parameters are invalid', async () => { From a6f1a2b49a5fc5563ea98fa9bb4b28a4eaeb65e4 Mon Sep 17 00:00:00 2001 From: mlguys Date: Wed, 2 Aug 2023 15:23:55 +0700 Subject: [PATCH 25/39] fix getTokens --- src/chains/chain.routes.ts | 8 +++--- src/chains/xrpl/xrpl.controllers.ts | 34 ++++++++++++++++++++----- src/chains/xrpl/xrpl.helpers.ts | 17 +++++++++++++ src/chains/xrpl/xrpl.requests.ts | 6 +++++ src/chains/xrpl/xrpl.ts | 29 ++++++++++----------- src/chains/xrpl/xrpl.validators.ts | 2 +- test/chains/xrpl/xrpl.routes.test.ts | 22 ++++++++++++++++ test/chains/xrpl/xrpl.validator.test.ts | 4 +-- 8 files changed, 92 insertions(+), 30 deletions(-) diff --git a/src/chains/chain.routes.ts b/src/chains/chain.routes.ts index f1678958d0..f53128a5af 100644 --- a/src/chains/chain.routes.ts +++ b/src/chains/chain.routes.ts @@ -121,14 +121,14 @@ export namespace ChainRoutes { '/tokens', asyncHandler( async ( - req: Request<{}, {}, {}, TokensRequest>, + req: Request<{}, {}, TokensRequest>, res: Response ) => { const chain = await getInitializedChain( - req.query.chain as string, - req.query.network as string + req.body.chain as string, + req.body.network as string ); - res.status(200).json(await getTokens(chain, req.query)); + res.status(200).json(await getTokens(chain, req.body)); } ) ); diff --git a/src/chains/xrpl/xrpl.controllers.ts b/src/chains/xrpl/xrpl.controllers.ts index 46e2c5e474..7698ee1ee7 100644 --- a/src/chains/xrpl/xrpl.controllers.ts +++ b/src/chains/xrpl/xrpl.controllers.ts @@ -1,12 +1,15 @@ import { Wallet, TxResponse } from 'xrpl'; -import { TokenInfo, XRPLish } from './xrpl'; -import { latency } from '../../services/base'; +import { XRPTokenInfo, XRPLish } from './xrpl'; +import { TokenInfo, latency } from '../../services/base'; import { HttpException, LOAD_WALLET_ERROR_CODE, LOAD_WALLET_ERROR_MESSAGE, } from '../../services/error-handler'; -import { getNotNullOrThrowError } from '../../chains/xrpl/xrpl.helpers'; +import { + getNetworkId, + getNotNullOrThrowError, +} from '../../chains/xrpl/xrpl.helpers'; import { XRPLBalanceRequest, @@ -81,22 +84,39 @@ export class XRPLController { }; } - static async getTokens(xrplish: XRPLish, req: TokensRequest) { + static async getTokens( + xrplish: XRPLish, + req: TokensRequest + ): Promise<{ tokens: TokenInfo[] }> { validateXRPLGetTokenRequest(req); - let tokens: TokenInfo[] = []; + let xrpTokens: XRPTokenInfo[] = []; if (req.tokenSymbols?.length === 0) { - tokens = xrplish.storedTokenList; + xrpTokens = xrplish.storedTokenList; } else { for (const t of req.tokenSymbols as []) { const arr = xrplish.getTokenForSymbol(t); if (arr !== undefined) { arr.forEach((token) => { - tokens.push(token); + xrpTokens.push(token); }); } } } + const tokens: TokenInfo[] = []; + + // Convert xrpTokens into tokens + xrpTokens.map((xrpToken) => { + const token: TokenInfo = { + address: xrpToken.issuer, + chainId: getNetworkId(req.network), + decimals: 15, + name: xrpToken.title, + symbol: xrpToken.code, + }; + tokens.push(token); + }); + return { tokens }; } } diff --git a/src/chains/xrpl/xrpl.helpers.ts b/src/chains/xrpl/xrpl.helpers.ts index 72ce429591..b5f107acdc 100644 --- a/src/chains/xrpl/xrpl.helpers.ts +++ b/src/chains/xrpl/xrpl.helpers.ts @@ -1,5 +1,6 @@ import web3 from 'web3'; import { default as constants } from './../../chains/xrpl/xrpl.constants'; +import { XRPLNetworkID } from './xrpl.requests'; /** * @@ -120,3 +121,19 @@ export const runWithRetryAndTimeout = async ( throw Error('Unknown error.'); }; + +export function getNetworkId(network: string = ''): number { + switch (network) { + case 'mainnet': + return XRPLNetworkID.MAINNET; + + case 'testnet': + return XRPLNetworkID.TESTNET; + + case 'devnet': + return XRPLNetworkID.DEVNET; + + default: + return 0; + } +} diff --git a/src/chains/xrpl/xrpl.requests.ts b/src/chains/xrpl/xrpl.requests.ts index 77fc7a6a79..49b2ba10e9 100644 --- a/src/chains/xrpl/xrpl.requests.ts +++ b/src/chains/xrpl/xrpl.requests.ts @@ -52,3 +52,9 @@ export interface XRPLPollResponse { txLedgerIndex?: number; txData: TxResponse | null; } + +export enum XRPLNetworkID { + MAINNET = 1000, + TESTNET = 2000, + DEVNET = 3000, +} diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 67ad5d8a46..2f82987863 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -26,7 +26,7 @@ import { OrderTracker } from './xrpl.order-tracker'; import { ReferenceCountingCloseable } from '../../services/refcounting-closeable'; import { XRPLController } from './xrpl.controllers'; -export type TokenInfo = { +export type XRPTokenInfo = { id: number; code: string; issuer: string; @@ -56,9 +56,9 @@ export class XRPL implements XRPLish { public rpcUrl; public fee: Fee; - protected tokenList: TokenInfo[] = []; + protected tokenList: XRPTokenInfo[] = []; protected marketList: MarketInfo[] = []; - private _tokenMap: Record = {}; + private _tokenMap: Record = {}; private _marketMap: Record = {}; private _client: Client; @@ -204,7 +204,7 @@ export class XRPL implements XRPLish { ): Promise { this.tokenList = await this.getTokenList(tokenListSource, tokenListType); if (this.tokenList) { - this.tokenList.forEach((token: TokenInfo) => { + this.tokenList.forEach((token: XRPTokenInfo) => { if (!this._tokenMap[token.code]) { this._tokenMap[token.code] = []; } @@ -236,14 +236,13 @@ export class XRPL implements XRPLish { async getTokenList( tokenListSource: string, tokenListType: TokenListType - ): Promise { + ): Promise { let tokens; if (tokenListType === 'URL') { - ({ - data: { tokens }, - } = await axios.get(tokenListSource)); + const resp = await axios.get(tokenListSource); + tokens = resp.data.tokens; } else { - ({ tokens } = JSON.parse(await fs.readFile(tokenListSource, 'utf8'))); + tokens = JSON.parse(await fs.readFile(tokenListSource, 'utf8')); } return tokens; } @@ -252,17 +251,17 @@ export class XRPL implements XRPLish { marketListSource: string, marketListType: TokenListType ): Promise { - let tokens; + let markets; if (marketListType === 'URL') { const resp = await axios.get(marketListSource); - tokens = resp.data.tokens; + markets = resp.data.tokens; } else { - tokens = JSON.parse(await fs.readFile(marketListSource, 'utf8')); + markets = JSON.parse(await fs.readFile(marketListSource, 'utf8')); } - return tokens; + return markets; } - public get storedTokenList(): TokenInfo[] { + public get storedTokenList(): XRPTokenInfo[] { return this.tokenList; } @@ -270,7 +269,7 @@ export class XRPL implements XRPLish { return this.marketList; } - public getTokenForSymbol(code: string): TokenInfo[] | undefined { + public getTokenForSymbol(code: string): XRPTokenInfo[] | undefined { return this._tokenMap[code] ? this._tokenMap[code] : undefined; } diff --git a/src/chains/xrpl/xrpl.validators.ts b/src/chains/xrpl/xrpl.validators.ts index 287ffddbc7..8325c82cec 100644 --- a/src/chains/xrpl/xrpl.validators.ts +++ b/src/chains/xrpl/xrpl.validators.ts @@ -43,5 +43,5 @@ export const validateXRPLPollRequest: RequestValidator = mkRequestValidator([ ]); export const validateXRPLGetTokenRequest: RequestValidator = mkRequestValidator( - [validateTokenSymbols, validateXRPLAddress] + [validateTokenSymbols] ); diff --git a/test/chains/xrpl/xrpl.routes.test.ts b/test/chains/xrpl/xrpl.routes.test.ts index 1767efe93c..ac292e4753 100644 --- a/test/chains/xrpl/xrpl.routes.test.ts +++ b/test/chains/xrpl/xrpl.routes.test.ts @@ -77,3 +77,25 @@ describe('POST /chain/poll', () => { expect(res.statusCode).toEqual(404); }); }); + +describe('GET /chain/tokens', () => { + it('should return 200 with correct parameters', async () => { + const res = await request(gatewayApp) + .get('/chain/tokens') + .send({ + chain: 'xrpl', + network: 'testnet', + tokenSymbols: ['XRP'], + }); + expect(res.statusCode).toEqual(200); + }); + + it('should get unknown error with invalid tokenSymbols', async () => { + const res = await request(gatewayApp).get('/chain/tokens').send({ + chain: 'xrpl', + network: 'testnet', + tokenSymbols: 123, + }); + expect(res.statusCode).toEqual(404); + }); +}); diff --git a/test/chains/xrpl/xrpl.validator.test.ts b/test/chains/xrpl/xrpl.validator.test.ts index 3a00760e6b..553c676176 100644 --- a/test/chains/xrpl/xrpl.validator.test.ts +++ b/test/chains/xrpl/xrpl.validator.test.ts @@ -112,9 +112,7 @@ describe('validateXRPLGetTokenRequest', () => { }); } catch (error) { expect((error as HttpException).message).toEqual( - [missingParameter('tokenSymbols'), missingParameter('address')].join( - ', ' - ) + missingParameter('tokenSymbols') ); } }); From be86ef022a083672c3eeaf4f30f74a83acf4b04f Mon Sep 17 00:00:00 2001 From: mlguys Date: Wed, 9 Aug 2023 00:39:57 +0700 Subject: [PATCH 26/39] Fix bugs --- src/chains/xrpl/xrpl.controllers.ts | 25 ++++++------ src/chains/xrpl/xrpl.requests.ts | 6 ++- src/chains/xrpl/xrpl.ts | 29 +++++++++----- src/connectors/xrpl/xrpl.ts | 59 ++++++++++++++++++++--------- src/connectors/xrpl/xrpl.types.ts | 4 +- 5 files changed, 82 insertions(+), 41 deletions(-) diff --git a/src/chains/xrpl/xrpl.controllers.ts b/src/chains/xrpl/xrpl.controllers.ts index 7698ee1ee7..c8d74956e3 100644 --- a/src/chains/xrpl/xrpl.controllers.ts +++ b/src/chains/xrpl/xrpl.controllers.ts @@ -1,4 +1,4 @@ -import { Wallet, TxResponse } from 'xrpl'; +import { Wallet } from 'xrpl'; import { XRPTokenInfo, XRPLish } from './xrpl'; import { TokenInfo, latency } from '../../services/base'; import { @@ -6,10 +6,7 @@ import { LOAD_WALLET_ERROR_CODE, LOAD_WALLET_ERROR_MESSAGE, } from '../../services/error-handler'; -import { - getNetworkId, - getNotNullOrThrowError, -} from '../../chains/xrpl/xrpl.helpers'; +import { getNetworkId } from '../../chains/xrpl/xrpl.helpers'; import { XRPLBalanceRequest, @@ -49,7 +46,12 @@ export class XRPLController { ); } - const balances = await xrplish.getAllBalance(wallet); + const xrplBalances = await xrplish.getAllBalance(wallet); + + const balances: Record = {}; + xrplBalances.forEach((balance) => { + balances[balance.currency] = balance.value; + }); return { network: xrplish.network, @@ -68,19 +70,20 @@ export class XRPLController { const initTime = Date.now(); const currentLedgerIndex = await xrplish.getCurrentLedgerIndex(); - const txData = getNotNullOrThrowError( - await xrplish.getTransaction(req.txHash) - ); + const txData = await xrplish.getTransaction(req.txHash); const txStatus = await xrplish.getTransactionStatusCode(txData); + const sequence = txData ? txData.result.Sequence : undefined; + const txLedgerIndex = txData ? txData.result.ledger_index : undefined; return { network: xrplish.network, timestamp: initTime, currentLedgerIndex: currentLedgerIndex, + sequence: sequence, txHash: req.txHash, txStatus: txStatus, - txLedgerIndex: txData.result.ledger_index, - txData: getNotNullOrThrowError(txData), + txLedgerIndex: txLedgerIndex, + txData: txData, }; } diff --git a/src/chains/xrpl/xrpl.requests.ts b/src/chains/xrpl/xrpl.requests.ts index 49b2ba10e9..a4682054ed 100644 --- a/src/chains/xrpl/xrpl.requests.ts +++ b/src/chains/xrpl/xrpl.requests.ts @@ -11,7 +11,7 @@ export interface XRPLBalanceResponse { timestamp: number; latency: number; address: string; - balances: Array; + balances: Record; } export type TokenBalance = { @@ -40,6 +40,7 @@ export interface XRPLPollRequest extends NetworkSelectionRequest { export enum TransactionResponseStatusCode { FAILED = -1, + PENDING = 0, CONFIRMED = 1, } @@ -47,10 +48,11 @@ export interface XRPLPollResponse { network: string; timestamp: number; currentLedgerIndex: number; + sequence?: number; txHash: string; txStatus: number; txLedgerIndex?: number; - txData: TxResponse | null; + txData?: TxResponse; } export enum XRPLNetworkID { diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index 2f82987863..c569d19786 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -351,6 +351,13 @@ export class XRPL implements XRPLish { value: token.value, }); } else { + const filtered = this.getTokenForSymbol(token.currency); + + // TODO: this is for filtering out tokens that are not in token list + if (filtered === undefined) { + return; + } + balances.push({ currency: token.currency, issuer: token.issuer, @@ -429,21 +436,25 @@ export class XRPL implements XRPLish { if (!txData) { txStatus = TransactionResponseStatusCode.FAILED; } else { - if ((txData.result.meta).TransactionResult) { - const result = (txData.result.meta) - .TransactionResult; - txStatus = - result == 'tesSUCCESS' - ? TransactionResponseStatusCode.CONFIRMED - : TransactionResponseStatusCode.FAILED; + if (txData.result.validated === false) { + txStatus = TransactionResponseStatusCode.PENDING; } else { - txStatus = TransactionResponseStatusCode.FAILED; + if ((txData.result.meta).TransactionResult) { + const result = (txData.result.meta) + .TransactionResult; + txStatus = + result == 'tesSUCCESS' + ? TransactionResponseStatusCode.CONFIRMED + : TransactionResponseStatusCode.FAILED; + } else { + txStatus = TransactionResponseStatusCode.FAILED; + } } } return txStatus; } - async getTransaction(txHash: string): Promise { + async getTransaction(txHash: string): Promise { await this.ensureConnection(); const tx_resp = await this._client.request({ command: 'tx', diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index 5d720d5433..fe2d56efb9 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -45,7 +45,7 @@ import { isUndefined } from 'mathjs'; // const XRP_FACTOR = 1000000; const ORDERBOOK_LIMIT = 100; - +const TXN_SUBMIT_DELAY = 100; export class XRPLCLOB implements CLOBish { private static _instances: LRUCache; private readonly _client: Client; @@ -53,6 +53,7 @@ export class XRPLCLOB implements CLOBish { private readonly _orderStorage: XRPLOrderStorage; private _ready: boolean = false; + private _isSubmittingTxn: boolean = false; public parsedMarkets: CLOBMarkets = {}; public chain: string; @@ -111,13 +112,19 @@ export class XRPLCLOB implements CLOBish { // TODO: Find and correct the required market info in client public async markets( req: ClobMarketsRequest - ): Promise<{ markets: CLOBMarkets }> { + ): Promise<{ markets: Array }> { if (req.market && req.market.split('-').length === 2) { - const resp: CLOBMarkets = {}; - resp[req.market] = this.parsedMarkets[req.market]; - return { markets: resp }; + const marketsAsArray: Array = []; + marketsAsArray.push(this.parsedMarkets[req.market]); + return { markets: marketsAsArray }; + } + + const marketsAsArray: Array = []; + for (const market in this.parsedMarkets) { + marketsAsArray.push(this.parsedMarkets[market]); } - return { markets: this.parsedMarkets }; + + return { markets: marketsAsArray }; } public async orderBook(req: ClobOrderbookRequest): Promise { @@ -139,6 +146,10 @@ export class XRPLCLOB implements CLOBish { } async getMarket(market: MarketInfo): Promise { + console.log( + '🪧 -> file: xrpl.ts:148 -> XRPLCLOB -> getMarket -> market:', + market + ); if (!market) throw new MarketNotFoundError(`No market informed.`); let baseTickSize, baseTransferRate, @@ -198,7 +209,9 @@ export class XRPLCLOB implements CLOBish { const result = { marketId: market.marketId, minimumOrderSize: minimumOrderSize, - tickSize: smallestTickSize, + smallestTickSize: smallestTickSize, + baseTickSize, + quoteTickSize, baseTransferRate: baseTransferRate, quoteTransferRate: quoteTransferRate, baseIssuer: baseIssuer, @@ -316,16 +329,20 @@ export class XRPLCLOB implements CLOBish { public async ticker( req: ClobTickerRequest - ): Promise<{ markets: CLOBMarkets }> { + ): Promise<{ markets: Array }> { const getMarkets = await this.markets(req); - const markets: MarketInfo[] = Object.values(getMarkets.markets); + const markets = getMarkets.markets; - for (const market of markets) { - getMarkets.markets[market.marketId]['midprice'] = - await this.getMidPriceForMarket(market); - } + const marketsWithMidprice = await Promise.all( + markets.map(async (market) => { + const midprice = await this.getMidPriceForMarket( + this.parsedMarkets[market.marketId] + ); + return { ...market, midprice }; + }) + ); - return getMarkets; + return { markets: marketsWithMidprice }; } public async orders( @@ -388,25 +405,25 @@ export class XRPLCLOB implements CLOBish { currency: baseCurrency, issuer: baseIssuer, value: Number( - parseFloat(req.amount).toPrecision(market.tickSize) + parseFloat(req.amount).toPrecision(market.smallestTickSize) ).toString(), }; we_get = { currency: quoteCurrency, issuer: quoteIssuer, - value: Number(total.toPrecision(market.tickSize)).toString(), + value: Number(total.toPrecision(market.smallestTickSize)).toString(), }; } else { we_pay = { currency: quoteCurrency, issuer: quoteIssuer, - value: Number(total.toPrecision(market.tickSize)).toString(), + value: Number(total.toPrecision(market.smallestTickSize)).toString(), }; we_get = { currency: baseCurrency, issuer: baseIssuer, value: Number( - parseFloat(req.amount).toPrecision(market.tickSize) + parseFloat(req.amount).toPrecision(market.smallestTickSize) ).toString(), }; } @@ -506,9 +523,15 @@ export class XRPLCLOB implements CLOBish { } private async submitTxn(offer: Transaction, wallet: Wallet) { + while (this._isSubmittingTxn) { + await new Promise((resolve) => setTimeout(resolve, TXN_SUBMIT_DELAY)); + } + + this._isSubmittingTxn = true; const prepared = await this._client.autofill(offer); const signed = wallet.sign(prepared); await this._client.submit(signed.tx_blob); + this._isSubmittingTxn = false; return { prepared, signed }; } diff --git a/src/connectors/xrpl/xrpl.types.ts b/src/connectors/xrpl/xrpl.types.ts index 5b8ac427a3..71db38ae42 100644 --- a/src/connectors/xrpl/xrpl.types.ts +++ b/src/connectors/xrpl/xrpl.types.ts @@ -68,7 +68,9 @@ export interface GetMarketResponse { export interface Market { marketId: string; minimumOrderSize: number; - tickSize: number; + smallestTickSize: number; + baseTickSize: number; + quoteTickSize: number; baseTransferRate: number; quoteTransferRate: number; baseIssuer: string; From 8443ba3a43fab8af0caa357f5e4b6fabf791cf72 Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 10 Aug 2023 23:53:15 +0700 Subject: [PATCH 27/39] fix getting order status & trading fees --- src/chains/xrpl/xrpl.helpers.ts | 8 +++ src/chains/xrpl/xrpl.order-tracker.ts | 72 ++++++++++++++------------- src/connectors/xrpl/xrpl.ts | 7 +-- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/chains/xrpl/xrpl.helpers.ts b/src/chains/xrpl/xrpl.helpers.ts index b5f107acdc..ab6b21c5ad 100644 --- a/src/chains/xrpl/xrpl.helpers.ts +++ b/src/chains/xrpl/xrpl.helpers.ts @@ -137,3 +137,11 @@ export function getNetworkId(network: string = ''): number { return 0; } } + +export function getBaseTokenFromMarketId(marketId: string): string { + return marketId.split('-')[0]; +} + +export function getQuoteTokenFromMarketId(marketId: string): string { + return marketId.split('-')[1]; +} diff --git a/src/chains/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts index aab404d805..7e3239cda6 100644 --- a/src/chains/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -10,6 +10,7 @@ import { dropsToXrp, } from 'xrpl'; import { XRPL } from './xrpl'; +import { XRPLCLOB } from '../../../src/connectors/xrpl/xrpl'; import { getXRPLConfig } from './xrpl.config'; import { OrderStatus, @@ -24,8 +25,13 @@ import { ModifiedNode, DeletedNode, FillData, + Market, } from '../../connectors/xrpl/xrpl.types'; import { OrderMutexManager } from '../../connectors/xrpl/xrpl.utils'; +import { + getBaseTokenFromMarketId, + getQuoteTokenFromMarketId, +} from './xrpl.helpers'; import { isModifiedNode, isDeletedNode, @@ -44,6 +50,7 @@ import { XRPLOrderStorage } from './xrpl.order-storage'; export class OrderTracker { private static _instances: LRUCache; private readonly _xrpl: XRPL; + private readonly _xrplClob: XRPLCLOB; private readonly _orderStorage: XRPLOrderStorage; private _wallet: Wallet; private _orderMutexManager: OrderMutexManager; @@ -60,6 +67,7 @@ export class OrderTracker { this.network = network; this._xrpl = XRPL.getInstance(network); + this._xrplClob = XRPLCLOB.getInstance(chain, network); this._orderStorage = this._xrpl.orderStorage; this._wallet = wallet; this._inflightOrders = {}; @@ -894,18 +902,28 @@ export class OrderTracker { const orderHash: number = order.hash; if (node === undefined) { + const feeToken = + order.tradeType === 'BUY' + ? getQuoteTokenFromMarketId(order.marketId) + : getBaseTokenFromMarketId(order.marketId); + const fee = this.calculateFee( + order.marketId, + order.tradeType, + order.amount + ); + const timestamp = transaction.transaction.date + ? rippleTimeToUnixTime(transaction.transaction.date) + : 0; return { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tradeId: `${intent.type}-${transaction.transaction.hash!}`, orderHash: order.hash, price: order.price, quantity: order.amount, - feeToken: 'XRP', + feeToken: feeToken, side: order.tradeType, - fee: this.getFeeFromTransaction(transaction).toString(), - timestamp: transaction.transaction.date - ? rippleTimeToUnixTime(transaction.transaction.date) - : 0, + fee: fee, + timestamp: timestamp, type: 'Taker', }; } @@ -924,9 +942,12 @@ export class OrderTracker { node, order.tradeType ).toString(); - const feeToken = 'XRP'; + const feeToken = + order.tradeType === 'BUY' + ? getQuoteTokenFromMarketId(order.marketId) + : getBaseTokenFromMarketId(order.marketId); const side = order.tradeType; - const fee = this.getFeeFromTransaction(transaction).toString(); + const fee = this.calculateFee(order.marketId, order.tradeType, quantity); const timestamp = transaction.transaction.date ? rippleTimeToUnixTime(transaction.transaction.date) : 0; @@ -945,38 +966,19 @@ export class OrderTracker { }; } - getFeeFromTransaction( - transaction: TransactionStream | TransaformedAccountTransaction - ): string { + calculateFee(marketId: string, tradeType: string, quantity: string): string { let fee = '0'; + const market = this._xrplClob.parsedMarkets[marketId] as Market; - if (transaction.meta === undefined) { - return fee; + if (market === undefined) { + return '0'; } - transaction.meta.AffectedNodes.forEach((node) => { - if (isModifiedNode(node)) { - if (node.ModifiedNode.LedgerEntryType === 'AccountRoot') { - try { - const previousFields = node.ModifiedNode.PreviousFields as any; - const finalFields = node.ModifiedNode.FinalFields as any; - - if ( - previousFields.Balance !== undefined && - finalFields.Balance !== undefined - ) { - fee = dropsToXrp( - parseInt(previousFields.Balance) - parseInt(finalFields.Balance) - ); - } - } catch (error) { - throw new Error( - 'Error parsing fee from transaction: ' + transaction - ); - } - } - } - }); + if (tradeType === 'BUY') { + fee = (parseFloat(quantity) * market.quoteTransferRate).toString(); + } else { + fee = (parseFloat(quantity) * market.baseTransferRate).toString(); + } return fee; } diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index fe2d56efb9..02a3f2829a 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -204,7 +204,7 @@ export class XRPLCLOB implements CLOBish { } const smallestTickSize = Math.min(baseTickSize, quoteTickSize); - const minimumOrderSize = smallestTickSize; + const minimumOrderSize = Math.pow(10, -smallestTickSize); const result = { marketId: market.marketId, @@ -348,7 +348,6 @@ export class XRPLCLOB implements CLOBish { public async orders( req: ClobGetOrderRequest ): Promise<{ orders: ClobGetOrderResponse['orders'] }> { - if (!req.market) return { orders: [] }; if (!req.address) return { orders: [] }; if (!req.orderId) return { orders: [] }; @@ -366,12 +365,10 @@ export class XRPLCLOB implements CLOBish { return { orders: ordersArray } as ClobGetOrderResponse; } else { - const marketId = this.parsedMarkets[req.market].marketId; - const orders = await this._orderStorage.getOrderByMarketAndHash( + const orders = await this._orderStorage.getOrdersByHash( this.chain, this.network, req.address, - marketId, req.orderId ); From d941a4e3e41036f606965a12c963853113bc87a8 Mon Sep 17 00:00:00 2001 From: mlguys Date: Fri, 11 Aug 2023 18:50:04 +0700 Subject: [PATCH 28/39] Prepare PR WIP --- package.json | 4 +- src/connectors/xrpl/xrpl.ts | 3 + test/connectors/xrpl/xrpl.e2e.test.ts | 471 +++++++++++++---------- test/connectors/xrpl/xrpl.routes.test.ts | 26 +- 4 files changed, 283 insertions(+), 221 deletions(-) diff --git a/package.json b/package.json index c052dde938..58399f6e7a 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,8 @@ "test:unit": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --verbose ./test/", "test:cov": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/", "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts", - "test:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.routes.test.ts", "test:e2e:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.e2e.test.ts", - "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts", - "test:chain:xrpl": "jest -i --verbose test/chains/xrpl/*.test.ts" + "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts" }, "dependencies": { "@cosmjs/proto-signing": "^0.28.10", diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index 02a3f2829a..e355dfcbeb 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -348,6 +348,9 @@ export class XRPLCLOB implements CLOBish { public async orders( req: ClobGetOrderRequest ): Promise<{ orders: ClobGetOrderResponse['orders'] }> { + // TODO: Check this on client why it give zero orders + console.log('🪧 -> file: xrpl.ts:351 -> XRPLCLOB -> req:', req); + if (!req.market) return { orders: [] }; if (!req.address) return { orders: [] }; if (!req.orderId) return { orders: [] }; diff --git a/test/connectors/xrpl/xrpl.e2e.test.ts b/test/connectors/xrpl/xrpl.e2e.test.ts index f1e513a09d..b1c6b20b8b 100644 --- a/test/connectors/xrpl/xrpl.e2e.test.ts +++ b/test/connectors/xrpl/xrpl.e2e.test.ts @@ -14,10 +14,10 @@ const wallet2 = Wallet.fromSecret('sEd7oiMn5napJBthB2z4CtN5nVi56Bd'); // r3z4R6K const MARKET = 'USD-VND'; let postedOrderTxn: string; -const INVALID_REQUEST = { - chain: 'unknown', - network: 'testnet', -}; +// const INVALID_REQUEST = { +// chain: 'unknown', +// network: 'testnet', +// }; const patchWallet1 = () => { patch(xrpl, 'getWallet', (walletAddress: string) => { @@ -49,213 +49,215 @@ afterAll(async () => { // 6. Get posted order details // 7. Cancel the posted order -describe('Get estimated gas price', () => { - it('should return 200 with proper request', async () => { - await request(gatewayApp) - .get(`/clob/estimateGas`) - .query({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => expect(res.body.gasPrice).toBeDefined()); - }); - - it('should return 404 when parameters are invalid', async () => { - await request(gatewayApp) - .get(`/clob/estimateGas`) - .query(INVALID_REQUEST) - .expect(404); - }); -}); - -describe('Get Markets List', () => { - it('should return a list of markets', async () => { - await request(gatewayApp) - .get(`/clob/markets`) - .query({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => { - expect(res.body.markets.length).toBeGreaterThan(0); - }); - }); -}); - -describe(`Get ticker info for ${MARKET}`, () => { - it('should return 200 with proper request', async () => { - await request(gatewayApp) - .get(`/clob/ticker`) - .query({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - market: MARKET, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => expect(res.body.markets.baseCurrency).toEqual('USD')) - .expect((res) => expect(res.body.markets.quoteCurrency).toEqual('VND')); - }); - - it('should return 404 when parameters are invalid', async () => { - await request(gatewayApp) - .get(`/clob/ticker`) - .query(INVALID_REQUEST) - .expect(404); - }); -}); - -describe('Post order', () => { - it('should return 200 with proper request', async () => { - await request(gatewayApp) - .post(`/clob/orders`) - .send({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock - market: MARKET, - price: '20000', - amount: '0.1', - side: 'BUY', - orderType: 'LIMIT', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => { - expect(res.body.txHash).toBeDefined(); - postedOrderTxn = res.body.txHash; - }); - }); - - it('should return PENDING_OPEN with proper request', async () => { - await checkOrderStatus( - postedOrderTxn, - 10, - 'PENDING_OPEN', - 500, - wallet1.address, - true - ); - }); - - it('should return OPEN with proper request', async () => { - await checkOrderStatus( - postedOrderTxn, - 9, - 'OPEN', - 1000, - wallet1.address, - true - ); - }); - - it('should return 404 when parameters are invalid', async () => { - await request(gatewayApp) - .post(`/clob/orders`) - .send(INVALID_REQUEST) - .expect(404); - }); - - it('should return 404 when parameters are invalid', async () => { - await request(gatewayApp) - .get(`/clob/orders`) - .query(INVALID_REQUEST) - .expect(404); - }); -}); - -describe('Get orderbook details', () => { - it('should return 200 with proper request with USD-VND', async () => { - await request(gatewayApp) - .get(`/clob/orderBook`) - .query({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - market: MARKET, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => { - expect(res.body.buys.length).toBeGreaterThan(0); - }); - }); - - it('should return 404 when parameters are invalid', async () => { - await request(gatewayApp) - .get(`/clob/orderBook`) - .query(INVALID_REQUEST) - .expect(404); - }); -}); - -describe('Delete order', () => { - it('should return 200 with proper request', async () => { - const postedOrderSequence = await getsSequenceNumberFromTxn( - 'testnet', - postedOrderTxn - ); - - expect(postedOrderSequence).toBeDefined(); - - await request(gatewayApp) - .delete(`/clob/orders`) - .send({ - chain: 'xrpl', - network: 'testnet', - connector: 'xrpl', - address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock - market: MARKET, - orderId: postedOrderSequence?.toString(), - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => expect(res.body.txHash).toBeDefined()); - }); - - it('should return PENDING_CANCEL with proper request', async () => { - await checkOrderStatus( - postedOrderTxn, - 10, - 'PENDING_CANCEL', - 500, - wallet1.address, - true - ); - }); - - it('should return CANCELED with proper request', async () => { - await checkOrderStatus( - postedOrderTxn, - 9, - 'CANCELED', - 1000, - wallet1.address, - true - ); - }); - - it('should return 404 when parameters are invalid', async () => { - await request(gatewayApp) - .delete(`/clob/orders`) - .send(INVALID_REQUEST) - .expect(404); - }); -}); +// describe('Get estimated gas price', () => { +// it('should return 200 with proper request', async () => { +// await request(gatewayApp) +// .get(`/clob/estimateGas`) +// .query({ +// chain: 'xrpl', +// network: 'testnet', +// connector: 'xrpl', +// }) +// .set('Accept', 'application/json') +// .expect('Content-Type', /json/) +// .expect(200) +// .expect((res) => expect(res.body.gasPrice).toBeDefined()); +// }); + +// it('should return 404 when parameters are invalid', async () => { +// await request(gatewayApp) +// .get(`/clob/estimateGas`) +// .query(INVALID_REQUEST) +// .expect(404); +// }); +// }); + +// describe('Get Markets List', () => { +// it('should return a list of markets', async () => { +// await request(gatewayApp) +// .get(`/clob/markets`) +// .query({ +// chain: 'xrpl', +// network: 'testnet', +// connector: 'xrpl', +// }) +// .set('Accept', 'application/json') +// .expect('Content-Type', /json/) +// .expect(200) +// .expect((res) => { +// expect(res.body.markets.length).toBeGreaterThan(0); +// }); +// }); +// }); + +// describe(`Get ticker info for ${MARKET}`, () => { +// it('should return 200 with proper request', async () => { +// await request(gatewayApp) +// .get(`/clob/ticker`) +// .query({ +// chain: 'xrpl', +// network: 'testnet', +// connector: 'xrpl', +// market: MARKET, +// }) +// .set('Accept', 'application/json') +// .expect('Content-Type', /json/) +// .expect(200) +// .expect((res) => expect(res.body.markets[0].baseCurrency).toEqual('USD')) +// .expect((res) => +// expect(res.body.markets[0].quoteCurrency).toEqual('VND') +// ); +// }); + +// it('should return 404 when parameters are invalid', async () => { +// await request(gatewayApp) +// .get(`/clob/ticker`) +// .query(INVALID_REQUEST) +// .expect(404); +// }); +// }); + +// describe('Post order', () => { +// it('should return 200 with proper request', async () => { +// await request(gatewayApp) +// .post(`/clob/orders`) +// .send({ +// chain: 'xrpl', +// network: 'testnet', +// connector: 'xrpl', +// address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock +// market: MARKET, +// price: '20000', +// amount: '0.1', +// side: 'BUY', +// orderType: 'LIMIT', +// }) +// .set('Accept', 'application/json') +// .expect('Content-Type', /json/) +// .expect(200) +// .expect((res) => { +// expect(res.body.txHash).toBeDefined(); +// postedOrderTxn = res.body.txHash; +// }); +// }); + +// it('should return PENDING_OPEN with proper request', async () => { +// await checkOrderStatus( +// postedOrderTxn, +// 10, +// 'PENDING_OPEN', +// 500, +// wallet1.address, +// true +// ); +// }); + +// it('should return OPEN with proper request', async () => { +// await checkOrderStatus( +// postedOrderTxn, +// 9, +// 'OPEN', +// 1000, +// wallet1.address, +// true +// ); +// }); + +// it('should return 404 when parameters are invalid', async () => { +// await request(gatewayApp) +// .post(`/clob/orders`) +// .send(INVALID_REQUEST) +// .expect(404); +// }); + +// it('should return 404 when parameters are invalid', async () => { +// await request(gatewayApp) +// .get(`/clob/orders`) +// .query(INVALID_REQUEST) +// .expect(404); +// }); +// }); + +// describe('Get orderbook details', () => { +// it('should return 200 with proper request with USD-VND', async () => { +// await request(gatewayApp) +// .get(`/clob/orderBook`) +// .query({ +// chain: 'xrpl', +// network: 'testnet', +// connector: 'xrpl', +// market: MARKET, +// }) +// .set('Accept', 'application/json') +// .expect('Content-Type', /json/) +// .expect(200) +// .expect((res) => { +// expect(res.body.buys.length).toBeGreaterThan(0); +// }); +// }); + +// it('should return 404 when parameters are invalid', async () => { +// await request(gatewayApp) +// .get(`/clob/orderBook`) +// .query(INVALID_REQUEST) +// .expect(404); +// }); +// }); + +// describe('Delete order', () => { +// it('should return 200 with proper request', async () => { +// const postedOrderSequence = await getsSequenceNumberFromTxn( +// 'testnet', +// postedOrderTxn +// ); + +// expect(postedOrderSequence).toBeDefined(); + +// await request(gatewayApp) +// .delete(`/clob/orders`) +// .send({ +// chain: 'xrpl', +// network: 'testnet', +// connector: 'xrpl', +// address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock +// market: MARKET, +// orderId: postedOrderSequence?.toString(), +// }) +// .set('Accept', 'application/json') +// .expect('Content-Type', /json/) +// .expect(200) +// .expect((res) => expect(res.body.txHash).toBeDefined()); +// }); + +// it('should return PENDING_CANCEL with proper request', async () => { +// await checkOrderStatus( +// postedOrderTxn, +// 10, +// 'PENDING_CANCEL', +// 500, +// wallet1.address, +// true +// ); +// }); + +// it('should return CANCELED with proper request', async () => { +// await checkOrderStatus( +// postedOrderTxn, +// 9, +// 'CANCELED', +// 1000, +// wallet1.address, +// true +// ); +// }); + +// it('should return 404 when parameters are invalid', async () => { +// await request(gatewayApp) +// .delete(`/clob/orders`) +// .send(INVALID_REQUEST) +// .expect(404); +// }); +// }); // 2nd Senario: // 1. Post an order @@ -379,6 +381,53 @@ describe('Post order to be consumed', () => { true ); }); + + it('should cancel outstanding orders', async () => { + // const outstandingOrders: string[] = []; + + // wait for 5 seconds to let the order be posted + await new Promise((resolve) => setTimeout(resolve, 5000)); + + await request(gatewayApp) + .get(`/clob/orders`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: wallet1.classicAddress, + market: MARKET, + orderId: 'all', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .then((res) => { + console.log( + '🪧 -> file: xrpl.e2e.test.ts:403 -> .then -> res.body.orders:', + res.body.orders + ); + }); + + await request(gatewayApp) + .get(`/clob/orders`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: wallet2.classicAddress, + market: MARKET, + orderId: 'all', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .then((res) => { + console.log( + '🪧 -> file: xrpl.e2e.test.ts:423 -> .then -> res.body.orders:', + res.body.orders + ); + }); + }); }); }); diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index 3a1eaa7958..af68c8dace 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -362,6 +362,10 @@ const patchGetOrderBookFromXRPL = (orderbook: any) => { const patchOrders = () => { patch(xrplCLOB, '_orderStorage', { + async getOrdersByHash() { + const orders: Record = { [ORDER.hash]: ORDER }; + return orders; + }, async getOrderByMarketAndHash() { const orders: Record = { [ORDER.hash]: ORDER }; return orders; @@ -494,12 +498,15 @@ describe('GET /clob/ticker', () => { .expect('Content-Type', /json/) .expect(200) .expect((res) => { - expect(res.body.markets['USD-XRP'].baseCurrency).toEqual('USD'); + console.log( + '🪧 -> file: xrpl.routes.test.ts:497 -> .expect -> res:', + res.body + ); + + expect(res.body.markets[0].baseCurrency).toEqual('USD'); }) - .expect((res) => - expect(res.body.markets['USD-XRP'].quoteCurrency).toEqual('XRP') - ) - .expect((res) => expect(res.body.markets['USD-XRP'].midprice).toEqual(1)); + .expect((res) => expect(res.body.markets[0].quoteCurrency).toEqual('XRP')) + .expect((res) => expect(res.body.markets[0].midprice).toEqual(1)); }); it('should return 404 when parameters are invalid', async () => { @@ -521,13 +528,18 @@ describe('GET /clob/orders', () => { network: 'testnet', connector: 'xrpl', address: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', // noqa: mock - market: MARKET, orderId: 1234567, }) .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) - .expect((res) => expect(res.body.orders.length).toEqual(1)); + .expect((res) => { + console.log( + '🪧 -> file: xrpl.routes.test.ts:535 -> .expect -> res:', + res.body + ); + expect(res.body.orders.length).toEqual(1); + }); }); it('should return 404 when parameters are invalid', async () => { From 7d48910df7463455f9c42056db065b377972fefd Mon Sep 17 00:00:00 2001 From: mlguys Date: Sat, 12 Aug 2023 00:19:10 +0700 Subject: [PATCH 29/39] polish test cases --- package.json | 3 +- src/chains/xrpl/xrpl.helpers.ts | 86 ++-- src/chains/xrpl/xrpl.order-storage.ts | 42 +- src/connectors/xrpl/xrpl.ts | 3 +- test/connectors/xrpl/xrpl.e2e.test.ts | 498 ++++++++++++----------- test/connectors/xrpl/xrpl.routes.test.ts | 10 +- 6 files changed, 342 insertions(+), 300 deletions(-) diff --git a/package.json b/package.json index 58399f6e7a..fcd5562bd4 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,7 @@ "test:unit": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --verbose ./test/", "test:cov": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/", "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts", - "test:e2e:xrpl": "jest -i --verbose test/connectors/xrpl/xrpl.e2e.test.ts", - "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/connectors/xrpl/*.test.ts" + "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage --runTestsByPath ./test/chains/xrpl/*.test.ts ./test/connectors/xrpl/*.test.ts" }, "dependencies": { "@cosmjs/proto-signing": "^0.28.10", diff --git a/src/chains/xrpl/xrpl.helpers.ts b/src/chains/xrpl/xrpl.helpers.ts index ab6b21c5ad..0f27134b18 100644 --- a/src/chains/xrpl/xrpl.helpers.ts +++ b/src/chains/xrpl/xrpl.helpers.ts @@ -1,4 +1,4 @@ -import web3 from 'web3'; +// import web3 from 'web3'; import { default as constants } from './../../chains/xrpl/xrpl.constants'; import { XRPLNetworkID } from './xrpl.requests'; @@ -69,9 +69,9 @@ export const promiseAllInBatches = async ( /** * */ -export const getRandonBN = () => { - return web3.utils.toBN(web3.utils.randomHex(32)); -}; +// export const getRandonBN = () => { +// return web3.utils.toBN(web3.utils.randomHex(32)); +// }; /** * @param targetObject @@ -82,45 +82,45 @@ export const getRandonBN = () => { * @param timeout 0 means no timeout (milliseconds) * @param timeoutMessage */ -export const runWithRetryAndTimeout = async ( - targetObject: any, - targetFunction: (...args: any[]) => R, - targetParameters: any, - maxNumberOfRetries: number = constants.retry.all.maxNumberOfRetries, - delayBetweenRetries: number = constants.retry.all.delayBetweenRetries, - timeout: number = constants.timeout.all, - timeoutMessage: string = 'Timeout exceeded.' -): Promise => { - let retryCount = 0; - let timer: any; - - if (timeout > 0) { - timer = setTimeout(() => new Error(timeoutMessage), timeout); - } - - do { - try { - const result = await targetFunction.apply(targetObject, targetParameters); - - if (timeout > 0) { - clearTimeout(timer); - } - - return result as R; - } catch (error) { - retryCount++; - if (retryCount < maxNumberOfRetries) { - if (delayBetweenRetries > 0) { - await sleep(delayBetweenRetries); - } - } else { - throw error; - } - } - } while (retryCount < maxNumberOfRetries); - - throw Error('Unknown error.'); -}; +// export const runWithRetryAndTimeout = async ( +// targetObject: any, +// targetFunction: (...args: any[]) => R, +// targetParameters: any, +// maxNumberOfRetries: number = constants.retry.all.maxNumberOfRetries, +// delayBetweenRetries: number = constants.retry.all.delayBetweenRetries, +// timeout: number = constants.timeout.all, +// timeoutMessage: string = 'Timeout exceeded.' +// ): Promise => { +// let retryCount = 0; +// let timer: any; + +// if (timeout > 0) { +// timer = setTimeout(() => new Error(timeoutMessage), timeout); +// } + +// do { +// try { +// const result = await targetFunction.apply(targetObject, targetParameters); + +// if (timeout > 0) { +// clearTimeout(timer); +// } + +// return result as R; +// } catch (error) { +// retryCount++; +// if (retryCount < maxNumberOfRetries) { +// if (delayBetweenRetries > 0) { +// await sleep(delayBetweenRetries); +// } +// } else { +// throw error; +// } +// } +// } while (retryCount < maxNumberOfRetries); + +// throw Error('Unknown error.'); +// }; export function getNetworkId(network: string = ''): number { switch (network) { diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index 12d8d94ad5..c5217abbc4 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -138,27 +138,27 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { }); } - public async getOrdersByHash( - chain: string, - network: string, - walletAddress: string, - hash: string - ): Promise> { - return this.localStorage.get((key: string, value: string) => { - const splitKey = key.split('/'); - if ( - splitKey.length === 4 && - splitKey[0] === chain && - splitKey[1] === network && - splitKey[2] === walletAddress && - splitKey[3] === hash - ) { - const order: Order = JSON.parse(value); - return [splitKey[3], order]; - } - return; - }); - } + // public async getOrdersByHash( + // chain: string, + // network: string, + // walletAddress: string, + // hash: string + // ): Promise> { + // return this.localStorage.get((key: string, value: string) => { + // const splitKey = key.split('/'); + // if ( + // splitKey.length === 4 && + // splitKey[0] === chain && + // splitKey[1] === network && + // splitKey[2] === walletAddress && + // splitKey[3] === hash + // ) { + // const order: Order = JSON.parse(value); + // return [splitKey[3], order]; + // } + // return; + // }); + // } public async getInflightOrders( chain: string, diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index e355dfcbeb..da887bcb1e 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -368,10 +368,11 @@ export class XRPLCLOB implements CLOBish { return { orders: ordersArray } as ClobGetOrderResponse; } else { - const orders = await this._orderStorage.getOrdersByHash( + const orders = await this._orderStorage.getOrderByMarketAndHash( this.chain, this.network, req.address, + req.market, req.orderId ); diff --git a/test/connectors/xrpl/xrpl.e2e.test.ts b/test/connectors/xrpl/xrpl.e2e.test.ts index b1c6b20b8b..74d07407f2 100644 --- a/test/connectors/xrpl/xrpl.e2e.test.ts +++ b/test/connectors/xrpl/xrpl.e2e.test.ts @@ -14,12 +14,12 @@ const wallet2 = Wallet.fromSecret('sEd7oiMn5napJBthB2z4CtN5nVi56Bd'); // r3z4R6K const MARKET = 'USD-VND'; let postedOrderTxn: string; -// const INVALID_REQUEST = { -// chain: 'unknown', -// network: 'testnet', -// }; +const INVALID_REQUEST = { + chain: 'unknown', + network: 'testnet', +}; -const patchWallet1 = () => { +const patchWalletXRPL = () => { patch(xrpl, 'getWallet', (walletAddress: string) => { if (walletAddress === 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16') return wallet1; @@ -32,11 +32,14 @@ beforeAll(async () => { await xrpl.init(); xrplCLOB = XRPLCLOB.getInstance('xrpl', 'testnet'); await xrplCLOB.init(); - patchWallet1(); + patchWalletXRPL(); +}); + +beforeEach(() => { + patchWalletXRPL(); }); afterAll(async () => { - unpatch(); await xrpl.close(); }); @@ -49,215 +52,215 @@ afterAll(async () => { // 6. Get posted order details // 7. Cancel the posted order -// describe('Get estimated gas price', () => { -// it('should return 200 with proper request', async () => { -// await request(gatewayApp) -// .get(`/clob/estimateGas`) -// .query({ -// chain: 'xrpl', -// network: 'testnet', -// connector: 'xrpl', -// }) -// .set('Accept', 'application/json') -// .expect('Content-Type', /json/) -// .expect(200) -// .expect((res) => expect(res.body.gasPrice).toBeDefined()); -// }); - -// it('should return 404 when parameters are invalid', async () => { -// await request(gatewayApp) -// .get(`/clob/estimateGas`) -// .query(INVALID_REQUEST) -// .expect(404); -// }); -// }); - -// describe('Get Markets List', () => { -// it('should return a list of markets', async () => { -// await request(gatewayApp) -// .get(`/clob/markets`) -// .query({ -// chain: 'xrpl', -// network: 'testnet', -// connector: 'xrpl', -// }) -// .set('Accept', 'application/json') -// .expect('Content-Type', /json/) -// .expect(200) -// .expect((res) => { -// expect(res.body.markets.length).toBeGreaterThan(0); -// }); -// }); -// }); - -// describe(`Get ticker info for ${MARKET}`, () => { -// it('should return 200 with proper request', async () => { -// await request(gatewayApp) -// .get(`/clob/ticker`) -// .query({ -// chain: 'xrpl', -// network: 'testnet', -// connector: 'xrpl', -// market: MARKET, -// }) -// .set('Accept', 'application/json') -// .expect('Content-Type', /json/) -// .expect(200) -// .expect((res) => expect(res.body.markets[0].baseCurrency).toEqual('USD')) -// .expect((res) => -// expect(res.body.markets[0].quoteCurrency).toEqual('VND') -// ); -// }); - -// it('should return 404 when parameters are invalid', async () => { -// await request(gatewayApp) -// .get(`/clob/ticker`) -// .query(INVALID_REQUEST) -// .expect(404); -// }); -// }); - -// describe('Post order', () => { -// it('should return 200 with proper request', async () => { -// await request(gatewayApp) -// .post(`/clob/orders`) -// .send({ -// chain: 'xrpl', -// network: 'testnet', -// connector: 'xrpl', -// address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock -// market: MARKET, -// price: '20000', -// amount: '0.1', -// side: 'BUY', -// orderType: 'LIMIT', -// }) -// .set('Accept', 'application/json') -// .expect('Content-Type', /json/) -// .expect(200) -// .expect((res) => { -// expect(res.body.txHash).toBeDefined(); -// postedOrderTxn = res.body.txHash; -// }); -// }); - -// it('should return PENDING_OPEN with proper request', async () => { -// await checkOrderStatus( -// postedOrderTxn, -// 10, -// 'PENDING_OPEN', -// 500, -// wallet1.address, -// true -// ); -// }); - -// it('should return OPEN with proper request', async () => { -// await checkOrderStatus( -// postedOrderTxn, -// 9, -// 'OPEN', -// 1000, -// wallet1.address, -// true -// ); -// }); - -// it('should return 404 when parameters are invalid', async () => { -// await request(gatewayApp) -// .post(`/clob/orders`) -// .send(INVALID_REQUEST) -// .expect(404); -// }); - -// it('should return 404 when parameters are invalid', async () => { -// await request(gatewayApp) -// .get(`/clob/orders`) -// .query(INVALID_REQUEST) -// .expect(404); -// }); -// }); - -// describe('Get orderbook details', () => { -// it('should return 200 with proper request with USD-VND', async () => { -// await request(gatewayApp) -// .get(`/clob/orderBook`) -// .query({ -// chain: 'xrpl', -// network: 'testnet', -// connector: 'xrpl', -// market: MARKET, -// }) -// .set('Accept', 'application/json') -// .expect('Content-Type', /json/) -// .expect(200) -// .expect((res) => { -// expect(res.body.buys.length).toBeGreaterThan(0); -// }); -// }); - -// it('should return 404 when parameters are invalid', async () => { -// await request(gatewayApp) -// .get(`/clob/orderBook`) -// .query(INVALID_REQUEST) -// .expect(404); -// }); -// }); - -// describe('Delete order', () => { -// it('should return 200 with proper request', async () => { -// const postedOrderSequence = await getsSequenceNumberFromTxn( -// 'testnet', -// postedOrderTxn -// ); - -// expect(postedOrderSequence).toBeDefined(); - -// await request(gatewayApp) -// .delete(`/clob/orders`) -// .send({ -// chain: 'xrpl', -// network: 'testnet', -// connector: 'xrpl', -// address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock -// market: MARKET, -// orderId: postedOrderSequence?.toString(), -// }) -// .set('Accept', 'application/json') -// .expect('Content-Type', /json/) -// .expect(200) -// .expect((res) => expect(res.body.txHash).toBeDefined()); -// }); - -// it('should return PENDING_CANCEL with proper request', async () => { -// await checkOrderStatus( -// postedOrderTxn, -// 10, -// 'PENDING_CANCEL', -// 500, -// wallet1.address, -// true -// ); -// }); - -// it('should return CANCELED with proper request', async () => { -// await checkOrderStatus( -// postedOrderTxn, -// 9, -// 'CANCELED', -// 1000, -// wallet1.address, -// true -// ); -// }); - -// it('should return 404 when parameters are invalid', async () => { -// await request(gatewayApp) -// .delete(`/clob/orders`) -// .send(INVALID_REQUEST) -// .expect(404); -// }); -// }); +describe('Get estimated gas price', () => { + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .get(`/clob/estimateGas`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.gasPrice).toBeDefined()); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/estimateGas`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Get Markets List', () => { + it('should return a list of markets', async () => { + await request(gatewayApp) + .get(`/clob/markets`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.markets.length).toBeGreaterThan(0); + }); + }); +}); + +describe(`Get ticker info for ${MARKET}`, () => { + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .get(`/clob/ticker`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + market: MARKET, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.markets[0].baseCurrency).toEqual('USD')) + .expect((res) => + expect(res.body.markets[0].quoteCurrency).toEqual('VND') + ); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/ticker`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Post order', () => { + it('should return 200 with proper request', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock + market: MARKET, + price: '20000', + amount: '0.1', + side: 'BUY', + orderType: 'LIMIT', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.txHash).toBeDefined(); + postedOrderTxn = res.body.txHash; + }); + }); + + it('should return PENDING_OPEN with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 10, + 'PENDING_OPEN', + 500, + wallet1.address, + true + ); + }); + + it('should return OPEN with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 9, + 'OPEN', + 1000, + wallet1.address, + true + ); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .post(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/orders`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Get orderbook details', () => { + it('should return 200 with proper request with USD-VND', async () => { + await request(gatewayApp) + .get(`/clob/orderBook`) + .query({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + market: MARKET, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.buys.length).toBeGreaterThan(0); + }); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .get(`/clob/orderBook`) + .query(INVALID_REQUEST) + .expect(404); + }); +}); + +describe('Delete order', () => { + it('should return 200 with proper request', async () => { + const postedOrderSequence = await getsSequenceNumberFromTxn( + 'testnet', + postedOrderTxn + ); + + expect(postedOrderSequence).toBeDefined(); + + await request(gatewayApp) + .delete(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: 'r9wmQfStbNfPJ2XqAN7KH4iP8NJKmqPe16', // noqa: mock + market: MARKET, + orderId: postedOrderSequence?.toString(), + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.txHash).toBeDefined()); + }); + + it('should return PENDING_CANCEL with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 10, + 'PENDING_CANCEL', + 500, + wallet1.address, + true + ); + }); + + it('should return CANCELED with proper request', async () => { + await checkOrderStatus( + postedOrderTxn, + 9, + 'CANCELED', + 1000, + wallet1.address, + true + ); + }); + + it('should return 404 when parameters are invalid', async () => { + await request(gatewayApp) + .delete(`/clob/orders`) + .send(INVALID_REQUEST) + .expect(404); + }); +}); // 2nd Senario: // 1. Post an order @@ -356,7 +359,7 @@ describe('Post order to be consumed', () => { chain: 'xrpl', network: 'testnet', connector: 'xrpl', - address: wallet1.address, // noqa: mock + address: wallet1.classicAddress, // noqa: mock market: MARKET, price: '19999', amount: '0.051', @@ -383,10 +386,8 @@ describe('Post order to be consumed', () => { }); it('should cancel outstanding orders', async () => { - // const outstandingOrders: string[] = []; - - // wait for 5 seconds to let the order be posted - await new Promise((resolve) => setTimeout(resolve, 5000)); + let wallet1OutstandingOrders: Order[] = []; + let wallet2OutstandingOrders: Order[] = []; await request(gatewayApp) .get(`/clob/orders`) @@ -402,10 +403,9 @@ describe('Post order to be consumed', () => { .expect('Content-Type', /json/) .expect(200) .then((res) => { - console.log( - '🪧 -> file: xrpl.e2e.test.ts:403 -> .then -> res.body.orders:', - res.body.orders - ); + wallet1OutstandingOrders = res.body.orders.filter((order: Order) => { + return order.state !== 'FILLED' && order.state !== 'CANCELED'; + }); }); await request(gatewayApp) @@ -422,11 +422,51 @@ describe('Post order to be consumed', () => { .expect('Content-Type', /json/) .expect(200) .then((res) => { - console.log( - '🪧 -> file: xrpl.e2e.test.ts:423 -> .then -> res.body.orders:', - res.body.orders - ); + wallet2OutstandingOrders = res.body.orders.filter((order: Order) => { + return order.state !== 'FILLED' && order.state !== 'CANCELED'; + }); + }); + + if (wallet1OutstandingOrders.length > 0) { + wallet1OutstandingOrders.forEach(async (order) => { + await request(gatewayApp) + .delete(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: wallet1.classicAddress, // noqa: mock + market: MARKET, + orderId: order.hash.toString(), + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.txHash).toBeDefined()); + }); + } + + if (wallet2OutstandingOrders.length > 0) { + wallet2OutstandingOrders.forEach(async (order) => { + await request(gatewayApp) + .delete(`/clob/orders`) + .send({ + chain: 'xrpl', + network: 'testnet', + connector: 'xrpl', + address: wallet2.classicAddress, // noqa: mock + market: MARKET, + orderId: order.hash.toString(), + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => expect(res.body.txHash).toBeDefined()); }); + } + + await new Promise((resolve) => setTimeout(resolve, 5000)); + unpatch(); }); }); }); diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index af68c8dace..fef3da355c 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -301,12 +301,13 @@ beforeEach(() => { patchMarkets(); }); -afterEach(() => { - unpatch(); -}); +// afterEach(() => { +// unpatch(); +// }); afterAll(async () => { await xrpl.close(); + unpatch(); }); const patchConnect = () => { @@ -527,8 +528,9 @@ describe('GET /clob/orders', () => { chain: 'xrpl', network: 'testnet', connector: 'xrpl', + market: MARKET, address: 'rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx', // noqa: mock - orderId: 1234567, + orderId: '1234567', }) .set('Accept', 'application/json') .expect('Content-Type', /json/) From 7e798137f97ed3c495a3aa1ef30777ad8a2c5952 Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 15 Aug 2023 15:54:30 +0700 Subject: [PATCH 30/39] fix getting order from db --- src/chains/xrpl/xrpl.order-storage.ts | 57 ++++++++++++------------ src/connectors/xrpl/xrpl.ts | 7 +-- test/connectors/xrpl/xrpl.routes.test.ts | 8 ++-- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index c5217abbc4..24f1765da2 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -114,34 +114,12 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { }); } - public async getOrderByMarketAndHash( - chain: string, - network: string, - walletAddress: string, - marketId: string, - hash: string - ): Promise> { - return this.localStorage.get((key: string, value: string) => { - const splitKey = key.split('/'); - if ( - splitKey.length === 4 && - splitKey[0] === chain && - splitKey[1] === network && - splitKey[2] === walletAddress - ) { - const order: Order = JSON.parse(value); - if (order.marketId === marketId && order.hash === parseInt(hash)) { - return [splitKey[3], order]; - } - } - return; - }); - } - - // public async getOrdersByHash( + // TODO: Investigate why this method is giving empty results, considering removeing it + // public async getOrderByMarketAndHash( // chain: string, // network: string, // walletAddress: string, + // marketId: string, // hash: string // ): Promise> { // return this.localStorage.get((key: string, value: string) => { @@ -150,16 +128,39 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { // splitKey.length === 4 && // splitKey[0] === chain && // splitKey[1] === network && - // splitKey[2] === walletAddress && - // splitKey[3] === hash + // splitKey[2] === walletAddress // ) { // const order: Order = JSON.parse(value); - // return [splitKey[3], order]; + // if (order.marketId === marketId && order.hash === parseInt(hash)) { + // return [splitKey[3], order]; + // } // } // return; // }); // } + public async getOrdersByHash( + chain: string, + network: string, + walletAddress: string, + hash: string + ): Promise> { + return this.localStorage.get((key: string, value: string) => { + const splitKey = key.split('/'); + if ( + splitKey.length === 4 && + splitKey[0] === chain && + splitKey[1] === network && + splitKey[2] === walletAddress && + splitKey[3] === hash + ) { + const order: Order = JSON.parse(value); + return [splitKey[3], order]; + } + return; + }); + } + public async getInflightOrders( chain: string, network: string, diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index da887bcb1e..836b264ea3 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -348,13 +348,11 @@ export class XRPLCLOB implements CLOBish { public async orders( req: ClobGetOrderRequest ): Promise<{ orders: ClobGetOrderResponse['orders'] }> { - // TODO: Check this on client why it give zero orders - console.log('🪧 -> file: xrpl.ts:351 -> XRPLCLOB -> req:', req); - if (!req.market) return { orders: [] }; if (!req.address) return { orders: [] }; if (!req.orderId) return { orders: [] }; if (req.orderId === 'all') { + if (!req.market) return { orders: [] }; const marketId = this.parsedMarkets[req.market].marketId; const orders = await this._orderStorage.getOrdersByMarket( this.chain, @@ -368,11 +366,10 @@ export class XRPLCLOB implements CLOBish { return { orders: ordersArray } as ClobGetOrderResponse; } else { - const orders = await this._orderStorage.getOrderByMarketAndHash( + const orders = await this._orderStorage.getOrdersByHash( this.chain, this.network, req.address, - req.market, req.orderId ); diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index fef3da355c..065013084b 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -367,10 +367,10 @@ const patchOrders = () => { const orders: Record = { [ORDER.hash]: ORDER }; return orders; }, - async getOrderByMarketAndHash() { - const orders: Record = { [ORDER.hash]: ORDER }; - return orders; - }, + // async getOrderByMarketAndHash() { + // const orders: Record = { [ORDER.hash]: ORDER }; + // return orders; + // }, async getOrdersByMarket() { const orders: Record = { [ORDER.hash]: ORDER }; return orders; From 67e6cb696cc241917e7a4ef5a04c0bf1f85d3f1f Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 22 Aug 2023 20:39:50 +0700 Subject: [PATCH 31/39] rebase latest changes --- package.json | 3 +- src/chains/xrpl/xrpl.routes.ts | 78 ------ test/chains/xrpl/xrpl.routes.test.ts | 17 +- yarn.lock | 381 +++++++++++++-------------- 4 files changed, 200 insertions(+), 279 deletions(-) delete mode 100644 src/chains/xrpl/xrpl.routes.ts diff --git a/package.json b/package.json index fcd5562bd4..f6752f1766 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "test:debug": "node --inspect node_modules/.bin/jest --watch --runInBand", "test:unit": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --verbose ./test/", "test:cov": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage ./test/", - "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts", - "test:cov:xrpl": "NODE_OPTIONS=--max_old_space_size=10240 jest --runInBand --coverage --runTestsByPath ./test/chains/xrpl/*.test.ts ./test/connectors/xrpl/*.test.ts" + "test:scripts": "jest -i --verbose ./test-scripts/*.test.ts" }, "dependencies": { "@cosmjs/proto-signing": "^0.28.10", diff --git a/src/chains/xrpl/xrpl.routes.ts b/src/chains/xrpl/xrpl.routes.ts deleted file mode 100644 index 8ef5ecd0ab..0000000000 --- a/src/chains/xrpl/xrpl.routes.ts +++ /dev/null @@ -1,78 +0,0 @@ -// TODO: Deprecated, remove soon -// import { NextFunction, Request, Response, Router } from 'express'; -// import { ParamsDictionary } from 'express-serve-static-core'; -// import { XRPL } from './xrpl'; -// import { verifyXRPLIsAvailable } from './xrpl-middlewares'; -// import { asyncHandler } from '../../services/error-handler'; -// import { balances, poll } from './xrpl.controllers'; -// import { -// XRPLBalanceRequest, -// XRPLBalanceResponse, -// XRPLPollRequest, -// XRPLPollResponse, -// } from './xrpl.requests'; -// import { -// validateXRPLBalanceRequest, -// validateXRPLPollRequest, -// } from './xrpl.validators'; - -// export namespace XRPLRoutes { -// export const router = Router(); - -// export const getXRPL = async (request: Request) => { -// const xrpl = await XRPL.getInstance(request.body.network); -// await xrpl.init(); - -// return xrpl; -// }; - -// router.use(asyncHandler(verifyXRPLIsAvailable)); - -// router.get( -// '/', -// asyncHandler(async (request: Request, response: Response) => { -// const xrpl = await getXRPL(request); - -// const rpcUrl = xrpl.rpcUrl; - -// response.status(200).json({ -// network: xrpl.network, -// rpcUrl: rpcUrl, -// connection: true, -// timestamp: Date.now(), -// }); -// }) -// ); - -// router.get( -// '/balances', -// asyncHandler( -// async ( -// request: Request, -// response: Response, -// _next: NextFunction -// ) => { -// const xrpl = await getXRPL(request); - -// validateXRPLBalanceRequest(request.body); -// response.status(200).json(await balances(xrpl, request.body)); -// } -// ) -// ); - -// // TODO: change this to GET -// router.get( -// '/poll', -// asyncHandler( -// async ( -// request: Request, -// response: Response -// ) => { -// const xrpl = await getXRPL(request); - -// validateXRPLPollRequest(request.body); -// response.status(200).json(await poll(xrpl, request.body)); -// } -// ) -// ); -// } diff --git a/test/chains/xrpl/xrpl.routes.test.ts b/test/chains/xrpl/xrpl.routes.test.ts index ac292e4753..1acc4ad2db 100644 --- a/test/chains/xrpl/xrpl.routes.test.ts +++ b/test/chains/xrpl/xrpl.routes.test.ts @@ -17,9 +17,24 @@ const patchWallet = () => { }); }; +const patchDatabase = () => { + patch(xrplChain, '_orderStorage', { + declareOwnership: () => { + return; + }, + init: () => { + return; + }, + close: () => { + return; + }, + }); +}; + beforeAll(async () => { xrplChain = XRPL.getInstance('testnet'); await xrplChain.init(); + patchDatabase(); }); afterAll(async () => { @@ -63,7 +78,7 @@ describe('POST /chain/poll', () => { chain: 'xrpl', network: 'testnet', txHash: - '61DD63760B94102E929BBC2EF0954E513EC41CCAF57619E1B079E7AA48B4F889', // noqa: mock + 'EF074CD8C98E639B3560200C5664029E4E7133D4803EF75F6991788E09E04CDB', // noqa: mock }); expect(res.statusCode).toEqual(200); }); diff --git a/yarn.lock b/yarn.lock index 6b5fa422b9..c0c88279d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -987,137 +987,137 @@ "@ethersproject-xdc/abi@file:vendor/@ethersproject-xdc/abi": version "5.7.0" dependencies: - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/hash" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-088ce754-1c63-4ee5-b47b-420371da9c1c-1691357225522/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-62fce592-91e5-4041-bc5c-a05cd76c3ac2-1692708974712/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/abstract-provider@file:vendor/@ethersproject-xdc/abstract-provider": version "5.7.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-5d3d0404-c9ed-4b35-bb7e-c99d104663d6-1691357225504/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-5d3d0404-c9ed-4b35-bb7e-c99d104663d6-1691357225504/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-5d3d0404-c9ed-4b35-bb7e-c99d104663d6-1691357225504/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/networks" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-5d3d0404-c9ed-4b35-bb7e-c99d104663d6-1691357225504/node_modules/@ethersproject-xdc/networks" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-5d3d0404-c9ed-4b35-bb7e-c99d104663d6-1691357225504/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/transactions" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-5d3d0404-c9ed-4b35-bb7e-c99d104663d6-1691357225504/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/web" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-5d3d0404-c9ed-4b35-bb7e-c99d104663d6-1691357225504/node_modules/@ethersproject-xdc/web" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-4772e6ce-7ac1-42bb-933f-486645f55a41-1692708974710/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-4772e6ce-7ac1-42bb-933f-486645f55a41-1692708974710/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-4772e6ce-7ac1-42bb-933f-486645f55a41-1692708974710/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/networks" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-4772e6ce-7ac1-42bb-933f-486645f55a41-1692708974710/node_modules/@ethersproject-xdc/networks" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-4772e6ce-7ac1-42bb-933f-486645f55a41-1692708974710/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-4772e6ce-7ac1-42bb-933f-486645f55a41-1692708974710/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/web" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-4772e6ce-7ac1-42bb-933f-486645f55a41-1692708974710/node_modules/@ethersproject-xdc/web" "@ethersproject-xdc/abstract-signer@file:vendor/@ethersproject-xdc/abstract-signer": version "5.7.0" dependencies: - "@ethersproject-xdc/abstract-provider" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-be66c2be-15e6-42af-8268-8cd352a11244-1691357225503/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-be66c2be-15e6-42af-8268-8cd352a11244-1691357225503/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-be66c2be-15e6-42af-8268-8cd352a11244-1691357225503/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-be66c2be-15e6-42af-8268-8cd352a11244-1691357225503/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-be66c2be-15e6-42af-8268-8cd352a11244-1691357225503/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-8988adda-f176-4d63-a867-a40c847bf1a5-1692708974723/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-8988adda-f176-4d63-a867-a40c847bf1a5-1692708974723/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-8988adda-f176-4d63-a867-a40c847bf1a5-1692708974723/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-8988adda-f176-4d63-a867-a40c847bf1a5-1692708974723/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-8988adda-f176-4d63-a867-a40c847bf1a5-1692708974723/node_modules/@ethersproject-xdc/properties" "@ethersproject-xdc/address@file:vendor/@ethersproject-xdc/address": version "5.7.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-address-5.7.0-0e881625-1893-48b5-83f9-bd779a225481-1691357225506/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-address-5.7.0-0e881625-1893-48b5-83f9-bd779a225481-1691357225506/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-address-5.7.0-0e881625-1893-48b5-83f9-bd779a225481-1691357225506/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-address-5.7.0-0e881625-1893-48b5-83f9-bd779a225481-1691357225506/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/rlp" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-address-5.7.0-0e881625-1893-48b5-83f9-bd779a225481-1691357225506/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-b5be7e1c-3d8f-4af0-8b9b-ff710852feb3-1692708974711/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-b5be7e1c-3d8f-4af0-8b9b-ff710852feb3-1692708974711/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-b5be7e1c-3d8f-4af0-8b9b-ff710852feb3-1692708974711/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-b5be7e1c-3d8f-4af0-8b9b-ff710852feb3-1692708974711/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-b5be7e1c-3d8f-4af0-8b9b-ff710852feb3-1692708974711/node_modules/@ethersproject-xdc/rlp" "@ethersproject-xdc/base64@file:vendor/@ethersproject-xdc/base64": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-base64-5.7.0-86cb62f5-4401-4d5d-933e-8d3a38b40d55-1691357225508/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-base64-5.7.0-e0e702b2-deac-4b99-bd43-65c059b07f58-1692708974711/node_modules/@ethersproject-xdc/bytes" "@ethersproject-xdc/basex@file:vendor/@ethersproject-xdc/basex": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-a689f23f-7e8f-4589-9cf9-e1a5d816d40c-1691357225510/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-a689f23f-7e8f-4589-9cf9-e1a5d816d40c-1691357225510/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-4eff50d1-b11d-4354-b3f7-711f6d28d91b-1692708974710/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-4eff50d1-b11d-4354-b3f7-711f6d28d91b-1692708974710/node_modules/@ethersproject-xdc/properties" "@ethersproject-xdc/bignumber@file:vendor/@ethersproject-xdc/bignumber": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-b61b4660-705d-4f39-a229-a0c22e49cd4d-1691357225509/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-b61b4660-705d-4f39-a229-a0c22e49cd4d-1691357225509/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-3a80493a-4143-4535-8a66-16e74aacb987-1692708974712/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-3a80493a-4143-4535-8a66-16e74aacb987-1692708974712/node_modules/@ethersproject-xdc/logger" bn.js "^5.2.1" "@ethersproject-xdc/bytes@file:vendor/@ethersproject-xdc/bytes": version "5.7.0" dependencies: - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-bytes-5.7.0-74df01c2-808e-45f9-b747-b4ab3191bb60-1691357225504/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bytes-5.7.0-9dbe2bc0-7cb4-40e5-ac55-de9a8eb82dc9-1692708974725/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/constants@file:vendor/@ethersproject-xdc/constants": version "5.7.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-constants-5.7.0-f2bc470a-d1d0-46af-bbdd-e1720e04a9a4-1691357225515/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-constants-5.7.0-31915966-1e2a-4fde-8337-9eeaa253d9c9-1692708974726/node_modules/@ethersproject-xdc/bignumber" "@ethersproject-xdc/contracts@file:vendor/@ethersproject-xdc/contracts": version "5.6.0" dependencies: - "@ethersproject-xdc/abi" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/abi" - "@ethersproject-xdc/abstract-provider" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/transactions" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-50b0a554-cfa9-4cc4-8898-7de06631680b-1691357225516/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/abi" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/abi" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-07afa922-b709-4c3d-8e12-42ca82751dcd-1692708974720/node_modules/@ethersproject-xdc/transactions" "@ethersproject-xdc/hash@file:vendor/@ethersproject-xdc/hash": version "5.7.0" dependencies: - "@ethersproject-xdc/abstract-signer" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/base64" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/base64" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-5b0ca1ee-49dd-4d6c-be87-471337b85073-1691357225506/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/base64" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/base64" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-fb533a2f-f52a-4cf9-a9d8-687db47c8b87-1692708974722/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/hdnode@file:vendor/@ethersproject-xdc/hdnode": version "5.7.0" dependencies: - "@ethersproject-xdc/abstract-signer" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/basex" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/basex" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/pbkdf2" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/pbkdf2" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/sha2" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/signing-key" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/signing-key" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/wordlists" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-8226246c-18e3-40bd-aa01-4992de43bf23-1691357225511/node_modules/@ethersproject-xdc/wordlists" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/basex" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/basex" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/pbkdf2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/pbkdf2" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/wordlists" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-22bf3df7-920a-410b-924b-195cf11e24d0-1692708974716/node_modules/@ethersproject-xdc/wordlists" "@ethersproject-xdc/json-wallets@file:vendor/@ethersproject-xdc/json-wallets": version "5.6.0" dependencies: - "@ethersproject-xdc/abstract-signer" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/hdnode" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/hdnode" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/pbkdf2" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/pbkdf2" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/random" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-0bee5fd6-6122-4c11-b23d-202429b5a805-1691357225513/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/hdnode" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/hdnode" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/pbkdf2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/pbkdf2" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-760f58ee-c8e5-4ad8-97cc-34d9c7c51396-1692708974713/node_modules/@ethersproject-xdc/transactions" aes-js "3.0.0" scrypt-js "3.0.1" "@ethersproject-xdc/keccak256@file:vendor/@ethersproject-xdc/keccak256": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-keccak256-5.7.0-4170c223-5383-49cd-9022-dbbd878d8938-1691357225512/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-keccak256-5.7.0-c12bf223-b36d-4ab0-bf9f-af7050bf0661-1692708974717/node_modules/@ethersproject-xdc/bytes" js-sha3 "0.8.0" "@ethersproject-xdc/logger@file:vendor/@ethersproject-xdc/logger": @@ -1126,67 +1126,67 @@ "@ethersproject-xdc/networks@file:vendor/@ethersproject-xdc/networks": version "5.7.1" dependencies: - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-networks-5.7.1-43b40363-db22-4437-b011-2e528f881ad6-1691357225517/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-networks-5.7.1-d01bb536-ce91-4ffd-a26d-bed36a590842-1692708974719/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/pbkdf2@file:vendor/@ethersproject-xdc/pbkdf2": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-375f641b-d3de-4932-8483-0ab301e6d057-1691357225513/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/sha2" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-375f641b-d3de-4932-8483-0ab301e6d057-1691357225513/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-daabc055-8217-4109-9066-da7f5969326e-1692708974729/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-daabc055-8217-4109-9066-da7f5969326e-1692708974729/node_modules/@ethersproject-xdc/sha2" "@ethersproject-xdc/properties@file:vendor/@ethersproject-xdc/properties": version "5.7.0" dependencies: - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-properties-5.7.0-27794c9c-9714-4915-a8a2-5f8170257e83-1691357225515/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-properties-5.7.0-88c82da7-ea52-4816-be16-b5d2d39e74ee-1692708974725/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/providers@file:vendor/@ethersproject-xdc/providers": version "5.6.2" dependencies: - "@ethersproject-xdc/abstract-provider" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/basex" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/basex" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/hash" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/networks" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/networks" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/random" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/rlp" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/rlp" - "@ethersproject-xdc/sha2" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/web" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-0a526b39-5661-434f-86f5-2c51157180f9-1691357225518/node_modules/@ethersproject-xdc/web" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/basex" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/basex" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/networks" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/networks" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/web" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-e2e1604d-d941-4ad8-97f2-ef1ee0bfe558-1692708974718/node_modules/@ethersproject-xdc/web" bech32 "1.1.4" ws "7.4.6" "@ethersproject-xdc/random@file:vendor/@ethersproject-xdc/random": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-random-5.7.0-2cde07eb-2189-4b2e-b1cd-515b460d9579-1691357225517/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-random-5.7.0-2cde07eb-2189-4b2e-b1cd-515b460d9579-1691357225517/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-random-5.7.0-bd07024d-126d-4b55-bda5-a84fbcf2db63-1692708974719/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-random-5.7.0-bd07024d-126d-4b55-bda5-a84fbcf2db63-1692708974719/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/rlp@file:vendor/@ethersproject-xdc/rlp": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-ea17636f-dbc4-49b3-aded-90c4a2963594-1691357225519/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-ea17636f-dbc4-49b3-aded-90c4a2963594-1691357225519/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-38864b91-83ee-4c93-b58a-5f9d8065f457-1692708974718/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-38864b91-83ee-4c93-b58a-5f9d8065f457-1692708974718/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/sha2@file:vendor/@ethersproject-xdc/sha2": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-79e99016-3a03-4fde-ba49-9a43a5b1b32e-1691357225517/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-79e99016-3a03-4fde-ba49-9a43a5b1b32e-1691357225517/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-e84bc0e4-0b83-4de4-910f-5a566bebb73f-1692708974719/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-e84bc0e4-0b83-4de4-910f-5a566bebb73f-1692708974719/node_modules/@ethersproject-xdc/logger" hash.js "1.1.7" "@ethersproject-xdc/signing-key@file:vendor/@ethersproject-xdc/signing-key": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-84b547b3-2254-44f2-809e-a00772c28ef0-1691357225515/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-84b547b3-2254-44f2-809e-a00772c28ef0-1691357225515/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-84b547b3-2254-44f2-809e-a00772c28ef0-1691357225515/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-19469f17-aa30-4967-bf97-ae0a7f09c2b3-1692708974721/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-19469f17-aa30-4967-bf97-ae0a7f09c2b3-1692708974721/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-19469f17-aa30-4967-bf97-ae0a7f09c2b3-1692708974721/node_modules/@ethersproject-xdc/properties" bn.js "^5.2.1" elliptic "6.5.4" hash.js "1.1.7" @@ -1194,76 +1194,76 @@ "@ethersproject-xdc/solidity@file:vendor/@ethersproject-xdc/solidity": version "5.6.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-467b4ed9-f953-45b3-bea6-b2db43101b80-1691357225538/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-467b4ed9-f953-45b3-bea6-b2db43101b80-1691357225538/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-467b4ed9-f953-45b3-bea6-b2db43101b80-1691357225538/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-467b4ed9-f953-45b3-bea6-b2db43101b80-1691357225538/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/sha2" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-467b4ed9-f953-45b3-bea6-b2db43101b80-1691357225538/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-467b4ed9-f953-45b3-bea6-b2db43101b80-1691357225538/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-2e3167d6-c3e2-44bd-804f-ca2d81c3640b-1692708974719/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-2e3167d6-c3e2-44bd-804f-ca2d81c3640b-1692708974719/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-2e3167d6-c3e2-44bd-804f-ca2d81c3640b-1692708974719/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-2e3167d6-c3e2-44bd-804f-ca2d81c3640b-1692708974719/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-2e3167d6-c3e2-44bd-804f-ca2d81c3640b-1692708974719/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-2e3167d6-c3e2-44bd-804f-ca2d81c3640b-1692708974719/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/strings@file:vendor/@ethersproject-xdc/strings": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-30e27f16-cd13-4436-aa97-546b3f55888d-1691357225519/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-30e27f16-cd13-4436-aa97-546b3f55888d-1691357225519/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-30e27f16-cd13-4436-aa97-546b3f55888d-1691357225519/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-2e0cc5b7-244c-4eb1-ae6e-332b90f7071b-1692708974726/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-2e0cc5b7-244c-4eb1-ae6e-332b90f7071b-1692708974726/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-2e0cc5b7-244c-4eb1-ae6e-332b90f7071b-1692708974726/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/transactions@file:vendor/@ethersproject-xdc/transactions": version "5.7.0" dependencies: - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/rlp" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/rlp" - "@ethersproject-xdc/signing-key" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-4e496f53-34f7-4ab3-8f83-0f2bc2095650-1691357225527/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-f37c94d0-d890-4ac1-8102-a1a764bf8dc1-1692708974725/node_modules/@ethersproject-xdc/signing-key" "@ethersproject-xdc/units@file:vendor/@ethersproject-xdc/units": version "5.6.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-units-5.6.0-b54e4fd9-d6c4-4cf9-a50e-ba4e6151dc3a-1691357225534/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/constants" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-units-5.6.0-b54e4fd9-d6c4-4cf9-a50e-ba4e6151dc3a-1691357225534/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-units-5.6.0-b54e4fd9-d6c4-4cf9-a50e-ba4e6151dc3a-1691357225534/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-9cdd1741-e461-412a-b011-8fde9b913cf8-1692708974721/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-9cdd1741-e461-412a-b011-8fde9b913cf8-1692708974721/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-9cdd1741-e461-412a-b011-8fde9b913cf8-1692708974721/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/wallet@file:vendor/@ethersproject-xdc/wallet": version "5.6.0" dependencies: - "@ethersproject-xdc/abstract-provider" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/hash" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/hdnode" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/hdnode" - "@ethersproject-xdc/json-wallets" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/json-wallets" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/random" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/signing-key" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/signing-key" - "@ethersproject-xdc/transactions" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/wordlists" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-70f9a323-6868-466b-a3d3-0b4439a720c6-1691357225533/node_modules/@ethersproject-xdc/wordlists" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/hdnode" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/hdnode" + "@ethersproject-xdc/json-wallets" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/json-wallets" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/wordlists" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-c27914d1-121a-4e68-a165-f0bd76845cce-1692708974720/node_modules/@ethersproject-xdc/wordlists" "@ethersproject-xdc/web@file:vendor/@ethersproject-xdc/web": version "5.7.1" dependencies: - "@ethersproject-xdc/base64" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-web-5.7.1-f2e9bd2f-1b63-41c8-b8b5-1fe7fff73327-1691357225538/node_modules/@ethersproject-xdc/base64" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-web-5.7.1-f2e9bd2f-1b63-41c8-b8b5-1fe7fff73327-1691357225538/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-web-5.7.1-f2e9bd2f-1b63-41c8-b8b5-1fe7fff73327-1691357225538/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-web-5.7.1-f2e9bd2f-1b63-41c8-b8b5-1fe7fff73327-1691357225538/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-web-5.7.1-f2e9bd2f-1b63-41c8-b8b5-1fe7fff73327-1691357225538/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/base64" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-c1ea7620-ce89-4f95-b100-e585c8b398e3-1692708974726/node_modules/@ethersproject-xdc/base64" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-c1ea7620-ce89-4f95-b100-e585c8b398e3-1692708974726/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-c1ea7620-ce89-4f95-b100-e585c8b398e3-1692708974726/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-c1ea7620-ce89-4f95-b100-e585c8b398e3-1692708974726/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-c1ea7620-ce89-4f95-b100-e585c8b398e3-1692708974726/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/wordlists@file:vendor/@ethersproject-xdc/wordlists": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-95ab5617-de6d-45e5-90c0-4e232a4e63ce-1691357225540/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/hash" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-95ab5617-de6d-45e5-90c0-4e232a4e63ce-1691357225540/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-95ab5617-de6d-45e5-90c0-4e232a4e63ce-1691357225540/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-95ab5617-de6d-45e5-90c0-4e232a4e63ce-1691357225540/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-95ab5617-de6d-45e5-90c0-4e232a4e63ce-1691357225540/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-71666f29-3e26-43b6-aff4-6b4ec4f7003c-1692708974729/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-71666f29-3e26-43b6-aff4-6b4ec4f7003c-1692708974729/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-71666f29-3e26-43b6-aff4-6b4ec4f7003c-1692708974729/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-71666f29-3e26-43b6-aff4-6b4ec4f7003c-1692708974729/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-71666f29-3e26-43b6-aff4-6b4ec4f7003c-1692708974729/node_modules/@ethersproject-xdc/strings" "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": version "5.7.0" @@ -6542,7 +6542,7 @@ bip32@^2.0.6: typeforce "^1.11.5" wif "^2.0.6" -bip39@3.0.4: +bip39@3.0.4, bip39@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw== @@ -6570,16 +6570,6 @@ bip39@^3.0.2, bip39@^3.0.3: dependencies: "@noble/hashes" "^1.2.0" -bip39@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" - integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw== - dependencies: - "@types/node" "11.11.6" - create-hash "^1.1.0" - pbkdf2 "^3.0.9" - randombytes "^2.0.1" - bip66@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" @@ -8862,36 +8852,36 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: "ethers-xdc@file:./vendor/ethers-xdc": version "5.7.2" dependencies: - "@ethersproject-xdc/abi" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/abi" - "@ethersproject-xdc/abstract-provider" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/base64" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/base64" - "@ethersproject-xdc/basex" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/basex" - "@ethersproject-xdc/bignumber" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/contracts" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/contracts" - "@ethersproject-xdc/hash" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/hdnode" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/hdnode" - "@ethersproject-xdc/json-wallets" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/json-wallets" - "@ethersproject-xdc/keccak256" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/networks" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/networks" - "@ethersproject-xdc/pbkdf2" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/pbkdf2" - "@ethersproject-xdc/properties" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/providers" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/providers" - "@ethersproject-xdc/random" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/rlp" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/rlp" - "@ethersproject-xdc/sha2" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/signing-key" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/signing-key" - "@ethersproject-xdc/solidity" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/solidity" - "@ethersproject-xdc/strings" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/units" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/units" - "@ethersproject-xdc/wallet" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/wallet" - "@ethersproject-xdc/web" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/web" - "@ethersproject-xdc/wordlists" "file:../../../.cache/yarn/v6/npm-ethers-xdc-5.7.2-33019c16-0f82-4c78-b6e4-6f8d74c4c64d-1691357225458/node_modules/@ethersproject-xdc/wordlists" + "@ethersproject-xdc/abi" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/abi" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/base64" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/base64" + "@ethersproject-xdc/basex" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/basex" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/contracts" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/contracts" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/hdnode" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/hdnode" + "@ethersproject-xdc/json-wallets" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/json-wallets" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/networks" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/networks" + "@ethersproject-xdc/pbkdf2" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/pbkdf2" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/providers" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/providers" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/solidity" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/solidity" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/units" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/units" + "@ethersproject-xdc/wallet" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/wallet" + "@ethersproject-xdc/web" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/web" + "@ethersproject-xdc/wordlists" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-63cd80dd-3a82-42fd-8195-f58d5f6fe0e4-1692708974700/node_modules/@ethersproject-xdc/wordlists" ethers@4.0.0-beta.3: version "4.0.0-beta.3" @@ -17212,11 +17202,6 @@ ws@~8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ== - xhr-request-promise@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" From b08cf973f887bd0736c47ca03125e8acba961fc1 Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 22 Aug 2023 21:10:06 +0700 Subject: [PATCH 32/39] Remove network.routes.ts --- src/network/network.routes.ts | 123 ---------------------------------- 1 file changed, 123 deletions(-) delete mode 100644 src/network/network.routes.ts diff --git a/src/network/network.routes.ts b/src/network/network.routes.ts deleted file mode 100644 index b69c9234d4..0000000000 --- a/src/network/network.routes.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import { NextFunction, Request, Response, Router } from 'express'; -import * as ethereumControllers from '../chains/ethereum/ethereum.controllers'; -// import * as xrplControllers from '../chains/xrpl/xrpl.controllers'; -import { Ethereumish } from '../services/common-interfaces'; -import { ConfigManagerV2 } from '../services/config-manager-v2'; -import { getInitializedChain } from '../services/connection-manager'; -import { asyncHandler } from '../services/error-handler'; -import { - mkRequestValidator, - RequestValidator, - validateTxHash, -} from '../services/validators'; -import { getStatus, getTokens } from './network.controllers'; -import { - BalanceRequest, - BalanceResponse, - PollRequest, - PollResponse, - StatusRequest, - StatusResponse, - TokensRequest, - TokensResponse, -} from './network.requests'; -import { - validateBalanceRequest as validateEthereumBalanceRequest, - validateChain as validateEthereumChain, - validateNetwork as validateEthereumNetwork, -} from '../chains/ethereum/ethereum.validators'; -// import { XRPLish } from '../chains/xrpl/xrpl'; -// import { -// validateXRPLBalanceRequest, -// validateXRPLPollRequest, -// } from '../chains/xrpl/xrpl.validators'; -// import { -// XRPLBalanceResponse, -// XRPLPollRequest, -// XRPLPollResponse, -// } from '../chains/xrpl/xrpl.requests'; - -export const validatePollRequest: RequestValidator = mkRequestValidator([ - validateTxHash, -]); - -export const validateTokensRequest: RequestValidator = mkRequestValidator([ - validateEthereumChain, - validateEthereumNetwork, -]); - -export namespace NetworkRoutes { - export const router = Router(); - - router.get( - '/status', - asyncHandler( - async ( - req: Request<{}, {}, {}, StatusRequest>, - res: Response - ) => { - res.status(200).json(await getStatus(req.query)); - } - ) - ); - - router.get('/config', (_req: Request, res: Response) => { - res.status(200).json(ConfigManagerV2.getInstance().allConfigurations); - }); - - router.post( - '/balances', - asyncHandler( - async ( - req: Request<{}, {}, BalanceRequest>, - res: Response, - _next: NextFunction - ) => { - validateEthereumBalanceRequest(req.body); - const chain = await getInitializedChain( - req.body.chain, - req.body.network - ); - - // TODO: Add get xrpl balances - - res - .status(200) - .json(await ethereumControllers.balances(chain, req.body)); - } - ) - ); - - router.post( - '/poll', - asyncHandler( - async ( - req: Request<{}, {}, PollRequest>, - res: Response - ) => { - validatePollRequest(req.body); - - const chain = await getInitializedChain( - req.body.chain, - req.body.network - ); - - res.status(200).json(await ethereumControllers.poll(chain, req.body)); - } - ) - ); - - router.get( - '/tokens', - asyncHandler( - async ( - req: Request<{}, {}, {}, TokensRequest>, - res: Response - ) => { - validateTokensRequest(req.query); - res.status(200).json(await getTokens(req.query)); - } - ) - ); -} From e4d7778131bd311020fac2bcf206e37ab58f4077 Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 29 Aug 2023 16:56:02 +0700 Subject: [PATCH 33/39] Fix test errors on CI/CD --- src/chains/xrpl/xrpl.order-storage.ts | 4 + src/chains/xrpl/xrpl_tokens_small.json | 170 ------------------ .../lists}/xrpl_markets.json | 0 .../lists}/xrpl_markets_devnet.json | 0 .../lists}/xrpl_markets_testnet.json | 0 .../xrpl => templates/lists}/xrpl_tokens.json | 0 .../lists}/xrpl_tokens_devnet.json | 0 .../lists}/xrpl_tokens_testnet.json | 0 src/templates/xrpl.yml | 14 +- test/chains/xrpl/xrpl.routes.test.ts | 2 +- test/connectors/xrpl/xrpl.e2e.test.ts | 7 +- test/connectors/xrpl/xrpl.routes.test.ts | 5 +- 12 files changed, 18 insertions(+), 184 deletions(-) delete mode 100644 src/chains/xrpl/xrpl_tokens_small.json rename src/{chains/xrpl => templates/lists}/xrpl_markets.json (100%) rename src/{chains/xrpl => templates/lists}/xrpl_markets_devnet.json (100%) rename src/{chains/xrpl => templates/lists}/xrpl_markets_testnet.json (100%) rename src/{chains/xrpl => templates/lists}/xrpl_tokens.json (100%) rename src/{chains/xrpl => templates/lists}/xrpl_tokens_devnet.json (100%) rename src/{chains/xrpl => templates/lists}/xrpl_tokens_testnet.json (100%) diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index 24f1765da2..d990c04429 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -16,6 +16,10 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async init(): Promise { try { + console.log( + '🪧 -> file: xrpl.order-storage.ts:20 -> XRPLOrderStorage -> init -> this.storageStatus();:', + this.storageStatus() + ); await this.localStorage.init(); } catch (error) { throw new Error('Failed to initialize local storage: ' + error); diff --git a/src/chains/xrpl/xrpl_tokens_small.json b/src/chains/xrpl/xrpl_tokens_small.json deleted file mode 100644 index 0ad91ad7ac..0000000000 --- a/src/chains/xrpl/xrpl_tokens_small.json +++ /dev/null @@ -1,170 +0,0 @@ -[ - { - "id": 0, - "code": "XRP", - "issuer": "", - "title": "XRP", - "trustlines": -1, - "placeInTop": 0 - }, - { - "id": 31, - "code": "SOLO", - "issuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz", - "title": "Sologenic", - "trustlines": 288976, - "placeInTop": 1 - }, - { - "id": 16602, - "code": "CSC", - "issuer": "rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr", - "title": "CasinoCoin", - "trustlines": 54154, - "placeInTop": 2 - }, - { - "id": 18190, - "code": "CORE", - "issuer": "rcoreNywaoz2ZCQ8Lg2EbSLnGuRBmun6D", - "title": "Coreum", - "trustlines": 65817, - "placeInTop": 3 - }, - { - "id": 248, - "code": "ELS", - "issuer": "rHXuEaRYnnJHbDeuBH5w8yPh5uwNVh5zAg", - "title": "Elysian", - "trustlines": 109452, - "placeInTop": 4 - }, - { - "id": 16605, - "code": "Equilibrium", - "issuer": "rpakCr61Q92abPXJnVboKENmpKssWyHpwu", - "title": "Equilibrium", - "trustlines": 34827, - "placeInTop": 5 - }, - { - "id": 17073, - "code": "BTC", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", - "title": "Bitstamp", - "trustlines": 18187, - "placeInTop": 6 - }, - { - "id": 16603, - "code": "USD", - "issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", - "title": "Gatehub", - "trustlines": 110980, - "placeInTop": 7 - }, - { - "id": 17074, - "code": "USD", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", - "title": "Bitstamp", - "trustlines": 26696, - "placeInTop": 8 - }, - { - "id": 16818, - "code": "XPUNK", - "issuer": "rHEL3bM4RFsvF8kbQj3cya8YiDvjoEmxLq", - "title": "XRPL PUNKS", - "trustlines": 12074, - "placeInTop": 9 - }, - { - "id": 103, - "code": "VGB", - "issuer": "rhcyBrowwApgNonehKBj8Po5z4gTyRknaU", - "title": "Vagabond VGB", - "trustlines": 56661, - "placeInTop": 10 - }, - { - "id": 182, - "code": "RPR", - "issuer": "r3qWgpz2ry3BhcRJ8JE6rxM8esrfhuKp4R", - "title": "The Reaper", - "trustlines": 24140, - "placeInTop": 11 - }, - { - "id": 18542, - "code": "xSPECTAR", - "issuer": "rh5jzTCdMRCVjQ7LT6zucjezC47KATkuvv", - "title": "xSPECTAR NFT", - "trustlines": 9868, - "placeInTop": 12 - }, - { - "id": 16826, - "code": "BAY", - "issuer": "r4uq8urnYrT6LnZaadPyusyKCS68HVJtRn", - "title": "Bored Apes XRP Club", - "trustlines": 8247, - "placeInTop": 13 - }, - { - "id": 17296, - "code": "OXP", - "issuer": "rrno7Nj4RkFJLzC4nRaZiLF5aHwcTVon3d", - "title": "onXRP", - "trustlines": 9851, - "placeInTop": 14 - }, - { - "id": 16726, - "code": "ADV", - "issuer": "rPneN8WPHZJaMT9pF4Ynyyq4pZZZSeTuHu", - "title": "AdvisorBid", - "trustlines": 10454, - "placeInTop": 15 - }, - { - "id": 17363, - "code": "xBIBLx", - "issuer": "rnH9o6qdym34K293Pq3FZp5Ko7SpZxEbeG", - "title": "Bibliomp", - "trustlines": 4441, - "placeInTop": 16 - }, - { - "id": 16600, - "code": "EUR", - "issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", - "title": "Gatehub", - "trustlines": 85461, - "placeInTop": 17 - }, - { - "id": 225, - "code": "XRdoge", - "issuer": "rLqUC2eCPohYvJCEBJ77eCCqVL2uEiczjA", - "title": "XRdoge Labs", - "trustlines": 26611, - "placeInTop": 18 - }, - { - "id": 99, - "code": "XDX", - "issuer": "rMJAXYsbNzhwp7FfYnAsYP5ty3R9XnurPo", - "title": "D.P.MonksFinance LTD", - "trustlines": 28866, - "placeInTop": 19 - }, - { - "id": 18469, - "code": "STX", - "issuer": "rSTAYKxF2K77ZLZ8GoAwTqPGaphAqMyXV", - "title": "StaykX", - "trustlines": 12098, - "placeInTop": 20 - } -] diff --git a/src/chains/xrpl/xrpl_markets.json b/src/templates/lists/xrpl_markets.json similarity index 100% rename from src/chains/xrpl/xrpl_markets.json rename to src/templates/lists/xrpl_markets.json diff --git a/src/chains/xrpl/xrpl_markets_devnet.json b/src/templates/lists/xrpl_markets_devnet.json similarity index 100% rename from src/chains/xrpl/xrpl_markets_devnet.json rename to src/templates/lists/xrpl_markets_devnet.json diff --git a/src/chains/xrpl/xrpl_markets_testnet.json b/src/templates/lists/xrpl_markets_testnet.json similarity index 100% rename from src/chains/xrpl/xrpl_markets_testnet.json rename to src/templates/lists/xrpl_markets_testnet.json diff --git a/src/chains/xrpl/xrpl_tokens.json b/src/templates/lists/xrpl_tokens.json similarity index 100% rename from src/chains/xrpl/xrpl_tokens.json rename to src/templates/lists/xrpl_tokens.json diff --git a/src/chains/xrpl/xrpl_tokens_devnet.json b/src/templates/lists/xrpl_tokens_devnet.json similarity index 100% rename from src/chains/xrpl/xrpl_tokens_devnet.json rename to src/templates/lists/xrpl_tokens_devnet.json diff --git a/src/chains/xrpl/xrpl_tokens_testnet.json b/src/templates/lists/xrpl_tokens_testnet.json similarity index 100% rename from src/chains/xrpl/xrpl_tokens_testnet.json rename to src/templates/lists/xrpl_tokens_testnet.json diff --git a/src/templates/xrpl.yml b/src/templates/xrpl.yml index a9cd319b78..11bfe42f2f 100644 --- a/src/templates/xrpl.yml +++ b/src/templates/xrpl.yml @@ -2,30 +2,30 @@ networks: mainnet: nodeURL: wss://xrplcluster.com/ tokenListType: "FILE" - tokenListSource: "src/chains/xrpl/xrpl_tokens.json" + tokenListSource: "/home/gateway/conf/lists/xrpl_tokens.json" marketListType: "FILE" - marketListSource: "src/chains/xrpl/xrpl_markets.json" + marketListSource: "/home/gateway/conf/lists/xrpl_markets.json" nativeCurrencySymbol: "XRP" testnet: nodeURL: wss://s.altnet.rippletest.net/ tokenListType: "FILE" - tokenListSource: "src/chains/xrpl/xrpl_tokens_testnet.json" + tokenListSource: "/home/gateway/conf/lists/xrpl_tokens_testnet.json" marketListType: "FILE" - marketListSource: "src/chains/xrpl/xrpl_markets_testnet.json" + marketListSource: "/home/gateway/conf/lists/xrpl_markets_testnet.json" nativeCurrencySymbol: "XRP" devnet: nodeURL: wss://s.devnet.rippletest.net/ tokenListType: "FILE" - tokenListSource: "src/chains/xrpl/xrpl_tokens_devnet.json" + tokenListSource: "/home/gateway/conf/lists/xrpl_tokens_devnet.json" marketListType: "FILE" - marketListSource: "src/chains/xrpl/xrpl_markets_devnet.json" + marketListSource: "/home/gateway/conf/lists/xrpl_markets_devnet.json" nativeCurrencySymbol: "XRP" network: "xrpl" requestTimeout: 2000 connectionTimeout: 2000 feeCushion: 1.2 maxFeeXRP: "2" -orderDbPath: "/db/xrpl.level" +orderDbPath: "db/xrpl.level" maxLRUCacheInstances: 10 retry: all: diff --git a/test/chains/xrpl/xrpl.routes.test.ts b/test/chains/xrpl/xrpl.routes.test.ts index 1acc4ad2db..53c70ded49 100644 --- a/test/chains/xrpl/xrpl.routes.test.ts +++ b/test/chains/xrpl/xrpl.routes.test.ts @@ -33,8 +33,8 @@ const patchDatabase = () => { beforeAll(async () => { xrplChain = XRPL.getInstance('testnet'); - await xrplChain.init(); patchDatabase(); + await xrplChain.init(); }); afterAll(async () => { diff --git a/test/connectors/xrpl/xrpl.e2e.test.ts b/test/connectors/xrpl/xrpl.e2e.test.ts index 74d07407f2..60d87dc21d 100644 --- a/test/connectors/xrpl/xrpl.e2e.test.ts +++ b/test/connectors/xrpl/xrpl.e2e.test.ts @@ -29,10 +29,13 @@ const patchWalletXRPL = () => { beforeAll(async () => { xrpl = XRPL.getInstance('testnet'); - await xrpl.init(); xrplCLOB = XRPLCLOB.getInstance('xrpl', 'testnet'); - await xrplCLOB.init(); patchWalletXRPL(); + await xrpl.init(); + await xrplCLOB.init(); + + // wait for 1 second to make sure the database is ready + await new Promise((resolve) => setTimeout(resolve, 1000)); }); beforeEach(() => { diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index 065013084b..07a806b3e5 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -286,15 +286,12 @@ const INVALID_REQUEST = { beforeAll(async () => { xrpl = XRPL.getInstance('testnet'); - patchConnect(); - // await xrpl.init(); xrplCLOB = XRPLCLOB.getInstance('xrpl', 'testnet'); - // await xrplCLOB.init(); + patchConnect(); }); // eslint-disable-next-line @typescript-eslint/no-empty-function beforeEach(() => { - patchConnect(); patchFee(); patchOrderTracking(); patchCurrentBlockNumber(); From 7b98601b675846b172ddb87dc76216aaa7aac2dc Mon Sep 17 00:00:00 2001 From: mlguys Date: Tue, 29 Aug 2023 23:39:47 +0700 Subject: [PATCH 34/39] fix client not disconect on close --- src/chains/xrpl/xrpl.ts | 1 + src/templates/xrpl.yml | 4 ++-- test/connectors/xrpl/xrpl.routes.test.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/chains/xrpl/xrpl.ts b/src/chains/xrpl/xrpl.ts index c569d19786..de63335f22 100644 --- a/src/chains/xrpl/xrpl.ts +++ b/src/chains/xrpl/xrpl.ts @@ -471,6 +471,7 @@ export class XRPL implements XRPLish { if (this._network in XRPL._instances) { await OrderTracker.stopTrackingOnAllInstancesForNetwork(this._network); await this._orderStorage.close(this._refCountingHandle); + await this._client.disconnect(); delete XRPL._instances[this._network]; } } diff --git a/src/templates/xrpl.yml b/src/templates/xrpl.yml index 11bfe42f2f..f689206695 100644 --- a/src/templates/xrpl.yml +++ b/src/templates/xrpl.yml @@ -7,14 +7,14 @@ networks: marketListSource: "/home/gateway/conf/lists/xrpl_markets.json" nativeCurrencySymbol: "XRP" testnet: - nodeURL: wss://s.altnet.rippletest.net/ + nodeURL: wss://s.altnet.rippletest.net:51233/ tokenListType: "FILE" tokenListSource: "/home/gateway/conf/lists/xrpl_tokens_testnet.json" marketListType: "FILE" marketListSource: "/home/gateway/conf/lists/xrpl_markets_testnet.json" nativeCurrencySymbol: "XRP" devnet: - nodeURL: wss://s.devnet.rippletest.net/ + nodeURL: wss://s.devnet.rippletest.net:51233/ tokenListType: "FILE" tokenListSource: "/home/gateway/conf/lists/xrpl_tokens_devnet.json" marketListType: "FILE" diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index 07a806b3e5..650d646e5d 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -292,6 +292,7 @@ beforeAll(async () => { // eslint-disable-next-line @typescript-eslint/no-empty-function beforeEach(() => { + patchConnect(); patchFee(); patchOrderTracking(); patchCurrentBlockNumber(); From 2a8a778349b730811da03fb4bbd8656eb1cf6a45 Mon Sep 17 00:00:00 2001 From: mlguys Date: Wed, 30 Aug 2023 00:32:20 +0700 Subject: [PATCH 35/39] fix tests --- src/chains/chain.routes.ts | 8 ++++---- src/chains/xrpl/xrpl.controllers.ts | 2 +- src/chains/xrpl/xrpl.order-storage.ts | 4 ---- src/connectors/xrpl/xrpl.ts | 4 ---- test/chains/xrpl/xrpl.routes.test.ts | 6 +++--- test/connectors/xrpl/xrpl.routes.test.ts | 9 --------- 6 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/chains/chain.routes.ts b/src/chains/chain.routes.ts index f53128a5af..f1678958d0 100644 --- a/src/chains/chain.routes.ts +++ b/src/chains/chain.routes.ts @@ -121,14 +121,14 @@ export namespace ChainRoutes { '/tokens', asyncHandler( async ( - req: Request<{}, {}, TokensRequest>, + req: Request<{}, {}, {}, TokensRequest>, res: Response ) => { const chain = await getInitializedChain( - req.body.chain as string, - req.body.network as string + req.query.chain as string, + req.query.network as string ); - res.status(200).json(await getTokens(chain, req.body)); + res.status(200).json(await getTokens(chain, req.query)); } ) ); diff --git a/src/chains/xrpl/xrpl.controllers.ts b/src/chains/xrpl/xrpl.controllers.ts index c8d74956e3..f632484fbf 100644 --- a/src/chains/xrpl/xrpl.controllers.ts +++ b/src/chains/xrpl/xrpl.controllers.ts @@ -93,7 +93,7 @@ export class XRPLController { ): Promise<{ tokens: TokenInfo[] }> { validateXRPLGetTokenRequest(req); let xrpTokens: XRPTokenInfo[] = []; - if (req.tokenSymbols?.length === 0) { + if (!req.tokenSymbols) { xrpTokens = xrplish.storedTokenList; } else { for (const t of req.tokenSymbols as []) { diff --git a/src/chains/xrpl/xrpl.order-storage.ts b/src/chains/xrpl/xrpl.order-storage.ts index d990c04429..24f1765da2 100644 --- a/src/chains/xrpl/xrpl.order-storage.ts +++ b/src/chains/xrpl/xrpl.order-storage.ts @@ -16,10 +16,6 @@ export class XRPLOrderStorage extends ReferenceCountingCloseable { public async init(): Promise { try { - console.log( - '🪧 -> file: xrpl.order-storage.ts:20 -> XRPLOrderStorage -> init -> this.storageStatus();:', - this.storageStatus() - ); await this.localStorage.init(); } catch (error) { throw new Error('Failed to initialize local storage: ' + error); diff --git a/src/connectors/xrpl/xrpl.ts b/src/connectors/xrpl/xrpl.ts index 836b264ea3..f065ee75e4 100644 --- a/src/connectors/xrpl/xrpl.ts +++ b/src/connectors/xrpl/xrpl.ts @@ -146,10 +146,6 @@ export class XRPLCLOB implements CLOBish { } async getMarket(market: MarketInfo): Promise { - console.log( - '🪧 -> file: xrpl.ts:148 -> XRPLCLOB -> getMarket -> market:', - market - ); if (!market) throw new MarketNotFoundError(`No market informed.`); let baseTickSize, baseTransferRate, diff --git a/test/chains/xrpl/xrpl.routes.test.ts b/test/chains/xrpl/xrpl.routes.test.ts index 53c70ded49..2c6d827f0d 100644 --- a/test/chains/xrpl/xrpl.routes.test.ts +++ b/test/chains/xrpl/xrpl.routes.test.ts @@ -97,16 +97,16 @@ describe('GET /chain/tokens', () => { it('should return 200 with correct parameters', async () => { const res = await request(gatewayApp) .get('/chain/tokens') - .send({ + .query({ chain: 'xrpl', network: 'testnet', - tokenSymbols: ['XRP'], + tokenSymbols: ['XRP', 'SOLO'], }); expect(res.statusCode).toEqual(200); }); it('should get unknown error with invalid tokenSymbols', async () => { - const res = await request(gatewayApp).get('/chain/tokens').send({ + const res = await request(gatewayApp).get('/chain/tokens').query({ chain: 'xrpl', network: 'testnet', tokenSymbols: 123, diff --git a/test/connectors/xrpl/xrpl.routes.test.ts b/test/connectors/xrpl/xrpl.routes.test.ts index 650d646e5d..cbd6da15be 100644 --- a/test/connectors/xrpl/xrpl.routes.test.ts +++ b/test/connectors/xrpl/xrpl.routes.test.ts @@ -497,11 +497,6 @@ describe('GET /clob/ticker', () => { .expect('Content-Type', /json/) .expect(200) .expect((res) => { - console.log( - '🪧 -> file: xrpl.routes.test.ts:497 -> .expect -> res:', - res.body - ); - expect(res.body.markets[0].baseCurrency).toEqual('USD'); }) .expect((res) => expect(res.body.markets[0].quoteCurrency).toEqual('XRP')) @@ -534,10 +529,6 @@ describe('GET /clob/orders', () => { .expect('Content-Type', /json/) .expect(200) .expect((res) => { - console.log( - '🪧 -> file: xrpl.routes.test.ts:535 -> .expect -> res:', - res.body - ); expect(res.body.orders.length).toEqual(1); }); }); From ceec75f09d61744075224f7801b96566da13672d Mon Sep 17 00:00:00 2001 From: mlguys Date: Thu, 7 Sep 2023 20:29:51 +0700 Subject: [PATCH 36/39] remove swagger doc changes --- docs/swagger/definitions.yml | 65 +++++++++++++++++++++--------------- docs/swagger/xrpl-routes.yml | 65 ------------------------------------ 2 files changed, 39 insertions(+), 91 deletions(-) delete mode 100644 docs/swagger/xrpl-routes.yml diff --git a/docs/swagger/definitions.yml b/docs/swagger/definitions.yml index b48628b7e6..13ff7ed182 100644 --- a/docs/swagger/definitions.yml +++ b/docs/swagger/definitions.yml @@ -35,7 +35,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' NonceResponse: type: 'object' @@ -70,7 +70,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' AllowancesResponse: type: 'object' @@ -189,7 +189,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' ApproveResponse: type: 'object' @@ -243,7 +243,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' PollResponse: type: 'object' @@ -328,7 +328,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' PriceResponse: type: 'object' @@ -418,7 +418,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' connector: type: 'string' example: 'uniswap' @@ -496,7 +496,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' CancelResponse: type: 'object' @@ -518,19 +518,32 @@ definitions: type: 'string' example: '0xa321bbe8888c3bc88ecb1ad4f03f22a71e6f5715dfcb19e0a2dca9036c981b6d' # noqa: documentation - GetWalletRequest: + AddWalletRequest: type: 'object' required: - - 'chain' - - 'network' + - 'chainName' + - 'privateKey' properties: - chain: + chainName: type: 'string' example: 'ethereum' - network: + privateKey: type: 'string' - example: 'testnet' - + example: '6078d949c953351685fd2026646028f2a862e6148d25d504967ba63898d720c0' # noqa: documentation + + RemoveWalletRequest: + type: 'object' + required: + - 'chainName' + - 'address' + properties: + chainName: + type: 'string' + example: 'ethereum' + address: + type: 'string' + example: '0xd0A1E359811322d97991E03f863a0C30C2cF029C' + GetWalletResponse: type: 'object' required: @@ -626,7 +639,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' connector: type: 'string' example: 'uniswap' @@ -702,7 +715,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' connector: type: 'string' example: 'uniswapLP' @@ -722,7 +735,7 @@ definitions: properties: network: type: 'string' - example: 'kovan' + example: 'goerli' timestamp: type: 'integer' example: 1636368085740 @@ -768,7 +781,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' connector: type: 'string' example: 'uniswapLP' @@ -787,7 +800,7 @@ definitions: properties: network: type: 'string' - example: 'kovan' + example: 'goerli' timestamp: type: 'integer' example: 1636368085740 @@ -830,7 +843,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' connector: type: 'string' example: 'uniswapLP' @@ -849,7 +862,7 @@ definitions: properties: network: type: 'string' - example: 'kovan' + example: 'goerli' timestamp: type: 'integer' example: 1636368085740 @@ -884,7 +897,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' connector: type: 'string' example: 'uniswapLP' @@ -907,7 +920,7 @@ definitions: properties: network: type: 'string' - example: 'kovan' + example: 'goerli' timestamp: type: 'integer' example: 1636368085740 @@ -971,7 +984,7 @@ definitions: example: 'ethereum' network: type: 'string' - example: 'kovan' + example: 'goerli' connector: type: 'string' example: 'uniswapLP' @@ -989,7 +1002,7 @@ definitions: properties: network: type: 'string' - example: 'kovan' + example: 'goerli' timestamp: type: 'integer' example: 1636368085740 @@ -2090,4 +2103,4 @@ definitions: example: 0.5 txHash: type: 'string' - example: '0x...' + example: '0x...' \ No newline at end of file diff --git a/docs/swagger/xrpl-routes.yml b/docs/swagger/xrpl-routes.yml deleted file mode 100644 index 09222bb935..0000000000 --- a/docs/swagger/xrpl-routes.yml +++ /dev/null @@ -1,65 +0,0 @@ -paths: - /xrpl: - get: - tags: - - 'xrpl' - summary: 'View the XRPL network and RPC URL that gateway is configured to use' - description: 'The user can change this by editing src/chains/xrpl/xrpl.config.ts' - operationId: 'root' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - required: true - schema: - $ref: '#/definitions/XRPLConfigRequest' - responses: - '200': - description: 'XRPL config' - schema: - $ref: '#/definitions/XRPLConfigResponse' - - /xrpl/balances: - get: - tags: - - 'xrpl' - summary: 'Get the balances of an XRPL secret key' - operationId: 'balances' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - required: true - schema: - $ref: '#/definitions/XRPLBalancesRequest' - responses: - '200': - description: 'XRPL wallet balances' - schema: - $ref: '#/definitions/XRPLBalancesResponse' - - /xrpl/poll: - get: - tags: - - 'xrpl' - summary: 'Get transaction details from transaction hash' - operationId: 'poll' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - required: true - schema: - $ref: '#/definitions/XRPLPollRequest' - responses: - '200': - description: 'XRPL transaction details' - schema: - $ref: '#/definitions/XRPLPollResponse' From a1a1ef5a9a111d7956b9a6cde91bcd5a6765773b Mon Sep 17 00:00:00 2001 From: mlguys Date: Fri, 10 Nov 2023 23:40:49 +0700 Subject: [PATCH 37/39] fix UNKNOW state due to double offer cancel --- src/chains/xrpl/xrpl.order-tracker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chains/xrpl/xrpl.order-tracker.ts b/src/chains/xrpl/xrpl.order-tracker.ts index 7e3239cda6..61a2c2f3c8 100644 --- a/src/chains/xrpl/xrpl.order-tracker.ts +++ b/src/chains/xrpl/xrpl.order-tracker.ts @@ -819,7 +819,7 @@ export class OrderTracker { if (deleteNodeCount === 0) { intents.push({ - type: TransactionIntentType.UNKNOWN, + type: TransactionIntentType.OFFER_CANCEL_FINALIZED, sequence: transaction.transaction.OfferSequence, tx: transaction, }); From b84f8d703c70111ad4a1c95de379b837a8389b34 Mon Sep 17 00:00:00 2001 From: mlguys Date: Sat, 11 Nov 2023 11:01:58 +0700 Subject: [PATCH 38/39] update test --- test/chains/xrpl/xrpl.routes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/chains/xrpl/xrpl.routes.test.ts b/test/chains/xrpl/xrpl.routes.test.ts index 2c6d827f0d..2dc46cce09 100644 --- a/test/chains/xrpl/xrpl.routes.test.ts +++ b/test/chains/xrpl/xrpl.routes.test.ts @@ -78,7 +78,7 @@ describe('POST /chain/poll', () => { chain: 'xrpl', network: 'testnet', txHash: - 'EF074CD8C98E639B3560200C5664029E4E7133D4803EF75F6991788E09E04CDB', // noqa: mock + 'A4A9E7C76ACA5527AC09A7540F263CA48FF40F5CFE04DD19B7173B05E3685077', // noqa: mock }); expect(res.statusCode).toEqual(200); }); From 6f6bd8f505ec671964b76b61f6ca0c9b44faa42b Mon Sep 17 00:00:00 2001 From: mlguys Date: Mon, 13 Nov 2023 19:50:44 +0700 Subject: [PATCH 39/39] update yarn --- yarn.lock | 507 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 308 insertions(+), 199 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0628f1363f..ff58fc4637 100644 --- a/yarn.lock +++ b/yarn.lock @@ -852,137 +852,137 @@ "@ethersproject-xdc/abi@file:vendor/@ethersproject-xdc/abi": version "5.7.0" dependencies: - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/hash" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-6024f5a3-4abc-4355-baca-a47ac7bfd18b-1699544273447/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abi-5.7.0-287c0d50-e686-4800-920b-152fa5ff8db6-1699634734483/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/abstract-provider@file:vendor/@ethersproject-xdc/abstract-provider": version "5.7.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-1ad41686-4fd5-4795-926c-6421b898d298-1699544273445/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-1ad41686-4fd5-4795-926c-6421b898d298-1699544273445/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-1ad41686-4fd5-4795-926c-6421b898d298-1699544273445/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/networks" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-1ad41686-4fd5-4795-926c-6421b898d298-1699544273445/node_modules/@ethersproject-xdc/networks" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-1ad41686-4fd5-4795-926c-6421b898d298-1699544273445/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/transactions" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-1ad41686-4fd5-4795-926c-6421b898d298-1699544273445/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/web" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-1ad41686-4fd5-4795-926c-6421b898d298-1699544273445/node_modules/@ethersproject-xdc/web" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-92e3e4e9-3421-40a0-b31e-9cf6b7a379cd-1699634734482/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-92e3e4e9-3421-40a0-b31e-9cf6b7a379cd-1699634734482/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-92e3e4e9-3421-40a0-b31e-9cf6b7a379cd-1699634734482/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/networks" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-92e3e4e9-3421-40a0-b31e-9cf6b7a379cd-1699634734482/node_modules/@ethersproject-xdc/networks" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-92e3e4e9-3421-40a0-b31e-9cf6b7a379cd-1699634734482/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-92e3e4e9-3421-40a0-b31e-9cf6b7a379cd-1699634734482/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/web" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-provider-5.7.0-92e3e4e9-3421-40a0-b31e-9cf6b7a379cd-1699634734482/node_modules/@ethersproject-xdc/web" "@ethersproject-xdc/abstract-signer@file:vendor/@ethersproject-xdc/abstract-signer": version "5.7.0" dependencies: - "@ethersproject-xdc/abstract-provider" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-97ee1767-f83d-4ffd-8218-9fadfa989527-1699544273460/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-97ee1767-f83d-4ffd-8218-9fadfa989527-1699544273460/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-97ee1767-f83d-4ffd-8218-9fadfa989527-1699544273460/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-97ee1767-f83d-4ffd-8218-9fadfa989527-1699544273460/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-97ee1767-f83d-4ffd-8218-9fadfa989527-1699544273460/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-b9022cdf-c441-4815-890a-c5d86ec24270-1699634734493/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-b9022cdf-c441-4815-890a-c5d86ec24270-1699634734493/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-b9022cdf-c441-4815-890a-c5d86ec24270-1699634734493/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-b9022cdf-c441-4815-890a-c5d86ec24270-1699634734493/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-abstract-signer-5.7.0-b9022cdf-c441-4815-890a-c5d86ec24270-1699634734493/node_modules/@ethersproject-xdc/properties" "@ethersproject-xdc/address@file:vendor/@ethersproject-xdc/address": version "5.7.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-ab4a4ae2-080c-442f-90fe-ad26856dcba2-1699544273448/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-ab4a4ae2-080c-442f-90fe-ad26856dcba2-1699544273448/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-ab4a4ae2-080c-442f-90fe-ad26856dcba2-1699544273448/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-ab4a4ae2-080c-442f-90fe-ad26856dcba2-1699544273448/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/rlp" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-ab4a4ae2-080c-442f-90fe-ad26856dcba2-1699544273448/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-e6716448-8911-4f5e-a65d-65e3acfcd179-1699634734482/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-e6716448-8911-4f5e-a65d-65e3acfcd179-1699634734482/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-e6716448-8911-4f5e-a65d-65e3acfcd179-1699634734482/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-e6716448-8911-4f5e-a65d-65e3acfcd179-1699634734482/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-address-5.7.0-e6716448-8911-4f5e-a65d-65e3acfcd179-1699634734482/node_modules/@ethersproject-xdc/rlp" "@ethersproject-xdc/base64@file:vendor/@ethersproject-xdc/base64": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-base64-5.7.0-3edb9e13-78d1-44a6-a4a4-b0902d040be8-1699544273461/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-base64-5.7.0-4e3fb767-c10b-4a30-8b66-8e53e46401d4-1699634734482/node_modules/@ethersproject-xdc/bytes" "@ethersproject-xdc/basex@file:vendor/@ethersproject-xdc/basex": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-1cbdd2ae-8e29-45b4-916c-10c23b6f6633-1699544273456/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-1cbdd2ae-8e29-45b4-916c-10c23b6f6633-1699544273456/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-09a3bcd9-c23f-4413-bb28-eed63f88a9cc-1699634734483/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-basex-5.7.0-09a3bcd9-c23f-4413-bb28-eed63f88a9cc-1699634734483/node_modules/@ethersproject-xdc/properties" "@ethersproject-xdc/bignumber@file:vendor/@ethersproject-xdc/bignumber": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-b01798d9-e09b-49f9-8495-74cfa2dbf802-1699544273448/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-b01798d9-e09b-49f9-8495-74cfa2dbf802-1699544273448/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-28268744-9acf-4da6-8f10-d9dec87c51c4-1699634734484/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bignumber-5.7.0-28268744-9acf-4da6-8f10-d9dec87c51c4-1699634734484/node_modules/@ethersproject-xdc/logger" bn.js "^5.2.1" "@ethersproject-xdc/bytes@file:vendor/@ethersproject-xdc/bytes": version "5.7.0" dependencies: - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bytes-5.7.0-207e3a6d-14a2-46b9-852f-8aef97f5174d-1699544273449/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-bytes-5.7.0-ce017954-2869-49fd-bcec-dbcdbfaecf1d-1699634734483/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/constants@file:vendor/@ethersproject-xdc/constants": version "5.7.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-constants-5.7.0-c2d7c3df-0dd6-463b-9211-45fd3cf45c5d-1699544273450/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-constants-5.7.0-41a840bb-9ea3-4d3f-8806-1e61d36e0bd4-1699634734484/node_modules/@ethersproject-xdc/bignumber" "@ethersproject-xdc/contracts@file:vendor/@ethersproject-xdc/contracts": version "5.6.0" dependencies: - "@ethersproject-xdc/abi" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/abi" - "@ethersproject-xdc/abstract-provider" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/transactions" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-44fb8195-094f-436d-88d3-320713758986-1699544273450/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/abi" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/abi" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-contracts-5.6.0-62798fb4-3faa-4751-abee-660e3462b5c9-1699634734485/node_modules/@ethersproject-xdc/transactions" "@ethersproject-xdc/hash@file:vendor/@ethersproject-xdc/hash": version "5.7.0" dependencies: - "@ethersproject-xdc/abstract-signer" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/base64" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/base64" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-f05bfe44-9693-4592-8e89-837c11da3276-1699544273453/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/base64" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/base64" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hash-5.7.0-77045ac3-1f59-4b84-9c07-4095c6736872-1699634734486/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/hdnode@file:vendor/@ethersproject-xdc/hdnode": version "5.7.0" dependencies: - "@ethersproject-xdc/abstract-signer" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/basex" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/basex" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/pbkdf2" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/pbkdf2" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/sha2" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/signing-key" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/signing-key" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/wordlists" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-f9bfc6ca-4234-4269-838e-07288ce5d698-1699544273451/node_modules/@ethersproject-xdc/wordlists" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/basex" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/basex" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/pbkdf2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/pbkdf2" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/wordlists" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-hdnode-5.7.0-715a8c42-da98-4c82-b9ea-26c30ef7e988-1699634734484/node_modules/@ethersproject-xdc/wordlists" "@ethersproject-xdc/json-wallets@file:vendor/@ethersproject-xdc/json-wallets": version "5.6.0" dependencies: - "@ethersproject-xdc/abstract-signer" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/hdnode" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/hdnode" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/pbkdf2" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/pbkdf2" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/random" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-eac8a495-e58f-4d5f-9d9f-72ae58a0c671-1699544273452/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/hdnode" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/hdnode" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/pbkdf2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/pbkdf2" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-json-wallets-5.6.0-1df4b504-3290-418d-8b4f-69bd934dbec3-1699634734487/node_modules/@ethersproject-xdc/transactions" aes-js "3.0.0" scrypt-js "3.0.1" "@ethersproject-xdc/keccak256@file:vendor/@ethersproject-xdc/keccak256": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-keccak256-5.7.0-9f7c9c00-4882-47e7-af88-68002fea0312-1699544273454/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-keccak256-5.7.0-2cdf78e6-6a14-4ee9-b747-18aa062a2b7f-1699634734485/node_modules/@ethersproject-xdc/bytes" js-sha3 "0.8.0" "@ethersproject-xdc/logger@file:vendor/@ethersproject-xdc/logger": @@ -991,67 +991,67 @@ "@ethersproject-xdc/networks@file:vendor/@ethersproject-xdc/networks": version "5.7.1" dependencies: - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-networks-5.7.1-c48b1620-44b0-4a44-b439-99411f1272da-1699544273455/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-networks-5.7.1-0f0a16ba-62e4-449a-9f91-8df2a3ab6acb-1699634734486/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/pbkdf2@file:vendor/@ethersproject-xdc/pbkdf2": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-5a026cfb-3828-4b64-97bd-950cfac1e2cc-1699544273455/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/sha2" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-5a026cfb-3828-4b64-97bd-950cfac1e2cc-1699544273455/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-56c0f784-4604-412c-9d85-e0dde7a3fdd6-1699634734486/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-pbkdf2-5.7.0-56c0f784-4604-412c-9d85-e0dde7a3fdd6-1699634734486/node_modules/@ethersproject-xdc/sha2" "@ethersproject-xdc/properties@file:vendor/@ethersproject-xdc/properties": version "5.7.0" dependencies: - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-properties-5.7.0-89d95167-4d85-4cfa-8843-190ca73c7f59-1699544273456/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-properties-5.7.0-42c6e8d5-47b9-45f3-9dad-7d0ecd34501d-1699634734491/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/providers@file:vendor/@ethersproject-xdc/providers": version "5.6.2" dependencies: - "@ethersproject-xdc/abstract-provider" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/basex" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/basex" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/hash" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/networks" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/networks" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/random" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/rlp" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/rlp" - "@ethersproject-xdc/sha2" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/web" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-4b268051-4b7a-4f4c-a0fc-0301f3467e78-1699544273457/node_modules/@ethersproject-xdc/web" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/basex" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/basex" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/networks" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/networks" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/web" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-providers-5.6.2-959f8d02-d8b6-4ff3-b2d9-ce154ac5bc9c-1699634734492/node_modules/@ethersproject-xdc/web" bech32 "1.1.4" ws "7.4.6" "@ethersproject-xdc/random@file:vendor/@ethersproject-xdc/random": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-random-5.7.0-7424bdb5-2e73-48f3-b490-905f21b24e45-1699544273458/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-random-5.7.0-7424bdb5-2e73-48f3-b490-905f21b24e45-1699544273458/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-random-5.7.0-ed14b7f0-b6c8-4fcd-8866-0a66cd5865c7-1699634734488/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-random-5.7.0-ed14b7f0-b6c8-4fcd-8866-0a66cd5865c7-1699634734488/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/rlp@file:vendor/@ethersproject-xdc/rlp": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-93a5aa38-1443-454e-9d44-a19920f2b43d-1699544273460/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-93a5aa38-1443-454e-9d44-a19920f2b43d-1699544273460/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-ae62a9ab-e8f9-4238-8d42-551968b1ce05-1699634734487/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-rlp-5.7.0-ae62a9ab-e8f9-4238-8d42-551968b1ce05-1699634734487/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/sha2@file:vendor/@ethersproject-xdc/sha2": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-04080786-9a93-4103-bd1b-64d6343596ac-1699544273458/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-04080786-9a93-4103-bd1b-64d6343596ac-1699544273458/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-a4d8940c-fe93-4b5e-998a-8695e64c0b76-1699634734487/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-sha2-5.7.0-a4d8940c-fe93-4b5e-998a-8695e64c0b76-1699634734487/node_modules/@ethersproject-xdc/logger" hash.js "1.1.7" "@ethersproject-xdc/signing-key@file:vendor/@ethersproject-xdc/signing-key": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-1a642aca-614e-460e-b055-7788ae2e7448-1699544273458/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-1a642aca-614e-460e-b055-7788ae2e7448-1699544273458/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-1a642aca-614e-460e-b055-7788ae2e7448-1699544273458/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-3c60aa4c-b33d-49f0-84db-6e6686665ddc-1699634734488/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-3c60aa4c-b33d-49f0-84db-6e6686665ddc-1699634734488/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-signing-key-5.7.0-3c60aa4c-b33d-49f0-84db-6e6686665ddc-1699634734488/node_modules/@ethersproject-xdc/properties" bn.js "^5.2.1" elliptic "6.5.4" hash.js "1.1.7" @@ -1059,76 +1059,76 @@ "@ethersproject-xdc/solidity@file:vendor/@ethersproject-xdc/solidity": version "5.6.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-f125853a-9fd1-4f69-aea7-827d2f84dc83-1699544273459/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-f125853a-9fd1-4f69-aea7-827d2f84dc83-1699544273459/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-f125853a-9fd1-4f69-aea7-827d2f84dc83-1699544273459/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-f125853a-9fd1-4f69-aea7-827d2f84dc83-1699544273459/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/sha2" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-f125853a-9fd1-4f69-aea7-827d2f84dc83-1699544273459/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-f125853a-9fd1-4f69-aea7-827d2f84dc83-1699544273459/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-896ca8ac-16c6-480a-91f7-48df2d131d5b-1699634734491/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-896ca8ac-16c6-480a-91f7-48df2d131d5b-1699634734491/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-896ca8ac-16c6-480a-91f7-48df2d131d5b-1699634734491/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-896ca8ac-16c6-480a-91f7-48df2d131d5b-1699634734491/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-896ca8ac-16c6-480a-91f7-48df2d131d5b-1699634734491/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-solidity-5.6.0-896ca8ac-16c6-480a-91f7-48df2d131d5b-1699634734491/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/strings@file:vendor/@ethersproject-xdc/strings": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-4229a395-6e0d-4f43-8b05-3c70686b0fea-1699544273459/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-4229a395-6e0d-4f43-8b05-3c70686b0fea-1699544273459/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-4229a395-6e0d-4f43-8b05-3c70686b0fea-1699544273459/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-3e4e37ca-7151-4f71-af90-693c3837df68-1699634734493/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-3e4e37ca-7151-4f71-af90-693c3837df68-1699634734493/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-strings-5.7.0-3e4e37ca-7151-4f71-af90-693c3837df68-1699634734493/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/transactions@file:vendor/@ethersproject-xdc/transactions": version "5.7.0" dependencies: - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/rlp" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/rlp" - "@ethersproject-xdc/signing-key" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-9d64ec01-3a66-4485-9528-3eafba97c901-1699544273484/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-transactions-5.7.0-d564341d-9fae-457e-9585-66b8e92484d0-1699634734491/node_modules/@ethersproject-xdc/signing-key" "@ethersproject-xdc/units@file:vendor/@ethersproject-xdc/units": version "5.6.0" dependencies: - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-ab57d87f-807a-49ea-88ee-83dbea457287-1699544273460/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/constants" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-ab57d87f-807a-49ea-88ee-83dbea457287-1699544273460/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-ab57d87f-807a-49ea-88ee-83dbea457287-1699544273460/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-05c7f203-75b5-4112-aa4d-447f2440f983-1699634734491/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-05c7f203-75b5-4112-aa4d-447f2440f983-1699634734491/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-units-5.6.0-05c7f203-75b5-4112-aa4d-447f2440f983-1699634734491/node_modules/@ethersproject-xdc/logger" "@ethersproject-xdc/wallet@file:vendor/@ethersproject-xdc/wallet": version "5.6.0" dependencies: - "@ethersproject-xdc/abstract-provider" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/hash" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/hdnode" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/hdnode" - "@ethersproject-xdc/json-wallets" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/json-wallets" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/random" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/signing-key" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/signing-key" - "@ethersproject-xdc/transactions" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/wordlists" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-7c3313db-ea38-40c5-b50a-2df1438986cf-1699544273469/node_modules/@ethersproject-xdc/wordlists" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/hdnode" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/hdnode" + "@ethersproject-xdc/json-wallets" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/json-wallets" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/wordlists" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wallet-5.6.0-04f8d38c-9f2e-4fe7-82c0-b07f8b350d9d-1699634734493/node_modules/@ethersproject-xdc/wordlists" "@ethersproject-xdc/web@file:vendor/@ethersproject-xdc/web": version "5.7.1" dependencies: - "@ethersproject-xdc/base64" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-0482b02a-dd1f-4617-aea3-5edfed9bd0a9-1699544273462/node_modules/@ethersproject-xdc/base64" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-0482b02a-dd1f-4617-aea3-5edfed9bd0a9-1699544273462/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-0482b02a-dd1f-4617-aea3-5edfed9bd0a9-1699544273462/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-0482b02a-dd1f-4617-aea3-5edfed9bd0a9-1699544273462/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-0482b02a-dd1f-4617-aea3-5edfed9bd0a9-1699544273462/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/base64" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-d876a610-ed36-4716-b8fe-49ddd2cbe0e3-1699634734492/node_modules/@ethersproject-xdc/base64" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-d876a610-ed36-4716-b8fe-49ddd2cbe0e3-1699634734492/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-d876a610-ed36-4716-b8fe-49ddd2cbe0e3-1699634734492/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-d876a610-ed36-4716-b8fe-49ddd2cbe0e3-1699634734492/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-web-5.7.1-d876a610-ed36-4716-b8fe-49ddd2cbe0e3-1699634734492/node_modules/@ethersproject-xdc/strings" "@ethersproject-xdc/wordlists@file:vendor/@ethersproject-xdc/wordlists": version "5.7.0" dependencies: - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-fc750c66-4553-4357-8d7b-b2166642ef5f-1699544273461/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/hash" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-fc750c66-4553-4357-8d7b-b2166642ef5f-1699544273461/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-fc750c66-4553-4357-8d7b-b2166642ef5f-1699544273461/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-fc750c66-4553-4357-8d7b-b2166642ef5f-1699544273461/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-fc750c66-4553-4357-8d7b-b2166642ef5f-1699544273461/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-d4cc8dd3-704f-4755-9d16-05dbac42642d-1699634734495/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-d4cc8dd3-704f-4755-9d16-05dbac42642d-1699634734495/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-d4cc8dd3-704f-4755-9d16-05dbac42642d-1699634734495/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-d4cc8dd3-704f-4755-9d16-05dbac42642d-1699634734495/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-@ethersproject-xdc-wordlists-5.7.0-d4cc8dd3-704f-4755-9d16-05dbac42642d-1699634734495/node_modules/@ethersproject-xdc/strings" "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": version "5.7.0" @@ -3728,6 +3728,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.3.tgz#f0b991c32cfc6a4e7f3399d6cb4b8cf9a0315014" integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== +"@types/node@10.12.18": + version "10.12.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" + integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== + "@types/node@11.11.6": version "11.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" @@ -4700,6 +4705,17 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== +assert@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -4878,7 +4894,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-x@^3.0.2, base-x@^3.0.6, base-x@^3.0.8: +base-x@^3.0.2, base-x@^3.0.6, base-x@^3.0.8, base-x@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== @@ -4932,6 +4948,11 @@ bech32@^2.0.0: resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== +big-integer@^1.6.48: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.1.1.tgz#63b35b19dc9775c94991ee5db7694880655d5537" @@ -4983,6 +5004,19 @@ bindings@^1.3.0, bindings@^1.3.1, bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bip32@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134" + integrity sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA== + dependencies: + "@types/node" "10.12.18" + bs58check "^2.1.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + tiny-secp256k1 "^1.1.3" + typeforce "^1.11.5" + wif "^2.0.6" + bip39@3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" @@ -5153,7 +5187,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1, brorand@^1.1.0: +brorand@^1.0.1, brorand@^1.0.5, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== @@ -5261,7 +5295,7 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" -bs58check@^2.1.2: +bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== @@ -5323,6 +5357,14 @@ buffer@6.0.1: base64-js "^1.3.1" ieee754 "^1.2.1" +buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -5331,14 +5373,6 @@ buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3, buffer@~6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - bufferutil@4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.5.tgz#da9ea8166911cc276bf677b8aed2d02d31f59028" @@ -6136,7 +6170,7 @@ decimal.js-light@^2.5.0, decimal.js-light@^2.5.1: resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -decimal.js@^10.2.1, decimal.js@^10.3.1, decimal.js@^10.4.3: +decimal.js@^10.2.0, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -7087,36 +7121,36 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: "ethers-xdc@file:./vendor/ethers-xdc": version "5.7.2" dependencies: - "@ethersproject-xdc/abi" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/abi" - "@ethersproject-xdc/abstract-provider" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/abstract-provider" - "@ethersproject-xdc/abstract-signer" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/abstract-signer" - "@ethersproject-xdc/address" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/address" - "@ethersproject-xdc/base64" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/base64" - "@ethersproject-xdc/basex" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/basex" - "@ethersproject-xdc/bignumber" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/bignumber" - "@ethersproject-xdc/bytes" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/bytes" - "@ethersproject-xdc/constants" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/constants" - "@ethersproject-xdc/contracts" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/contracts" - "@ethersproject-xdc/hash" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/hash" - "@ethersproject-xdc/hdnode" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/hdnode" - "@ethersproject-xdc/json-wallets" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/json-wallets" - "@ethersproject-xdc/keccak256" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/keccak256" - "@ethersproject-xdc/logger" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/logger" - "@ethersproject-xdc/networks" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/networks" - "@ethersproject-xdc/pbkdf2" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/pbkdf2" - "@ethersproject-xdc/properties" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/properties" - "@ethersproject-xdc/providers" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/providers" - "@ethersproject-xdc/random" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/random" - "@ethersproject-xdc/rlp" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/rlp" - "@ethersproject-xdc/sha2" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/sha2" - "@ethersproject-xdc/signing-key" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/signing-key" - "@ethersproject-xdc/solidity" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/solidity" - "@ethersproject-xdc/strings" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/strings" - "@ethersproject-xdc/transactions" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/transactions" - "@ethersproject-xdc/units" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/units" - "@ethersproject-xdc/wallet" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/wallet" - "@ethersproject-xdc/web" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/web" - "@ethersproject-xdc/wordlists" "file:../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-ce429709-1187-444e-814a-9d6b5ba0a9c2-1699544273425/node_modules/@ethersproject-xdc/wordlists" + "@ethersproject-xdc/abi" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/abi" + "@ethersproject-xdc/abstract-provider" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/abstract-provider" + "@ethersproject-xdc/abstract-signer" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/abstract-signer" + "@ethersproject-xdc/address" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/address" + "@ethersproject-xdc/base64" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/base64" + "@ethersproject-xdc/basex" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/basex" + "@ethersproject-xdc/bignumber" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/bignumber" + "@ethersproject-xdc/bytes" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/bytes" + "@ethersproject-xdc/constants" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/constants" + "@ethersproject-xdc/contracts" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/contracts" + "@ethersproject-xdc/hash" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/hash" + "@ethersproject-xdc/hdnode" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/hdnode" + "@ethersproject-xdc/json-wallets" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/json-wallets" + "@ethersproject-xdc/keccak256" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/keccak256" + "@ethersproject-xdc/logger" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/logger" + "@ethersproject-xdc/networks" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/networks" + "@ethersproject-xdc/pbkdf2" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/pbkdf2" + "@ethersproject-xdc/properties" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/properties" + "@ethersproject-xdc/providers" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/providers" + "@ethersproject-xdc/random" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/random" + "@ethersproject-xdc/rlp" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/rlp" + "@ethersproject-xdc/sha2" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/sha2" + "@ethersproject-xdc/signing-key" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/signing-key" + "@ethersproject-xdc/solidity" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/solidity" + "@ethersproject-xdc/strings" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/strings" + "@ethersproject-xdc/transactions" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/transactions" + "@ethersproject-xdc/units" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/units" + "@ethersproject-xdc/wallet" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/wallet" + "@ethersproject-xdc/web" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/web" + "@ethersproject-xdc/wordlists" "file:../../../../Library/Caches/Yarn/v6/npm-ethers-xdc-5.7.2-4772872e-6532-4c2d-a2e3-c5d7db383f9b-1699634734469/node_modules/@ethersproject-xdc/wordlists" ethers@4.0.0-beta.3: version "4.0.0-beta.3" @@ -8837,6 +8871,14 @@ is-lower-case@^1.1.0: dependencies: lower-case "^1.1.0" +is-nan@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -10169,7 +10211,7 @@ lodash.values@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" integrity sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q== -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.2.1, lodash@^4.7.0: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -10838,10 +10880,10 @@ nan@2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== -nan@^2.14.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nan@^2.13.2: + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== nan@^2.14.0: version "2.16.0" @@ -11141,6 +11183,14 @@ object-inspect@^1.12.3, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -12429,6 +12479,37 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: hash-base "^3.0.0" inherits "^2.0.1" +ripple-address-codec@^4.2.5, ripple-address-codec@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.3.1.tgz#68fbaf646bb8567f70743af7f1ce4479f73efbf6" + integrity sha512-Qa3+9wKVvpL/xYtT6+wANsn0A1QcC5CT6IMZbRJZ/1lGt7gmwIfsrCuz1X0+LCEO7zgb+3UT1I1dc0k/5dwKQQ== + dependencies: + base-x "^3.0.9" + create-hash "^1.1.2" + +ripple-binary-codec@^1.4.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.10.0.tgz#549f7fb3d3faf6b2d09fe7032bdcc4e6f8b5a511" + integrity sha512-qWXxubgXBV3h5NTaaLiusZ1FhPqSy+bCYHHarfZ3bMmO2alRa1Ox61jvX1Zyozok8PcF3gs3bKwZci4RTlA07w== + dependencies: + assert "^2.0.0" + big-integer "^1.6.48" + buffer "6.0.3" + create-hash "^1.2.0" + decimal.js "^10.2.0" + ripple-address-codec "^4.3.1" + +ripple-keypairs@^1.1.5: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.3.1.tgz#7fa531df36b138134afb53555a87d7f5eb465b2e" + integrity sha512-dmPlraWKJciFJxHcoubDahGnoIalG5e/BtV6HNDUs7wLXmtnLMHt6w4ed9R8MTL2zNrVPiIdI/HCtMMo0Tm7JQ== + dependencies: + bn.js "^5.1.1" + brorand "^1.0.5" + elliptic "^6.5.4" + hash.js "^1.0.3" + ripple-address-codec "^4.3.1" + rlp@^2.2.3, rlp@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" @@ -13506,6 +13587,17 @@ tiny-invariant@^1.1.0: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== +tiny-secp256k1@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c" + integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA== + dependencies: + bindings "^1.3.0" + bn.js "^4.11.8" + create-hmac "^1.1.7" + elliptic "^6.4.0" + nan "^2.13.2" + tiny-typed-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" @@ -13857,6 +13949,11 @@ typedarray-to-buffer@^4.0.0: resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz#cdd2933c61dd3f5f02eda5d012d441f95bfeb50a" integrity sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ== +typeforce@^1.11.5: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + typescript-compare@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425" @@ -14779,6 +14876,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wif@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== + dependencies: + bs58check "<3.0.0" + winston-daily-rotate-file@^4.5.5: version "4.7.1" resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz#f60a643af87f8867f23170d8cd87dbe3603a625f" @@ -14888,6 +14992,11 @@ ws@^7, ws@^7.2.0, ws@^7.4.5, ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@^8.2.2: + version "8.14.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" + integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== + ws@^8.5.0: version "8.13.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"