@gearbox-protocol/sdk 0.0.53 → 0.0.56

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.
Files changed (63) hide show
  1. package/lib/core/contracts.d.ts +29 -16
  2. package/lib/core/contracts.js +45 -4
  3. package/lib/core/contractsRegister.js +24 -6
  4. package/lib/core/creditAccount.js +26 -20
  5. package/lib/core/events.d.ts +10 -10
  6. package/lib/core/events.js +22 -22
  7. package/lib/core/pathfinder.d.ts +35 -5
  8. package/lib/core/pathfinder.js +547 -67
  9. package/lib/core/priceFeeds.js +61 -0
  10. package/lib/core/token.d.ts +47 -15
  11. package/lib/core/token.js +204 -36
  12. package/lib/core/tradeTypes.d.ts +2 -0
  13. package/lib/core/transactions.d.ts +2 -2
  14. package/lib/core/transactions.js +10 -10
  15. package/lib/index.d.ts +2 -0
  16. package/lib/index.js +6 -1
  17. package/lib/pathfinder/connectorPathAsset.d.ts +7 -0
  18. package/lib/pathfinder/connectorPathAsset.js +144 -0
  19. package/lib/pathfinder/convexLPTokenPathAsset.d.ts +5 -0
  20. package/lib/pathfinder/convexLPTokenPathAsset.js +77 -0
  21. package/lib/pathfinder/curveLPPathAsset.d.ts +5 -0
  22. package/lib/pathfinder/curveLPPathAsset.js +72 -0
  23. package/lib/pathfinder/metaCurveLPPathAsset.d.ts +5 -0
  24. package/lib/pathfinder/metaCurveLPPathAsset.js +72 -0
  25. package/lib/pathfinder/normalTokenPathAsset.d.ts +5 -0
  26. package/lib/pathfinder/normalTokenPathAsset.js +136 -0
  27. package/lib/pathfinder/path.d.ts +41 -0
  28. package/lib/pathfinder/path.js +302 -0
  29. package/lib/pathfinder/pathfinder.d.ts +28 -0
  30. package/lib/pathfinder/pathfinder.js +519 -0
  31. package/lib/pathfinder/yearnVaultOfCurveLPPathAsset.d.ts +5 -0
  32. package/lib/pathfinder/yearnVaultOfCurveLPPathAsset.js +87 -0
  33. package/lib/pathfinder/yearnVaultOfMetaCurveLPPathAsset.d.ts +5 -0
  34. package/lib/pathfinder/yearnVaultOfMetaCurveLPPathAsset.js +87 -0
  35. package/lib/pathfinder/yearnVaultPathAsset.d.ts +5 -0
  36. package/lib/pathfinder/yearnVaultPathAsset.js +87 -0
  37. package/lib/utils/mappers.d.ts +6 -0
  38. package/lib/utils/mappers.js +33 -0
  39. package/lib/utils/types.d.ts +3 -0
  40. package/lib/utils/types.js +2 -0
  41. package/package.json +1 -1
  42. package/src/core/adapters.ts +13 -15
  43. package/src/core/contracts.ts +111 -44
  44. package/src/core/contractsRegister.ts +16 -8
  45. package/src/core/creditAccount.ts +33 -19
  46. package/src/core/events.ts +30 -22
  47. package/src/core/priceFeeds.ts +66 -0
  48. package/src/core/token.ts +645 -370
  49. package/src/core/tradeTypes.ts +2 -0
  50. package/src/core/transactions.ts +10 -10
  51. package/src/index.ts +2 -0
  52. package/src/pathfinder/connectorPathAsset.ts +69 -0
  53. package/src/pathfinder/convexLPTokenPathAsset.ts +33 -0
  54. package/src/pathfinder/curveLPPathAsset.ts +10 -0
  55. package/src/pathfinder/metaCurveLPPathAsset.ts +12 -0
  56. package/src/pathfinder/normalTokenPathAsset.ts +68 -0
  57. package/src/pathfinder/path.ts +259 -0
  58. package/src/pathfinder/yearnVaultOfCurveLPPathAsset.ts +22 -0
  59. package/src/pathfinder/yearnVaultOfMetaCurveLPPathAsset.ts +22 -0
  60. package/src/pathfinder/yearnVaultPathAsset.ts +27 -0
  61. package/src/utils/mappers.ts +23 -0
  62. package/src/utils/types.ts +3 -0
  63. package/src/core/pathfinder.ts +0 -115
@@ -34,10 +34,12 @@ export type TradeAction =
34
34
  | {
35
35
  type: TradeType.UniswapV2Swap;
36
36
  contract: UniswapV2Contract;
37
+ tokenOut?: NormalToken;
37
38
  }
38
39
  | {
39
40
  type: TradeType.UniswapV3Swap;
40
41
  contract: "UNISWAP_V3_ROUTER";
42
+ tokenOut?: NormalToken;
41
43
  }
42
44
  | {
43
45
  type: TradeType.CurveExchange;
@@ -77,7 +77,7 @@ export class TxAddLiquidity extends EVMTx {
77
77
  }
78
78
 
79
79
  toString(tokenData: Record<string, TokenData>): string {
80
- const token = tokenData[this.underlyingToken];
80
+ const token = tokenData[this.underlyingToken.toLowerCase()];
81
81
  return `${getContractName(this.pool)}: Deposit ${formatBN(
82
82
  this.amount,
83
83
  token?.decimals || 18
@@ -115,7 +115,7 @@ export class TxRemoveLiquidity extends EVMTx {
115
115
  }
116
116
 
117
117
  toString(tokenData: Record<string, TokenData>): string {
118
- const dtoken = tokenData[this.dieselToken];
118
+ const dtoken = tokenData[this.dieselToken.toLowerCase()];
119
119
  return `${getContractName(this.pool)}: Withdraw ${formatBN(
120
120
  this.amount,
121
121
  dtoken?.decimals || 18
@@ -165,11 +165,11 @@ export class TXSwap extends EVMTx {
165
165
  }
166
166
 
167
167
  toString(tokenData: Record<string, TokenData>): string {
168
- const tokenFrom = tokenData[this.tokenFrom];
168
+ const tokenFrom = tokenData[this.tokenFrom.toLowerCase()];
169
169
 
170
170
  let toPart = "";
171
171
  if (this.tokenTo && this.amountTo) {
172
- const tokenTo = tokenData[this.tokenTo];
172
+ const tokenTo = tokenData[this.tokenTo.toLowerCase()];
173
173
  toPart = ` ⇒ ${formatBN(this.amountTo, tokenTo?.decimals || 18)} ${
174
174
  tokenTo?.symbol || ""
175
175
  }`;
@@ -213,7 +213,7 @@ export class TxAddCollateral extends EVMTx {
213
213
  }
214
214
 
215
215
  toString(tokenData: Record<string, TokenData>): string {
216
- const addedToken = tokenData[this.addedToken];
216
+ const addedToken = tokenData[this.addedToken.toLowerCase()];
217
217
  return `Credit account ${getContractName(
218
218
  this.creditManager
219
219
  )}: Added ${formatBN(this.amount, addedToken.decimals)} ${
@@ -252,7 +252,7 @@ export class TxIncreaseBorrowAmount extends EVMTx {
252
252
  }
253
253
 
254
254
  toString(tokenData: Record<string, TokenData>): string {
255
- const token = tokenData[this.underlyingToken];
255
+ const token = tokenData[this.underlyingToken.toLowerCase()];
256
256
  return `Credit account ${getContractName(
257
257
  this.creditManager
258
258
  )}: Borrowed amount was increased for ${formatBN(
@@ -295,7 +295,7 @@ export class TxOpenAccount extends EVMTx {
295
295
  }
296
296
 
297
297
  toString(tokenData: Record<string, TokenData>): string {
298
- const token = tokenData[this.underlyingToken];
298
+ const token = tokenData[this.underlyingToken.toLowerCase()];
299
299
  return `Credit account ${getContractName(
300
300
  this.creditManager
301
301
  )}: opening ${formatBN(this.amount, token?.decimals || 18)} ${
@@ -333,7 +333,7 @@ export class TxRepayAccount extends EVMTx {
333
333
  this.creditManager = opts.creditManager;
334
334
  }
335
335
 
336
- toString(_tokenData: Record<string, TokenData>): string {
336
+ toString(_: Record<string, TokenData>): string {
337
337
  return `Credit account ${getContractName(
338
338
  this.creditManager
339
339
  )}: Repaying account`;
@@ -363,7 +363,7 @@ export class TxCloseAccount extends EVMTx {
363
363
  this.creditManager = opts.creditManager;
364
364
  }
365
365
 
366
- toString(_tokenData: Record<string, TokenData>): string {
366
+ toString(_: Record<string, TokenData>): string {
367
367
  return `Credit account ${getContractName(
368
368
  this.creditManager
369
369
  )}: Closing account`;
@@ -394,7 +394,7 @@ export class TxApprove extends EVMTx {
394
394
  }
395
395
 
396
396
  toString(tokenData: Record<string, TokenData>): string {
397
- const token = tokenData[this.token];
397
+ const token = tokenData[this.token.toLowerCase()];
398
398
  return `Approve ${token?.symbol}`;
399
399
  }
400
400
 
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./core/contracts";
6
6
  export * from "./core/events";
7
7
  export * from "./core/errors";
8
8
  export * from "./core/pool";
9
+ export * from "./core/protocols";
9
10
  export * from "./core/priceFeeds";
10
11
  export * from "./core/operations";
11
12
  export * from "./core/oracles";
@@ -43,3 +44,4 @@ export { MultiCallContract } from "./utils/multicall";
43
44
  export { callRepeater } from "./utils/repeater";
44
45
  export { getContractName } from "./core/contractsRegister";
45
46
  export { AdapterInterface } from "./core/adapters";
47
+ export { objectEntries, swapKeyValue, keyToLowercase } from "./utils/mappers";
@@ -0,0 +1,69 @@
1
+ import {BigNumber} from "ethers";
2
+ import {
3
+ SupportedToken,
4
+ supportedTokens,
5
+ tokenDataByNetwork,
6
+ TokenDataI,
7
+ TokenType
8
+ } from "../core/token";
9
+ import {Path, PathAsset, ActionData} from "./path";
10
+
11
+ export class ConnectorPathAsset extends PathAsset {
12
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
13
+ type ConnectorDataI = Extract<TokenDataI, { type: TokenType.CONNECTOR }>;
14
+
15
+ const currentBalance: BigNumber = p.balances[currentToken].sub(1);
16
+ const currentTokenAddress: string = tokenDataByNetwork[p.networkType][currentToken];
17
+ const currentTokenData: TokenDataI = supportedTokens[currentToken] as ConnectorDataI;
18
+ // connector token will only swap to pool
19
+ const nextToken: SupportedToken = p.pool;
20
+ const nextTokenAddress: string = tokenDataByNetwork[p.networkType][nextToken];
21
+
22
+ let actionData: ActionData = {
23
+ callData: {
24
+ targetContract: "",
25
+ callData: ""
26
+ },
27
+ amountOut: BigNumber.from(0),
28
+ gasLimit: BigNumber.from(0),
29
+ };
30
+ for (let swapAction of currentTokenData.swapActions) {
31
+ const tmpActionData: ActionData = await this.getActionData(swapAction, currentTokenAddress, currentToken, currentBalance, nextTokenAddress, nextToken, p);
32
+
33
+ if (tmpActionData.amountOut > actionData.amountOut) {
34
+ actionData = tmpActionData;
35
+ }
36
+ }
37
+
38
+ if (actionData.callData.targetContract != "") {
39
+ p.balances[nextToken].add(actionData.amountOut);
40
+ p.balances[currentToken] = BigNumber.from(1);
41
+ p.totalGasLimit.add(actionData.gasLimit);
42
+ p.calls.push(actionData.callData);
43
+ }
44
+
45
+ return await p.getBestPath();
46
+ }
47
+
48
+ // Only compute the maximum amount of pool token output, will not modify path.
49
+ async getMaxPoolAmount(currentToken: SupportedToken, currentBalance: BigNumber, p: Path): Promise<BigNumber> {
50
+ type ConnectorDataI = Extract<TokenDataI, { type: TokenType.CONNECTOR }>;
51
+
52
+ const currentTokenAddress: string = tokenDataByNetwork[p.networkType][currentToken];
53
+ const currentTokenData: TokenDataI = supportedTokens[currentToken] as ConnectorDataI;
54
+ // connector token will only swap to pool
55
+ const nextToken: SupportedToken = p.pool;
56
+ const nextTokenAddress: string = tokenDataByNetwork[p.networkType][nextToken];
57
+
58
+ let maxAmountOut: BigNumber = BigNumber.from(0);
59
+ for (let swapAction of currentTokenData.swapActions) {
60
+ const actionData: ActionData = await this.getActionData(swapAction, currentTokenAddress, currentToken, currentBalance, nextTokenAddress, nextToken, p);
61
+
62
+ if (actionData.amountOut > maxAmountOut) {
63
+ maxAmountOut = actionData.amountOut;
64
+ }
65
+ }
66
+
67
+ return maxAmountOut;
68
+ }
69
+ }
@@ -0,0 +1,33 @@
1
+ import {BigNumber} from "ethers";
2
+ import {
3
+ SupportedToken,
4
+ supportedTokens,
5
+ TokenDataI,
6
+ TokenType
7
+ } from "../core/token";
8
+ import {contractsByNetwork} from "../core/contracts";
9
+ import {TradeType} from "../core/tradeTypes";
10
+ import {Path, PathAsset} from "./path";
11
+
12
+ export class ConvexLPTokenPathAsset extends PathAsset {
13
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
14
+ throw Error("Implementation not finished!");
15
+ const currentBalance: BigNumber = p.balances[currentToken].sub(1);
16
+ type ConvexLPTokenDataI = Extract<TokenDataI, { type: TokenType.CONVEX_LP_TOKEN }>;
17
+ const currentTokenData: ConvexLPTokenDataI = supportedTokens[currentToken] as ConvexLPTokenDataI;
18
+
19
+ for (let lpAction of currentTokenData.lpActions) {
20
+ if (lpAction.type == TradeType.ConvexWithdrawLP) {
21
+ const actionContractAddress: string = contractsByNetwork[p.networkType][lpAction.contract];
22
+ const adapterAddress: string = p.creditManager.adapters[actionContractAddress];
23
+ const outputToken: SupportedToken = lpAction.tokenOut as SupportedToken;
24
+ console.log(currentBalance);
25
+ console.log(adapterAddress);
26
+ console.log(outputToken);
27
+ // TODO(byli) wait convex adapter types
28
+ }
29
+ }
30
+
31
+ return await p.getBestPath();
32
+ }
33
+ }
@@ -0,0 +1,10 @@
1
+ import {SupportedToken} from "../core/token";
2
+ import {Path, PathAsset} from "./path";
3
+
4
+ export class CurveLPPathAsset extends PathAsset {
5
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
6
+ console.log(currentToken);
7
+ console.log(p);
8
+ throw Error("Not implemented yet.");
9
+ }
10
+ }
@@ -0,0 +1,12 @@
1
+ import {
2
+ SupportedToken,
3
+ } from "../core/token";
4
+ import {Path, PathAsset} from "./path";
5
+
6
+ export class MetaCurveLPPathAsset extends PathAsset {
7
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
8
+ console.log(currentToken);
9
+ console.log(p);
10
+ throw Error("Not implemented yet.");
11
+ }
12
+ }
@@ -0,0 +1,68 @@
1
+ import {BigNumber} from "ethers";
2
+ import {
3
+ ConnectorTokens,
4
+ SupportedToken,
5
+ supportedTokens,
6
+ tokenDataByNetwork,
7
+ TokenDataI,
8
+ TokenType
9
+ } from "../core/token";
10
+ import {ActionData, Path, PathAsset} from "./path";
11
+ import {ConnectorPathAsset} from "./connectorPathAsset";
12
+
13
+ export class NormalTokenPathAsset extends PathAsset {
14
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
15
+ type ConnectorDataI = Extract<TokenDataI, { type: TokenType.NORMAL_TOKEN }>;
16
+
17
+ const currentBalance: BigNumber = p.balances[currentToken].sub(1);
18
+ const currentTokenAddress: string = tokenDataByNetwork[p.networkType][currentToken];
19
+ const currentTokenData: TokenDataI = supportedTokens[currentToken] as ConnectorDataI;
20
+
21
+ let actionData: ActionData = {
22
+ callData: {
23
+ targetContract: "",
24
+ callData: ""
25
+ },
26
+ amountOut: BigNumber.from(0),
27
+ gasLimit: BigNumber.from(0),
28
+ };
29
+ let nextToken: SupportedToken = p.pool;
30
+ let maxPoolAmountOut: BigNumber = BigNumber.from(0);
31
+
32
+ // enumerate connector token and find a best one
33
+ for (let connToken of ConnectorTokens) {
34
+ const connTokenAddress: string = tokenDataByNetwork[p.networkType][connToken];
35
+
36
+ for (let swapAction of currentTokenData.swapActions!) {
37
+ const tmpActionData: ActionData = await this.getActionData(swapAction, currentTokenAddress, currentToken, currentBalance, connTokenAddress, connToken, p);
38
+
39
+ // compute connToken to pool token
40
+ if (connToken != p.pool) {
41
+ const connectorPathAsset = new ConnectorPathAsset();
42
+ const poolAmountOut: BigNumber = await connectorPathAsset.getMaxPoolAmount(connToken, actionData.amountOut, p);
43
+ if (poolAmountOut > maxPoolAmountOut) {
44
+ maxPoolAmountOut = poolAmountOut;
45
+
46
+ actionData = tmpActionData;
47
+ nextToken = connToken;
48
+ }
49
+ } else {
50
+ if (actionData.amountOut > maxPoolAmountOut) {
51
+ maxPoolAmountOut = actionData.amountOut;
52
+
53
+ actionData = tmpActionData;
54
+ nextToken = p.pool;
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ if (actionData.callData.targetContract != "") {
61
+ p.balances[nextToken].add(actionData.amountOut);
62
+ p.balances[currentToken] = BigNumber.from(1);
63
+ p.totalGasLimit.add(actionData.gasLimit);
64
+ p.calls.push(actionData.callData);
65
+ }
66
+ return await p.getBestPath();
67
+ }
68
+ }
@@ -0,0 +1,259 @@
1
+ import {BigNumber, ethers} from "ethers";
2
+ import {CreditManagerData} from "../core/creditManager";
3
+ import {MultiCall} from "../core/multicall";
4
+ import {
5
+ Curve3CrvUnderlyingTokenIndex, NormalToken,
6
+ priority,
7
+ SupportedToken,
8
+ supportedTokens,
9
+ TokenType
10
+ } from "../core/token";
11
+ import {NetworkType} from "../core/constants";
12
+ import {CreditAccountData} from "../core/creditAccount";
13
+ import {ConnectorPathAsset} from "./connectorPathAsset";
14
+ import {YearnVaultPathAsset} from "./yearnVaultPathAsset";
15
+ import {ConvexLPTokenPathAsset} from "./convexLPTokenPathAsset";
16
+ import {CurveLPPathAsset} from "./curveLPPathAsset";
17
+ import {MetaCurveLPPathAsset} from "./metaCurveLPPathAsset";
18
+ import {NormalTokenPathAsset} from "./normalTokenPathAsset";
19
+ import {YearnVaultOfCurveLPPathAsset} from "./yearnVaultOfCurveLPPathAsset";
20
+ import {YearnVaultOfMetaCurveLPPathAsset} from "./yearnVaultOfMetaCurveLPPathAsset";
21
+ import {
22
+ CurveV1Adapter__factory,
23
+ IQuoter__factory,
24
+ UniswapV2Adapter__factory,
25
+ UniswapV3Adapter__factory, YearnAdapter__factory
26
+ } from "../types";
27
+ import {contractsByNetwork, SupportedContract, UNISWAP_V3_QUOTER} from "../core/contracts";
28
+ import {TradeAction, TradeType} from "../core/tradeTypes";
29
+
30
+
31
+ export class Path {
32
+ public readonly calls: Array<MultiCall> = [];
33
+ public readonly balances: Record<SupportedToken, BigNumber>;
34
+ public readonly pool: SupportedToken;
35
+ public readonly creditManager: CreditManagerData;
36
+ public readonly creditAccount: CreditAccountData;
37
+ public readonly networkType: NetworkType;
38
+ public readonly provider: ethers.providers.Provider;
39
+ public readonly totalGasLimit: BigNumber;
40
+
41
+
42
+ // Path is for selling all assets in the credit account, it will return the calls data to sell all the assets, the amount of pool token will got after selling and the total gas limit for all the calls.
43
+ // This is an example to describe how to use Path:
44
+ // const path = new Path({gasUsed:0, balances: creditAccount.balances, })
45
+ // const closurePath = await path.getBestPath();
46
+ // closurePath.calls -> multicalls (!)
47
+ // closurePath.balances[path.pool] = (X)
48
+ // closurePath.balances[!path.pool] = 0 / 1;
49
+ //
50
+ // balances: the balance of each asset in the credit account
51
+ // pool: the pool token
52
+ // creditManager: credit manager data
53
+ // creditAccount: credit account data
54
+ // networkType: network type (Mainnet, Kovan)
55
+ // provider: ehters rpc provider
56
+ // totalGasLimit: the base gas limit, you can pass zero for not adding any base gas limit
57
+
58
+ constructor(opts: {
59
+ balances: Record<SupportedToken, BigNumber>;
60
+ pool: SupportedToken;
61
+ creditManager: CreditManagerData;
62
+ creditAccount: CreditAccountData;
63
+ networkType: NetworkType;
64
+ provider: ethers.providers.Provider;
65
+ totalGasLimit: BigNumber;
66
+ }) {
67
+ this.balances = opts.balances;
68
+ this.pool = opts.pool;
69
+ this.creditManager = opts.creditManager;
70
+ this.creditAccount = opts.creditAccount;
71
+ this.networkType = opts.networkType;
72
+ this.provider = opts.provider;
73
+ this.totalGasLimit = opts.totalGasLimit;
74
+ }
75
+
76
+ private comparedByPriority([tokenA, _balanceA]: [string, BigNumber], [tokenB, _balanceB]: [string, BigNumber]): number {
77
+ const priorityTokenA = priority[supportedTokens[tokenA as SupportedToken].type];
78
+ const priorityTokenB = priority[supportedTokens[tokenB as SupportedToken].type];
79
+
80
+ if (priorityTokenA > priorityTokenB) {
81
+ return -1;
82
+ } else if (priorityTokenA < priorityTokenB) {
83
+ return 1;
84
+ }
85
+ return 0;
86
+ }
87
+
88
+ async getBestPath(): Promise<Path> {
89
+ const existingTokens = Object.entries(this.balances).filter(([_token, balance]) => balance.gt(1)).sort(this.comparedByPriority);
90
+
91
+ if (existingTokens.length == 1) {
92
+ return this;
93
+ }
94
+
95
+ const nextToken = existingTokens.at(0)![0] as SupportedToken;
96
+ let pathAsset: PathAsset;
97
+ // Get balances and keep non-zero only
98
+ // Find token with highest priority
99
+ // Get token type of this token
100
+ switch (supportedTokens[nextToken].type) {
101
+ case TokenType.CONNECTOR:
102
+ pathAsset = new ConnectorPathAsset();
103
+ break;
104
+
105
+ case TokenType.YEARN_VAULT:
106
+ pathAsset = new YearnVaultPathAsset();
107
+ break;
108
+
109
+ case TokenType.CONVEX_LP_TOKEN:
110
+ pathAsset = new ConvexLPTokenPathAsset();
111
+ break;
112
+
113
+ case TokenType.CURVE_LP:
114
+ pathAsset = new CurveLPPathAsset();
115
+ break;
116
+
117
+ case TokenType.META_CURVE_LP:
118
+ pathAsset = new MetaCurveLPPathAsset();
119
+ break;
120
+
121
+ case TokenType.NORMAL_TOKEN:
122
+ pathAsset = new NormalTokenPathAsset();
123
+ break;
124
+
125
+ case TokenType.YEARN_VAULT_OF_CURVE_LP:
126
+ pathAsset = new YearnVaultOfCurveLPPathAsset();
127
+ break;
128
+
129
+ case TokenType.YEARN_VAULT_OF_META_CURVE_LP:
130
+ pathAsset = new YearnVaultOfMetaCurveLPPathAsset();
131
+ break;
132
+
133
+ default:
134
+ throw new Error("Token type not supported yet");
135
+ }
136
+
137
+ return await pathAsset.getBestPath(nextToken, this);
138
+ }
139
+ }
140
+
141
+ export interface ActionData {
142
+ callData: MultiCall;
143
+ amountOut: BigNumber;
144
+ gasLimit: BigNumber;
145
+ };
146
+
147
+ export abstract class PathAsset {
148
+ abstract getBestPath(currentToken: SupportedToken, p: Path): Promise<Path>;
149
+
150
+
151
+ async getUniswapV2SwapData(adapterAddress: string, currentTokenAddress: string, currentBalance: BigNumber, nextTokenAddress: string, p: Path): Promise<ActionData> {
152
+
153
+ const deadline = Math.floor(Date.now() / 1000) + 1200;
154
+ const uniswapV2Adapter = UniswapV2Adapter__factory.connect(adapterAddress, p.provider);
155
+ const path: Array<string> = [currentTokenAddress, nextTokenAddress];
156
+ const amountsOut: Array<BigNumber> = await uniswapV2Adapter.getAmountsOut(currentBalance, path);
157
+
158
+ const amountOut: BigNumber = amountsOut[amountsOut.length - 1];
159
+ const gasLimit: BigNumber = await uniswapV2Adapter.estimateGas.swapExactTokensForTokens(currentBalance, amountOut, path, p.creditAccount.addr, deadline);
160
+ const call: MultiCall = {
161
+ targetContract: adapterAddress,
162
+ callData: UniswapV2Adapter__factory.createInterface().encodeFunctionData("swapExactTokensForTokens", [currentBalance, amountOut, path, p.creditAccount.addr, deadline])
163
+ }
164
+
165
+ return { callData: call, amountOut: amountOut, gasLimit: gasLimit };
166
+ }
167
+
168
+ async getUniswapV3SwapData(adapterAddress: string, currentTokenAddress: string, currentBalance: BigNumber, nextTokenAddress: string, p: Path): Promise<ActionData> {
169
+ const uniswapV3Adapter = UniswapV3Adapter__factory.connect(adapterAddress, p.provider);
170
+ const iQuoter = IQuoter__factory.connect(UNISWAP_V3_QUOTER, p.provider);
171
+ const amountOut: BigNumber = await iQuoter.callStatic.quoteExactInputSingle(currentTokenAddress, nextTokenAddress, 3000, currentBalance, 0);
172
+ const deadline = Math.floor(Date.now() / 1000) + 1200;
173
+ const exactInputSingleOrder = {
174
+ "tokenIn": currentTokenAddress,
175
+ "tokenOut": nextTokenAddress,
176
+ "fee": 3000,
177
+ "recipient": p.creditAccount.addr,
178
+ "amountIn": currentBalance,
179
+ "amountOutMinimum": amountOut,
180
+ "deadline": deadline,
181
+ "sqrtPriceLimitX96": 0
182
+ };
183
+ const call: MultiCall = {
184
+ targetContract: adapterAddress,
185
+ callData: UniswapV3Adapter__factory.createInterface().encodeFunctionData("exactInputSingle", [exactInputSingleOrder])
186
+ }
187
+ const gasLimit: BigNumber = await uniswapV3Adapter.estimateGas.exactInputSingle(exactInputSingleOrder);
188
+
189
+ return { callData: call, amountOut: amountOut, gasLimit: gasLimit };
190
+ }
191
+
192
+ async getCurveActionData(adapterAddress: string, currentToken: SupportedToken, currentBalance: BigNumber, nextToken: SupportedToken, p: Path): Promise<ActionData> {
193
+ const curve3CrvPool = CurveV1Adapter__factory.connect(adapterAddress, p.provider);
194
+ const currentIndex: BigNumber = Curve3CrvUnderlyingTokenIndex[currentToken]!;
195
+ const outputIndex: BigNumber = Curve3CrvUnderlyingTokenIndex[nextToken]!;
196
+
197
+ const amountOut: BigNumber = await curve3CrvPool.get_dy_underlying(currentIndex, outputIndex, currentBalance);
198
+ const gasLimit: BigNumber = await curve3CrvPool.estimateGas.exchange_underlying(currentIndex, outputIndex, currentBalance, amountOut);
199
+ const call: MultiCall = {
200
+ targetContract: adapterAddress,
201
+ callData: CurveV1Adapter__factory.createInterface().encodeFunctionData("exchange_underlying", [currentIndex, outputIndex, currentBalance, amountOut])
202
+ };
203
+
204
+ return { callData: call, amountOut: amountOut, gasLimit: gasLimit };
205
+ }
206
+
207
+ async getActionData(swapAction: TradeAction, currentTokenAddress: string, currentToken: SupportedToken, currentBalance: BigNumber, nextTokenAddress: string, nextToken: SupportedToken, p: Path): Promise<ActionData> {
208
+ const actionType: TradeType = swapAction.type;
209
+ const actionContract: SupportedContract = swapAction.contract;
210
+ const actionContractAddress: string = contractsByNetwork[p.networkType][actionContract];
211
+ const adapterAddress: string = p.creditManager.adapters[actionContractAddress];
212
+
213
+ switch (actionType) {
214
+ case TradeType.UniswapV2Swap:
215
+ return await this.getUniswapV2SwapData(adapterAddress, currentTokenAddress, currentBalance, nextTokenAddress, p);
216
+
217
+ case TradeType.UniswapV3Swap:
218
+ return await this.getUniswapV3SwapData(adapterAddress, currentTokenAddress, currentBalance, nextTokenAddress, p);
219
+
220
+ case TradeType.CurveExchange:
221
+ if (swapAction.tokenOut!.includes(p.pool as NormalToken)) {
222
+ return await this.getCurveActionData(adapterAddress, currentToken, currentBalance, nextToken, p);
223
+ } else {
224
+ return {
225
+ callData: {
226
+ targetContract: "",
227
+ callData: ""
228
+ },
229
+ amountOut: BigNumber.from(0),
230
+ gasLimit: BigNumber.from(0)
231
+ };
232
+ }
233
+
234
+ default:
235
+ throw Error(`TradeType not supported. ${actionType}`);
236
+ }
237
+ }
238
+
239
+ async getYearnActionData(lpAction: TradeAction, currentBalance: BigNumber, p: Path): Promise<ActionData> {
240
+ // Yearn Vault only has one lp action
241
+ const actionContract: SupportedContract = lpAction.contract;
242
+ const allowedContract: string = contractsByNetwork[p.networkType][actionContract];
243
+ // For Yearn Vault the token address is the contract to withdraw
244
+ const adapterAddress = p.creditManager.adapters[allowedContract];
245
+ const call: MultiCall = {
246
+ targetContract: adapterAddress,
247
+ callData: YearnAdapter__factory.createInterface().encodeFunctionData('withdraw', [currentBalance, p.creditAccount.addr])
248
+ };
249
+
250
+ const IYVault = YearnAdapter__factory.connect(allowedContract, p.provider);
251
+ const price: BigNumber = await IYVault.pricePerShare(); // get price
252
+ const gasLimit: BigNumber = await IYVault.estimateGas["withdraw(uint256,address)"](currentBalance, p.creditAccount.addr);
253
+ return {
254
+ callData: call,
255
+ amountOut: currentBalance.mul(price),
256
+ gasLimit: gasLimit
257
+ }
258
+ }
259
+ }
@@ -0,0 +1,22 @@
1
+ import {SupportedToken, supportedTokens, TokenDataI, TokenType} from "../core/token";
2
+ import {ActionData, Path, PathAsset} from "./path";
3
+ import {BigNumber} from "ethers";
4
+
5
+ export class YearnVaultOfCurveLPPathAsset extends PathAsset {
6
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
7
+ const currentBalance: BigNumber = p.balances[currentToken].sub(1);
8
+ type YearnVaultOfMetaCurveDataI = Extract<TokenDataI, { type: TokenType.YEARN_VAULT_OF_CURVE_LP }>;
9
+ const currentTokenData = supportedTokens[currentToken] as YearnVaultOfMetaCurveDataI;
10
+
11
+ // Yearn Vault only has one lp action
12
+ const outputToken: SupportedToken = currentTokenData.lpActions[0].tokenOut as SupportedToken;
13
+ const yearnActionData: ActionData = await this.getYearnActionData(currentTokenData.lpActions[0], currentBalance, p);
14
+
15
+ p.balances[outputToken].add(yearnActionData.amountOut);
16
+ p.balances[currentToken] = BigNumber.from(1);
17
+ p.totalGasLimit.add(yearnActionData.gasLimit);
18
+ p.calls.push(yearnActionData.callData);
19
+
20
+ return await p.getBestPath();
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ import {SupportedToken, supportedTokens, TokenDataI, TokenType} from "../core/token";
2
+ import {ActionData, Path, PathAsset} from "./path";
3
+ import {BigNumber} from "ethers";
4
+
5
+ export class YearnVaultOfMetaCurveLPPathAsset extends PathAsset {
6
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
7
+ const currentBalance: BigNumber = p.balances[currentToken].sub(1);
8
+ type YearnVaultOfMetaCurveDataI = Extract<TokenDataI, { type: TokenType.YEARN_VAULT_OF_META_CURVE_LP }>;
9
+ const currentTokenData = supportedTokens[currentToken] as YearnVaultOfMetaCurveDataI;
10
+
11
+ // Yearn Vault only has one lp action
12
+ const outputToken: SupportedToken = currentTokenData.lpActions[0].tokenOut as SupportedToken;
13
+ const yearnActionData: ActionData = await this.getYearnActionData(currentTokenData.lpActions[0], currentBalance, p);
14
+
15
+ p.balances[outputToken].add(yearnActionData.amountOut);
16
+ p.balances[currentToken] = BigNumber.from(1);
17
+ p.totalGasLimit.add(yearnActionData.gasLimit);
18
+ p.calls.push(yearnActionData.callData);
19
+
20
+ return await p.getBestPath();
21
+ }
22
+ }
@@ -0,0 +1,27 @@
1
+ import {BigNumber} from "ethers";
2
+ import {
3
+ SupportedToken,
4
+ supportedTokens,
5
+ TokenDataI,
6
+ TokenType
7
+ } from "../core/token";
8
+ import {ActionData, Path, PathAsset} from "./path";
9
+
10
+ export class YearnVaultPathAsset extends PathAsset {
11
+ async getBestPath(currentToken: SupportedToken, p: Path): Promise<Path> {
12
+ const currentBalance: BigNumber = p.balances[currentToken].sub(1);
13
+ type YearnVaultDataI = Extract<TokenDataI, { type: TokenType.YEARN_VAULT }>;
14
+ const currentTokenData = supportedTokens[currentToken] as YearnVaultDataI;
15
+
16
+ // Yearn Vault only has one lp action
17
+ const outputToken: SupportedToken = currentTokenData.lpActions[0].tokenOut as SupportedToken;
18
+ const yearnActionData: ActionData = await this.getYearnActionData(currentTokenData.lpActions[0], currentBalance, p);
19
+
20
+ p.balances[outputToken].add(yearnActionData.amountOut);
21
+ p.balances[currentToken] = BigNumber.from(1);
22
+ p.totalGasLimit.add(yearnActionData.gasLimit);
23
+ p.calls.push(yearnActionData.callData);
24
+
25
+ return await p.getBestPath();
26
+ }
27
+ }
@@ -0,0 +1,23 @@
1
+ type SupportedValue = string | number;
2
+
3
+ const objectEntries = <K extends SupportedValue, T>(
4
+ o: Record<K, T>
5
+ ): Array<[K, T]> => Object.entries(o) as Array<[K, T]>;
6
+
7
+ const swapKeyValue = <K extends SupportedValue, T extends SupportedValue>(
8
+ o: Record<K, T>
9
+ ): Record<T, K> =>
10
+ objectEntries(o).reduce((sum, [key, value]) => {
11
+ return { ...sum, [value]: key };
12
+ }, {} as Record<T, K>);
13
+
14
+ const keyToLowercase = <K extends SupportedValue, T extends SupportedValue>(
15
+ o: Record<K, T>
16
+ ): Record<K, T> =>
17
+ objectEntries(o).reduce((sum, [key, value]) => {
18
+ const keyTransformed = typeof key === "string" ? key.toLowerCase() : key;
19
+ return { ...sum, [keyTransformed]: value };
20
+ }, {} as Record<K, T>);
21
+
22
+ export type { SupportedValue };
23
+ export { objectEntries, swapKeyValue, keyToLowercase };
@@ -0,0 +1,3 @@
1
+ export type PartialRecord<K extends keyof any, T> = {
2
+ [P in K]?: T;
3
+ };