Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add enoughRouterLiquidity #6018

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion packages/agents/sdk-wrapper/src/sdkUtils.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<BigNumber> {
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);
}
}
28 changes: 28 additions & 0 deletions packages/agents/sdk-wrapper/test/sdkUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as MockableFns from "../src/mockable";

import { expect } from "@connext/nxtp-utils";
import {
SdkEnoughRouterLiquidityParams,
SdkCheckRouterLiquidityParams,
SdkGetRouterLiquidityParams,
SdkGetRoutersDataParams,
Expand Down Expand Up @@ -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);
});
});
});
9 changes: 9 additions & 0 deletions packages/agents/sdk/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,15 @@ export const SdkCheckRouterLiquidityParamsSchema = Type.Object({
});
export type SdkCheckRouterLiquidityParams = Static<typeof SdkCheckRouterLiquidityParamsSchema>;

// enoughRouterLiquidity
export const SdkEnoughRouterLiquidityParamsSchema = Type.Object({
domainId: Type.String(),
asset: Type.String(),
minLiquidity: Type.Number(),
maxN: Type.Optional(Type.Number()),
});
export type SdkEnoughRouterLiquidityParams = Static<typeof SdkEnoughRouterLiquidityParamsSchema>;

/************************************
SDK Router Types
*************************************/
Expand Down
49 changes: 48 additions & 1 deletion packages/agents/sdk/src/sdkUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { utils, BigNumber } from "ethers";
import { utils, BigNumber, BigNumberish } from "ethers";
import {
Logger,
ChainData,
Expand Down Expand Up @@ -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<boolean> {
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;
}
}
101 changes: 101 additions & 0 deletions packages/agents/sdk/test/sdkUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,107 @@ describe("SdkUtils", () => {
});
});

describe("#enoughRouterLiquidity", () => {
it("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,
"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 <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,
}
]);
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;
Expand Down
12 changes: 12 additions & 0 deletions packages/examples/sdk-server/examples/index.http
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions packages/examples/sdk-server/src/routes/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
},
);
};
Loading