diff --git a/packages/agents/sdk-wrapper/src/sdkUtils.ts b/packages/agents/sdk-wrapper/src/sdkUtils.ts index 404614793c..b511dfc948 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"; @@ -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; @@ -59,6 +60,32 @@ export class SdkUtils extends SdkShared { return BigNumber.from(response.data); } + async enoughRouterLiquidity( + domainId: string, + asset: string, + minLiquidity: BigNumberish, + maxN?: number, + bufferPercentage?: number + ): Promise { + const params: { + domainId: string; + asset: string; + minLiquidity: BigNumberish, + maxN?: number, + bufferPercentage?: number + } = + { + domainId, + asset, + minLiquidity, + maxN, + bufferPercentage + }; + const response = await axiosPost(`${this.baseUri}/enoughRouterLiquidity`, params); + + return BigNumber.from(response.data); + } + async getLatestAssetPrice(domainId: string, asset: string): Promise { const params: { domainId: string; asset: string } = { domainId, diff --git a/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts b/packages/agents/sdk-wrapper/test/sdkUtils.spec.ts index 3c53259af4..adfa147b5f 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, @@ -185,6 +186,37 @@ describe("#SDKUtils", () => { }); }); + 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.enoughRouterLiquidity( + expectedArgs.domainId, + expectedArgs.asset, + expectedArgs.minLiquidity + ); + + expect(axiosPostStub).to.have.been.calledWithExactly(expectedBaseUri + expectedEndpoint, expectedArgs); + expect(res).to.be.deep.eq(expectedRes); + }); + }); + describe("#getLatestAssetPrice", async () => { it("happy: should send request with correct params", async () => { const expectedEndpoint = "/getLatestAssetPrice"; 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/interfaces/index.ts b/packages/agents/sdk/src/interfaces/index.ts index 3315aec62d..d59a2a2def 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; @@ -844,6 +845,16 @@ 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()), + bufferPercentage: Type.Optional(Type.Number()), +}); +export type SdkEnoughRouterLiquidityParams = Static; + // getLatestAssetPrice export const SdkGetLatestAssetPriceParamsSchema = Type.Object({ domainId: Type.String(), diff --git a/packages/agents/sdk/src/sdkPool.ts b/packages/agents/sdk/src/sdkPool.ts index 041aa08bdc..65b1e4392c 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. @@ -244,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"); } @@ -308,14 +310,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/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/src/sdkUtils.ts b/packages/agents/sdk/src/sdkUtils.ts index 1f321711f2..dd13b8fcac 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, @@ -70,9 +70,14 @@ 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". + * @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 @@ -94,16 +99,36 @@ export class SdkUtils extends SdkShared { * ``` */ async getRoutersData(params?: { + domain?: string; + localAsset?: string; + adoptedAsset?: string; + canonicalId?: string; order?: { orderBy?: string; ascOrDesc?: "asc" | "desc" }; + limit?: number; }): Promise { - const { order } = params ?? {}; + const { domain, localAsset, adoptedAsset, canonicalId, order, limit } = params ?? {}; - const orderBy = order?.orderBy ? order.orderBy : ""; - const ascOrDesc = order?.ascOrDesc ? "." + order.ascOrDesc : ""; - const orderIdentifier = orderBy ? `order=${orderBy}${ascOrDesc}` : ""; + 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 uri = formatUrl(this.config.cartographerUrl!, "routers_with_balances?", orderIdentifier); - // Validate uri + 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!, + "routers_with_balances?", + searchIdentifier + orderIdentifier + limitIdentifier + ); validateUri(uri); return await axiosGetRequest(uri); @@ -309,6 +334,60 @@ export class SdkUtils extends SdkShared { .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 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. + * + */ + async enoughRouterLiquidity( + domainId: string, + asset: string, + minLiquidity: BigNumberish, + 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, + localAsset: _asset, + order: { orderBy: "balance", ascOrDesc: "desc" }, + limit: _maxN, + }); + + let totalLiquidity = BigNumber.from(0); + for (const routerBalance of routersByLargestBalance) { + const balanceBN = BigNumber.from(this.scientificToBigInt(routerBalance.balance.toString())); + totalLiquidity = totalLiquidity.add(balanceBN); + } + + const totalLiquidityWithBuffer = _minLiquidityBN.mul(BigNumber.from(100 + _bufferPercentage)).div(100); + return totalLiquidity.gte(totalLiquidityWithBuffer); + } + + 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; + } + /** * Fetches asset prices that match filter criteria from Cartographer. * diff --git a/packages/agents/sdk/test/sdkPool.spec.ts b/packages/agents/sdk/test/sdkPool.spec.ts index ad0546acdb..1d400b3ed8 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 @@ -432,21 +437,26 @@ 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, 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% @@ -462,17 +472,20 @@ 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, 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 () => { @@ -487,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, @@ -509,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, @@ -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 = { @@ -548,17 +564,20 @@ 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, 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 () => { @@ -583,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, 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", () => { diff --git a/packages/agents/sdk/test/sdkUtils.spec.ts b/packages/agents/sdk/test/sdkUtils.spec.ts index 403848bd67..3c2e6d7437 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,13 +16,13 @@ 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); - nxtpUtils = await SdkUtils.create(mockConfig, undefined, mockChainData); }); @@ -44,23 +44,96 @@ describe("SdkUtils", () => { }); describe("#getRoutersData", () => { - it("happy: should work", async () => { + beforeEach(async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; - const res = await nxtpUtils.getRoutersData(); + stubAxiosGetRequest = stub(SharedFns, 'axiosGetRequest').resolves([ + { "address": mkAddress("0x1") } + ]); + }); - expect(res).to.not.be.undefined; + it("happy: should work", async () => { + 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 () => { + 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 () => { + 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("happy: should work with domain", async () => { + 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 () => { + await nxtpUtils.getRoutersData({ + localAsset: mock.asset.A.address + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?local=eq.${mock.asset.A.address.toLowerCase()}&`) + ).to.be.true; + }); + + it("happy: should work with adoptedAsset", async () => { + await nxtpUtils.getRoutersData({ + adoptedAsset: mock.asset.A.address + }); + + expect(stubAxiosGetRequest.calledWith( + config.cartographerUrl + `/routers_with_balances?adopted=eq.${mock.asset.A.address.toLowerCase()}&`) + ).to.be.true; + }); + + it("happy: should work with canonicalId", async () => { + 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 () => { @@ -79,6 +152,163 @@ 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; + + 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, + "100", + 2 + ); + + expect(res).to.be.true; + }); + + it("should be true when enough liquidity between N routers", 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 + ); + + expect(res).to.be.true; + }); + + it("should be false when not enough liquidity between { + (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; + + stubAxiosGetRequest.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("should be false when not enough liquidity between N routers", 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, + "400", + 2 + ); + + 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", () => { it("happy: should work with userAddress", async () => { (nxtpUtils as any).config.cartographerUrl = config.cartographerUrl; @@ -184,7 +414,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); 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/examples/index.http b/packages/examples/sdk-server/examples/index.http index 48657f1489..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 } @@ -771,6 +771,18 @@ Content-Type: application/json "topN": 4 } +############## +### POST enoughRouterLiquidity +POST {{baseUrl}}/enoughRouterLiquidity +Content-Type: application/json + +{ + "domainId": "1869640809", + "asset": "0x67E51f46e8e14D4E4cab9dF48c59ad8F512486DD", + "minLiquidity": "100000000000", + "maxN": 4 +} + ############## ### POST getLatestAssetPrice POST {{baseUrl}}/getLatestAssetPrice diff --git a/packages/examples/sdk-server/src/cacheMiddleware.ts b/packages/examples/sdk-server/src/cacheMiddleware.ts new file mode 100644 index 0000000000..7b23f31b87 --- /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 !== null) { + 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, }, 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/routes/utils.ts b/packages/examples/sdk-server/src/routes/utils.ts index c16661f54e..2613b3dec5 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, @@ -5,14 +7,15 @@ import { SdkGetTransfersParams, SdkCheckRouterLiquidityParamsSchema, SdkCheckRouterLiquidityParams, + SdkEnoughRouterLiquidityParamsSchema, + SdkEnoughRouterLiquidityParams, SdkGetLatestAssetPriceParamsSchema, 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; @@ -29,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); }); @@ -58,6 +60,28 @@ export const utilsRoutes = async (server: FastifyInstance, options: UtilsRoutesO }, ); + s.post<{ Body: SdkEnoughRouterLiquidityParams }>( + "/enoughRouterLiquidity", + { + schema: { + body: SdkEnoughRouterLiquidityParamsSchema, + }, + }, + async (request, reply) => { + await cacheMiddleware( + server, + request, + reply, + async () => { + const { domainId, asset, minLiquidity, maxN, bufferPercentage } = request.body; + return sdkUtilsInstance.enoughRouterLiquidity(domainId, asset, minLiquidity, maxN, bufferPercentage); + }, + "enoughRouterLiquidity", + options + ); + } + ); + s.post<{ Body: SdkGetLatestAssetPriceParams }>( "/getLatestAssetPrice", { diff --git a/packages/examples/sdk-server/src/server.ts b/packages/examples/sdk-server/src/server.ts index 6c680be8b7..f8bf5e0c1c 100644 --- a/packages/examples/sdk-server/src/server.ts +++ b/packages/examples/sdk-server/src/server.ts @@ -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, + }); +}