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

[GSW-488] Swap Route #237

Merged
merged 22 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b312ce0
fix: Change sqrt calculation expression
jinoosss Nov 20, 2023
cd4e374
fix: Fix pool bin data
jinoosss Nov 20, 2023
46430d4
[GSW-556] feat: Implement a function to find all swap paths
jinoosss Nov 20, 2023
1c2800e
fix: Fix pool data model
jinoosss Nov 20, 2023
ddbd39c
feat: Implement a SwapRouter
jinoosss Nov 26, 2023
47bff0b
[GSW-556] feat: Implements Swap Router
jinoosss Nov 28, 2023
25e1de4
[GSW-556] feat: Implements Swap Router
jinoosss Nov 29, 2023
c491b0c
chore: Update project environment
jinoosss Nov 29, 2023
e5e4c4a
[GSW-556] feat: Implements Swap Router
jinoosss Nov 29, 2023
dfc6b4c
[GSW-581] feat: Add test environments and test cases
jinoosss Nov 29, 2023
e7c077a
[GSW-556] feat: Implements Swap Router
jinoosss Nov 29, 2023
a94474b
[GSW-581] feat: Add test environments and test cases
jinoosss Nov 29, 2023
aa2a11a
chore: Update package.json
jinoosss Nov 29, 2023
a820364
fix: Remove unused line
jinoosss Nov 29, 2023
87f9bee
chore: Update yarn version
jinoosss Nov 30, 2023
e89015a
fix: Remove unused modules
jinoosss Nov 30, 2023
c09410c
chore: Update build cache
jinoosss Nov 30, 2023
2bcf2bc
chore: Update action scripts
jinoosss Nov 30, 2023
ea316ff
Merge pull request #234 from gnoswap-labs/GSW-556-calculate-swap-pool…
jinoosss Nov 30, 2023
cb97f64
[GSW-593] feat: Change swap router key generation (#236)
jinoosss Dec 1, 2023
a9b2dde
[GSW-450] feat: Implement Swap Expected Result Details (#235)
jinoosss Dec 1, 2023
0c9c273
fix: Change provider
jinoosss Dec 1, 2023
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
9 changes: 9 additions & 0 deletions packages/web/src/models/swap/estimated-route-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { RouteModel } from "./route-model";

export interface EstimatedRouteModel extends RouteModel {
quote: number;

inputAmount: number;

outputAmount: number;
}
5 changes: 5 additions & 0 deletions packages/web/src/models/swap/route-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PoolModel } from "@models/pool/pool-model";

export interface RouteModel {
pools: PoolModel[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface EstimatedRouteSimpleInfo {
pools: string[];

quote: number;

inputAmount: number;

outputAmount: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RouteModel } from "@models/swap/route-model";
import { TokenModel } from "@models/token/token-model";

export interface EstimateRouteQuotesRequest {
inputToken: TokenModel;

outputToken: TokenModel;

amount: number;

exactType: "EXACT_IN" | "EXACT_OUT";

routes: RouteModel[];

distributionRatio: number;
}
1 change: 1 addition & 0 deletions packages/web/src/repositories/swap/request/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./swap-request";
export * from "./estimate-route-quotes-request";
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { EstimatedRouteSimpleInfo } from "../info/estimated-route-simple-info";

export interface EstimateRouteQuotesResponse {
estimatedRoutes: EstimatedRouteSimpleInfo[];
}
170 changes: 170 additions & 0 deletions packages/web/src/repositories/swap/swap-router-repository-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { EstimateRouteQuotesRequest } from "./request";
import { WalletClient } from "@common/clients/wallet-client";
import { SwapRouterRepository } from "./swap-router-repository";
import { EstimateRouteQuotesResponse } from "./response/estimate-route-quotes-response";
import { PoolModel } from "@models/pool/pool-model";
import { TokenModel } from "@models/token/token-model";
import { RouteModel } from "@models/swap/route-model";
import { Queue } from "@utils/common";
import { makeRoutesQuery } from "@utils/swap-route-utils";
import { GnoProvider } from "@gnolang/gno-js-client";
import { CommonError } from "@common/errors";
import { SwapError } from "@common/errors/swap";
import { evaluateExpressionToObject, makeABCIParams } from "@utils/rpc-utils";
import { EstimatedRouteSimpleInfo } from "./info/estimated-route-simple-info";

const POOL_PACKAGE_PATH = process.env.NEXT_PUBLIC_PACKAGE_POOL_PATH;

export class SwapRouterRepositoryMock implements SwapRouterRepository {
private rpcProvider: GnoProvider | null;
private walletClient: WalletClient | null;

constructor(rpcProvider: GnoProvider, walletClient: WalletClient) {
this.rpcProvider = rpcProvider;
this.walletClient = walletClient;
}

public findAllRoutesBy = (
inputToken: TokenModel,
outputToken: TokenModel,
pools: PoolModel[],
routeSize = 3,
jinoosss marked this conversation as resolved.
Show resolved Hide resolved
) => {
function getPoolTokenPaths(pool: PoolModel) {
return [pool.tokenA.path, pool.tokenB.path];
}

function getOtherToken(pool: PoolModel, tokenPath: string) {
return pool.tokenA.path === tokenPath ? pool.tokenB : pool.tokenA;
}

const routes: RouteModel[] = [];
const inputTokenPath = inputToken.path;
const outputTokenPath = outputToken.path;

// Pool list of tokens
const tokenPoolsMap = pools.reduce<{ [key in string]: PoolModel[] }>(
(result, current) => {
const tokenAPath = current.tokenA.path;
const tokenBPath = current.tokenB.path;
if (Array.isArray(result[tokenAPath])) {
result[tokenAPath].push(current);
} else {
result[tokenAPath] = [current];
}
if (Array.isArray(result[tokenBPath])) {
result[tokenBPath].push(current);
} else {
result[tokenBPath] = [current];
}
return result;
},
{},
);

// Queue for BFS search
const routeQueue = new Queue<{
lastToken: TokenModel;
route: RouteModel;
}>();

// Initialize queue
const initialPools = tokenPoolsMap[inputTokenPath];
initialPools.forEach(pool =>
routeQueue.enqueue({
lastToken: getOtherToken(pool, inputTokenPath),
route: { pools: [pool] },
}),
);

// Exit the loop when the queue is empty
while (routeQueue.arr.length !== 0) {
const queueItems = routeQueue.arr;

for (let index = 0; index < queueItems.length; index++) {
const current = routeQueue.dequeue();
if (
!current ||
current.route.pools.length === 0 ||
current.route.pools.length > routeSize
) {
continue;
}

// Adding route when it's the output token
if (current.lastToken.path === outputTokenPath) {
routes.push({ pools: current.route.pools });
continue;
}

// Token paths in route
const tokenPathsOfRoute = current.route.pools
.flatMap(getPoolTokenPaths)
.filter(path => current.lastToken.path !== path);
const nextPools = tokenPoolsMap[current.lastToken.path].filter(
({ tokenA, tokenB }) =>
!tokenPathsOfRoute.includes(tokenA.path) &&
!tokenPathsOfRoute.includes(tokenB.path),
);

// Add a queue item
for (const nextPool of nextPools) {
const nextLastToken = getOtherToken(nextPool, current.lastToken.path);
const nextPools = [...current.route.pools, nextPool];

routeQueue.enqueue({
lastToken: nextLastToken,
route: { pools: nextPools },
});
}
}
}
return routes;
};

public estimateRouteQuotes = async (
request: EstimateRouteQuotesRequest,
): Promise<EstimateRouteQuotesResponse> => {
if (!POOL_PACKAGE_PATH || !this.rpcProvider) {
throw new CommonError("FAILED_INITIALIZE_GNO_PROVIDER");
}
const {
inputToken,
outputToken,
amount,
exactType,
routes,
distributionRatio,
} = request;
if (Number.isNaN(amount)) {
throw new SwapError("INVALID_PARAMS");
}
const inputTokenPath = inputToken.path;
const outputTokenPath = outputToken.path;
const routesQuery = makeRoutesQuery(routes);
const param = makeABCIParams("EstimateRouteQuotes", [
inputTokenPath,
outputTokenPath,
amount,
exactType,
routesQuery,
distributionRatio,
]);
const result = await this.rpcProvider
.evaluateExpression(POOL_PACKAGE_PATH, param)
.then(evaluateExpressionToObject<{
response: {
data: {
estimatedRoutes: EstimatedRouteSimpleInfo[]
}
}
}>);

if (result === null) {
throw new SwapError("NOT_FOUND_SWAP_POOL");
}
return {
estimatedRoutes: result.response.data.estimatedRoutes
};
};
}
112 changes: 112 additions & 0 deletions packages/web/src/repositories/swap/swap-router-repository-mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { SwapRouterRepository } from "./swap-router-repository";
import { EstimateRouteQuotesResponse } from "./response/estimate-route-quotes-response";
import { PoolModel } from "@models/pool/pool-model";
import { TokenModel } from "@models/token/token-model";
import { RouteModel } from "@models/swap/route-model";
import { Queue } from "@utils/common";

export class SwapRouterRepositoryMock implements SwapRouterRepository {
public findAllRoutesBy = (
inputToken: TokenModel,
outputToken: TokenModel,
pools: PoolModel[],
routeSize = 3,
) => {
function getPoolTokenPaths(pool: PoolModel) {
return [pool.tokenA.path, pool.tokenB.path];
}

function getOtherToken(pool: PoolModel, tokenPath: string) {
return pool.tokenA.path === tokenPath ? pool.tokenB : pool.tokenA;
}

const routes: RouteModel[] = [];
const inputTokenPath = inputToken.path;
const outputTokenPath = outputToken.path;

// Pool list of tokens
const tokenPoolsMap = pools.reduce<{ [key in string]: PoolModel[] }>(
(result, current) => {
const tokenAPath = current.tokenA.path;
const tokenBPath = current.tokenB.path;
if (Array.isArray(result[tokenAPath])) {
result[tokenAPath].push(current);
} else {
result[tokenAPath] = [current];
}
if (Array.isArray(result[tokenBPath])) {
result[tokenBPath].push(current);
} else {
result[tokenBPath] = [current];
}
return result;
},
{},
);

// Queue for BFS search
const routeQueue = new Queue<{
lastToken: TokenModel;
route: RouteModel;
}>();

// Initialize queue
const initialPools = tokenPoolsMap[inputTokenPath];
initialPools.forEach(pool =>
routeQueue.enqueue({
lastToken: getOtherToken(pool, inputTokenPath),
route: { pools: [pool] },
}),
);

// Exit the loop when the queue is empty
while (routeQueue.arr.length !== 0) {
const queueItems = routeQueue.arr;

for (let index = 0; index < queueItems.length; index++) {
const current = routeQueue.dequeue();
if (
!current ||
current.route.pools.length === 0 ||
current.route.pools.length > routeSize
) {
continue;
}

// Adding route when it's the output token
if (current.lastToken.path === outputTokenPath) {
routes.push({ pools: current.route.pools });
continue;
}

// Token paths in route
const tokenPathsOfRoute = current.route.pools
.flatMap(getPoolTokenPaths)
.filter(path => current.lastToken.path !== path);
const nextPools = tokenPoolsMap[current.lastToken.path].filter(
({ tokenA, tokenB }) =>
!tokenPathsOfRoute.includes(tokenA.path) &&
!tokenPathsOfRoute.includes(tokenB.path),
);

// Add a queue item
for (const nextPool of nextPools) {
const nextLastToken = getOtherToken(nextPool, current.lastToken.path);
const nextPools = [...current.route.pools, nextPool];

routeQueue.enqueue({
lastToken: nextLastToken,
route: { pools: nextPools },
});
}
}
}
return routes;
};

public estimateRouteQuotes = async (): Promise<
EstimateRouteQuotesResponse
> => {
return { estimatedRoutes: [] };
};
}
Loading