@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,359 @@
|
|
|
1
|
+
import { PoolService, PoolBase, Hop, Pool, PoolAsset, Swap } from '../types';
|
|
2
|
+
import { RouteSuggester } from '../suggester';
|
|
3
|
+
import { Edge } from '../suggester';
|
|
4
|
+
import { PoolFactory } from '../pool';
|
|
5
|
+
import { BigNumber } from '../utils/bignumber';
|
|
6
|
+
import { calculateTradeFee } from '../utils/math';
|
|
7
|
+
|
|
8
|
+
export class Router {
|
|
9
|
+
private readonly routeSuggester: RouteSuggester;
|
|
10
|
+
private readonly poolService: PoolService;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param poolService - Fetch pool data from substrate based pools
|
|
14
|
+
*/
|
|
15
|
+
constructor(poolService: PoolService) {
|
|
16
|
+
this.poolService = poolService;
|
|
17
|
+
this.routeSuggester = new RouteSuggester();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Return all pools
|
|
22
|
+
*
|
|
23
|
+
* @returns {PoolBase[]} List of all substrate based pools
|
|
24
|
+
*/
|
|
25
|
+
getPools(): Promise<PoolBase[]> {
|
|
26
|
+
return this.poolService.getPools();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return list of all available assets from substrate based pools
|
|
31
|
+
*
|
|
32
|
+
* @returns {PoolAsset[]} List of all available assets
|
|
33
|
+
*/
|
|
34
|
+
async getAllAssets(): Promise<PoolAsset[]> {
|
|
35
|
+
const asset = await this.getAssets();
|
|
36
|
+
return [...new Map(asset).values()];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculate and return list of all assets, given token can be trade with
|
|
41
|
+
*
|
|
42
|
+
* @param {string} token - Storage key of token
|
|
43
|
+
* @returns {PoolAsset[]} List of all available assets, given token can be trade with
|
|
44
|
+
*/
|
|
45
|
+
async getAssetPairs(token: string): Promise<PoolAsset[]> {
|
|
46
|
+
const pools = await this.poolService.getPools();
|
|
47
|
+
if (pools.length === 0) return [];
|
|
48
|
+
const { assets, poolsMap } = await this.validateToken(token, pools);
|
|
49
|
+
const hops = this.getPaths(token, null, poolsMap, pools);
|
|
50
|
+
const dest = hops.map((hop) => hop[hop.length - 1].tokenOut);
|
|
51
|
+
return this.toPoolAssets([...new Set(dest)], assets);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Calculate and return all possible paths for best swap tokenIn>tokenOut
|
|
56
|
+
*
|
|
57
|
+
* @param {string} tokenIn - Storage key of tokenIn
|
|
58
|
+
* @param {string} tokenOut - Storage key of tokenOut
|
|
59
|
+
* @returns {<Hop[][]>} All possible paths containing route hops
|
|
60
|
+
*/
|
|
61
|
+
async getAllPaths(tokenIn: string, tokenOut: string): Promise<Hop[][]> {
|
|
62
|
+
const pools = await this.poolService.getPools();
|
|
63
|
+
if (pools.length === 0) return [];
|
|
64
|
+
const { poolsMap } = await this.validateTokenPair(tokenIn, tokenOut, pools);
|
|
65
|
+
return this.getPaths(tokenIn, tokenOut, poolsMap, pools);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Calculate and return best possible sell price for tokenIn>tokenOut
|
|
70
|
+
*
|
|
71
|
+
* @param {string} tokenIn - Storage key of tokenIn
|
|
72
|
+
* @param {string} tokenOut - Storage key of tokenOut
|
|
73
|
+
* @param {BigNumber} amountIn - Amount of tokenIn to sell for tokenOut
|
|
74
|
+
* @returns Best possible swaps(sells) of given token pair
|
|
75
|
+
*/
|
|
76
|
+
async getBestSellPrice(
|
|
77
|
+
tokenIn: string,
|
|
78
|
+
tokenOut: string,
|
|
79
|
+
amountIn: BigNumber
|
|
80
|
+
): Promise<Swap[]> {
|
|
81
|
+
const pools = await this.poolService.getPools();
|
|
82
|
+
if (pools.length === 0) return [];
|
|
83
|
+
const { poolsMap } = await this.validateTokenPair(tokenIn, tokenOut, pools);
|
|
84
|
+
const paths = this.getPaths(tokenIn, tokenOut, poolsMap, pools);
|
|
85
|
+
const swaps = paths.map((path) =>
|
|
86
|
+
this.toSellSwaps(amountIn, path, poolsMap)
|
|
87
|
+
);
|
|
88
|
+
const sorted = swaps.sort((a, b) => {
|
|
89
|
+
const swapAFinal = a[a.length - 1].returnFinalAmount;
|
|
90
|
+
const swapBFinal = b[b.length - 1].returnFinalAmount;
|
|
91
|
+
return swapAFinal.isGreaterThan(swapBFinal) ? -1 : 1;
|
|
92
|
+
});
|
|
93
|
+
return sorted[0];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Calculate and return sell swaps for given path
|
|
98
|
+
* - final amount of previous swap is entry to next one
|
|
99
|
+
*
|
|
100
|
+
* @param amountIn - Amount of tokenIn to sell for tokenOut
|
|
101
|
+
* @param path - current path
|
|
102
|
+
* @param poolsMap - pools map
|
|
103
|
+
* @returns Sell swaps for given path
|
|
104
|
+
*/
|
|
105
|
+
private toSellSwaps(
|
|
106
|
+
amountIn: BigNumber,
|
|
107
|
+
path: Hop[],
|
|
108
|
+
poolsMap: Map<string, Pool>
|
|
109
|
+
): Swap[] {
|
|
110
|
+
const swaps: Swap[] = [];
|
|
111
|
+
for (let i = 0; i < path.length; i++) {
|
|
112
|
+
const hop = path[i];
|
|
113
|
+
const pool = poolsMap.get(hop.poolId);
|
|
114
|
+
if (pool == null) throw new Error('Pool does not exit');
|
|
115
|
+
|
|
116
|
+
let aIn: BigNumber;
|
|
117
|
+
if (i > 0) {
|
|
118
|
+
aIn = swaps[i - 1].returnFinalAmount;
|
|
119
|
+
} else {
|
|
120
|
+
aIn = amountIn;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const poolPair = pool.parsePoolPair(hop.tokenIn, hop.tokenOut);
|
|
124
|
+
const calculated = pool.calculateOutGivenIn(poolPair, aIn);
|
|
125
|
+
const fee = calculateTradeFee(calculated, poolPair.swapFee);
|
|
126
|
+
const spotPrice = pool.getSpotPriceOut(poolPair);
|
|
127
|
+
|
|
128
|
+
swaps.push({
|
|
129
|
+
...hop,
|
|
130
|
+
swapAmount: aIn,
|
|
131
|
+
returnAmount: calculated,
|
|
132
|
+
returnFinalAmount: calculated.minus(fee),
|
|
133
|
+
swapFee: fee,
|
|
134
|
+
spotPrice: spotPrice,
|
|
135
|
+
} as Swap);
|
|
136
|
+
}
|
|
137
|
+
return swaps;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Calculate and return best possible buy price for tokenIn>tokenOut
|
|
142
|
+
*
|
|
143
|
+
* @param {string} tokenIn - Storage key of tokenIn
|
|
144
|
+
* @param {string} tokenOut - Storage key of tokenOut
|
|
145
|
+
* @param {BigNumber} amountOut - Amount of tokenOut to buy for tokenIn
|
|
146
|
+
* @returns Best possible swaps(buys) of given token pair
|
|
147
|
+
*/
|
|
148
|
+
async getBestBuyPrice(
|
|
149
|
+
tokenIn: string,
|
|
150
|
+
tokenOut: string,
|
|
151
|
+
amountOut: BigNumber
|
|
152
|
+
): Promise<Swap[]> {
|
|
153
|
+
const pools = await this.poolService.getPools();
|
|
154
|
+
if (pools.length === 0) return [];
|
|
155
|
+
const { poolsMap } = await this.validateTokenPair(tokenIn, tokenOut, pools);
|
|
156
|
+
const paths = this.getPaths(tokenIn, tokenOut, poolsMap, pools);
|
|
157
|
+
const swaps = paths.map((path) =>
|
|
158
|
+
this.toBuySwaps(amountOut, path, poolsMap)
|
|
159
|
+
);
|
|
160
|
+
const sorted = swaps.sort((a, b) => {
|
|
161
|
+
const swapAFinal = a[0].returnFinalAmount;
|
|
162
|
+
const swapBFinal = b[0].returnFinalAmount;
|
|
163
|
+
return swapAFinal.isGreaterThan(swapBFinal) ? 1 : -1;
|
|
164
|
+
});
|
|
165
|
+
return sorted[0];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Calculate and return buy swaps for given path
|
|
170
|
+
* - final amount of previous swap is entry to next one
|
|
171
|
+
* - calculation is done backwards
|
|
172
|
+
*
|
|
173
|
+
* @param amountOut - Amount of tokenOut to buy for tokenIn
|
|
174
|
+
* @param path - current path
|
|
175
|
+
* @param poolsMap - pools map
|
|
176
|
+
* @returns Buy swaps for given path
|
|
177
|
+
*/
|
|
178
|
+
private toBuySwaps(
|
|
179
|
+
amountOut: BigNumber,
|
|
180
|
+
path: Hop[],
|
|
181
|
+
poolsMap: Map<string, Pool>
|
|
182
|
+
): Swap[] {
|
|
183
|
+
const swaps: Swap[] = [];
|
|
184
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
185
|
+
const hop = path[i];
|
|
186
|
+
const pool = poolsMap.get(hop.poolId);
|
|
187
|
+
if (pool == null) throw new Error('Pool does not exit');
|
|
188
|
+
|
|
189
|
+
let aOut: BigNumber;
|
|
190
|
+
if (i == path.length - 1) {
|
|
191
|
+
aOut = amountOut;
|
|
192
|
+
} else {
|
|
193
|
+
aOut = swaps[0].returnFinalAmount;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const poolPair = pool.parsePoolPair(hop.tokenIn, hop.tokenOut);
|
|
197
|
+
const calculated = pool.calculateInGivenOut(poolPair, aOut);
|
|
198
|
+
const fee = calculateTradeFee(calculated, poolPair.swapFee);
|
|
199
|
+
const spotPrice = pool.getSpotPriceIn(poolPair);
|
|
200
|
+
|
|
201
|
+
swaps.unshift({
|
|
202
|
+
...hop,
|
|
203
|
+
swapAmount: aOut,
|
|
204
|
+
returnAmount: calculated,
|
|
205
|
+
returnFinalAmount: calculated.plus(fee),
|
|
206
|
+
swapFee: fee,
|
|
207
|
+
spotPrice: spotPrice,
|
|
208
|
+
} as Swap);
|
|
209
|
+
}
|
|
210
|
+
return swaps;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Return map of all available assets from substrate based pools
|
|
215
|
+
*
|
|
216
|
+
* @returns Map of all available assets
|
|
217
|
+
*/
|
|
218
|
+
private async getAssets(): Promise<Map<string, PoolAsset>> {
|
|
219
|
+
const pools = await this.poolService.getPools();
|
|
220
|
+
if (pools.length === 0) return new Map<string, PoolAsset>();
|
|
221
|
+
const assets = pools
|
|
222
|
+
.map((pool: PoolBase) => {
|
|
223
|
+
return pool.tokens.map(({ id, symbol }) => {
|
|
224
|
+
return { token: id, symbol } as PoolAsset;
|
|
225
|
+
});
|
|
226
|
+
})
|
|
227
|
+
.flat();
|
|
228
|
+
return new Map(assets.map((asset) => [asset.token, asset]));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Calculate and return all possible paths for best swap tokenIn>tokenOut
|
|
233
|
+
*
|
|
234
|
+
* @param tokenIn - Storage key of tokenIn
|
|
235
|
+
* @param tokenOut - Storage key of tokenOut
|
|
236
|
+
* @param poolsMap - pools map
|
|
237
|
+
* @param pools - pools
|
|
238
|
+
* @returns All possible paths containing route hops
|
|
239
|
+
*/
|
|
240
|
+
private getPaths(
|
|
241
|
+
tokenIn: string,
|
|
242
|
+
tokenOut: string | null,
|
|
243
|
+
poolsMap: Map<string, Pool>,
|
|
244
|
+
pools: PoolBase[]
|
|
245
|
+
): Hop[][] {
|
|
246
|
+
const routeProposals = this.routeSuggester.getProposals(
|
|
247
|
+
tokenIn,
|
|
248
|
+
tokenOut,
|
|
249
|
+
pools
|
|
250
|
+
);
|
|
251
|
+
const routes = routeProposals
|
|
252
|
+
.filter((path: Edge[]) => this.validPath(path, poolsMap))
|
|
253
|
+
.map((path: Edge[]) => this.toHops(path, poolsMap));
|
|
254
|
+
return routes;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Ckeck if input asset pair is valid and throw expection if not
|
|
259
|
+
*
|
|
260
|
+
* @param tokenIn - Storage key of tokenIn
|
|
261
|
+
* @param tokenOut - Storage key of tokenOut
|
|
262
|
+
* @returns Pool assets & map
|
|
263
|
+
*/
|
|
264
|
+
private async validateTokenPair(
|
|
265
|
+
tokenIn: string,
|
|
266
|
+
tokenOut: string,
|
|
267
|
+
pools: PoolBase[]
|
|
268
|
+
) {
|
|
269
|
+
const assets = await this.getAssets();
|
|
270
|
+
if (assets.get(tokenIn) == null)
|
|
271
|
+
throw new Error(tokenIn + ' is not supported token');
|
|
272
|
+
if (assets.get(tokenOut) == null)
|
|
273
|
+
throw new Error(tokenOut + ' is not supported token');
|
|
274
|
+
const poolsMap = this.getPoolMap(pools);
|
|
275
|
+
return { assets, poolsMap };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Ckeck if input asset is valid and throw expection if not
|
|
280
|
+
*
|
|
281
|
+
* @param token - Storage key of token
|
|
282
|
+
* @returns Pool assets & map
|
|
283
|
+
*/
|
|
284
|
+
private async validateToken(token: string, pools: PoolBase[]) {
|
|
285
|
+
const assets = await this.getAssets();
|
|
286
|
+
if (assets.get(token) == null)
|
|
287
|
+
throw new Error(token + ' is not supported token');
|
|
288
|
+
const poolsMap = this.getPoolMap(pools);
|
|
289
|
+
return { assets, poolsMap };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Create pool map from substrate based pools
|
|
294
|
+
*/
|
|
295
|
+
private getPoolMap(pools: PoolBase[]): Map<string, Pool> {
|
|
296
|
+
return new Map<string, Pool>(
|
|
297
|
+
pools.map((i) => [i.address, PoolFactory.get(i)])
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Check if path is valid -> all edges are valid token pairs
|
|
303
|
+
*
|
|
304
|
+
* @param proposedPath - proposed path
|
|
305
|
+
* @param poolsMap - pools map
|
|
306
|
+
* @returns only valid paths
|
|
307
|
+
*/
|
|
308
|
+
private validPath(
|
|
309
|
+
proposedPath: Edge[],
|
|
310
|
+
poolsMap: Map<string, Pool>
|
|
311
|
+
): boolean {
|
|
312
|
+
return (
|
|
313
|
+
proposedPath.length > 0 &&
|
|
314
|
+
proposedPath
|
|
315
|
+
.map((edge: Edge) => this.validEdge(edge, poolsMap))
|
|
316
|
+
.reduce((prev, curr) => prev && curr)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Check if edge (token pair) of corresponding pool is valid combination
|
|
322
|
+
*
|
|
323
|
+
* @param edge - current edge (token pair)
|
|
324
|
+
* @param poolsMap - pools map
|
|
325
|
+
* @returns true if edge (token pair) is valid, otherwise false
|
|
326
|
+
*/
|
|
327
|
+
private validEdge(
|
|
328
|
+
[id, from, to]: Edge,
|
|
329
|
+
poolsMap: Map<string, Pool>
|
|
330
|
+
): boolean {
|
|
331
|
+
return poolsMap.get(id)?.validPair(from, to) || false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private toHops(path: Edge[], poolsMap: Map<string, Pool>): Hop[] {
|
|
335
|
+
return path.map(([id, from, to]: Edge) => {
|
|
336
|
+
const pool = poolsMap.get(id);
|
|
337
|
+
return {
|
|
338
|
+
poolId: id,
|
|
339
|
+
poolType: pool?.type,
|
|
340
|
+
tokenIn: from,
|
|
341
|
+
tokenOut: to,
|
|
342
|
+
fee: pool?.swapFee,
|
|
343
|
+
} as Hop;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private toPoolAssets(
|
|
348
|
+
tokens: string[],
|
|
349
|
+
assets: Map<string, PoolAsset>
|
|
350
|
+
): PoolAsset[] {
|
|
351
|
+
return tokens.map((token) => {
|
|
352
|
+
const asset = assets.get(token);
|
|
353
|
+
return {
|
|
354
|
+
token: token,
|
|
355
|
+
symbol: asset?.symbol,
|
|
356
|
+
} as PoolAsset;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PolkadotClient } from './polkadot';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ApiPromise } from '@polkadot/api';
|
|
2
|
+
|
|
3
|
+
import type { StorageKey } from '@polkadot/types';
|
|
4
|
+
import type { AnyTuple, Codec } from '@polkadot/types/types';
|
|
5
|
+
import type { AssetMetadata } from '@polkadot/types/interfaces';
|
|
6
|
+
import type { TokensAccountData } from './types';
|
|
7
|
+
import '@polkadot/api-augment';
|
|
8
|
+
|
|
9
|
+
export class PolkadotClient {
|
|
10
|
+
protected readonly api: ApiPromise;
|
|
11
|
+
|
|
12
|
+
constructor(api: ApiPromise) {
|
|
13
|
+
this.api = api;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getStorageKey(asset: [StorageKey<AnyTuple>, Codec], index: number): string {
|
|
17
|
+
return (asset[0].toHuman() as string[])[index];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getStorageEntryArray(asset: [StorageKey<AnyTuple>, Codec]): string[] {
|
|
21
|
+
return asset[1].toHuman() as string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async getAssetMetadata(tokenKey: string): Promise<AssetMetadata> {
|
|
25
|
+
return await this.api.query.assetRegistry.assetMetadataMap<AssetMetadata>(
|
|
26
|
+
tokenKey
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getSystemAccountBalance(accountId: string): Promise<string> {
|
|
31
|
+
const {
|
|
32
|
+
data: { free },
|
|
33
|
+
} = await this.api.query.system.account(accountId);
|
|
34
|
+
return free.toString();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getTokenAccountBalance(
|
|
38
|
+
accountId: string,
|
|
39
|
+
tokenKey: string
|
|
40
|
+
): Promise<string> {
|
|
41
|
+
const { free } = await this.api.query.tokens.accounts<TokensAccountData>(
|
|
42
|
+
accountId,
|
|
43
|
+
tokenKey
|
|
44
|
+
);
|
|
45
|
+
return free.toString();
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PoolBase, PoolService } from '../types';
|
|
2
|
+
import { XykPolkadotClient } from './xyk/xykPolkadotClient';
|
|
3
|
+
|
|
4
|
+
import { ApiPromise } from '@polkadot/api';
|
|
5
|
+
|
|
6
|
+
export class PolkadotPoolService implements PoolService {
|
|
7
|
+
private readonly api: ApiPromise;
|
|
8
|
+
|
|
9
|
+
constructor(api: ApiPromise) {
|
|
10
|
+
this.api = api;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async getPools(): Promise<PoolBase[]> {
|
|
14
|
+
const pools: PoolBase[][] = [];
|
|
15
|
+
const xykPools = await new XykPolkadotClient(this.api).getPools();
|
|
16
|
+
pools.push(xykPools);
|
|
17
|
+
return pools.flat();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Pool, PoolBase, PoolType } from '../types';
|
|
2
|
+
import { XykPool } from './xyk/xykPool';
|
|
3
|
+
|
|
4
|
+
export class PoolFactory {
|
|
5
|
+
static get(pool: PoolBase): Pool {
|
|
6
|
+
switch (pool.type) {
|
|
7
|
+
case PoolType.XYK:
|
|
8
|
+
return XykPool.fromPool(pool);
|
|
9
|
+
default: {
|
|
10
|
+
throw new Error('Pool type ' + pool.type + ' is not supported yet');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
calculate_in_given_out,
|
|
3
|
+
calculate_out_given_in,
|
|
4
|
+
get_spot_price,
|
|
5
|
+
} from 'hydra-dx-wasm/build/xyk/bundler';
|
|
6
|
+
|
|
7
|
+
function getSpotPrice(a: string, b: string, c: string): string {
|
|
8
|
+
return get_spot_price(a, b, c);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function calculateInGivenOut(a: string, b: string, c: string): string {
|
|
12
|
+
return calculate_in_given_out(a, b, c);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function calculateOutGivenIn(a: string, b: string, c: string): string {
|
|
16
|
+
return calculate_out_given_in(a, b, c);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default { getSpotPrice, calculateInGivenOut, calculateOutGivenIn };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
calculate_in_given_out,
|
|
3
|
+
calculate_out_given_in,
|
|
4
|
+
get_spot_price,
|
|
5
|
+
} from 'hydra-dx-wasm/build/xyk/nodejs';
|
|
6
|
+
|
|
7
|
+
function getSpotPrice(a: string, b: string, c: string): string {
|
|
8
|
+
return get_spot_price(a, b, c);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function calculateInGivenOut(a: string, b: string, c: string): string {
|
|
12
|
+
return calculate_in_given_out(a, b, c);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function calculateOutGivenIn(a: string, b: string, c: string): string {
|
|
16
|
+
return calculate_out_given_in(a, b, c);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default { getSpotPrice, calculateInGivenOut, calculateOutGivenIn };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { PolkadotClient } from '../../client';
|
|
2
|
+
import type { StorageKey } from '@polkadot/types';
|
|
3
|
+
import type { AnyTuple, Codec } from '@polkadot/types/types';
|
|
4
|
+
import { PoolBase, PoolToken, PoolType } from '../../types';
|
|
5
|
+
|
|
6
|
+
export class XykPolkadotClient extends PolkadotClient {
|
|
7
|
+
async getPools(): Promise<PoolBase[]> {
|
|
8
|
+
const poolAssets = await this.api.query.xyk.poolAssets.entries();
|
|
9
|
+
const pools = poolAssets.map(
|
|
10
|
+
async (asset: [StorageKey<AnyTuple>, Codec]) => {
|
|
11
|
+
const poolAddress = this.getStorageKey(asset, 0);
|
|
12
|
+
const poolEntries = this.getStorageEntryArray(asset);
|
|
13
|
+
const poolTokens = await this.getPoolTokens(poolAddress, poolEntries);
|
|
14
|
+
return {
|
|
15
|
+
address: poolAddress,
|
|
16
|
+
type: PoolType.XYK,
|
|
17
|
+
swapFee: this.getSwapFee(),
|
|
18
|
+
tokens: poolTokens,
|
|
19
|
+
} as PoolBase;
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
return Promise.all(pools);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getPoolTokens(
|
|
26
|
+
poolAddress: string,
|
|
27
|
+
assetKeys: string[]
|
|
28
|
+
): Promise<PoolToken[]> {
|
|
29
|
+
const poolTokens = assetKeys.map(async (id) => {
|
|
30
|
+
const balance = await this.getBalance(poolAddress, id);
|
|
31
|
+
const metadata = await super.getAssetMetadata(id);
|
|
32
|
+
const metadataJson = metadata.toHuman();
|
|
33
|
+
return {
|
|
34
|
+
id,
|
|
35
|
+
balance: balance,
|
|
36
|
+
decimals: metadataJson ? metadataJson.decimals : '12',
|
|
37
|
+
symbol: metadataJson ? metadataJson.symbol : 'BSX',
|
|
38
|
+
} as PoolToken;
|
|
39
|
+
});
|
|
40
|
+
return Promise.all(poolTokens);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getBalance(poolAddress: string, assetKey: string): Promise<string> {
|
|
44
|
+
if (assetKey === '0') {
|
|
45
|
+
return this.getSystemAccountBalance(poolAddress);
|
|
46
|
+
} else {
|
|
47
|
+
return this.getTokenAccountBalance(poolAddress, assetKey);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getSwapFee(): string {
|
|
52
|
+
const exchangeFee = this.api.consts.xyk.getExchangeFee;
|
|
53
|
+
return (
|
|
54
|
+
(exchangeFee[0].toNumber() / exchangeFee[1].toNumber()) *
|
|
55
|
+
100
|
|
56
|
+
).toString();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Pool, PoolBase, PoolPair, PoolToken, PoolType } from '../../types';
|
|
2
|
+
import { BigNumber, bnum, scale } from '../../utils/bignumber';
|
|
3
|
+
import math from './math/nodejs';
|
|
4
|
+
import { tradeFee, normalizeAmount } from '../../utils/math';
|
|
5
|
+
|
|
6
|
+
export class XykPool implements Pool {
|
|
7
|
+
type: PoolType;
|
|
8
|
+
address: string;
|
|
9
|
+
swapFee: string;
|
|
10
|
+
tokens: PoolToken[];
|
|
11
|
+
|
|
12
|
+
static fromPool(pool: PoolBase): XykPool {
|
|
13
|
+
return new XykPool(pool.address, pool.swapFee, pool.tokens);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(address: string, swapFee: string, tokens: PoolToken[]) {
|
|
17
|
+
this.type = PoolType.XYK;
|
|
18
|
+
this.address = address;
|
|
19
|
+
this.swapFee = swapFee;
|
|
20
|
+
this.tokens = tokens;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
validPair(_tokenIn: string, _tokenOut: string): boolean {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
parsePoolPair(tokenIn: string, tokenOut: string): PoolPair {
|
|
28
|
+
const tokensMap = new Map(this.tokens.map((token) => [token.id, token]));
|
|
29
|
+
const tokenInMeta = tokensMap.get(tokenIn);
|
|
30
|
+
const tokenOutMeta = tokensMap.get(tokenOut);
|
|
31
|
+
|
|
32
|
+
if (tokenInMeta == null) throw new Error('Pool does not contain tokenIn');
|
|
33
|
+
if (tokenOutMeta == null) throw new Error('Pool does not contain tokenOut');
|
|
34
|
+
|
|
35
|
+
const balanceIn = bnum(tokenInMeta.balance);
|
|
36
|
+
const balanceOut = bnum(tokenOutMeta.balance);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
swapFee: tradeFee(this.swapFee),
|
|
40
|
+
tokenIn: tokenIn,
|
|
41
|
+
tokenOut: tokenOut,
|
|
42
|
+
balanceIn: normalizeAmount(balanceIn, tokenInMeta.decimals),
|
|
43
|
+
balanceOut: normalizeAmount(balanceOut, tokenOutMeta.decimals),
|
|
44
|
+
} as PoolPair;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
calculateInGivenOut(poolPair: PoolPair, amountOut: BigNumber): BigNumber {
|
|
48
|
+
const price = math.calculateInGivenOut(
|
|
49
|
+
poolPair.balanceIn.toString(),
|
|
50
|
+
poolPair.balanceOut.toString(),
|
|
51
|
+
amountOut.toString()
|
|
52
|
+
);
|
|
53
|
+
return bnum(price);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getSpotPriceIn(poolPair: PoolPair): BigNumber {
|
|
57
|
+
const price = math.getSpotPrice(
|
|
58
|
+
poolPair.balanceOut.toString(),
|
|
59
|
+
poolPair.balanceIn.toString(),
|
|
60
|
+
scale(bnum(1), 12).toString()
|
|
61
|
+
);
|
|
62
|
+
return bnum(price);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
calculateOutGivenIn(poolPair: PoolPair, amountIn: BigNumber): BigNumber {
|
|
66
|
+
const price = math.calculateOutGivenIn(
|
|
67
|
+
poolPair.balanceIn.toString(),
|
|
68
|
+
poolPair.balanceOut.toString(),
|
|
69
|
+
amountIn.toString()
|
|
70
|
+
);
|
|
71
|
+
return bnum(price);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getSpotPriceOut(poolPair: PoolPair): BigNumber {
|
|
75
|
+
const price = math.getSpotPrice(
|
|
76
|
+
poolPair.balanceIn.toString(),
|
|
77
|
+
poolPair.balanceOut.toString(),
|
|
78
|
+
scale(bnum(1), 12).toString()
|
|
79
|
+
);
|
|
80
|
+
return bnum(price);
|
|
81
|
+
}
|
|
82
|
+
}
|