From 5efe14d4023647d7ba93382369df6ae289f33e4b Mon Sep 17 00:00:00 2001 From: just-a-node Date: Wed, 3 Apr 2024 10:07:59 -0600 Subject: [PATCH 01/18] feat: add enoughRouterLiquidity --- packages/agents/sdk-wrapper/src/sdkUtils.ts | 19 +++- .../agents/sdk-wrapper/test/sdkUtils.spec.ts | 28 +++++ packages/agents/sdk/src/interfaces/index.ts | 9 ++ packages/agents/sdk/src/sdkUtils.ts | 49 ++++++++- packages/agents/sdk/test/sdkUtils.spec.ts | 101 ++++++++++++++++++ .../examples/sdk-server/examples/index.http | 12 +++ .../examples/sdk-server/src/routes/utils.ts | 16 +++ 7 files changed, 232 insertions(+), 2 deletions(-) diff --git a/packages/agents/sdk-wrapper/src/sdkUtils.ts b/packages/agents/sdk-wrapper/src/sdkUtils.ts index a65810841d..d8241ba690 100644 --- a/packages/agents/sdk-wrapper/src/sdkUtils.ts +++ b/packages/agents/sdk-wrapper/src/sdkUtils.ts @@ -1,4 +1,4 @@ -import { BigNumber } from "ethers"; +import { BigNumber, BigNumberish } from "ethers"; import { Logger, ChainData, XTransferStatus, XTransferErrorStatus } from "@connext/nxtp-utils"; import type { SdkConfig, RouterBalance, Transfer } from "./sdk-types"; @@ -58,4 +58,21 @@ export class SdkUtils extends SdkShared { return BigNumber.from(response.data); } + + async enoughRouterLiquidity( + domainId: string, + asset: string, + minLiquidity: BigNumberish, + maxN?: number + ): Promise { + const params: { domainId: string; asset: string; minLiquidity: BigNumberish, maxN?: number } = { + domainId, + asset, + minLiquidity, + maxN + }; + const response = await axiosPost(`${this.baseUri}/enoughRouterLiquidity`, params); + + return BigNumber.from(response.data); + } } diff --git a/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts b/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts index 4339cf16f0..5f27329e0c 100644 --- a/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts +++ b/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts @@ -6,6 +6,7 @@ import * as MockableFns from "../src/mockable"; import { expect } from "@connext/nxtp-utils"; import { + SdkEnoughRouterLiquidityParams, SdkCheckRouterLiquidityParams, SdkGetRouterLiquidityParams, SdkGetRoutersDataParams, @@ -183,4 +184,31 @@ describe("#SDKUtils", () => { expect(res).to.be.deep.eq(expectedRes); }); }); + + describe("#enoughRouterLiquidity", async () => { + it("happy: should send request with correct params", async () => { + const expectedEndpoint = "/enoughRouterLiquidity"; + const expectedArgs: SdkEnoughRouterLiquidityParams = { + domainId: mock.domain.A, + asset: mock.asset.A.address, + minLiquidity: 100, + maxN: undefined, + }; + const mockServerRes = { + type: "BigNumber", + hex: "0x1", + }; + const expectedRes = BigNumber.from(mockServerRes); + + axiosPostStub.resolves({ + data: mockServerRes, + status: 200, + }); + + const res = await sdkUtils.checkRouterLiquidity(expectedArgs.domainId, expectedArgs.asset); + + expect(axiosPostStub).to.have.been.calledWithExactly(expectedBaseUri + expectedEndpoint, expectedArgs); + expect(res).to.be.deep.eq(expectedRes); + }); + }); }); diff --git a/packages/agents/sdk/src/interfaces/index.ts b/packages/agents/sdk/src/interfaces/index.ts index b65512fa71..5ccee7e9cd 100644 --- a/packages/agents/sdk/src/interfaces/index.ts +++ b/packages/agents/sdk/src/interfaces/index.ts @@ -844,6 +844,15 @@ export const SdkCheckRouterLiquidityParamsSchema = Type.Object({ }); export type SdkCheckRouterLiquidityParams = Static; +// enoughRouterLiquidity +export const SdkEnoughRouterLiquidityParamsSchema = Type.Object({ + domainId: Type.String(), + asset: Type.String(), + minLiquidity: Type.Number(), + maxN: Type.Optional(Type.Number()), +}); +export type SdkEnoughRouterLiquidityParams = Static; + /************************************ SDK Router Types *************************************/ diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index 9d0198da4e..a59d261936 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -1,4 +1,4 @@ -import { utils, BigNumber } from "ethers"; +import { utils, BigNumber, BigNumberish } from "ethers"; import { Logger, ChainData, @@ -308,4 +308,51 @@ export class SdkUtils extends SdkShared { .slice(0, _topN) .reduce((acc, router) => acc.add(BigNumber.from(router.balance.toString())), BigNumber.from(0)); } + + /** + * Checks if enough router liquidity is available for a specific asset. + * + * @param domainId - The domain ID where the asset exists. + * @param asset - The address of the asset. + * @param minLiquidity - The minimum liquidity to check against the sum of max N routers. + * @param maxN - (optional) The max N routers, should match the auction round depth (N = 2^(depth-1). + * @returns The total router liquidity available for the asset. + * + */ + async enoughRouterLiquidity(domainId: string, asset: string, minLiquidity: BigNumberish, maxN?: number): Promise { + const _asset = utils.getAddress(asset); + const _maxN = maxN ?? 4; + const _minLiquidityBN = BigNumber.from(this.scientificToBigInt(minLiquidity.toString())); + + const routersByLargestBalance = await this.getRoutersData({ order: { orderBy: "balance", ascOrDesc: "desc" } }); + + let totalLiquidity = BigNumber.from(0); + let processedRouters = 0; + + for (let routerBalance of routersByLargestBalance) { + if (routerBalance.domain == domainId && utils.getAddress(routerBalance.local) == _asset) { + const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); + totalLiquidity = totalLiquidity.add(balanceBN); + processedRouters += 1; + if (totalLiquidity.gte(_minLiquidityBN)) return true; + if (processedRouters == _maxN) break; + } + } + + return false; + } + + scientificToBigInt(scientificNotationString: string) { + const parts = scientificNotationString.split("e"); + const coeff = parseFloat(parts[0]); + const exp = parts.length > 1 ? parseFloat(parts[1]) : 0; + + const decimalParts = coeff.toString().split("."); + const numDecimals = decimalParts[1]?.length || 0; + + const bigIntCoeff = BigInt(decimalParts.join("")); + const bigIntExp = BigInt(exp - numDecimals); + + return bigIntCoeff * BigInt(10) ** bigIntExp; + } } diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index 679950fb60..8026433e52 100644 --- a/packages/agents/sdk/test/sdkUtils.spec.ts +++ b/packages/agents/sdk/test/sdkUtils.spec.ts @@ -80,6 +80,107 @@ describe("SdkUtils", () => { }); }); + describe("#enoughRouterLiquidity", () => { + it("should be true when enough liquidity between { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + restore(); + stub(SharedFns, 'axiosGetRequest').resolves([ + { + "balance": "100", + "local": mock.asset.A.address, + "domain": mock.domain.A, + }, + { + "balance": "200", + "local": mock.asset.A.address, + "domain": mock.domain.A, + } + ]); + const res = await nxtpUtils.enoughRouterLiquidity( + mock.domain.A, + mock.asset.A.address, + "100", + 2 + ); + + expect(res).to.be.true; + }); + + it("happy: should be true when enough liquidity between N routers", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + restore(); + stub(SharedFns, 'axiosGetRequest').resolves([ + { + "balance": "100", + "local": mock.asset.A.address, + "domain": mock.domain.A, + }, + { + "balance": "200", + "local": mock.asset.A.address, + "domain": mock.domain.A, + } + ]); + const res = await nxtpUtils.enoughRouterLiquidity( + mock.domain.A, + mock.asset.A.address, + "300", + 2 + ); + + expect(res).to.be.true; + }); + + it("happy: should be false when not enough liquidity between { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + restore(); + stub(SharedFns, 'axiosGetRequest').resolves([ + { + "balance": "100", + "local": mock.asset.A.address, + "domain": mock.domain.A, + } + ]); + const res = await nxtpUtils.enoughRouterLiquidity( + mock.domain.A, + mock.asset.A.address, + "200", + 2 + ); + + expect(res).to.be.false; + }); + + it("happy: should be false when not enough liquidity between N routers", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + restore(); + stub(SharedFns, 'axiosGetRequest').resolves([ + { + "balance": "100", + "local": mock.asset.A.address, + "domain": mock.domain.A, + }, + { + "balance": "200", + "local": mock.asset.A.address, + "domain": mock.domain.A, + } + ]); + const res = await nxtpUtils.enoughRouterLiquidity( + mock.domain.A, + mock.asset.A.address, + "400", + 2 + ); + + expect(res).to.be.false; + }); + }); + describe("#getAssetsData", () => { it("happy: should work", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; diff --git a/packages/examples/sdk-server/examples/index.http b/packages/examples/sdk-server/examples/index.http index d10348411f..eec4bbb7ca 100644 --- a/packages/examples/sdk-server/examples/index.http +++ b/packages/examples/sdk-server/examples/index.http @@ -771,6 +771,18 @@ Content-Type: application/json "topN": 4 } +############## +### POST enoughRouterLiquidity +POST {{baseUrl}}/enoughRouterLiquidity +Content-Type: application/json + +{ + "domainId": "1869640809", + "asset": "0x67E51f46e8e14D4E4cab9dF48c59ad8F512486DD", + "minLiquidity": "100000000000000000000000", + "maxN": 4 +} + ############## ### POST addLiquidityForRouter POST {{baseUrl}}/addLiquidityForRouter diff --git a/packages/examples/sdk-server/src/routes/utils.ts b/packages/examples/sdk-server/src/routes/utils.ts index 6dc3804274..40ca4e2d54 100644 --- a/packages/examples/sdk-server/src/routes/utils.ts +++ b/packages/examples/sdk-server/src/routes/utils.ts @@ -5,6 +5,8 @@ import { SdkGetTransfersParams, SdkCheckRouterLiquidityParamsSchema, SdkCheckRouterLiquidityParams, + SdkEnoughRouterLiquidityParamsSchema, + SdkEnoughRouterLiquidityParams } from "@connext/sdk-core"; import { createLoggingContext, jsonifyError } from "@connext/nxtp-utils"; import { FastifyInstance } from "fastify"; @@ -55,4 +57,18 @@ export const utilsRoutes = async (server: FastifyInstance, options: UtilsRoutesO reply.status(200).send(res); }, ); + + s.post<{ Body: SdkEnoughRouterLiquidityParams }>( + "/enoughRouterLiquidity", + { + schema: { + body: SdkEnoughRouterLiquidityParamsSchema, + }, + }, + async (request, reply) => { + const { domainId, asset, minLiquidity, maxN } = request.body; + const res = await sdkUtilsInstance.enoughRouterLiquidity(domainId, asset, minLiquidity, maxN); + reply.status(200).send(res); + }, + ); }; From a16963487d0b47e712e34ad3231c8b7535a7f814 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Sat, 20 Apr 2024 19:38:08 +0900 Subject: [PATCH 02/18] feat: route-specific caching --- .../sdk-server/src/cacheMiddleware.ts | 36 +++++++++++++++++++ packages/examples/sdk-server/src/config.ts | 13 ++++--- 2 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 packages/examples/sdk-server/src/cacheMiddleware.ts diff --git a/packages/examples/sdk-server/src/cacheMiddleware.ts b/packages/examples/sdk-server/src/cacheMiddleware.ts new file mode 100644 index 0000000000..397dfe18ad --- /dev/null +++ b/packages/examples/sdk-server/src/cacheMiddleware.ts @@ -0,0 +1,36 @@ +import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; +import { RoutesOptions } from "./server"; +import { DEFAULT_CACHE_EXPIRATION_SECS } from "./config"; + +export const getCacheExpirationTimeForRoute = (route: string, options: RoutesOptions): number => { + return options.cacheConfig?.cacheExpirationTimes?.[route] || DEFAULT_CACHE_EXPIRATION_SECS; +}; + +export async function cacheMiddleware( + server: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + handleRequest: () => Promise, + routeName: string, + options: RoutesOptions, +) { + const cacheKey = JSON.stringify(request.body); + const cacheConfig = options.cacheConfig || { enabled: false }; + + if (cacheConfig?.enabled) { + const cacheExpiration = getCacheExpirationTimeForRoute(routeName, options); + const cachedResult = await server.redis.get(cacheKey); + + if (cachedResult) { + reply.status(200).send(JSON.parse(cachedResult)); + return; + } + + const result = await handleRequest(); + await server.redis.set(cacheKey, JSON.stringify(result), "EX", cacheExpiration); + reply.status(200).send(result); + } else { + const result = await handleRequest(); + reply.status(200).send(result); + } +} diff --git a/packages/examples/sdk-server/src/config.ts b/packages/examples/sdk-server/src/config.ts index 77ec59b026..2ba69ebc52 100644 --- a/packages/examples/sdk-server/src/config.ts +++ b/packages/examples/sdk-server/src/config.ts @@ -4,7 +4,7 @@ import { Type, Static } from "@sinclair/typebox"; import { config as dotenvConfig } from "dotenv"; import { ajv } from "@connext/nxtp-utils"; -const CACHE_EXPIRATION_SECS = 300; +export const DEFAULT_CACHE_EXPIRATION_SECS = 300; dotenvConfig(); @@ -21,9 +21,9 @@ export const TServerConfig = Type.Object({ export const TRedisConfig = Type.Object({ enabled: Type.Optional(Type.Boolean()), - expirationTime: Type.Optional(Type.Integer()), host: Type.Optional(Type.String()), port: Type.Optional(Type.Integer({ minimum: 1, maximum: 65535 })), + cacheExpirationTimes: Type.Optional(Type.Record(Type.String(), Type.Integer())), }); export const SdkServerConfigSchema = Type.Object({ @@ -84,11 +84,10 @@ export const getEnvConfig = (): SdkServerConfig => { mnemonic: process.env.SDK_SERVER_MNEMONIC || configJson.mnemonic || configFile.mnemonic, redis: { enabled: process.env.SDK_SERVER_REDIS_ENABLED || configJson.redis?.enabled || configFile.redis?.enabled || false, - expirationTime: - process.env.SDK_SERVER_REDIS_EXPIRATION_TIME || - configJson.redis?.expirationTime || - configFile.redis?.expirationTime || - CACHE_EXPIRATION_SECS, + cacheExpirationTimes: + process.env.SDK_SERVER_REDIS_EXPIRATION_TIMES || + configJson.redis?.cacheExpirationTimes || + configFile.redis?.cacheExpirationTimes, host: process.env.SDK_SERVER_REDIS_HOST || configJson.redis?.host || configFile.redis?.host || "localhost", port: process.env.SDK_SERVER_REDIS_PORT || configJson.redis?.port || configFile.redis?.port || 6379, }, From f10f6c75b9e31a05ae38147769e2b0b487732af4 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Tue, 23 Apr 2024 16:41:21 +0900 Subject: [PATCH 03/18] feat: add limit to getRoutersData --- packages/agents/sdk/src/interfaces/index.ts | 1 + packages/agents/sdk/src/sdkUtils.ts | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/agents/sdk/src/interfaces/index.ts b/packages/agents/sdk/src/interfaces/index.ts index 3df06257a9..f89e72502f 100644 --- a/packages/agents/sdk/src/interfaces/index.ts +++ b/packages/agents/sdk/src/interfaces/index.ts @@ -807,6 +807,7 @@ export const TRange = Type.Object({ export const SdkGetRoutersDataParamsSchema = Type.Optional( Type.Object({ order: Type.Optional(TOrderBy), + limit: Type.Optional(Type.Number()), }), ); export type SdkGetRoutersDataParams = Static; diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index bc2fc472e7..a628c850e3 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -73,6 +73,7 @@ export class SdkUtils extends SdkShared { * @param params.order - (optional) The object with orderBy and ascOrDesc options. * @param params.order.orderBy - (optional) Field to order by. * @param params.order.ascOrDesc - (optional) Sort order, either "asc" or "desc". + * @param params.limit - (optional) The number of results to get. * @returns Array of objects containing the router address and liquidity information, in the form of: * * ```ts @@ -93,17 +94,22 @@ export class SdkUtils extends SdkShared { *} * ``` */ - async getRoutersData(params?: { + async getRoutersData(params?: { order?: { orderBy?: string; ascOrDesc?: "asc" | "desc" }; + limit?: number; }): Promise { - const { order } = params ?? {}; + const { order, limit } = params ?? {}; - const orderBy = order?.orderBy ? order.orderBy : ""; - const ascOrDesc = order?.ascOrDesc ? "." + order.ascOrDesc : ""; - const orderIdentifier = orderBy ? `order=${orderBy}${ascOrDesc}` : ""; + const orderBy = order?.orderBy || ""; + const ascOrDesc = order?.ascOrDesc ? `.${order.ascOrDesc}` : ""; + const orderIdentifier = orderBy ? `order=${orderBy}${ascOrDesc}&` : ""; + const limitIdentifier = limit ? `limit=${limit}` : ""; - const uri = formatUrl(this.config.cartographerUrl!, "routers_with_balances?", orderIdentifier); - // Validate uri + const uri = formatUrl( + this.config.cartographerUrl!, + "routers_with_balances?", + orderIdentifier + limitIdentifier + ); validateUri(uri); return await axiosGetRequest(uri); From 1433b0fc3e38557fbd28d5c2d6f414c300203e65 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Tue, 23 Apr 2024 16:45:15 +0900 Subject: [PATCH 04/18] test: getRoutersData limit tests --- packages/agents/sdk/test/sdkUtils.spec.ts | 50 ++++++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index a47449596a..783362acc5 100644 --- a/packages/agents/sdk/test/sdkUtils.spec.ts +++ b/packages/agents/sdk/test/sdkUtils.spec.ts @@ -1,5 +1,5 @@ import { reset, restore, stub } from "sinon"; -import { expect, XTransferStatus, getRandomBytes32, XTransferErrorStatus } from "@connext/nxtp-utils"; +import { expect, XTransferStatus, getRandomBytes32, XTransferErrorStatus, mkAddress } from "@connext/nxtp-utils"; import { mock } from "./mock"; import { SdkUtils } from "../src/sdkUtils"; import { getEnvConfig } from "../src/config"; @@ -16,12 +16,16 @@ const chainId = 1337; describe("SdkUtils", () => { let nxtpUtils: SdkUtils; let config: ConfigFns.SdkConfig; + let stubAxiosGetRequest; beforeEach(async () => { config = getEnvConfig(mockConfig, mockChainData, mockDeployments); stub(ConfigFns, "getConfig").resolves({ nxtpConfig: config, chainData: mockChainData }); stub(SharedFns, "domainToChainId").returns(chainId); + stubAxiosGetRequest = stub(SharedFns, 'axiosGetRequest').resolves([ + { "address": mkAddress("0x1") } + ]); nxtpUtils = await SdkUtils.create(mockConfig, undefined, mockChainData); }); @@ -46,21 +50,55 @@ describe("SdkUtils", () => { describe("#getRoutersData", () => { it("happy: should work", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - const res = await nxtpUtils.getRoutersData(); - expect(res).to.not.be.undefined; + await nxtpUtils.getRoutersData(); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?` + )).to.be.true; }); it("happy: should work with order", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - const res = await nxtpUtils.getRoutersData({ + + await nxtpUtils.getRoutersData({ order: { orderBy: "balance", ascOrDesc: "desc", }, }); - expect(res).to.not.be.undefined; + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?order=balance.desc&`) + ).to.be.true; + }); + + it("happy: should work with limit", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + await nxtpUtils.getRoutersData({ + limit: 1 + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?limit=1`) + ).to.be.true; + }); + + it("happy: should work with order and limit", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + await nxtpUtils.getRoutersData({ + order: { + orderBy: "balance", + ascOrDesc: "desc", + }, + limit: 1 + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?order=balance.desc&limit=1`) + ).to.be.true; }); it("should error if validateUri fails", async () => { @@ -285,7 +323,7 @@ describe("SdkUtils", () => { it("happy: should work", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; stub(nxtpUtils, "getCanonicalTokenId").resolves(["123", "0xabc"]); - stub(SharedFns, "axiosGetRequest").resolves({}); + // stub(SharedFns, "axiosGetRequest").resolves({}); const res = await nxtpUtils.getLatestAssetPrice(mock.domain.A, mock.asset.A.address); From eca8ea88b303ca87cf41cd4a13c5f25a2684e2b1 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Tue, 23 Apr 2024 17:21:06 +0900 Subject: [PATCH 05/18] feat: add limit param to wrapper --- packages/agents/sdk-wrapper/src/sdkUtils.ts | 1 + .../examples/sdk-server/src/routes/base.ts | 54 ++++------------ packages/examples/sdk-server/src/server.ts | 61 +++++++++++-------- 3 files changed, 49 insertions(+), 67 deletions(-) diff --git a/packages/agents/sdk-wrapper/src/sdkUtils.ts b/packages/agents/sdk-wrapper/src/sdkUtils.ts index 774a0fbb5a..6edba5dda7 100644 --- a/packages/agents/sdk-wrapper/src/sdkUtils.ts +++ b/packages/agents/sdk-wrapper/src/sdkUtils.ts @@ -24,6 +24,7 @@ export class SdkUtils extends SdkShared { async getRoutersData(params?: { order?: { orderBy?: string; ascOrDesc?: "asc" | "desc" }; + limit?: number; }): Promise { const response = await axiosPost(`${this.baseUri}/getRoutersData`, params ?? {}); return response.data; diff --git a/packages/examples/sdk-server/src/routes/base.ts b/packages/examples/sdk-server/src/routes/base.ts index c65da05454..10b36bb277 100644 --- a/packages/examples/sdk-server/src/routes/base.ts +++ b/packages/examples/sdk-server/src/routes/base.ts @@ -14,6 +14,7 @@ import { import { createLoggingContext, jsonifyError } from "@connext/nxtp-utils"; import { RoutesOptions } from "../server"; +import { cacheMiddleware } from "../cacheMiddleware"; interface BaseRoutesOptions extends RoutesOptions { sdkBaseInstance: SdkBase; @@ -21,11 +22,9 @@ interface BaseRoutesOptions extends RoutesOptions { export const baseRoutes = async (server: FastifyInstance, options: BaseRoutesOptions): Promise => { const s = server.withTypeProvider(); - const { sdkBaseInstance, logger, cacheConfig } = options; + const { sdkBaseInstance, logger } = options; const { requestContext, methodContext } = createLoggingContext(baseRoutes.name); - const CACHE_EXPIRATION_SECS = cacheConfig?.expirationTime || 300; - server.setErrorHandler(function (error, request, reply) { logger?.error(`Error: ${error.message} ${request.body}`, requestContext, methodContext); reply.status(500).send(jsonifyError(error as Error)); @@ -52,47 +51,16 @@ export const baseRoutes = async (server: FastifyInstance, options: BaseRoutesOpt }, }, async (request, reply) => { - const { - originDomain, - destinationDomain, - callDataGasAmount, - priceIn, - isHighPriority, - originNativeTokenPrice, - destinationNativeTokenPrice, - destinationGasPrice, - } = request.body; - - const handleEstimateRelayerFee = async () => { - return sdkBaseInstance.estimateRelayerFee({ - originDomain, - destinationDomain, - callDataGasAmount, - priceIn, - isHighPriority, - originNativeTokenPrice, - destinationNativeTokenPrice, - destinationGasPrice, - }); - }; - - if (cacheConfig?.enabled) { - const cacheKey = JSON.stringify(request.body); - const cachedFee = await server.redis.get(cacheKey); - - if (cachedFee) { - reply.status(200).send(JSON.parse(cachedFee)); - } else { - const txReq = await handleEstimateRelayerFee(); - await server.redis.set(cacheKey, JSON.stringify(txReq), "EX", CACHE_EXPIRATION_SECS); - reply.status(200).send(txReq); - } - } else { - const txReq = await handleEstimateRelayerFee(); - reply.status(200).send(txReq); - } + await cacheMiddleware( + server, + request, + reply, + async () => sdkBaseInstance.estimateRelayerFee(request.body), + "estimateRelayerFee", + options + ); }, - ); +); s.post( "/bumpTransfer", diff --git a/packages/examples/sdk-server/src/server.ts b/packages/examples/sdk-server/src/server.ts index 6c680be8b7..65d4e1b4ec 100644 --- a/packages/examples/sdk-server/src/server.ts +++ b/packages/examples/sdk-server/src/server.ts @@ -1,4 +1,4 @@ -import fastify, { FastifyInstance } from "fastify"; +import fastify, { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; import { createLoggingContext, Logger, getBestProvider, jsonifyError } from "@connext/nxtp-utils"; import { fastifyRedis } from "@fastify/redis"; import cors from "@fastify/cors"; @@ -20,7 +20,7 @@ export interface RoutesOptions { logger?: Logger; cacheConfig?: { enabled?: boolean; - expirationTime?: number; + cacheExpirationTimes?: Record; // route-specific expiration times, in seconds }; } @@ -48,7 +48,6 @@ export const makeSdkServer = async (_configOverride?: SdkServerConfig): Promise< const provider = new ethers.providers.JsonRpcProvider(url); configuredProviders[key] = provider; } - const nxtpConfig: SdkConfig = { chains: chains, logLevel: context.config.logLevel, @@ -56,22 +55,22 @@ export const makeSdkServer = async (_configOverride?: SdkServerConfig): Promise< environment: context.config.environment, }; - const { sdkBase, sdkPool, sdkUtils, sdkRouter, sdkShared } = await create(nxtpConfig); - // Server configuration - setup redis plugin if enabled, CORS, register routes const server = fastify(); if (context.config.redis?.enabled) { - server.register(fastifyRedis, { + await server.register(fastifyRedis, { host: context.config.redis?.host, port: context.config.redis?.port, }); } - server.register(cors, { + await server.register(cors, { origin: "*", }); + await setupRoutes(server, nxtpConfig); + server.setErrorHandler(function (error, request, reply) { context.logger.error(`Error: ${error.message}`, requestContext, methodContext); reply.status(500).send(jsonifyError(error as Error)); @@ -81,27 +80,12 @@ export const makeSdkServer = async (_configOverride?: SdkServerConfig): Promise< return reply.status(200).send("pong\n"); }); - server.register(baseRoutes, { - sdkBaseInstance: sdkBase, - logger: context.logger, - cacheConfig: context.config.redis, - }); - server.register(poolRoutes, { sdkPoolInstance: sdkPool, logger: context.logger }); - server.register(utilsRoutes, { sdkUtilsInstance: sdkUtils, logger: context.logger }); - server.register(routerRoutes, { sdkRouterInstance: sdkRouter, logger: context.logger }); - server.register(sharedRoutes, { sdkSharedInstance: sdkShared, logger: context.logger }); - - server.listen({ host: context.config.server.http.host, port: context.config.server.http.port }, (err, address) => { - if (err) { - console.error(err); - process.exit(1); - } - context.logger.info(`Server listening at ${address}`); - }); + await server.listen({ host: context.config.server.http.host, port: context.config.server.http.port }); context.logger.info("SDK Server boot complete!", requestContext, methodContext, { config: { ...context.config }, }); + context.logger.info(`Server listening at ${context.config.server.http.host}:${context.config.server.http.port}`); return server; } catch (err: unknown) { @@ -109,3 +93,32 @@ export const makeSdkServer = async (_configOverride?: SdkServerConfig): Promise< process.exit(1); } }; + +async function setupRoutes(server: FastifyInstance, nxtpConfig: SdkConfig) { + const { sdkBase, sdkPool, sdkUtils, sdkRouter, sdkShared } = await create(nxtpConfig); + await server.register(baseRoutes, { + sdkBaseInstance: sdkBase, + logger: context.logger, + cacheConfig: context.config.redis, + }); + await server.register(poolRoutes, { + sdkPoolInstance: sdkPool, + logger: context.logger, + cacheConfig: context.config.redis, + }); + await server.register(utilsRoutes, { + sdkUtilsInstance: sdkUtils, + logger: context.logger, + cacheConfig: context.config.redis, + }); + await server.register(routerRoutes, { + sdkRouterInstance: sdkRouter, + logger: context.logger, + cacheConfig: context.config.redis, + }); + await server.register(sharedRoutes, { + sdkSharedInstance: sdkShared, + logger: context.logger, + cacheConfig: context.config.redis, + }); +} From a2de9b8f886904e38bab4e9de032d7c60756c87c Mon Sep 17 00:00:00 2001 From: just-a-node Date: Tue, 23 Apr 2024 17:21:47 +0900 Subject: [PATCH 06/18] feat: enable caching on enoughRouterLiquidity --- .../sdk-server/src/cacheMiddleware.ts | 2 +- .../examples/sdk-server/src/routes/utils.ts | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/examples/sdk-server/src/cacheMiddleware.ts b/packages/examples/sdk-server/src/cacheMiddleware.ts index 397dfe18ad..7b23f31b87 100644 --- a/packages/examples/sdk-server/src/cacheMiddleware.ts +++ b/packages/examples/sdk-server/src/cacheMiddleware.ts @@ -21,7 +21,7 @@ export async function cacheMiddleware( const cacheExpiration = getCacheExpirationTimeForRoute(routeName, options); const cachedResult = await server.redis.get(cacheKey); - if (cachedResult) { + if (cachedResult !== null) { reply.status(200).send(JSON.parse(cachedResult)); return; } diff --git a/packages/examples/sdk-server/src/routes/utils.ts b/packages/examples/sdk-server/src/routes/utils.ts index 0f0b459bc9..153bc18d70 100644 --- a/packages/examples/sdk-server/src/routes/utils.ts +++ b/packages/examples/sdk-server/src/routes/utils.ts @@ -1,3 +1,5 @@ +import { FastifyInstance } from "fastify"; +import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; import { SdkUtils, SdkGetRoutersDataParams, @@ -11,10 +13,9 @@ import { SdkGetLatestAssetPriceParams, } from "@connext/sdk-core"; import { createLoggingContext, jsonifyError } from "@connext/nxtp-utils"; -import { FastifyInstance } from "fastify"; -import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; import { RoutesOptions } from "../server"; +import { cacheMiddleware } from "../cacheMiddleware"; interface UtilsRoutesOptions extends RoutesOptions { sdkUtilsInstance: SdkUtils; @@ -31,7 +32,6 @@ export const utilsRoutes = async (server: FastifyInstance, options: UtilsRoutesO }); s.post<{ Body: SdkGetRoutersDataParams }>("/getRoutersData", async (request, reply) => { - console.log(request.body); // Log the request body const res = await sdkUtilsInstance.getRoutersData(request.body); reply.status(200).send(res); }); @@ -68,10 +68,18 @@ export const utilsRoutes = async (server: FastifyInstance, options: UtilsRoutesO }, }, async (request, reply) => { - const { domainId, asset, minLiquidity, maxN } = request.body; - const res = await sdkUtilsInstance.enoughRouterLiquidity(domainId, asset, minLiquidity, maxN); - reply.status(200).send(res); - }, + await cacheMiddleware( + server, + request, + reply, + async () => { + const { domainId, asset, minLiquidity, maxN } = request.body; + return sdkUtilsInstance.enoughRouterLiquidity(domainId, asset, minLiquidity, maxN); + }, + "enoughRouterLiquidity", + options + ); + } ); s.post<{ Body: SdkGetLatestAssetPriceParams }>( From 5fdd3f0a06b165b1dfbf7570335fa9b662dcbea1 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Wed, 24 Apr 2024 14:15:45 +0900 Subject: [PATCH 07/18] feat: add more filters to getRoutersData --- packages/agents/sdk/src/sdkUtils.ts | 45 ++++++++++++++------ packages/agents/sdk/test/sdkUtils.spec.ts | 50 ++++++++++++++++++++++- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index a628c850e3..4e8410069e 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -94,11 +94,26 @@ export class SdkUtils extends SdkShared { *} * ``` */ - async getRoutersData(params?: { + async getRoutersData(params?: { + domain?: string; + localAsset?: string; + adoptedAsset?: string; + canonicalId?: string; order?: { orderBy?: string; ascOrDesc?: "asc" | "desc" }; limit?: number; }): Promise { - const { order, limit } = params ?? {}; + const { domain, localAsset, adoptedAsset, canonicalId, order, limit } = params ?? {}; + + const domainIdentifier = domain ? `domain=eq.${domain}&` : ""; + const localAssetIdentifier = localAsset ? `local=eq.${localAsset}&` : ""; + const adoptedAssetIdentifier = adoptedAsset ? `adopted=eq.${adoptedAsset}&` : ""; + const canonicalIdIdentifier = canonicalId ? `canonical_id=eq.${canonicalId}&` : ""; + + const searchIdentifier = + domainIdentifier + + localAssetIdentifier + + adoptedAssetIdentifier + + canonicalIdIdentifier; const orderBy = order?.orderBy || ""; const ascOrDesc = order?.ascOrDesc ? `.${order.ascOrDesc}` : ""; @@ -108,7 +123,7 @@ export class SdkUtils extends SdkShared { const uri = formatUrl( this.config.cartographerUrl!, "routers_with_balances?", - orderIdentifier + limitIdentifier + searchIdentifier + orderIdentifier + limitIdentifier ); validateUri(uri); @@ -319,29 +334,35 @@ export class SdkUtils extends SdkShared { * Checks if enough router liquidity is available for a specific asset. * * @param domainId - The domain ID where the asset exists. - * @param asset - The address of the asset. + * @param asset - The address of the local asset. * @param minLiquidity - The minimum liquidity to check against the sum of max N routers. * @param maxN - (optional) The max N routers, should match the auction round depth (N = 2^(depth-1). * @returns The total router liquidity available for the asset. * */ - async enoughRouterLiquidity(domainId: string, asset: string, minLiquidity: BigNumberish, maxN?: number): Promise { - const _asset = utils.getAddress(asset); + async enoughRouterLiquidity( + domainId: string, + asset: string, + minLiquidity: BigNumberish, + maxN?: number + ): Promise { + const _asset = asset.toLowerCase(); const _maxN = maxN ?? 4; const _minLiquidityBN = BigNumber.from(this.scientificToBigInt(minLiquidity.toString())); - const routersByLargestBalance = await this.getRoutersData({ order: { orderBy: "balance", ascOrDesc: "desc" } }); + const routersByLargestBalance = await this.getRoutersData({ + domain: domainId, + localAsset: _asset, + order: { orderBy: "balance", ascOrDesc: "desc" }, + limit: _maxN + }); let totalLiquidity = BigNumber.from(0); - let processedRouters = 0; - for (let routerBalance of routersByLargestBalance) { - if (routerBalance.domain == domainId && utils.getAddress(routerBalance.local) == _asset) { + if (routerBalance.domain == domainId && routerBalance.local == _asset) { const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); totalLiquidity = totalLiquidity.add(balanceBN); - processedRouters += 1; if (totalLiquidity.gte(_minLiquidityBN)) return true; - if (processedRouters == _maxN) break; } } diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index 783362acc5..a781edc795 100644 --- a/packages/agents/sdk/test/sdkUtils.spec.ts +++ b/packages/agents/sdk/test/sdkUtils.spec.ts @@ -101,6 +101,54 @@ describe("SdkUtils", () => { ).to.be.true; }); + it("happy: should work with domain", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + await nxtpUtils.getRoutersData({ + domain: mock.domain.A + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?domain=eq.${mock.domain.A}&`) + ).to.be.true; + }); + + it("happy: should work with localAsset", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + await nxtpUtils.getRoutersData({ + localAsset: mock.asset.A.address + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?local=eq.${mock.asset.A.address}&`) + ).to.be.true; + }); + + it("happy: should work with adoptedAsset", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + await nxtpUtils.getRoutersData({ + adoptedAsset: mock.asset.A.address + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?adopted=eq.${mock.asset.A.address}&`) + ).to.be.true; + }); + + it("happy: should work with canonicalId", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + await nxtpUtils.getRoutersData({ + canonicalId: "1" + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?canonical_id=eq.1&`) + ).to.be.true; + }); + it("should error if validateUri fails", async () => { (nxtpUtils as any).config.cartographerUrl = "invalidUrl"; @@ -118,7 +166,7 @@ describe("SdkUtils", () => { }); describe("#enoughRouterLiquidity", () => { - it("should be true when enough liquidity between { + it.only("should be true when enough liquidity between { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; restore(); From b44430c063916637b7a868791e6f41c67aa7d041 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Wed, 24 Apr 2024 14:22:22 +0900 Subject: [PATCH 08/18] test: rm only --- packages/agents/sdk/test/sdkUtils.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index a781edc795..25ea657b53 100644 --- a/packages/agents/sdk/test/sdkUtils.spec.ts +++ b/packages/agents/sdk/test/sdkUtils.spec.ts @@ -166,7 +166,7 @@ describe("SdkUtils", () => { }); describe("#enoughRouterLiquidity", () => { - it.only("should be true when enough liquidity between { + it("should be true when enough liquidity between { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; restore(); From 2cb18225d216cebcc177292e4bb8e575c4429a9b Mon Sep 17 00:00:00 2001 From: just-a-node Date: Wed, 24 Apr 2024 15:30:45 +0900 Subject: [PATCH 09/18] fix: compare with lowercase router addresses --- packages/agents/sdk/src/sdkUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index 4e8410069e..93f3242803 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -356,10 +356,10 @@ export class SdkUtils extends SdkShared { order: { orderBy: "balance", ascOrDesc: "desc" }, limit: _maxN }); - + let totalLiquidity = BigNumber.from(0); for (let routerBalance of routersByLargestBalance) { - if (routerBalance.domain == domainId && routerBalance.local == _asset) { + if (routerBalance.domain == domainId && routerBalance.local.toLowerCase() == _asset) { const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); totalLiquidity = totalLiquidity.add(balanceBN); if (totalLiquidity.gte(_minLiquidityBN)) return true; From bc18006a18e60f5caac47c9556d338e4f253d7a5 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Wed, 24 Apr 2024 15:32:30 +0900 Subject: [PATCH 10/18] test: tests for enoughRouterLiquidity --- packages/agents/sdk/test/sdkUtils.spec.ts | 52 +++++++++++++---------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index 25ea657b53..82833095ad 100644 --- a/packages/agents/sdk/test/sdkUtils.spec.ts +++ b/packages/agents/sdk/test/sdkUtils.spec.ts @@ -23,10 +23,6 @@ describe("SdkUtils", () => { stub(ConfigFns, "getConfig").resolves({ nxtpConfig: config, chainData: mockChainData }); stub(SharedFns, "domainToChainId").returns(chainId); - stubAxiosGetRequest = stub(SharedFns, 'axiosGetRequest').resolves([ - { "address": mkAddress("0x1") } - ]); - nxtpUtils = await SdkUtils.create(mockConfig, undefined, mockChainData); }); @@ -48,6 +44,12 @@ describe("SdkUtils", () => { }); describe("#getRoutersData", () => { + beforeEach(async () => { + stubAxiosGetRequest = stub(SharedFns, 'axiosGetRequest').resolves([ + { "address": mkAddress("0x1") } + ]); + }); + it("happy: should work", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; @@ -166,11 +168,14 @@ describe("SdkUtils", () => { }); describe("#enoughRouterLiquidity", () => { + beforeEach(async () => { + stubAxiosGetRequest = stub(SharedFns, 'axiosGetRequest'); + }); + it("should be true when enough liquidity between { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - restore(); - stub(SharedFns, 'axiosGetRequest').resolves([ + stubAxiosGetRequest.resolves([ { "balance": "100", "local": mock.asset.A.address, @@ -182,6 +187,7 @@ describe("SdkUtils", () => { "domain": mock.domain.A, } ]); + const res = await nxtpUtils.enoughRouterLiquidity( mock.domain.A, mock.asset.A.address, @@ -195,19 +201,21 @@ describe("SdkUtils", () => { it("happy: should be true when enough liquidity between N routers", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - restore(); - stub(SharedFns, 'axiosGetRequest').resolves([ - { - "balance": "100", - "local": mock.asset.A.address, - "domain": mock.domain.A, - }, - { - "balance": "200", - "local": mock.asset.A.address, - "domain": mock.domain.A, - } - ]); + stubAxiosGetRequest.resolves( + [ + { + "balance": "100", + "local": mock.asset.A.address, + "domain": mock.domain.A, + }, + { + "balance": "200", + "local": mock.asset.A.address, + "domain": mock.domain.A, + } + ] + ); + const res = await nxtpUtils.enoughRouterLiquidity( mock.domain.A, mock.asset.A.address, @@ -221,8 +229,7 @@ describe("SdkUtils", () => { it("happy: should be false when not enough liquidity between { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - restore(); - stub(SharedFns, 'axiosGetRequest').resolves([ + stubAxiosGetRequest.resolves([ { "balance": "100", "local": mock.asset.A.address, @@ -242,8 +249,7 @@ describe("SdkUtils", () => { it("happy: should be false when not enough liquidity between N routers", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - restore(); - stub(SharedFns, 'axiosGetRequest').resolves([ + stubAxiosGetRequest.resolves([ { "balance": "100", "local": mock.asset.A.address, From b8dcf3e53a2bd4e1292f56c3b6c037e6edd0af46 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Wed, 24 Apr 2024 15:39:52 +0900 Subject: [PATCH 11/18] chore: annotations --- packages/agents/sdk/src/sdkUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index 93f3242803..b4ce1f590f 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -70,6 +70,10 @@ export class SdkUtils extends SdkShared { * Fetches a list of router liquidity data. * * @param params - (optional) Parameters object. + * @param params.domain - (optional) The domain to filter against. + * @param params.localAsset - (optional) The local asset address to filter against. + * @param params.adoptedAsset - (optional) The adopted asset address to filter against. + * @param params.canonicalId - (optional) The canonical ID to filter against. * @param params.order - (optional) The object with orderBy and ascOrDesc options. * @param params.order.orderBy - (optional) Field to order by. * @param params.order.ascOrDesc - (optional) Sort order, either "asc" or "desc". From a2fbb6bb2368a20f34044d405ef856a36037b971 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Wed, 24 Apr 2024 16:22:41 +0900 Subject: [PATCH 12/18] feat: add bufferPercentage param --- packages/agents/sdk-wrapper/src/sdkUtils.ts | 15 ++++- .../agents/sdk-wrapper/test/sdkUtils.spec.ts | 6 +- packages/agents/sdk/src/interfaces/index.ts | 1 + packages/agents/sdk/src/sdkUtils.ts | 9 ++- packages/agents/sdk/test/sdkUtils.spec.ts | 58 ++++++++++++++++++- packages/examples/sdk-server/.nycrc.json | 1 + .../examples/sdk-server/src/routes/utils.ts | 4 +- 7 files changed, 82 insertions(+), 12 deletions(-) diff --git a/packages/agents/sdk-wrapper/src/sdkUtils.ts b/packages/agents/sdk-wrapper/src/sdkUtils.ts index 6edba5dda7..b511dfc948 100644 --- a/packages/agents/sdk-wrapper/src/sdkUtils.ts +++ b/packages/agents/sdk-wrapper/src/sdkUtils.ts @@ -64,13 +64,22 @@ export class SdkUtils extends SdkShared { domainId: string, asset: string, minLiquidity: BigNumberish, - maxN?: number + maxN?: number, + bufferPercentage?: number ): Promise { - const params: { domainId: string; asset: string; minLiquidity: BigNumberish, maxN?: number } = { + const params: { + domainId: string; + asset: string; + minLiquidity: BigNumberish, + maxN?: number, + bufferPercentage?: number + } = + { domainId, asset, minLiquidity, - maxN + maxN, + bufferPercentage }; const response = await axiosPost(`${this.baseUri}/enoughRouterLiquidity`, params); diff --git a/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts b/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts index a6e011e14c..adfa147b5f 100644 --- a/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts +++ b/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts @@ -206,7 +206,11 @@ describe("#SDKUtils", () => { status: 200, }); - const res = await sdkUtils.checkRouterLiquidity(expectedArgs.domainId, expectedArgs.asset); + const res = await sdkUtils.enoughRouterLiquidity( + expectedArgs.domainId, + expectedArgs.asset, + expectedArgs.minLiquidity + ); expect(axiosPostStub).to.have.been.calledWithExactly(expectedBaseUri + expectedEndpoint, expectedArgs); expect(res).to.be.deep.eq(expectedRes); diff --git a/packages/agents/sdk/src/interfaces/index.ts b/packages/agents/sdk/src/interfaces/index.ts index f89e72502f..d59a2a2def 100644 --- a/packages/agents/sdk/src/interfaces/index.ts +++ b/packages/agents/sdk/src/interfaces/index.ts @@ -851,6 +851,7 @@ export const SdkEnoughRouterLiquidityParamsSchema = Type.Object({ asset: Type.String(), minLiquidity: Type.Number(), maxN: Type.Optional(Type.Number()), + bufferPercentage: Type.Optional(Type.Number()), }); export type SdkEnoughRouterLiquidityParams = Static; diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index b4ce1f590f..f7acfe082f 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -341,6 +341,7 @@ export class SdkUtils extends SdkShared { * @param asset - The address of the local asset. * @param minLiquidity - The minimum liquidity to check against the sum of max N routers. * @param maxN - (optional) The max N routers, should match the auction round depth (N = 2^(depth-1). + * @param bufferPercentage - (optional) The buffer percentage to apply on top of the minimum liquidity. * @returns The total router liquidity available for the asset. * */ @@ -348,11 +349,13 @@ export class SdkUtils extends SdkShared { domainId: string, asset: string, minLiquidity: BigNumberish, - maxN?: number + maxN?: number, + bufferPercentage?: number ): Promise { const _asset = asset.toLowerCase(); const _maxN = maxN ?? 4; const _minLiquidityBN = BigNumber.from(this.scientificToBigInt(minLiquidity.toString())); + const _bufferPercentage = bufferPercentage ?? 0; const routersByLargestBalance = await this.getRoutersData({ domain: domainId, @@ -366,11 +369,11 @@ export class SdkUtils extends SdkShared { if (routerBalance.domain == domainId && routerBalance.local.toLowerCase() == _asset) { const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); totalLiquidity = totalLiquidity.add(balanceBN); - if (totalLiquidity.gte(_minLiquidityBN)) return true; } } - return false; + const totalLiquidityWithBuffer = _minLiquidityBN.mul(BigNumber.from(100 + _bufferPercentage)).div(100); + return totalLiquidity.gte(totalLiquidityWithBuffer); } scientificToBigInt(scientificNotationString: string) { diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index 82833095ad..459f7cef81 100644 --- a/packages/agents/sdk/test/sdkUtils.spec.ts +++ b/packages/agents/sdk/test/sdkUtils.spec.ts @@ -198,7 +198,7 @@ describe("SdkUtils", () => { expect(res).to.be.true; }); - it("happy: should be true when enough liquidity between N routers", async () => { + it("should be true when enough liquidity between N routers", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; stubAxiosGetRequest.resolves( @@ -226,7 +226,7 @@ describe("SdkUtils", () => { expect(res).to.be.true; }); - it("happy: should be false when not enough liquidity between { + it("should be false when not enough liquidity between { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; stubAxiosGetRequest.resolves([ @@ -246,7 +246,7 @@ describe("SdkUtils", () => { expect(res).to.be.false; }); - it("happy: should be false when not enough liquidity between N routers", async () => { + it("should be false when not enough liquidity between N routers", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; stubAxiosGetRequest.resolves([ @@ -270,6 +270,58 @@ describe("SdkUtils", () => { expect(res).to.be.false; }); + + it("should be true when enough liquidity between N routers accounting for buffer", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + stubAxiosGetRequest.resolves([ + { + "balance": "100", + "local": mock.asset.A.address, + "domain": mock.domain.A, + }, + { + "balance": "200", + "local": mock.asset.A.address, + "domain": mock.domain.A, + } + ]); + const res = await nxtpUtils.enoughRouterLiquidity( + mock.domain.A, + mock.asset.A.address, + "330", + 2, + 10 + ); + + expect(res).to.be.false; + }); + + it("should be false when not enough liquidity between N routers accounting for buffer", async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + stubAxiosGetRequest.resolves([ + { + "balance": "100", + "local": mock.asset.A.address, + "domain": mock.domain.A, + }, + { + "balance": "200", + "local": mock.asset.A.address, + "domain": mock.domain.A, + } + ]); + const res = await nxtpUtils.enoughRouterLiquidity( + mock.domain.A, + mock.asset.A.address, + "300", + 2, + 10 + ); + + expect(res).to.be.false; + }); }); describe("#getTransfers", () => { diff --git a/packages/examples/sdk-server/.nycrc.json b/packages/examples/sdk-server/.nycrc.json index 744ac230dc..1f07da9291 100644 --- a/packages/examples/sdk-server/.nycrc.json +++ b/packages/examples/sdk-server/.nycrc.json @@ -1,6 +1,7 @@ { "extends": "../../../.nycrc.json", "exclude": [ + "src/cacheMiddleware.ts", "src/config.ts", "src/context.ts", "src/server.ts", diff --git a/packages/examples/sdk-server/src/routes/utils.ts b/packages/examples/sdk-server/src/routes/utils.ts index 153bc18d70..2613b3dec5 100644 --- a/packages/examples/sdk-server/src/routes/utils.ts +++ b/packages/examples/sdk-server/src/routes/utils.ts @@ -73,8 +73,8 @@ export const utilsRoutes = async (server: FastifyInstance, options: UtilsRoutesO request, reply, async () => { - const { domainId, asset, minLiquidity, maxN } = request.body; - return sdkUtilsInstance.enoughRouterLiquidity(domainId, asset, minLiquidity, maxN); + const { domainId, asset, minLiquidity, maxN, bufferPercentage } = request.body; + return sdkUtilsInstance.enoughRouterLiquidity(domainId, asset, minLiquidity, maxN, bufferPercentage); }, "enoughRouterLiquidity", options From 11e1760ff39eb32c081a53e9ae3f478685a01973 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Thu, 25 Apr 2024 04:26:47 +0900 Subject: [PATCH 13/18] feat: calculateAmountReceived uses enoughRouterLiquidity --- packages/agents/sdk/src/sdkPool.ts | 17 ++++++++++------- packages/agents/sdk/test/sdkPool.spec.ts | 21 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/agents/sdk/src/sdkPool.ts b/packages/agents/sdk/src/sdkPool.ts index 041aa08bdc..88db2efa90 100644 --- a/packages/agents/sdk/src/sdkPool.ts +++ b/packages/agents/sdk/src/sdkPool.ts @@ -20,6 +20,7 @@ import { validateUri, axiosGetRequest } from "./lib/helpers"; import { Pool, PoolAsset, AssetData, Options } from "./interfaces"; import { PriceFeed } from "./lib/priceFeed"; import { SdkShared } from "./sdkShared"; +import { SdkUtils } from "./sdkUtils"; /** * @classdesc SDK class encapsulating stableswap pool functions. @@ -308,14 +309,16 @@ export class SdkPool extends SdkShared { */ // Determine if fast liquidity is available (pre-destination-swap amount) - let isFastPath = true; + let isFastPath = false; if (checkFastLiquidity) { - const activeLiquidity = await this.getActiveLiquidity(destinationDomain, destinationAssetData.local); - this.logger.info("Active router liquidity", requestContext, methodContext, { signerAddress, activeLiquidity }); - if (activeLiquidity?.length > 0) { - const total_balance: string = activeLiquidity[0].total_balance.toString(); - isFastPath = BigNumber.from(this.scientificToBigInt(total_balance)).mul(70).div(100).gt(originAmountReceived); - } + const sdkUtils = await SdkUtils.create(this.config); + isFastPath = await sdkUtils.enoughRouterLiquidity( + destinationDomain, + destinationAssetData.local, + originAmountReceived, + 4, // default top 4 routers, this is enforced by protocol + 30 // apply 1.3x router liquidity buffer + ); } // Subtract router fee if fast liquidity is available diff --git a/packages/agents/sdk/test/sdkPool.spec.ts b/packages/agents/sdk/test/sdkPool.spec.ts index ad0546acdb..93d07ac40f 100644 --- a/packages/agents/sdk/test/sdkPool.spec.ts +++ b/packages/agents/sdk/test/sdkPool.spec.ts @@ -1,4 +1,4 @@ -import { reset, restore, stub, spy, match } from "sinon"; +import { reset, restore, stub, spy, match, createStubInstance } from "sinon"; import { expect, getCanonicalHash, getRandomBytes32, DEFAULT_ROUTER_FEE, mkAddress } from "@connext/nxtp-utils"; import { getConnextInterface } from "@connext/nxtp-txservice"; import { providers, utils, BigNumber, constants } from "ethers"; @@ -10,6 +10,7 @@ import { getEnvConfig } from "../src/config"; import * as ConfigFns from "../src/config"; import * as SharedFns from "../src/lib/helpers/shared"; import { UriInvalid, ParamsInvalid } from "../src/lib/errors"; +import { SdkUtils } from "../src/sdkUtils"; const mockConfig = mock.config(); const mockChainData = mock.chainData(); @@ -17,6 +18,7 @@ const mockDeployments = mock.contracts.deployments(); describe("SdkPool", () => { let sdkPool: SdkPool; + let sdkUtilsStub: SdkUtils; let config: ConfigFns.SdkConfig; const localAsset: PoolAsset = { @@ -93,6 +95,7 @@ describe("SdkPool", () => { stub(SharedFns, "axiosGetRequest").resolves([]); sdkPool = await SdkPool.create(config, undefined, mockChainData); + sdkUtilsStub = createStubInstance(SdkUtils); }); afterEach(() => { @@ -420,6 +423,8 @@ describe("SdkPool", () => { it("happy: should work with local origin asset and adopted destination asset", async () => { stub(sdkPool, "getPool").onCall(0).resolves(undefined).onCall(1).resolves(mockPool); + (sdkUtilsStub.enoughRouterLiquidity as any).returns(Promise.resolve(true)); + stub(SdkUtils, "create").resolves(sdkUtilsStub); const originAmount = BigNumber.from(100_000); const originSlippage = "0"; // 0% in BPS @@ -439,14 +444,19 @@ describe("SdkPool", () => { mockPool.domainId, mockPool.local.address, originAmount, + false, + true ); expect(res.originSlippage.toString()).to.equal(originSlippage); expect(res.destinationSlippage.toString()).to.equal(destinationSlippage); + expect(res.isFastPath).to.be.true; }); it("happy: should work with adopted origin asset and adopted destination asset", async () => { stub(sdkPool, "getPool").onCall(0).resolves(mockPool).onCall(1).resolves(mockPool); + (sdkUtilsStub.enoughRouterLiquidity as any).returns(Promise.resolve(true)); + stub(SdkUtils, "create").resolves(sdkUtilsStub); const originAmount = BigNumber.from(100_000); const originAmountAfterSwap = originAmount.mul(9).div(10); // assume swap ate 10% @@ -469,10 +479,13 @@ describe("SdkPool", () => { mockPool.domainId, mockPool.adopted.address, originAmount, + false, + true ); expect(res.originSlippage.toString()).to.equal(originSlippage); expect(res.destinationSlippage.toString()).to.equal(destinationSlippage); + expect(res.isFastPath).to.be.true; }); it("happy: should work with adopted origin asset and local destination asset", async () => { @@ -524,6 +537,9 @@ describe("SdkPool", () => { }); it("happy: should work with local origin asset and adopted destination asset, 6 and 18 decimals respectively", async () => { + (sdkUtilsStub.enoughRouterLiquidity as any).returns(Promise.resolve(true)); + stub(SdkUtils, "create").resolves(sdkUtilsStub); + const localAsset6Decimals = { ...localAsset, decimals: 6 }; const adoptedAsset18Decimals = { ...adoptedAsset, decimals: 18 }; const mockPoolDifferentDecimals = { @@ -555,10 +571,13 @@ describe("SdkPool", () => { mockPoolDifferentDecimals.domainId, mockPoolDifferentDecimals.local.address, originAmount, + false, + true ); expect(res.originSlippage.toString()).to.equal(originSlippage); expect(res.destinationSlippage.toString()).to.equal(destinationSlippage); + expect(res.isFastPath).to.be.true; }); it("happy: should work with adopted origin asset and local destination asset, 18 and 6 decimals respectively", async () => { From a9679bbd4d236c9c8042efedb0ece7858d0bc237 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Sat, 27 Apr 2024 02:04:18 +0900 Subject: [PATCH 14/18] feat: use lower case for asset addresses --- packages/agents/sdk/src/sdkUtils.ts | 8 ++++---- packages/agents/sdk/test/sdkUtils.spec.ts | 21 +++------------------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index f7acfe082f..f40a62c948 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -108,10 +108,10 @@ export class SdkUtils extends SdkShared { }): Promise { const { domain, localAsset, adoptedAsset, canonicalId, order, limit } = params ?? {}; - const domainIdentifier = domain ? `domain=eq.${domain}&` : ""; - const localAssetIdentifier = localAsset ? `local=eq.${localAsset}&` : ""; - const adoptedAssetIdentifier = adoptedAsset ? `adopted=eq.${adoptedAsset}&` : ""; - const canonicalIdIdentifier = canonicalId ? `canonical_id=eq.${canonicalId}&` : ""; + const domainIdentifier = domain ? `domain=eq.${domain.toString()}&` : ""; + const localAssetIdentifier = localAsset ? `local=eq.${localAsset.toLowerCase()}&` : ""; + const adoptedAssetIdentifier = adoptedAsset ? `adopted=eq.${adoptedAsset.toLowerCase()}&` : ""; + const canonicalIdIdentifier = canonicalId ? `canonical_id=eq.${canonicalId.toLowerCase()}&` : ""; const searchIdentifier = domainIdentifier + diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index 459f7cef81..3c2e6d7437 100644 --- a/packages/agents/sdk/test/sdkUtils.spec.ts +++ b/packages/agents/sdk/test/sdkUtils.spec.ts @@ -45,14 +45,13 @@ describe("SdkUtils", () => { describe("#getRoutersData", () => { beforeEach(async () => { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; stubAxiosGetRequest = stub(SharedFns, 'axiosGetRequest').resolves([ { "address": mkAddress("0x1") } ]); }); it("happy: should work", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData(); expect(stubAxiosGetRequest.calledWith( @@ -61,8 +60,6 @@ describe("SdkUtils", () => { }); it("happy: should work with order", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData({ order: { orderBy: "balance", @@ -76,8 +73,6 @@ describe("SdkUtils", () => { }); it("happy: should work with limit", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData({ limit: 1 }); @@ -88,8 +83,6 @@ describe("SdkUtils", () => { }); it("happy: should work with order and limit", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData({ order: { orderBy: "balance", @@ -104,8 +97,6 @@ describe("SdkUtils", () => { }); it("happy: should work with domain", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData({ domain: mock.domain.A }); @@ -116,32 +107,26 @@ describe("SdkUtils", () => { }); it("happy: should work with localAsset", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData({ localAsset: mock.asset.A.address }); expect(stubAxiosGetRequest.calledWith( - config.cartographerUrl + `/routers_with_balances?local=eq.${mock.asset.A.address}&`) + config.cartographerUrl + `/routers_with_balances?local=eq.${mock.asset.A.address.toLowerCase()}&`) ).to.be.true; }); it("happy: should work with adoptedAsset", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData({ adoptedAsset: mock.asset.A.address }); expect(stubAxiosGetRequest.calledWith( - config.cartographerUrl + `/routers_with_balances?adopted=eq.${mock.asset.A.address}&`) + config.cartographerUrl + `/routers_with_balances?adopted=eq.${mock.asset.A.address.toLowerCase()}&`) ).to.be.true; }); it("happy: should work with canonicalId", async () => { - (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - await nxtpUtils.getRoutersData({ canonicalId: "1" }); From b12108606fcb18c99ca649ad6cee95db99630874 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Sat, 27 Apr 2024 02:06:31 +0900 Subject: [PATCH 15/18] feat: additional filters for getAssetsData --- packages/agents/sdk/src/sdkShared.ts | 43 +++++++++-- packages/agents/sdk/test/sdkShared.spec.ts | 90 ++++++++++++++++++++-- 2 files changed, 123 insertions(+), 10 deletions(-) diff --git a/packages/agents/sdk/src/sdkShared.ts b/packages/agents/sdk/src/sdkShared.ts index fca9a6616e..684b68c8c8 100644 --- a/packages/agents/sdk/src/sdkShared.ts +++ b/packages/agents/sdk/src/sdkShared.ts @@ -403,14 +403,47 @@ export class SdkShared { * ``` */ getAssetsData = memoize( - async (): Promise => { - const uri = formatUrl(this.config.cartographerUrl!, "assets"); - // Validate uri + async (params?: { + domain?: string; + localAsset?: string; + adoptedAsset?: string; + canonicalId?: string; + order?: { orderBy: string; ascOrDesc: "asc" | "desc" }; + limit?: number; + }): Promise => { + const { domain, localAsset, adoptedAsset, canonicalId, order, limit } = params ?? {}; + + const domainIdentifier = domain ? `domain=eq.${domain.toString()}&` : ""; + const localAssetIdentifier = localAsset ? `local=eq.${localAsset.toLowerCase()}&` : ""; + const adoptedAssetIdentifier = adoptedAsset ? `adopted=eq.${adoptedAsset.toLowerCase()}&` : ""; + const canonicalIdIdentifier = canonicalId ? `canonical_id=eq.${canonicalId.toLowerCase()}&` : ""; + + const searchIdentifier = + domainIdentifier + + localAssetIdentifier + + adoptedAssetIdentifier + + canonicalIdIdentifier; + + const orderBy = order?.orderBy || ""; + const ascOrDesc = order?.ascOrDesc ? `.${order.ascOrDesc}` : ""; + const orderIdentifier = orderBy ? `order=${orderBy}${ascOrDesc}&` : ""; + const limitIdentifier = limit ? `limit=${limit}` : ""; + + const uri = formatUrl( + this.config.cartographerUrl!, + "assets?", + searchIdentifier + orderIdentifier + limitIdentifier + ); validateUri(uri); return await axiosGetRequest(uri); - }, - { promise: true, maxAge: 5 * 60 * 1000 }, // 5 min + }, { + promise: true, + maxAge: 5 * 60 * 1000, // 5 minutes + normalizer: function(args) { + return JSON.stringify(args[0]); + } + } ); getActiveLiquidity = memoize( diff --git a/packages/agents/sdk/test/sdkShared.spec.ts b/packages/agents/sdk/test/sdkShared.spec.ts index c269f43f77..fbf208dc0f 100644 --- a/packages/agents/sdk/test/sdkShared.spec.ts +++ b/packages/agents/sdk/test/sdkShared.spec.ts @@ -52,9 +52,6 @@ describe("SdkShared", () => { stub(SharedFns, "axiosGetRequest").resolves([]); sdkShared = new SdkShared(mockConfig, logger, mockChainData); - // console.log(sdkShared.config.chains); - // console.log(sdkShared.config.chains[13337]); - // console.log(sdkShared.config.chains[13338]); }); afterEach(() => { @@ -364,13 +361,96 @@ describe("SdkShared", () => { }); describe("#getAssetsData", () => { - it("happy: should work", async () => { + let stubAxiosGetRequest; + + beforeEach(async () => { sdkShared.config.cartographerUrl = config.cartographerUrl; restore(); - stub(SharedFns, "axiosGetRequest").resolves([mockAssetData]); + stubAxiosGetRequest = stub(SharedFns, 'axiosGetRequest').resolves([mockAssetData]); + }); + + it("happy: should work", async () => { const res = await sdkShared.getAssetsData(); + expect(res).to.be.deep.equal([mockAssetData]); }); + + it("happy: should work with order", async () => { + await sdkShared.getAssetsData({ + order: { + orderBy: "balance", + ascOrDesc: "desc", + }, + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/assets?order=balance.desc&`) + ).to.be.true; + }); + + it("happy: should work with limit", async () => { + await sdkShared.getAssetsData({ + limit: 1 + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/assets?limit=1`) + ).to.be.true; + }); + + it("happy: should work with order and limit", async () => { + await sdkShared.getAssetsData({ + order: { + orderBy: "balance", + ascOrDesc: "desc", + }, + limit: 1 + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/assets?order=balance.desc&limit=1`) + ).to.be.true; + }); + + it("happy: should work with domain", async () => { + await sdkShared.getAssetsData({ + domain: mock.domain.A + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/assets?domain=eq.${mock.domain.A}&`) + ).to.be.true; + }); + + it("happy: should work with localAsset", async () => { + await sdkShared.getAssetsData({ + localAsset: mock.asset.A.address + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/assets?local=eq.${mock.asset.A.address.toLowerCase()}&`) + ).to.be.true; + }); + + it("happy: should work with adoptedAsset", async () => { + await sdkShared.getAssetsData({ + adoptedAsset: mock.asset.A.address + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/assets?adopted=eq.${mock.asset.A.address.toLowerCase()}&`) + ).to.be.true; + }); + + it("happy: should work with canonicalId", async () => { + await sdkShared.getAssetsData({ + canonicalId: "1" + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/assets?canonical_id=eq.1&`) + ).to.be.true; + }); }); describe("#getAssetsDataByDomainAndAddress", () => { From 80584f13f446afab59b2615d8dddd2b0fd2d935e Mon Sep 17 00:00:00 2001 From: just-a-node Date: Sat, 27 Apr 2024 02:25:53 +0900 Subject: [PATCH 16/18] feat: calculateAmountReceived uses filtered getAssets --- packages/agents/sdk/src/sdkPool.ts | 13 +++++++------ packages/agents/sdk/test/sdkPool.spec.ts | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/agents/sdk/src/sdkPool.ts b/packages/agents/sdk/src/sdkPool.ts index 88db2efa90..65b1e4392c 100644 --- a/packages/agents/sdk/src/sdkPool.ts +++ b/packages/agents/sdk/src/sdkPool.ts @@ -245,13 +245,14 @@ export class SdkPool extends SdkShared { amount, signerAddress, }); - const [originPool, [canonicalDomain, canonicalId]] = await Promise.all([ - this.getPool(originDomain, _originTokenAddress), - this.getCanonicalTokenId(originDomain, _originTokenAddress), - ]); + + const originPool = await this.getPool(originDomain, _originTokenAddress); const isNextAsset = originPool ? utils.getAddress(originPool.local.address) === _originTokenAddress : undefined; - const key = this.calculateCanonicalKey(canonicalDomain, canonicalId); - const destinationAssetData = await this.getAssetsDataByDomainAndKey(destinationDomain, key); + const destinationAssetData = (await this.getAssetsData({ + domain: originDomain, + localAsset: _originTokenAddress, + limit: 1 + }))[0]; if (!destinationAssetData) { throw new Error("Origin token cannot be bridged to any token on this destination domain"); } diff --git a/packages/agents/sdk/test/sdkPool.spec.ts b/packages/agents/sdk/test/sdkPool.spec.ts index 93d07ac40f..1d400b3ed8 100644 --- a/packages/agents/sdk/test/sdkPool.spec.ts +++ b/packages/agents/sdk/test/sdkPool.spec.ts @@ -437,7 +437,7 @@ describe("SdkPool", () => { .onCall(0) // swap once for destination pool .resolves(destinationAmountAfterSwap); stub(sdkPool, "getCanonicalTokenId").resolves([mockAssetData.canonical_domain, mockAssetData.canonical_id]); - stub(sdkPool, "getAssetsDataByDomainAndKey").resolves(mockAssetData); + stub(sdkPool, "getAssetsData").resolves([mockAssetData]); const res = await sdkPool.calculateAmountReceived( mockPool.domainId, @@ -472,7 +472,7 @@ describe("SdkPool", () => { .onCall(1) // swap once for destination pool .resolves(destinationAmountAfterSwap); stub(sdkPool, "getCanonicalTokenId").resolves([mockAssetData.canonical_domain, mockAssetData.canonical_id]); - stub(sdkPool, "getAssetsDataByDomainAndKey").resolves(mockAssetData); + stub(sdkPool, "getAssetsData").resolves([mockAssetData]); const res = await sdkPool.calculateAmountReceived( mockPool.domainId, @@ -500,7 +500,7 @@ describe("SdkPool", () => { .onCall(0) // swap once for origin pool .resolves(originAmountAfterSwap); stub(sdkPool, "getCanonicalTokenId").resolves([mockAssetData.canonical_domain, mockAssetData.canonical_id]); - stub(sdkPool, "getAssetsDataByDomainAndKey").resolves(mockAssetData); + stub(sdkPool, "getAssetsData").resolves([mockAssetData]); const res = await sdkPool.calculateAmountReceived( mockPool.domainId, @@ -522,7 +522,7 @@ describe("SdkPool", () => { const destinationSlippage = "0"; // 0% in BPS stub(sdkPool, "getCanonicalTokenId").resolves([mockAssetData.canonical_domain, mockAssetData.canonical_id]); - stub(sdkPool, "getAssetsDataByDomainAndKey").resolves(mockAssetData); + stub(sdkPool, "getAssetsData").resolves([mockAssetData]); const res = await sdkPool.calculateAmountReceived( mockPool.domainId, @@ -564,7 +564,7 @@ describe("SdkPool", () => { .onCall(0) // swap once for destination pool .resolves(destinationAmountAfterSwapConverted); stub(sdkPool, "getCanonicalTokenId").resolves([mockAssetData.canonical_domain, mockAssetData.canonical_id]); - stub(sdkPool, "getAssetsDataByDomainAndKey").resolves(mockAssetData); + stub(sdkPool, "getAssetsData").resolves([mockAssetData]); const res = await sdkPool.calculateAmountReceived( mockPoolDifferentDecimals.domainId, @@ -602,7 +602,7 @@ describe("SdkPool", () => { .onCall(0) // swap once for origin pool .resolves(originAmountAfterSwapConverted); stub(sdkPool, "getCanonicalTokenId").resolves([mockAssetData.canonical_domain, mockAssetData.canonical_id]); - stub(sdkPool, "getAssetsDataByDomainAndKey").resolves(mockAssetData); + stub(sdkPool, "getAssetsData").resolves([mockAssetData]); const res = await sdkPool.calculateAmountReceived( mockPoolDifferentDecimals.domainId, From 6adfaaac876b4bbcf37150a4710bf7e3a1c9f9cc Mon Sep 17 00:00:00 2001 From: just-a-node Date: Thu, 23 May 2024 17:44:16 -0600 Subject: [PATCH 17/18] fix: already filtering by domain and asset --- packages/agents/sdk/src/sdkUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index f40a62c948..a714d6d790 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -366,10 +366,8 @@ export class SdkUtils extends SdkShared { let totalLiquidity = BigNumber.from(0); for (let routerBalance of routersByLargestBalance) { - if (routerBalance.domain == domainId && routerBalance.local.toLowerCase() == _asset) { - const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); - totalLiquidity = totalLiquidity.add(balanceBN); - } + const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); + totalLiquidity = totalLiquidity.add(balanceBN); } const totalLiquidityWithBuffer = _minLiquidityBN.mul(BigNumber.from(100 + _bufferPercentage)).div(100); From cbca8ae6b61ea37e2f8d337fc088fc5a091cac57 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Thu, 23 May 2024 17:55:02 -0600 Subject: [PATCH 18/18] fix: lints --- packages/agents/sdk/src/config.ts | 6 +++--- packages/agents/sdk/src/sdkUtils.ts | 4 ++-- packages/examples/sdk-server/examples/index.http | 16 ++++++++-------- packages/examples/sdk-server/src/server.ts | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/agents/sdk/src/config.ts b/packages/agents/sdk/src/config.ts index 0dc9c8a7cf..bad54f7865 100644 --- a/packages/agents/sdk/src/config.ts +++ b/packages/agents/sdk/src/config.ts @@ -198,7 +198,7 @@ export const domainsToChainNames: Record = { "1935897199": "scroll", "1936027759": "sepolia", "1869640549": "optimism-sepolia", - "1633842021": "arbitrum-sepolia" + "1633842021": "arbitrum-sepolia", }; // Need to add more domains here. @@ -216,7 +216,7 @@ export const XERC20REGISTRY_DOMAIN_ADDRESS: Record = { "1935897199": "0x397aEEEDd44f40326f9eB583a1DFB8A7A673C40B", // scroll "1936027759": "0x2a3fe9a49fb50536f1ed099192c2ae2404de7bb5", // sepolia "1869640549": "0x18b5b08b10a2e351180f07e31f4fef94d14e28f6", // op-sepolia - "1633842021": "0x343d827d5109e8038bbb71e9ba4f3fd0d546b9ff" // arb-sepolia + "1633842021": "0x343d827d5109e8038bbb71e9ba4f3fd0d546b9ff", // arb-sepolia }; // Need to add more domains here. @@ -234,5 +234,5 @@ export const LOCKBOX_ADAPTER_DOMAIN_ADDRESS: Record = { "1935897199": "", // scroll (TODO) "1936027759": "0xcF021fCFB9bd72E5aA7ab390cFA4fCfDF895c7Cf", // sepolia "1869640549": "0x20b4789065DE09c71848b9A4FcAABB2c10006FA2", // op-sepolia - "1633842021": "0x0f4Fe4903d01E0deb067A7297453fBEFdC36D189" // arb-sepolia + "1633842021": "0x0f4Fe4903d01E0deb067A7297453fBEFdC36D189", // arb-sepolia }; diff --git a/packages/agents/sdk/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index a714d6d790..dd13b8fcac 100644 --- a/packages/agents/sdk/src/sdkUtils.ts +++ b/packages/agents/sdk/src/sdkUtils.ts @@ -361,11 +361,11 @@ export class SdkUtils extends SdkShared { domain: domainId, localAsset: _asset, order: { orderBy: "balance", ascOrDesc: "desc" }, - limit: _maxN + limit: _maxN, }); let totalLiquidity = BigNumber.from(0); - for (let routerBalance of routersByLargestBalance) { + for (const routerBalance of routersByLargestBalance) { const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); totalLiquidity = totalLiquidity.add(balanceBN); } diff --git a/packages/examples/sdk-server/examples/index.http b/packages/examples/sdk-server/examples/index.http index 4fece5dc4b..5bee43b586 100644 --- a/packages/examples/sdk-server/examples/index.http +++ b/packages/examples/sdk-server/examples/index.http @@ -1,9 +1,9 @@ - -# @baseUrl = http://127.0.0.1:8080 + # +@baseUrl = http://127.0.0.1:8080 # @baseUrl = https://sdk-server.testnet.staging.connext.ninja # @baseUrl = https://sdk-server.testnet.connext.ninja -@baseUrl = https://sdk-server.mainnet.connext.ninja +# @baseUrl = https://sdk-server.mainnet.connext.ninja @yourAddress = 0x6d2A06543D23Cc6523AE5046adD8bb60817E0a94 ############## @@ -221,10 +221,10 @@ POST {{baseUrl}}/calculateAmountReceived Content-Type: application/json { - "originDomain": "9991", - "destinationDomain": "1735356532", - "originTokenAddress": "0xeDb95D8037f769B72AAab41deeC92903A98C9E16", - "amount": "100000", + "originDomain": "6648936", + "destinationDomain": "1869640809", + "originTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "amount": "100000000000", "receiveLocal": false, "checkFastLiquidity": true } @@ -779,7 +779,7 @@ Content-Type: application/json { "domainId": "1869640809", "asset": "0x67E51f46e8e14D4E4cab9dF48c59ad8F512486DD", - "minLiquidity": "100000000000000000000000", + "minLiquidity": "100000000000", "maxN": 4 } diff --git a/packages/examples/sdk-server/src/server.ts b/packages/examples/sdk-server/src/server.ts index 65d4e1b4ec..f8bf5e0c1c 100644 --- a/packages/examples/sdk-server/src/server.ts +++ b/packages/examples/sdk-server/src/server.ts @@ -1,4 +1,4 @@ -import fastify, { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; +import fastify, { FastifyInstance } from "fastify"; import { createLoggingContext, Logger, getBestProvider, jsonifyError } from "@connext/nxtp-utils"; import { fastifyRedis } from "@fastify/redis"; import cors from "@fastify/cors";