@galacticcouncil/sdk 0.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +1 -0
- package/.eslintrc.json +20 -0
- package/.nvmrc +1 -0
- package/.prettierrc.json +12 -0
- package/.vscode/settings.json +15 -0
- package/README.md +101 -0
- package/dist/cjs/hydra_dx_wasm_bg-ZX7K4FM7.wasm +0 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/esm/hydra_dx_wasm_bg-ZX7K4FM7.wasm +0 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/types/api/index.d.ts +1 -0
- package/dist/types/api/router.d.ts +129 -0
- package/dist/types/api/trader.d.ts +0 -0
- package/dist/types/client/capi.d.ts +0 -0
- package/dist/types/client/index.d.ts +1 -0
- package/dist/types/client/polkadot.d.ts +14 -0
- package/dist/types/client/types.d.ts +7 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/pool/index.d.ts +3 -0
- package/dist/types/pool/polkadotPoolService.d.ts +7 -0
- package/dist/types/pool/poolFactory.d.ts +4 -0
- package/dist/types/pool/xyk/math/bundler.d.ts +9 -0
- package/dist/types/pool/xyk/math/nodejs.d.ts +9 -0
- package/dist/types/pool/xyk/xykPolkadotClient.d.ts +8 -0
- package/dist/types/pool/xyk/xykPool.d.ts +16 -0
- package/dist/types/suggester/bfs.d.ts +37 -0
- package/dist/types/suggester/graph.d.ts +12 -0
- package/dist/types/suggester/index.d.ts +3 -0
- package/dist/types/suggester/suggester.d.ts +24 -0
- package/dist/types/types.d.ts +55 -0
- package/dist/types/utils/bignumber.d.ts +8 -0
- package/dist/types/utils/math.d.ts +4 -0
- package/dist/types/utils/queue.d.ts +13 -0
- package/dist/types/utils/stack.d.ts +15 -0
- package/dist/types/utils/traversal/bfs.d.ts +27 -0
- package/esbuild.mjs +36 -0
- package/jest.config.mjs +16 -0
- package/package.json +35 -0
- package/src/api/index.ts +1 -0
- package/src/api/router.ts +359 -0
- package/src/api/trader.ts +0 -0
- package/src/client/capi.ts +0 -0
- package/src/client/index.ts +1 -0
- package/src/client/polkadot.ts +47 -0
- package/src/client/types.ts +8 -0
- package/src/index.ts +4 -0
- package/src/pool/index.ts +3 -0
- package/src/pool/polkadotPoolService.ts +19 -0
- package/src/pool/poolFactory.ts +14 -0
- package/src/pool/xyk/math/bundler.ts +19 -0
- package/src/pool/xyk/math/nodejs.ts +19 -0
- package/src/pool/xyk/xykPolkadotClient.ts +58 -0
- package/src/pool/xyk/xykPool.ts +82 -0
- package/src/suggester/bfs.ts +106 -0
- package/src/suggester/graph.ts +31 -0
- package/src/suggester/index.ts +3 -0
- package/src/suggester/suggester.ts +66 -0
- package/src/types.ts +61 -0
- package/src/utils/bignumber.ts +25 -0
- package/src/utils/math.ts +24 -0
- package/src/utils/queue.ts +26 -0
- package/src/utils/stack.ts +31 -0
- package/src/utils/traversal/bfs.ts +74 -0
- package/test/api/router.spec.ts +87 -0
- package/test/data/xykPool.ts +21 -0
- package/test/data/xykPools.ts +61 -0
- package/test/lib/mockXykPoolService.ts +8 -0
- package/test/pool/xyk/xykPool.spec.ts +26 -0
- package/test/script/examples/router/getAllAssets.ts +14 -0
- package/test/script/examples/router/getAllPaths.ts +14 -0
- package/test/script/examples/router/getAssetPairs.ts +14 -0
- package/test/script/examples/router/getBestBuyPrice.ts +19 -0
- package/test/script/examples/router/getBestSellPrice.ts +19 -0
- package/test/script/executor.ts +45 -0
- package/test/suggester/bfs.spec.ts +34 -0
- package/test/suggester/graph.spec.ts +30 -0
- package/test/suggester/suggester.spec.ts +25 -0
- package/test/utils/traversal/bfs.spec.ts +28 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Queue } from '../utils/queue';
|
|
2
|
+
|
|
3
|
+
export type Path = Node[];
|
|
4
|
+
export type Node = [id: number, from: string];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Breadth First Search.
|
|
8
|
+
*
|
|
9
|
+
* - uses Queue to find the shortest path
|
|
10
|
+
* - slower than DFS (Depth First Search)
|
|
11
|
+
* - better when dst is closer to src
|
|
12
|
+
* - complexity O(N+E) where N are nodes and E are edges
|
|
13
|
+
*/
|
|
14
|
+
export class Bfs {
|
|
15
|
+
/**
|
|
16
|
+
* Check if current node is already present in path
|
|
17
|
+
*
|
|
18
|
+
* @param x - current node
|
|
19
|
+
* @param path - path
|
|
20
|
+
* @returns true if node in path, otherwise false
|
|
21
|
+
*/
|
|
22
|
+
isNotVisited(x: number, path: Path): boolean {
|
|
23
|
+
let notVisited: boolean = true;
|
|
24
|
+
path.forEach((pv) => {
|
|
25
|
+
if (pv[0] === x) {
|
|
26
|
+
notVisited = false;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return notVisited;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Finding paths in graph from given source to destination
|
|
34
|
+
*
|
|
35
|
+
* @param g - routes graph containing nodes & corresponding edges
|
|
36
|
+
* @param src - source node
|
|
37
|
+
* @param dst - destination node or null if requesting all posible paths from src
|
|
38
|
+
* @returns paths
|
|
39
|
+
*/
|
|
40
|
+
findPaths(g: Path[], src: number, dst: number | null): Path[] {
|
|
41
|
+
// Store the result paths
|
|
42
|
+
const paths: Path[] = [];
|
|
43
|
+
// Store the traversing paths
|
|
44
|
+
const queue = new Queue<Path>();
|
|
45
|
+
// Store the current path
|
|
46
|
+
const currentPath: Path = [];
|
|
47
|
+
|
|
48
|
+
// First node of path has no from (initial)
|
|
49
|
+
currentPath.push([src, '']);
|
|
50
|
+
queue.enqueue(currentPath);
|
|
51
|
+
|
|
52
|
+
while (queue.size() > 0) {
|
|
53
|
+
const path = queue.dequeue();
|
|
54
|
+
|
|
55
|
+
if (path == null) {
|
|
56
|
+
return paths;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const last = path[path.length - 1];
|
|
60
|
+
|
|
61
|
+
// If destination is undefined save all traversal to paths
|
|
62
|
+
// If last node is the desired destination save to paths
|
|
63
|
+
if (dst === null) {
|
|
64
|
+
paths.push(path);
|
|
65
|
+
} else if (last[0] === dst) {
|
|
66
|
+
paths.push(path);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Traverse to all nodes connected to current one and push path to queue
|
|
70
|
+
const lastNode = g[last[0]];
|
|
71
|
+
lastNode.forEach((segment) => {
|
|
72
|
+
if (this.isNotVisited(segment[0], path)) {
|
|
73
|
+
const newpath = [...path];
|
|
74
|
+
newpath.push(segment);
|
|
75
|
+
queue.enqueue(newpath);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return paths;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build and populate graph
|
|
84
|
+
*
|
|
85
|
+
* @param nodes - list of pool assets
|
|
86
|
+
* @param edges - list of all edges [id, from, to] between assets
|
|
87
|
+
* @returns - traversal graph
|
|
88
|
+
*/
|
|
89
|
+
buildAndPopulateGraph(
|
|
90
|
+
nodes: string[],
|
|
91
|
+
edges: [string, string, string][]
|
|
92
|
+
): Path[] {
|
|
93
|
+
const graph: Path[] = [];
|
|
94
|
+
for (let j = 0; j < nodes.length; j++) {
|
|
95
|
+
graph.push([]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const [address, from, to] of edges) {
|
|
99
|
+
const fromNumber = parseInt(from);
|
|
100
|
+
const toNumber = parseInt(to);
|
|
101
|
+
graph[fromNumber].push([toNumber, address]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return graph;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PoolBase } from '../types';
|
|
2
|
+
|
|
3
|
+
export type Edge = [address: string, from: string, to: string];
|
|
4
|
+
|
|
5
|
+
export type NodeEdges = {
|
|
6
|
+
[node: string]: Edge[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Calculate nodes & edges from substrate pools
|
|
11
|
+
*
|
|
12
|
+
* @param pools - given substrate pools
|
|
13
|
+
* @returns nodes & corresponding edges
|
|
14
|
+
*/
|
|
15
|
+
export function getNodesAndEdges(pools: PoolBase[]): NodeEdges {
|
|
16
|
+
const edgesFromNode: NodeEdges = {};
|
|
17
|
+
for (const pool of pools) {
|
|
18
|
+
const n = pool.tokens.length;
|
|
19
|
+
for (let i = 0; i < n; i++) {
|
|
20
|
+
if (!edgesFromNode[pool.tokens[i].id]) {
|
|
21
|
+
edgesFromNode[pool.tokens[i].id] = [];
|
|
22
|
+
}
|
|
23
|
+
for (let j = 0; j < n; j++) {
|
|
24
|
+
if (i == j) continue;
|
|
25
|
+
const edge: Edge = [pool.address, pool.tokens[i].id, pool.tokens[j].id];
|
|
26
|
+
edgesFromNode[pool.tokens[i].id].push(edge);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return edgesFromNode;
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PoolBase } from '../types';
|
|
2
|
+
import { Bfs, Node, Path } from './bfs';
|
|
3
|
+
|
|
4
|
+
import { getNodesAndEdges, Edge } from './graph';
|
|
5
|
+
|
|
6
|
+
export class RouteSuggester {
|
|
7
|
+
/**
|
|
8
|
+
* Proposals are ideal paths from
|
|
9
|
+
* 1) tokenIn to tokenOut
|
|
10
|
+
* 2) tokenIn to *(all possible paths are requested)
|
|
11
|
+
*
|
|
12
|
+
* calculated from all permutations of tokens of given pools.
|
|
13
|
+
*
|
|
14
|
+
* E.g. permutation of pool A={1,3} is 2, such as {1,3}, {3,1} where 1 are 3
|
|
15
|
+
* are pool assets(tokens)
|
|
16
|
+
*
|
|
17
|
+
* Filtering of valid paths and corresponding asset pairs is done by router itself!!!
|
|
18
|
+
*
|
|
19
|
+
* @param tokenIn - tokenIn
|
|
20
|
+
* @param tokenOut - tokenOut or null if all possible paths from tokenIn are requested
|
|
21
|
+
* @param pools - substrate based pools
|
|
22
|
+
* @returns all possible path proposals
|
|
23
|
+
*/
|
|
24
|
+
getProposals(
|
|
25
|
+
tokenIn: string,
|
|
26
|
+
tokenOut: string | null,
|
|
27
|
+
pools: PoolBase[]
|
|
28
|
+
): Edge[][] {
|
|
29
|
+
const nodeEdges = getNodesAndEdges(pools);
|
|
30
|
+
const poolAssets = Object.keys(nodeEdges);
|
|
31
|
+
const possiblePairs: Edge[] = poolAssets
|
|
32
|
+
.map((node) => nodeEdges[node])
|
|
33
|
+
.flat();
|
|
34
|
+
|
|
35
|
+
const bfs = new Bfs();
|
|
36
|
+
const bfsGraph = bfs.buildAndPopulateGraph(poolAssets, possiblePairs);
|
|
37
|
+
const possiblePaths = bfs.findPaths(
|
|
38
|
+
bfsGraph,
|
|
39
|
+
parseInt(tokenIn),
|
|
40
|
+
tokenOut ? parseInt(tokenOut) : null
|
|
41
|
+
);
|
|
42
|
+
return this.parsePaths(possiblePaths);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private parsePaths(possiblePaths: Path[]): Edge[][] {
|
|
46
|
+
const paths: Edge[][] = [];
|
|
47
|
+
for (const path of possiblePaths) {
|
|
48
|
+
const edges: Edge[] = [];
|
|
49
|
+
for (let i = 0; i < path.length; i++) {
|
|
50
|
+
const from = path[i];
|
|
51
|
+
const to = path[i + 1];
|
|
52
|
+
if (to == null) {
|
|
53
|
+
break;
|
|
54
|
+
} else {
|
|
55
|
+
edges.push(this.toEdge(from, to));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
paths.push(edges);
|
|
59
|
+
}
|
|
60
|
+
return paths;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private toEdge(from: Node, to: Node): Edge {
|
|
64
|
+
return [to[1], from[0].toString(), to[0].toString()] as Edge;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { BigNumber } from './utils/bignumber';
|
|
2
|
+
|
|
3
|
+
export type PoolAsset = { token: string; symbol: string };
|
|
4
|
+
|
|
5
|
+
export enum PoolType {
|
|
6
|
+
XYK = 'XYK',
|
|
7
|
+
LBP = 'LBP',
|
|
8
|
+
Stable = 'Stable',
|
|
9
|
+
Omni = 'Omni',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PoolPair {
|
|
13
|
+
swapFee: BigNumber;
|
|
14
|
+
tokenIn: string;
|
|
15
|
+
tokenOut: string;
|
|
16
|
+
balanceIn: BigNumber;
|
|
17
|
+
balanceOut: BigNumber;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type PoolBase = {
|
|
21
|
+
address: string;
|
|
22
|
+
type: PoolType;
|
|
23
|
+
swapFee: string;
|
|
24
|
+
tokens: PoolToken[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type PoolToken = {
|
|
28
|
+
id: string;
|
|
29
|
+
balance: string;
|
|
30
|
+
decimals: number;
|
|
31
|
+
symbol: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export interface Pool extends PoolBase {
|
|
35
|
+
validPair(tokenIn: string, tokenOut: string): boolean;
|
|
36
|
+
parsePoolPair(tokenIn: string, tokenOut: string): PoolPair;
|
|
37
|
+
calculateInGivenOut(poolPair: PoolPair, amountOut: BigNumber): BigNumber;
|
|
38
|
+
calculateOutGivenIn(poolPair: PoolPair, amountIn: BigNumber): BigNumber;
|
|
39
|
+
getSpotPriceIn(poolPair: PoolPair): BigNumber;
|
|
40
|
+
getSpotPriceOut(poolPair: PoolPair): BigNumber;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PoolService {
|
|
44
|
+
getPools(): Promise<PoolBase[]>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type Hop = {
|
|
48
|
+
poolType: PoolType;
|
|
49
|
+
poolId: string;
|
|
50
|
+
tokenIn: string;
|
|
51
|
+
tokenOut: string;
|
|
52
|
+
fee: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type Swap = Hop & {
|
|
56
|
+
swapAmount: BigNumber;
|
|
57
|
+
returnAmount: BigNumber;
|
|
58
|
+
returnFinalAmount: BigNumber;
|
|
59
|
+
swapFee: BigNumber;
|
|
60
|
+
spotPrice: BigNumber;
|
|
61
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BigNumber } from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
export const DECIMAL_PLACES = 12;
|
|
4
|
+
|
|
5
|
+
BigNumber.config({
|
|
6
|
+
EXPONENTIAL_AT: [-100, 100],
|
|
7
|
+
ROUNDING_MODE: 1,
|
|
8
|
+
DECIMAL_PLACES: DECIMAL_PLACES,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const ZERO = bnum(0);
|
|
12
|
+
export const ONE = bnum(1);
|
|
13
|
+
export const INFINITY = bnum('Infinity');
|
|
14
|
+
|
|
15
|
+
export function scale(input: BigNumber, decimalPlaces: number): BigNumber {
|
|
16
|
+
const scalePow = new BigNumber(decimalPlaces.toString());
|
|
17
|
+
const scaleMul = new BigNumber(10).pow(scalePow);
|
|
18
|
+
return input.times(scaleMul);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function bnum(val: string | number | BigNumber): BigNumber {
|
|
22
|
+
return new BigNumber(val.toString());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { BigNumber };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BigNumber, bnum, scale, DECIMAL_PLACES } from './bignumber';
|
|
2
|
+
|
|
3
|
+
export function tradeFee(percentage: string): BigNumber {
|
|
4
|
+
return bnum(parseFloat(percentage) / 100);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function calculateTradeFee(
|
|
8
|
+
amount: BigNumber,
|
|
9
|
+
tradeFee: BigNumber
|
|
10
|
+
): BigNumber {
|
|
11
|
+
return amount.multipliedBy(tradeFee).decimalPlaces(0, 1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizeAmount(
|
|
15
|
+
amount: BigNumber,
|
|
16
|
+
decimals: number
|
|
17
|
+
): BigNumber {
|
|
18
|
+
if (decimals == DECIMAL_PLACES) {
|
|
19
|
+
return amount;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const normalizedAmount = amount.shiftedBy(-1 * decimals);
|
|
23
|
+
return scale(normalizedAmount, 12);
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface IQueue<T> {
|
|
2
|
+
enqueue(item: T): void;
|
|
3
|
+
dequeue(): T | undefined;
|
|
4
|
+
size(): number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class Queue<T> implements IQueue<T> {
|
|
8
|
+
private storage: T[] = [];
|
|
9
|
+
|
|
10
|
+
constructor(private capacity: number = Infinity) {}
|
|
11
|
+
|
|
12
|
+
enqueue(item: T): void {
|
|
13
|
+
if (this.size() === this.capacity) {
|
|
14
|
+
throw Error('Queue has reached max capacity, you cannot add more items');
|
|
15
|
+
}
|
|
16
|
+
this.storage.push(item);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
dequeue(): T | undefined {
|
|
20
|
+
return this.storage.shift();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
size(): number {
|
|
24
|
+
return this.storage.length;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface IStack<T> {
|
|
2
|
+
push(item: T): void;
|
|
3
|
+
pop(): T | undefined;
|
|
4
|
+
peek(): T | undefined;
|
|
5
|
+
size(): number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class Stack<T> implements IStack<T> {
|
|
9
|
+
private storage: T[] = [];
|
|
10
|
+
|
|
11
|
+
constructor(private capacity: number = Infinity) {}
|
|
12
|
+
|
|
13
|
+
push(item: T): void {
|
|
14
|
+
if (this.size() === this.capacity) {
|
|
15
|
+
throw Error('Stack has reached max capacity, you cannot add more items');
|
|
16
|
+
}
|
|
17
|
+
this.storage.push(item);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pop(): T | undefined {
|
|
21
|
+
return this.storage.pop();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
peek(): T | undefined {
|
|
25
|
+
return this.storage[this.size() - 1];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
size(): number {
|
|
29
|
+
return this.storage.length;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Queue } from '../queue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Breadth First Search.
|
|
5
|
+
*
|
|
6
|
+
* - uses Queue to find the shortest path
|
|
7
|
+
* - slower than DFS (Depth First Search)
|
|
8
|
+
* - better when dst is closer to src
|
|
9
|
+
* - complexity O(N+E) where N are nodes and E are edges
|
|
10
|
+
*/
|
|
11
|
+
export class Bfs {
|
|
12
|
+
/**
|
|
13
|
+
* Check if current node is already present in path
|
|
14
|
+
*
|
|
15
|
+
* @param x - current node
|
|
16
|
+
* @param path - path
|
|
17
|
+
* @returns true if node in path, otherwise false
|
|
18
|
+
*/
|
|
19
|
+
isNotVisited(x: number, path: number[]): boolean {
|
|
20
|
+
let notVisited: boolean = true;
|
|
21
|
+
path.forEach((pv) => {
|
|
22
|
+
if (pv === x) {
|
|
23
|
+
notVisited = false;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return notVisited;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Finding paths in graph from given source to destination
|
|
31
|
+
*
|
|
32
|
+
* @param g - routes graph containing nodes & corresponding edges
|
|
33
|
+
* @param src - source node
|
|
34
|
+
* @param dst - destination node
|
|
35
|
+
* @returns paths
|
|
36
|
+
*/
|
|
37
|
+
findPaths(g: number[][], src: number, dst: number): number[][] {
|
|
38
|
+
// Store the results
|
|
39
|
+
const paths: number[][] = [];
|
|
40
|
+
// Store the traversing paths
|
|
41
|
+
const queue = new Queue<number[]>();
|
|
42
|
+
// Store the current path
|
|
43
|
+
const currentPath: number[] = [];
|
|
44
|
+
|
|
45
|
+
currentPath.push(src);
|
|
46
|
+
queue.enqueue(currentPath);
|
|
47
|
+
|
|
48
|
+
while (queue.size() > 0) {
|
|
49
|
+
const path = queue.dequeue();
|
|
50
|
+
|
|
51
|
+
if (path === undefined) {
|
|
52
|
+
return paths;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const last = path[path.length - 1];
|
|
56
|
+
|
|
57
|
+
// If last node is the desired destination save to paths
|
|
58
|
+
if (last === dst) {
|
|
59
|
+
paths.push(path);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Traverse to all nodes connected to current node and push path to queue
|
|
63
|
+
const lastNode = g[last];
|
|
64
|
+
lastNode.forEach((segment) => {
|
|
65
|
+
if (this.isNotVisited(segment, path)) {
|
|
66
|
+
const newpath = [...path];
|
|
67
|
+
newpath.push(segment);
|
|
68
|
+
queue.enqueue(newpath);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return paths;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Router } from '../../src/api';
|
|
2
|
+
import { PoolService, PoolType } from '../../src/types';
|
|
3
|
+
import { bnum, scale } from '../../src/utils/bignumber';
|
|
4
|
+
import { MockXykPoolService } from '../lib/mockXykPoolService';
|
|
5
|
+
|
|
6
|
+
describe('Router', () => {
|
|
7
|
+
let poolService: PoolService;
|
|
8
|
+
let router: Router;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
poolService = new MockXykPoolService();
|
|
12
|
+
router = new Router(poolService);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('Should return suggested hops from token 1 to 2 for given XYK pool', async () => {
|
|
16
|
+
expect(poolService).toBeDefined();
|
|
17
|
+
expect(router).toBeDefined();
|
|
18
|
+
const result = await router.getAllPaths('1', '2');
|
|
19
|
+
expect(result).toStrictEqual([
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
poolId: 'bXi1mHNp4jSRUNXuX3sY1fjCF9Um2EezkpzkFmQuLHaChdPM3',
|
|
23
|
+
poolType: PoolType.XYK,
|
|
24
|
+
tokenIn: '1',
|
|
25
|
+
tokenOut: '2',
|
|
26
|
+
fee: '0.3',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
{
|
|
31
|
+
poolId: 'bXn6KCrv8k2JV7B2c5jzLttBDqL4BurPCTcLa3NQk5SWDVXCJ',
|
|
32
|
+
poolType: PoolType.XYK,
|
|
33
|
+
tokenIn: '1',
|
|
34
|
+
tokenOut: '0',
|
|
35
|
+
fee: '0.3',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
poolId: 'bXjT2D2cuxUuP2JzddMxYusg4cKo3wENje5Xdk3jbNwtRvStq',
|
|
39
|
+
poolType: PoolType.XYK,
|
|
40
|
+
tokenIn: '0',
|
|
41
|
+
tokenOut: '2',
|
|
42
|
+
fee: '0.3',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('Should return all assets from given XYK pool', async () => {
|
|
49
|
+
expect(poolService).toBeDefined();
|
|
50
|
+
expect(router).toBeDefined();
|
|
51
|
+
const result = await router.getAllAssets();
|
|
52
|
+
expect(result).toStrictEqual([
|
|
53
|
+
{ token: '0', symbol: 'BSX' },
|
|
54
|
+
{ token: '2', symbol: 'AUSD' },
|
|
55
|
+
{ token: '1', symbol: 'KSM' },
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('Should return all assets pair reacheable from token 1 in given XYK pool', async () => {
|
|
60
|
+
expect(poolService).toBeDefined();
|
|
61
|
+
expect(router).toBeDefined();
|
|
62
|
+
const result = await router.getAssetPairs('1');
|
|
63
|
+
expect(result).toStrictEqual([
|
|
64
|
+
{ token: '2', symbol: 'AUSD' },
|
|
65
|
+
{ token: '0', symbol: 'BSX' },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('Should throw error if not-existing asset used in given XYK pool', async () => {
|
|
70
|
+
expect(poolService).toBeDefined();
|
|
71
|
+
expect(router).toBeDefined();
|
|
72
|
+
await expect(async () => {
|
|
73
|
+
await router.getAssetPairs('not-existing');
|
|
74
|
+
}).rejects.toThrow('not-existing is not supported token');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('Should return best sell price swaps', async () => {
|
|
78
|
+
expect(poolService).toBeDefined();
|
|
79
|
+
expect(router).toBeDefined();
|
|
80
|
+
const result = await router.getBestSellPrice(
|
|
81
|
+
'1',
|
|
82
|
+
'2',
|
|
83
|
+
scale(bnum('1'), 12)
|
|
84
|
+
);
|
|
85
|
+
// TODO write test assertions
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PoolType } from '../../src/types';
|
|
2
|
+
|
|
3
|
+
export const xykPool = {
|
|
4
|
+
address: 'bXi1mHNp4jSRUNXuX3sY1fjCF9Um2EezkpzkFmQuLHaChdPM3',
|
|
5
|
+
type: PoolType.XYK,
|
|
6
|
+
swapFee: '0.3',
|
|
7
|
+
tokens: [
|
|
8
|
+
{
|
|
9
|
+
id: '1',
|
|
10
|
+
balance: '3684960401086',
|
|
11
|
+
decimals: 12,
|
|
12
|
+
symbol: 'KSM',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: '2',
|
|
16
|
+
balance: '174291804564300',
|
|
17
|
+
decimals: 12,
|
|
18
|
+
symbol: 'AUSD',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { PoolType } from '../../src/types';
|
|
2
|
+
|
|
3
|
+
export const xykPools = [
|
|
4
|
+
{
|
|
5
|
+
address: 'bXjT2D2cuxUuP2JzddMxYusg4cKo3wENje5Xdk3jbNwtRvStq',
|
|
6
|
+
type: PoolType.XYK,
|
|
7
|
+
swapFee: '0.3',
|
|
8
|
+
tokens: [
|
|
9
|
+
{
|
|
10
|
+
id: '0',
|
|
11
|
+
balance: '235315220453344458259',
|
|
12
|
+
decimals: 12,
|
|
13
|
+
symbol: 'BSX',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: '2',
|
|
17
|
+
balance: '34783100690381537',
|
|
18
|
+
decimals: 12,
|
|
19
|
+
symbol: 'AUSD',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
address: 'bXi1mHNp4jSRUNXuX3sY1fjCF9Um2EezkpzkFmQuLHaChdPM3',
|
|
25
|
+
type: PoolType.XYK,
|
|
26
|
+
swapFee: '0.3',
|
|
27
|
+
tokens: [
|
|
28
|
+
{
|
|
29
|
+
id: '1',
|
|
30
|
+
balance: '3684960401086',
|
|
31
|
+
decimals: 12,
|
|
32
|
+
symbol: 'KSM',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: '2',
|
|
36
|
+
balance: '174291804564300',
|
|
37
|
+
decimals: 12,
|
|
38
|
+
symbol: 'AUSD',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
address: 'bXn6KCrv8k2JV7B2c5jzLttBDqL4BurPCTcLa3NQk5SWDVXCJ',
|
|
44
|
+
type: PoolType.XYK,
|
|
45
|
+
swapFee: '0.3',
|
|
46
|
+
tokens: [
|
|
47
|
+
{
|
|
48
|
+
id: '0',
|
|
49
|
+
balance: '261138390134511806721',
|
|
50
|
+
decimals: 12,
|
|
51
|
+
symbol: 'BSX',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: '1',
|
|
55
|
+
balance: '842366824680338',
|
|
56
|
+
decimals: 12,
|
|
57
|
+
symbol: 'KSM',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { XykPool } from '../../../src/pool';
|
|
2
|
+
import { xykPool } from '../../data/xykPool';
|
|
3
|
+
|
|
4
|
+
describe('Xyk Pool', () => {
|
|
5
|
+
let pool: XykPool;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
pool = XykPool.fromPool(xykPool);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('Should return valid PoolPair for assets 1 & 2', async () => {
|
|
12
|
+
expect(pool).toBeDefined();
|
|
13
|
+
const result = pool.parsePoolPair('1', '2');
|
|
14
|
+
expect(result.swapFee.toString()).toStrictEqual(
|
|
15
|
+
(parseFloat(xykPool.swapFee) / 100).toString()
|
|
16
|
+
);
|
|
17
|
+
expect(result.tokenIn).toStrictEqual(xykPool.tokens[0].id);
|
|
18
|
+
expect(result.balanceIn.toString()).toStrictEqual(
|
|
19
|
+
xykPool.tokens[0].balance
|
|
20
|
+
);
|
|
21
|
+
expect(result.tokenOut).toStrictEqual(xykPool.tokens[1].id);
|
|
22
|
+
expect(result.balanceOut.toString()).toStrictEqual(
|
|
23
|
+
xykPool.tokens[1].balance
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ApiPromise } from '@polkadot/api';
|
|
2
|
+
import { PolkadotExecutor } from '../../executor';
|
|
3
|
+
import { PolkadotPoolService } from '../../../../src/pool';
|
|
4
|
+
import { Router } from '../../../../src/api';
|
|
5
|
+
|
|
6
|
+
class GetAllAssetsExample extends PolkadotExecutor {
|
|
7
|
+
async script(api: ApiPromise): Promise<any> {
|
|
8
|
+
const poolService = new PolkadotPoolService(api);
|
|
9
|
+
const router = new Router(poolService);
|
|
10
|
+
return router.getAllAssets();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
new GetAllAssetsExample('wss://rpc.basilisk.cloud', 'Get all paths').run();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ApiPromise } from '@polkadot/api';
|
|
2
|
+
import { PolkadotExecutor } from '../../executor';
|
|
3
|
+
import { PolkadotPoolService } from '../../../../src/pool';
|
|
4
|
+
import { Router } from '../../../../src/api';
|
|
5
|
+
|
|
6
|
+
class GetAllPathsExample extends PolkadotExecutor {
|
|
7
|
+
async script(api: ApiPromise): Promise<any> {
|
|
8
|
+
const poolService = new PolkadotPoolService(api);
|
|
9
|
+
const router = new Router(poolService);
|
|
10
|
+
return router.getAllPaths('1', '2');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
new GetAllPathsExample('wss://rpc.basilisk.cloud', 'Get all paths').run();
|