@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.
- package/lib/core/contracts.d.ts +29 -16
- package/lib/core/contracts.js +45 -4
- package/lib/core/contractsRegister.js +24 -6
- package/lib/core/creditAccount.js +26 -20
- package/lib/core/events.d.ts +10 -10
- package/lib/core/events.js +22 -22
- package/lib/core/pathfinder.d.ts +35 -5
- package/lib/core/pathfinder.js +547 -67
- package/lib/core/priceFeeds.js +61 -0
- package/lib/core/token.d.ts +47 -15
- package/lib/core/token.js +204 -36
- package/lib/core/tradeTypes.d.ts +2 -0
- package/lib/core/transactions.d.ts +2 -2
- package/lib/core/transactions.js +10 -10
- package/lib/index.d.ts +2 -0
- package/lib/index.js +6 -1
- package/lib/pathfinder/connectorPathAsset.d.ts +7 -0
- package/lib/pathfinder/connectorPathAsset.js +144 -0
- package/lib/pathfinder/convexLPTokenPathAsset.d.ts +5 -0
- package/lib/pathfinder/convexLPTokenPathAsset.js +77 -0
- package/lib/pathfinder/curveLPPathAsset.d.ts +5 -0
- package/lib/pathfinder/curveLPPathAsset.js +72 -0
- package/lib/pathfinder/metaCurveLPPathAsset.d.ts +5 -0
- package/lib/pathfinder/metaCurveLPPathAsset.js +72 -0
- package/lib/pathfinder/normalTokenPathAsset.d.ts +5 -0
- package/lib/pathfinder/normalTokenPathAsset.js +136 -0
- package/lib/pathfinder/path.d.ts +41 -0
- package/lib/pathfinder/path.js +302 -0
- package/lib/pathfinder/pathfinder.d.ts +28 -0
- package/lib/pathfinder/pathfinder.js +519 -0
- package/lib/pathfinder/yearnVaultOfCurveLPPathAsset.d.ts +5 -0
- package/lib/pathfinder/yearnVaultOfCurveLPPathAsset.js +87 -0
- package/lib/pathfinder/yearnVaultOfMetaCurveLPPathAsset.d.ts +5 -0
- package/lib/pathfinder/yearnVaultOfMetaCurveLPPathAsset.js +87 -0
- package/lib/pathfinder/yearnVaultPathAsset.d.ts +5 -0
- package/lib/pathfinder/yearnVaultPathAsset.js +87 -0
- package/lib/utils/mappers.d.ts +6 -0
- package/lib/utils/mappers.js +33 -0
- package/lib/utils/types.d.ts +3 -0
- package/lib/utils/types.js +2 -0
- package/package.json +1 -1
- package/src/core/adapters.ts +13 -15
- package/src/core/contracts.ts +111 -44
- package/src/core/contractsRegister.ts +16 -8
- package/src/core/creditAccount.ts +33 -19
- package/src/core/events.ts +30 -22
- package/src/core/priceFeeds.ts +66 -0
- package/src/core/token.ts +645 -370
- package/src/core/tradeTypes.ts +2 -0
- package/src/core/transactions.ts +10 -10
- package/src/index.ts +2 -0
- package/src/pathfinder/connectorPathAsset.ts +69 -0
- package/src/pathfinder/convexLPTokenPathAsset.ts +33 -0
- package/src/pathfinder/curveLPPathAsset.ts +10 -0
- package/src/pathfinder/metaCurveLPPathAsset.ts +12 -0
- package/src/pathfinder/normalTokenPathAsset.ts +68 -0
- package/src/pathfinder/path.ts +259 -0
- package/src/pathfinder/yearnVaultOfCurveLPPathAsset.ts +22 -0
- package/src/pathfinder/yearnVaultOfMetaCurveLPPathAsset.ts +22 -0
- package/src/pathfinder/yearnVaultPathAsset.ts +27 -0
- package/src/utils/mappers.ts +23 -0
- package/src/utils/types.ts +3 -0
- package/src/core/pathfinder.ts +0 -115
package/src/core/tradeTypes.ts
CHANGED
|
@@ -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;
|
package/src/core/transactions.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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 };
|