@gearbox-protocol/sdk 2.1.8 → 2.1.10
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/contracts/AdapterData.sol +10 -10
- package/contracts/PriceFeedDataLive.sol +211 -42
- package/lib/apy/convexAPY.d.ts +25 -16
- package/lib/apy/convexAPY.js +101 -71
- package/lib/contracts/contracts.d.ts +3 -0
- package/lib/contracts/contracts.js +30 -15
- package/lib/contracts/contractsRegister.d.ts +19 -8
- package/lib/contracts/contractsRegister.js +56 -27
- package/lib/core/creditManager.d.ts +1 -0
- package/lib/core/creditManager.js +13 -8
- package/lib/core/strategy.d.ts +5 -8
- package/lib/core/strategy.js +20 -39
- package/lib/core/strategy.spec.js +2 -5
- package/lib/oracles/pricefeedType.d.ts +3 -0
- package/lib/oracles/pricefeedType.js +3 -1
- package/lib/pathfinder/pathfinder.d.ts +3 -1
- package/lib/pathfinder/pathfinder.js +19 -10
- package/lib/tokens/gear.d.ts +1 -0
- package/lib/tokens/gear.js +7 -2
- package/lib/tokens/token.js +3 -6
- package/lib/utils/price.d.ts +1 -1
- package/package.json +1 -1
package/lib/core/strategy.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ export interface StrategyPayload {
|
|
|
3
3
|
apy?: number;
|
|
4
4
|
name: string;
|
|
5
5
|
lpToken: string;
|
|
6
|
-
|
|
6
|
+
creditManagers: Array<string>;
|
|
7
|
+
baseAssets: Array<string>;
|
|
7
8
|
unleveragableCollateral: Array<string>;
|
|
8
9
|
leveragableCollateral: Array<string>;
|
|
9
|
-
baseAssets: Array<string>;
|
|
10
10
|
}
|
|
11
11
|
interface LiquidationPriceProps {
|
|
12
12
|
prices: Record<string, bigint>;
|
|
@@ -20,18 +20,15 @@ export declare class Strategy {
|
|
|
20
20
|
apy: number | undefined;
|
|
21
21
|
name: string;
|
|
22
22
|
lpToken: string;
|
|
23
|
-
|
|
23
|
+
baseAssets: Array<string>;
|
|
24
24
|
unleveragableCollateral: Array<string>;
|
|
25
25
|
leveragableCollateral: Array<string>;
|
|
26
|
-
|
|
26
|
+
creditManagers: Array<string>;
|
|
27
27
|
constructor(payload: StrategyPayload);
|
|
28
28
|
static maxLeverage(lpToken: string, cms: Array<PartialCM>): number;
|
|
29
29
|
maxAPY(baseAPY: number, maxLeverage: number, borrowAPY: number): number;
|
|
30
|
-
overallAPY(apy: number, leverage: number, depositCollateral: string, borrowAPY: number): number;
|
|
31
30
|
static liquidationPrice({ prices, liquidationThresholds, borrowed, underlyingToken, lpAmount, lpToken, }: LiquidationPriceProps): bigint;
|
|
32
|
-
protected
|
|
33
|
-
protected inBaseAssets(depositCollateral: string): boolean;
|
|
34
|
-
protected inLeveragableAssets(depositCollateral: string): boolean;
|
|
31
|
+
protected static maxLeverageThreshold(lpToken: string, cms: Array<PartialCM>): readonly [bigint, string];
|
|
35
32
|
}
|
|
36
33
|
type PartialCM = Pick<CreditManagerData, "liquidationThresholds" | "address">;
|
|
37
34
|
export {};
|
package/lib/core/strategy.js
CHANGED
|
@@ -9,21 +9,21 @@ class Strategy {
|
|
|
9
9
|
apy;
|
|
10
10
|
name;
|
|
11
11
|
lpToken;
|
|
12
|
-
|
|
12
|
+
baseAssets;
|
|
13
13
|
unleveragableCollateral;
|
|
14
14
|
leveragableCollateral;
|
|
15
|
-
|
|
15
|
+
creditManagers;
|
|
16
16
|
constructor(payload) {
|
|
17
17
|
this.apy = payload.apy;
|
|
18
18
|
this.name = payload.name;
|
|
19
19
|
this.lpToken = payload.lpToken.toLowerCase();
|
|
20
|
-
this.
|
|
20
|
+
this.creditManagers = payload.creditManagers.map(addr => addr.toLowerCase());
|
|
21
|
+
this.baseAssets = payload.baseAssets.map(addr => addr.toLowerCase());
|
|
21
22
|
this.unleveragableCollateral = payload.unleveragableCollateral.map(addr => addr.toLowerCase());
|
|
22
23
|
this.leveragableCollateral = payload.leveragableCollateral.map(addr => addr.toLowerCase());
|
|
23
|
-
this.baseAssets = payload.baseAssets.map(addr => addr.toLowerCase());
|
|
24
24
|
}
|
|
25
25
|
static maxLeverage(lpToken, cms) {
|
|
26
|
-
const [maxThreshold] = maxLeverageThreshold(lpToken, cms);
|
|
26
|
+
const [maxThreshold] = Strategy.maxLeverageThreshold(lpToken, cms);
|
|
27
27
|
const maxLeverage = (constants_1.PERCENTAGE_FACTOR * constants_1.LEVERAGE_DECIMALS) /
|
|
28
28
|
(constants_1.PERCENTAGE_FACTOR - maxThreshold);
|
|
29
29
|
return Number(maxLeverage - constants_1.LEVERAGE_DECIMALS);
|
|
@@ -33,10 +33,6 @@ class Strategy {
|
|
|
33
33
|
((baseAPY - borrowAPY) * (maxLeverage - Number(constants_1.LEVERAGE_DECIMALS))) /
|
|
34
34
|
Number(constants_1.LEVERAGE_DECIMALS));
|
|
35
35
|
}
|
|
36
|
-
overallAPY(apy, leverage, depositCollateral, borrowAPY) {
|
|
37
|
-
const farmLev = this.farmLev(leverage, depositCollateral);
|
|
38
|
-
return roi(apy, farmLev, leverage - Number(constants_1.LEVERAGE_DECIMALS), borrowAPY);
|
|
39
|
-
}
|
|
40
36
|
static liquidationPrice({ prices, liquidationThresholds, borrowed, underlyingToken, lpAmount, lpToken, }) {
|
|
41
37
|
const underlyingTokenAddressLC = underlyingToken.toLowerCase();
|
|
42
38
|
const underlyingTokenSymbol = token_1.tokenSymbolByAddress[underlyingTokenAddressLC];
|
|
@@ -56,36 +52,21 @@ class Strategy {
|
|
|
56
52
|
}
|
|
57
53
|
return 0n;
|
|
58
54
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
static maxLeverageThreshold(lpToken, cms) {
|
|
56
|
+
const lpTokenLC = lpToken.toLowerCase();
|
|
57
|
+
const ltByCM = cms.map(cm => {
|
|
58
|
+
const lt = cm.liquidationThresholds[lpTokenLC] || 0n;
|
|
59
|
+
return [cm.address, lt];
|
|
60
|
+
});
|
|
61
|
+
const sorted = ltByCM.sort(([, ltA], [, ltB]) => {
|
|
62
|
+
if (ltA > ltB)
|
|
63
|
+
return 1;
|
|
64
|
+
if (ltB > ltA)
|
|
65
|
+
return -1;
|
|
66
|
+
return 0;
|
|
67
|
+
});
|
|
68
|
+
const [cm = "", lt = 0n] = sorted[0] || [];
|
|
69
|
+
return [lt, cm];
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
exports.Strategy = Strategy;
|
|
73
|
-
function roi(apy, farmLev, debtLev, borrowAPY) {
|
|
74
|
-
return (apy * farmLev - borrowAPY * debtLev) / Number(constants_1.LEVERAGE_DECIMALS);
|
|
75
|
-
}
|
|
76
|
-
function maxLeverageThreshold(lpToken, cms) {
|
|
77
|
-
const lpTokenLC = lpToken.toLowerCase();
|
|
78
|
-
const ltByCM = cms.map(cm => {
|
|
79
|
-
const lt = cm.liquidationThresholds[lpTokenLC] || 0n;
|
|
80
|
-
return [cm.address, lt];
|
|
81
|
-
});
|
|
82
|
-
const sorted = ltByCM.sort(([, ltA], [, ltB]) => {
|
|
83
|
-
if (ltA > ltB)
|
|
84
|
-
return 1;
|
|
85
|
-
if (ltB > ltA)
|
|
86
|
-
return -1;
|
|
87
|
-
return 0;
|
|
88
|
-
});
|
|
89
|
-
const [cm = "", lt = 0n] = sorted[0] || [];
|
|
90
|
-
return [lt, cm];
|
|
91
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const chai_1 = require("chai");
|
|
4
|
+
const contractsRegister_1 = require("../contracts/contractsRegister");
|
|
4
5
|
const decimals_1 = require("../tokens/decimals");
|
|
5
6
|
const token_1 = require("../tokens/token");
|
|
6
7
|
const formatter_1 = require("../utils/formatter");
|
|
@@ -10,7 +11,7 @@ const lidoPayload = {
|
|
|
10
11
|
name: "Lido",
|
|
11
12
|
lpToken: token_1.tokenDataByNetwork.Mainnet.STETH,
|
|
12
13
|
apy: 38434,
|
|
13
|
-
|
|
14
|
+
creditManagers: [contractsRegister_1.creditManagerByNetwork.Mainnet.WETH_V2],
|
|
14
15
|
unleveragableCollateral: [
|
|
15
16
|
token_1.tokenDataByNetwork.Mainnet.USDC,
|
|
16
17
|
token_1.tokenDataByNetwork.Mainnet.DAI,
|
|
@@ -43,10 +44,6 @@ describe("Strategy test", () => {
|
|
|
43
44
|
const result = lidoStrategy.maxAPY(53203, 10 * Number(constants_1.LEVERAGE_DECIMALS), pools["0x1"].borrowRate);
|
|
44
45
|
(0, chai_1.expect)(result).to.be.eq(284143);
|
|
45
46
|
});
|
|
46
|
-
it("overallAPY calculation is correct", () => {
|
|
47
|
-
const result = lidoStrategy.overallAPY(lidoStrategy.apy || 0, 10 * Number(constants_1.LEVERAGE_DECIMALS), token_1.tokenDataByNetwork.Mainnet.WETH, pools["0x2"].borrowRate);
|
|
48
|
-
(0, chai_1.expect)(result).to.be.eq(332716);
|
|
49
|
-
});
|
|
50
47
|
it("liquidationPrice calculation is correct", () => {
|
|
51
48
|
const result = strategy_1.Strategy.liquidationPrice({
|
|
52
49
|
liquidationThresholds,
|
|
@@ -26,9 +26,12 @@ export declare enum PriceFeedType {
|
|
|
26
26
|
ERC4626_VAULT_ORACLE = 16,
|
|
27
27
|
NETWORK_DEPENDENT = 17
|
|
28
28
|
}
|
|
29
|
+
export declare const HOUR_1: number;
|
|
30
|
+
export declare const HOUR_24: number;
|
|
29
31
|
export type PriceFeedData = {
|
|
30
32
|
type: PriceFeedType.CHAINLINK_ORACLE;
|
|
31
33
|
address: string;
|
|
34
|
+
stalenessPeriod?: number;
|
|
32
35
|
} | {
|
|
33
36
|
type: PriceFeedType.YEARN_ORACLE;
|
|
34
37
|
token: SupportedToken;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PriceFeedType = void 0;
|
|
3
|
+
exports.HOUR_24 = exports.HOUR_1 = exports.PriceFeedType = void 0;
|
|
4
4
|
var PriceFeedType;
|
|
5
5
|
(function (PriceFeedType) {
|
|
6
6
|
PriceFeedType[PriceFeedType["CHAINLINK_ORACLE"] = 0] = "CHAINLINK_ORACLE";
|
|
@@ -22,3 +22,5 @@ var PriceFeedType;
|
|
|
22
22
|
PriceFeedType[PriceFeedType["ERC4626_VAULT_ORACLE"] = 16] = "ERC4626_VAULT_ORACLE";
|
|
23
23
|
PriceFeedType[PriceFeedType["NETWORK_DEPENDENT"] = 17] = "NETWORK_DEPENDENT";
|
|
24
24
|
})(PriceFeedType = exports.PriceFeedType || (exports.PriceFeedType = {}));
|
|
25
|
+
exports.HOUR_1 = 60 * 60;
|
|
26
|
+
exports.HOUR_24 = 24 * exports.HOUR_1;
|
|
@@ -15,7 +15,7 @@ export declare class PathFinder {
|
|
|
15
15
|
network: NetworkType;
|
|
16
16
|
static connectors: Array<SupportedToken>;
|
|
17
17
|
protected readonly _connectors: Array<string>;
|
|
18
|
-
constructor(address: string, provider: Signer | providers.Provider, network?: NetworkType, connectors?:
|
|
18
|
+
constructor(address: string, provider: Signer | providers.Provider, network?: NetworkType, connectors?: SupportedToken[]);
|
|
19
19
|
findAllSwaps(creditAccount: CreditAccountData, swapOperation: SwapOperation, tokenIn: SupportedToken | string, tokenOut: SupportedToken | string, amount: BigNumberish, slippage: number): Promise<Array<PathFinderResult>>;
|
|
20
20
|
findOneTokenPath(creditAccount: CreditAccountData, tokenIn: SupportedToken | string, tokenOut: SupportedToken | string, amount: BigNumberish, slippage: number): Promise<PathFinderResult>;
|
|
21
21
|
/**
|
|
@@ -41,4 +41,6 @@ export declare class PathFinder {
|
|
|
41
41
|
*/
|
|
42
42
|
findBestClosePath(creditAccount: CreditAccountData, slippage: number, noConcurency?: boolean): Promise<PathFinderCloseResult>;
|
|
43
43
|
static compare(r1: CloseResult, r2: CloseResult, gasPriceRAY: bigint): CloseResult;
|
|
44
|
+
getAvailableConnectors(availableList: Record<string, bigint> | Record<string, true>): string[];
|
|
45
|
+
static getAvailableConnectors(availableList: Record<string, bigint> | Record<string, true>, connectors: string[]): string[];
|
|
44
46
|
}
|
|
@@ -13,19 +13,22 @@ class PathFinder {
|
|
|
13
13
|
network;
|
|
14
14
|
static connectors = ["USDC", "WETH", "DAI"];
|
|
15
15
|
_connectors;
|
|
16
|
-
constructor(address, provider, network = "Mainnet", connectors) {
|
|
16
|
+
constructor(address, provider, network = "Mainnet", connectors = PathFinder.connectors) {
|
|
17
17
|
this.pathFinder = types_1.IRouter__factory.connect(address, provider);
|
|
18
18
|
this.network = network;
|
|
19
|
-
this._connectors =
|
|
19
|
+
this._connectors = connectors
|
|
20
|
+
.map(c => token_1.tokenDataByNetwork[this.network][c]?.toLowerCase())
|
|
21
|
+
.filter(t => !t);
|
|
20
22
|
}
|
|
21
23
|
async findAllSwaps(creditAccount, swapOperation, tokenIn, tokenOut, amount, slippage) {
|
|
24
|
+
const connectors = this.getAvailableConnectors(creditAccount.balances);
|
|
22
25
|
const swapTask = {
|
|
23
26
|
swapOperation: swapOperation,
|
|
24
27
|
creditAccount: creditAccount.addr,
|
|
25
28
|
tokenIn: token_1.tokenDataByNetwork[this.network][tokenIn] || tokenIn,
|
|
26
29
|
tokenOut: token_1.tokenDataByNetwork[this.network][tokenOut] ||
|
|
27
30
|
tokenOut,
|
|
28
|
-
connectors
|
|
31
|
+
connectors,
|
|
29
32
|
amount: amount,
|
|
30
33
|
slippage,
|
|
31
34
|
externalSlippage: false,
|
|
@@ -47,10 +50,8 @@ class PathFinder {
|
|
|
47
50
|
}
|
|
48
51
|
async findOneTokenPath(creditAccount, tokenIn, tokenOut, amount, slippage) {
|
|
49
52
|
const tokenInAddr = token_1.tokenDataByNetwork[this.network][tokenIn] || tokenIn;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// }
|
|
53
|
-
const result = await this.pathFinder.callStatic.findOneTokenPath(tokenInAddr, amount, token_1.tokenDataByNetwork[this.network][tokenOut] || tokenOut, creditAccount.addr, this._connectors, slippage, {
|
|
53
|
+
const connectors = this.getAvailableConnectors(creditAccount.balances);
|
|
54
|
+
const result = await this.pathFinder.callStatic.findOneTokenPath(tokenInAddr, amount, token_1.tokenDataByNetwork[this.network][tokenOut] || tokenOut, creditAccount.addr, connectors, slippage, {
|
|
54
55
|
gasLimit: GAS_PER_BLOCK,
|
|
55
56
|
});
|
|
56
57
|
return {
|
|
@@ -80,7 +81,8 @@ class PathFinder {
|
|
|
80
81
|
token,
|
|
81
82
|
balance: expectedBalancesAddr[token] || 0,
|
|
82
83
|
}));
|
|
83
|
-
const
|
|
84
|
+
const connectors = this.getAvailableConnectors(cm.supportedTokens);
|
|
85
|
+
const result = await this.pathFinder.callStatic.findOpenStrategyPath(cm.address, balances, targetAddr, connectors, slippage, {
|
|
84
86
|
gasLimit: GAS_PER_BLOCK,
|
|
85
87
|
});
|
|
86
88
|
const balancesAfter = result[0].reduce((acc, b) => {
|
|
@@ -104,16 +106,17 @@ class PathFinder {
|
|
|
104
106
|
async findBestClosePath(creditAccount, slippage, noConcurency = false) {
|
|
105
107
|
const loopsPerTx = Math.floor(GAS_PER_BLOCK / MAX_GAS_PER_ROUTE);
|
|
106
108
|
const pathOptions = pathOptions_1.PathOptionFactory.generatePathOptions(creditAccount.allBalances, loopsPerTx);
|
|
109
|
+
const connectors = this.getAvailableConnectors(creditAccount.balances);
|
|
107
110
|
let results = [];
|
|
108
111
|
if (noConcurency) {
|
|
109
112
|
for (const po of pathOptions) {
|
|
110
|
-
results.push(await this.pathFinder.callStatic.findBestClosePath(creditAccount.addr,
|
|
113
|
+
results.push(await this.pathFinder.callStatic.findBestClosePath(creditAccount.addr, connectors, slippage, po, loopsPerTx, false, {
|
|
111
114
|
gasLimit: GAS_PER_BLOCK,
|
|
112
115
|
}));
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
else {
|
|
116
|
-
const requests = pathOptions.map(po => this.pathFinder.callStatic.findBestClosePath(creditAccount.addr,
|
|
119
|
+
const requests = pathOptions.map(po => this.pathFinder.callStatic.findBestClosePath(creditAccount.addr, connectors, slippage, po, loopsPerTx, false, {
|
|
117
120
|
gasLimit: GAS_PER_BLOCK,
|
|
118
121
|
}));
|
|
119
122
|
results = await Promise.all(requests);
|
|
@@ -137,5 +140,11 @@ class PathFinder {
|
|
|
137
140
|
const comparator = ({ amount, gasUsage }, gasPrice) => amount - (gasUsage * gasPrice) / constants_1.RAY;
|
|
138
141
|
return comparator(r1, gasPriceRAY) > comparator(r2, gasPriceRAY) ? r1 : r2;
|
|
139
142
|
}
|
|
143
|
+
getAvailableConnectors(availableList) {
|
|
144
|
+
return PathFinder.getAvailableConnectors(availableList, this._connectors);
|
|
145
|
+
}
|
|
146
|
+
static getAvailableConnectors(availableList, connectors) {
|
|
147
|
+
return connectors.filter(t => availableList[t] !== undefined);
|
|
148
|
+
}
|
|
140
149
|
}
|
|
141
150
|
exports.PathFinder = PathFinder;
|
package/lib/tokens/gear.d.ts
CHANGED
|
@@ -13,3 +13,4 @@ export type GearboxTokenData = {
|
|
|
13
13
|
type: TokenType.GEAR_TOKEN;
|
|
14
14
|
} & TokenBase;
|
|
15
15
|
export declare const gearTokens: Record<DieselTokenTypes | GearboxToken, DieselTokenData | GearboxTokenData>;
|
|
16
|
+
export declare const isDieselToken: (t: unknown) => t is DieselTokenTypes;
|
package/lib/tokens/gear.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.gearTokens = void 0;
|
|
3
|
+
exports.isDieselToken = exports.gearTokens = void 0;
|
|
4
4
|
const tokenType_1 = require("./tokenType");
|
|
5
|
-
|
|
5
|
+
const dieselTokens = {
|
|
6
6
|
// GEARBOX
|
|
7
7
|
dDAI: {
|
|
8
8
|
name: "dDAI",
|
|
@@ -34,9 +34,14 @@ exports.gearTokens = {
|
|
|
34
34
|
symbol: "dFRAX",
|
|
35
35
|
type: tokenType_1.TokenType.DIESEL_LP_TOKEN,
|
|
36
36
|
},
|
|
37
|
+
};
|
|
38
|
+
exports.gearTokens = {
|
|
39
|
+
...dieselTokens,
|
|
37
40
|
GEAR: {
|
|
38
41
|
name: "GEAR",
|
|
39
42
|
symbol: "GEAR",
|
|
40
43
|
type: tokenType_1.TokenType.GEAR_TOKEN,
|
|
41
44
|
},
|
|
42
45
|
};
|
|
46
|
+
const isDieselToken = (t) => typeof t === "string" && !!dieselTokens[t];
|
|
47
|
+
exports.isDieselToken = isDieselToken;
|
package/lib/tokens/token.js
CHANGED
|
@@ -258,12 +258,9 @@ exports.tokenDataByNetwork = {
|
|
|
258
258
|
};
|
|
259
259
|
exports.tokenSymbolByAddress = mappers_1.TypedObjectUtils.entries(exports.tokenDataByNetwork).reduce((acc, [, tokens]) => ({
|
|
260
260
|
...acc,
|
|
261
|
-
...
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
.map(([k, v]) => [v.toLowerCase(), k])
|
|
265
|
-
.filter(k => !!k)),
|
|
266
|
-
},
|
|
261
|
+
...mappers_1.TypedObjectUtils.fromEntries(mappers_1.TypedObjectUtils.entries(tokens)
|
|
262
|
+
.map(([k, v]) => [v.toLowerCase(), k])
|
|
263
|
+
.filter(k => !!k)),
|
|
267
264
|
}), {});
|
|
268
265
|
const isSupportedToken = (t) => typeof t === "string" && !!exports.supportedTokens[t];
|
|
269
266
|
exports.isSupportedToken = isSupportedToken;
|
package/lib/utils/price.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ interface Target {
|
|
|
3
3
|
decimals: number | undefined;
|
|
4
4
|
}
|
|
5
5
|
export declare class PriceUtils {
|
|
6
|
-
static calcTotalPrice: (price: bigint, amount: bigint, decimals?: number) => bigint;
|
|
6
|
+
static calcTotalPrice: (price: bigint, amount: bigint, decimals?: number | undefined) => bigint;
|
|
7
7
|
static convertByPrice(totalMoney: bigint, { price: targetPrice, decimals: targetDecimals }: Target): bigint;
|
|
8
8
|
}
|
|
9
9
|
export {};
|