@mento-protocol/mento-sdk 3.1.0-beta.4 → 3.1.0-beta.5
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/dist/core/types/liquidity.d.ts +20 -0
- package/dist/esm/index.js +25 -9
- package/dist/esm/services/borrow/internal/borrowReadService.js +63 -34
- package/dist/esm/services/borrow/internal/borrowRegistryReader.js +45 -28
- package/dist/esm/services/index.js +1 -0
- package/dist/esm/services/liquidity/LiquidityService.js +8 -2
- package/dist/esm/services/liquidity/liquidityHelpers.js +27 -1
- package/dist/esm/services/liquidity/zapHelpers.js +20 -24
- package/dist/esm/services/liquidity/zapIn.js +68 -75
- package/dist/esm/services/liquidity/zapOut.js +89 -77
- package/dist/esm/services/pools/PoolService.js +117 -22
- package/dist/esm/services/pools/poolDetails.js +79 -38
- package/dist/esm/services/quotes/QuoteService.js +22 -20
- package/dist/esm/services/routes/RouteService.js +82 -24
- package/dist/esm/services/swap/SwapService.js +81 -37
- package/dist/esm/services/tokens/tokenService.js +142 -29
- package/dist/esm/services/trading/TradingService.js +51 -12
- package/dist/esm/utils/multicall.js +29 -2
- package/dist/index.d.ts +11 -1
- package/dist/index.js +25 -9
- package/dist/services/borrow/internal/borrowReadService.d.ts +1 -0
- package/dist/services/borrow/internal/borrowReadService.js +63 -34
- package/dist/services/borrow/internal/borrowRegistryReader.js +45 -28
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/liquidity/LiquidityService.d.ts +3 -1
- package/dist/services/liquidity/LiquidityService.js +6 -0
- package/dist/services/liquidity/liquidityHelpers.d.ts +6 -0
- package/dist/services/liquidity/liquidityHelpers.js +27 -0
- package/dist/services/liquidity/zapHelpers.js +20 -24
- package/dist/services/liquidity/zapIn.d.ts +2 -1
- package/dist/services/liquidity/zapIn.js +67 -73
- package/dist/services/liquidity/zapOut.d.ts +2 -10
- package/dist/services/liquidity/zapOut.js +90 -77
- package/dist/services/pools/PoolService.d.ts +5 -0
- package/dist/services/pools/PoolService.js +116 -21
- package/dist/services/pools/poolDetails.d.ts +2 -0
- package/dist/services/pools/poolDetails.js +81 -38
- package/dist/services/quotes/QuoteService.d.ts +1 -0
- package/dist/services/quotes/QuoteService.js +24 -21
- package/dist/services/routes/RouteService.d.ts +8 -0
- package/dist/services/routes/RouteService.js +82 -24
- package/dist/services/swap/SwapService.d.ts +19 -0
- package/dist/services/swap/SwapService.js +81 -37
- package/dist/services/tokens/tokenService.d.ts +7 -0
- package/dist/services/tokens/tokenService.js +142 -29
- package/dist/services/trading/TradingService.d.ts +3 -0
- package/dist/services/trading/TradingService.js +51 -12
- package/dist/utils/multicall.d.ts +5 -1
- package/dist/utils/multicall.js +29 -2
- package/package.json +1 -1
|
@@ -114,6 +114,20 @@ export interface ZapOutTransaction {
|
|
|
114
114
|
approval: TokenApproval | null;
|
|
115
115
|
zapOut: ZapOutDetails;
|
|
116
116
|
}
|
|
117
|
+
export interface PreparedZapIn {
|
|
118
|
+
routesA: RouterRoute[];
|
|
119
|
+
routesB: RouterRoute[];
|
|
120
|
+
quote: ZapInQuote;
|
|
121
|
+
approval?: TokenApproval | null;
|
|
122
|
+
details: ZapInDetails;
|
|
123
|
+
}
|
|
124
|
+
export interface PreparedZapOut {
|
|
125
|
+
routesA: RouterRoute[];
|
|
126
|
+
routesB: RouterRoute[];
|
|
127
|
+
quote: ZapOutQuote;
|
|
128
|
+
approval?: TokenApproval | null;
|
|
129
|
+
details: ZapOutDetails;
|
|
130
|
+
}
|
|
117
131
|
/** Input for adding liquidity (without approval handling) */
|
|
118
132
|
export interface AddLiquidityInput {
|
|
119
133
|
poolAddress: string;
|
|
@@ -140,6 +154,9 @@ export interface ZapInInput {
|
|
|
140
154
|
recipient: string;
|
|
141
155
|
options: LiquidityOptions;
|
|
142
156
|
}
|
|
157
|
+
export interface PrepareZapInInput extends ZapInInput {
|
|
158
|
+
owner?: string;
|
|
159
|
+
}
|
|
143
160
|
/** Input for zap out (without approval handling) */
|
|
144
161
|
export interface ZapOutInput {
|
|
145
162
|
poolAddress: string;
|
|
@@ -148,4 +165,7 @@ export interface ZapOutInput {
|
|
|
148
165
|
recipient: string;
|
|
149
166
|
options: LiquidityOptions;
|
|
150
167
|
}
|
|
168
|
+
export interface PrepareZapOutInput extends ZapOutInput {
|
|
169
|
+
owner?: string;
|
|
170
|
+
}
|
|
151
171
|
//# sourceMappingURL=liquidity.d.ts.map
|
package/dist/esm/index.js
CHANGED
|
@@ -10,6 +10,14 @@ import { SwapService } from './services/swap';
|
|
|
10
10
|
import { TradingService } from './services/trading';
|
|
11
11
|
import { LiquidityService } from './services/liquidity';
|
|
12
12
|
import { BorrowService } from './services/borrow';
|
|
13
|
+
const DEFAULT_HTTP_BATCH_OPTIONS = {
|
|
14
|
+
batchSize: 1000,
|
|
15
|
+
wait: 8,
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_MULTICALL_BATCH_OPTIONS = {
|
|
18
|
+
batchSize: 1024,
|
|
19
|
+
wait: 8,
|
|
20
|
+
};
|
|
13
21
|
/**
|
|
14
22
|
* @class Mento
|
|
15
23
|
* @description The main class for the Mento SDK. Initializes a viem PublicClient internally
|
|
@@ -70,14 +78,7 @@ export class Mento {
|
|
|
70
78
|
this.liquidity = liquidity;
|
|
71
79
|
this.borrow = borrow;
|
|
72
80
|
}
|
|
73
|
-
|
|
74
|
-
* Create a new Mento SDK instance
|
|
75
|
-
* @param chainId - The chain ID (e.g., ChainId.CELO, ChainId.CELO_SEPOLIA)
|
|
76
|
-
* @param rpcUrlOrClient - Optional RPC URL string or an existing viem PublicClient.
|
|
77
|
-
* If not provided, uses the default RPC URL for the chain.
|
|
78
|
-
* @returns A new Mento instance
|
|
79
|
-
*/
|
|
80
|
-
static async create(chainId, rpcUrlOrClient) {
|
|
81
|
+
static async create(chainId, rpcUrlOrClient, options) {
|
|
81
82
|
// Validate chainId is supported
|
|
82
83
|
const supportedChainIds = Object.values(ChainId).filter((v) => typeof v === 'number');
|
|
83
84
|
if (!supportedChainIds.includes(chainId)) {
|
|
@@ -89,8 +90,23 @@ export class Mento {
|
|
|
89
90
|
publicClient = rpcUrlOrClient;
|
|
90
91
|
}
|
|
91
92
|
else {
|
|
92
|
-
const transport = http(rpcUrlOrClient || getDefaultRpcUrl(chainId)
|
|
93
|
+
const transport = http(rpcUrlOrClient || getDefaultRpcUrl(chainId), {
|
|
94
|
+
batch: options?.httpBatch === false
|
|
95
|
+
? false
|
|
96
|
+
: {
|
|
97
|
+
...DEFAULT_HTTP_BATCH_OPTIONS,
|
|
98
|
+
...(options?.httpBatch ?? {}),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
93
101
|
publicClient = createPublicClient({
|
|
102
|
+
batch: {
|
|
103
|
+
multicall: options?.multicallBatch === false
|
|
104
|
+
? false
|
|
105
|
+
: {
|
|
106
|
+
...DEFAULT_MULTICALL_BATCH_OPTIONS,
|
|
107
|
+
...(options?.multicallBatch ?? {}),
|
|
108
|
+
},
|
|
109
|
+
},
|
|
94
110
|
chain: getChainConfig(chainId),
|
|
95
111
|
transport,
|
|
96
112
|
});
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import { BORROWER_OPERATIONS_ABI, HINT_HELPERS_ABI, MULTI_TROVE_GETTER_ABI, PRICE_FEED_ABI, TROVE_MANAGER_ABI, TROVE_NFT_ABI, } from '../../../core/abis';
|
|
2
2
|
import { COLL_INDEX } from '../../../core/constants';
|
|
3
|
+
import { multicall } from '../../../utils/multicall';
|
|
3
4
|
import { parseBorrowPosition } from './borrowPositionParser';
|
|
4
5
|
import { formatTroveId, MAX_SAFE_INTEGER_BIGINT, parseTroveId, requireAddress, requireNonNegativeBigInt, } from './borrowValidation';
|
|
6
|
+
const TROVE_ID_BATCH_SIZE = 64;
|
|
7
|
+
const TROVE_OWNER_BATCH_SIZE = 64;
|
|
8
|
+
const TROVE_DETAIL_BATCH_SIZE = 64;
|
|
5
9
|
export class BorrowReadService {
|
|
6
10
|
constructor(publicClient) {
|
|
7
11
|
this.publicClient = publicClient;
|
|
8
12
|
}
|
|
9
13
|
async getTroveData(ctx, troveId) {
|
|
10
14
|
const parsedTroveId = parseTroveId(troveId);
|
|
11
|
-
const [latestData, trovesData] = await
|
|
12
|
-
|
|
15
|
+
const [latestData, trovesData] = await this.readContractsInChunks([
|
|
16
|
+
{
|
|
13
17
|
address: ctx.addresses.troveManager,
|
|
14
18
|
abi: TROVE_MANAGER_ABI,
|
|
15
19
|
functionName: 'getLatestTroveData',
|
|
16
20
|
args: [parsedTroveId],
|
|
17
|
-
}
|
|
18
|
-
|
|
21
|
+
},
|
|
22
|
+
{
|
|
19
23
|
address: ctx.addresses.troveManager,
|
|
20
24
|
abi: TROVE_MANAGER_ABI,
|
|
21
25
|
functionName: 'Troves',
|
|
22
26
|
args: [parsedTroveId],
|
|
23
|
-
}
|
|
24
|
-
]);
|
|
27
|
+
},
|
|
28
|
+
], 2);
|
|
25
29
|
return parseBorrowPosition(formatTroveId(parsedTroveId), latestData, trovesData);
|
|
26
30
|
}
|
|
27
31
|
async getUserTroves(ctx, owner) {
|
|
@@ -32,41 +36,49 @@ export class BorrowReadService {
|
|
|
32
36
|
functionName: 'getTroveIdsCount',
|
|
33
37
|
args: [],
|
|
34
38
|
}));
|
|
35
|
-
|
|
39
|
+
if (troveCount === 0n) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const troveIdContracts = [];
|
|
36
43
|
for (let i = 0n; i < troveCount; i++) {
|
|
37
|
-
|
|
44
|
+
troveIdContracts.push({
|
|
38
45
|
address: ctx.addresses.troveManager,
|
|
39
46
|
abi: TROVE_MANAGER_ABI,
|
|
40
47
|
functionName: 'getTroveFromTroveIdsArray',
|
|
41
48
|
args: [i],
|
|
42
|
-
})
|
|
43
|
-
const troveOwner = (await this.publicClient.readContract({
|
|
44
|
-
address: ctx.addresses.troveNFT,
|
|
45
|
-
abi: TROVE_NFT_ABI,
|
|
46
|
-
functionName: 'ownerOf',
|
|
47
|
-
args: [troveId],
|
|
48
|
-
}));
|
|
49
|
-
if (troveOwner.toLowerCase() === ownerAddress.toLowerCase()) {
|
|
50
|
-
matchedTroveIds.push(troveId);
|
|
51
|
-
}
|
|
49
|
+
});
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
args: [troveId],
|
|
60
|
-
}),
|
|
61
|
-
this.publicClient.readContract({
|
|
62
|
-
address: ctx.addresses.troveManager,
|
|
63
|
-
abi: TROVE_MANAGER_ABI,
|
|
64
|
-
functionName: 'Troves',
|
|
65
|
-
args: [troveId],
|
|
66
|
-
}),
|
|
67
|
-
]);
|
|
68
|
-
return parseBorrowPosition(formatTroveId(troveId), latestData, trovesData);
|
|
51
|
+
const troveIds = (await this.readContractsInChunks(troveIdContracts, TROVE_ID_BATCH_SIZE)).map((troveId) => troveId);
|
|
52
|
+
const ownerContracts = troveIds.map((troveId) => ({
|
|
53
|
+
address: ctx.addresses.troveNFT,
|
|
54
|
+
abi: TROVE_NFT_ABI,
|
|
55
|
+
functionName: 'ownerOf',
|
|
56
|
+
args: [troveId],
|
|
69
57
|
}));
|
|
58
|
+
const troveOwners = await this.readContractsInChunks(ownerContracts, TROVE_OWNER_BATCH_SIZE);
|
|
59
|
+
const matchedTroveIds = troveIds.filter((troveId, index) => troveOwners[index].toLowerCase() === ownerAddress.toLowerCase());
|
|
60
|
+
if (matchedTroveIds.length === 0) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const troveDetailContracts = matchedTroveIds.flatMap((troveId) => ([
|
|
64
|
+
{
|
|
65
|
+
address: ctx.addresses.troveManager,
|
|
66
|
+
abi: TROVE_MANAGER_ABI,
|
|
67
|
+
functionName: 'getLatestTroveData',
|
|
68
|
+
args: [troveId],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
address: ctx.addresses.troveManager,
|
|
72
|
+
abi: TROVE_MANAGER_ABI,
|
|
73
|
+
functionName: 'Troves',
|
|
74
|
+
args: [troveId],
|
|
75
|
+
},
|
|
76
|
+
]));
|
|
77
|
+
const detailResults = await this.readContractsInChunks(troveDetailContracts, TROVE_DETAIL_BATCH_SIZE * 2);
|
|
78
|
+
return matchedTroveIds.map((troveId, index) => {
|
|
79
|
+
const offset = index * 2;
|
|
80
|
+
return parseBorrowPosition(formatTroveId(troveId), detailResults[offset], detailResults[offset + 1]);
|
|
81
|
+
});
|
|
70
82
|
}
|
|
71
83
|
async getCollateralPrice(ctx) {
|
|
72
84
|
return (await this.publicClient.readContract({
|
|
@@ -210,4 +222,21 @@ export class BorrowReadService {
|
|
|
210
222
|
}
|
|
211
223
|
return Number(ownerTroveCount);
|
|
212
224
|
}
|
|
225
|
+
async readContractsInChunks(contracts, chunkSize) {
|
|
226
|
+
if (contracts.length === 0) {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
const results = [];
|
|
230
|
+
for (let index = 0; index < contracts.length; index += chunkSize) {
|
|
231
|
+
const chunk = contracts.slice(index, index + chunkSize);
|
|
232
|
+
const chunkResults = await multicall(this.publicClient, chunk, { allowFailure: false });
|
|
233
|
+
for (const result of chunkResults) {
|
|
234
|
+
if (result.status === 'failure') {
|
|
235
|
+
throw result.error;
|
|
236
|
+
}
|
|
237
|
+
results.push(result.result);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
213
242
|
}
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import { ADDRESSES_REGISTRY_ABI, BORROWER_OPERATIONS_ABI, SYSTEM_PARAMS_ABI, } from '../../../core/abis';
|
|
2
|
+
import { multicall } from '../../../utils/multicall';
|
|
2
3
|
import { validateAddress } from '../../../utils/validation';
|
|
3
4
|
function readNoArgsContract(publicClient, address, abi, functionName) {
|
|
4
5
|
const readContract = publicClient.readContract;
|
|
5
6
|
return readContract({ address, abi, functionName, args: [] });
|
|
6
7
|
}
|
|
8
|
+
async function readNoArgsContracts(publicClient, contracts) {
|
|
9
|
+
if (contracts.length === 0) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const results = await multicall(publicClient, contracts.map((contract) => ({
|
|
13
|
+
...contract,
|
|
14
|
+
abi: contract.abi,
|
|
15
|
+
args: [],
|
|
16
|
+
})), { allowFailure: false });
|
|
17
|
+
return results.map((result) => {
|
|
18
|
+
if (result.status === 'failure') {
|
|
19
|
+
throw result.error;
|
|
20
|
+
}
|
|
21
|
+
return result.result;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
7
24
|
function requireAddress(value, fieldName) {
|
|
8
25
|
if (typeof value !== 'string') {
|
|
9
26
|
throw new Error(`${fieldName} must be a string address`);
|
|
@@ -23,26 +40,26 @@ function requireBigInt(value, fieldName) {
|
|
|
23
40
|
export async function resolveAddressesFromRegistry(publicClient, registryAddress) {
|
|
24
41
|
validateAddress(registryAddress, 'registryAddress');
|
|
25
42
|
const registry = registryAddress;
|
|
26
|
-
const [borrowerOperationsRaw, troveManagerRaw, sortedTrovesRaw, activePoolRaw, defaultPoolRaw, hintHelpersRaw, multiTroveGetterRaw, collTokenRaw, debtTokenRaw, troveNFTRaw, metadataNFTRaw, stabilityPoolRaw, priceFeedRaw, collSurplusPoolRaw, interestRouterRaw, collateralRegistryRaw, gasTokenRaw, gasPoolAddressRaw, liquidityStrategyRaw,] = await
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const [borrowerOperationsRaw, troveManagerRaw, sortedTrovesRaw, activePoolRaw, defaultPoolRaw, hintHelpersRaw, multiTroveGetterRaw, collTokenRaw, debtTokenRaw, troveNFTRaw, metadataNFTRaw, stabilityPoolRaw, priceFeedRaw, collSurplusPoolRaw, interestRouterRaw, collateralRegistryRaw, gasTokenRaw, gasPoolAddressRaw, liquidityStrategyRaw,] = await readNoArgsContracts(publicClient, [
|
|
44
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'borrowerOperations' },
|
|
45
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'troveManager' },
|
|
46
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'sortedTroves' },
|
|
47
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'activePool' },
|
|
48
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'defaultPool' },
|
|
49
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'hintHelpers' },
|
|
50
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'multiTroveGetter' },
|
|
51
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'collToken' },
|
|
52
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'boldToken' },
|
|
53
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'troveNFT' },
|
|
54
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'metadataNFT' },
|
|
55
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'stabilityPool' },
|
|
56
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'priceFeed' },
|
|
57
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'collSurplusPool' },
|
|
58
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'interestRouter' },
|
|
59
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'collateralRegistry' },
|
|
60
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'gasToken' },
|
|
61
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'gasPoolAddress' },
|
|
62
|
+
{ address: registry, abi: ADDRESSES_REGISTRY_ABI, functionName: 'liquidityStrategy' },
|
|
46
63
|
]);
|
|
47
64
|
return {
|
|
48
65
|
borrowerOperations: requireAddress(borrowerOperationsRaw, 'borrowerOperations'),
|
|
@@ -70,14 +87,14 @@ export async function readSystemParams(publicClient, borrowerOperations) {
|
|
|
70
87
|
validateAddress(borrowerOperations, 'borrowerOperations');
|
|
71
88
|
const systemParamsAddressRaw = await readNoArgsContract(publicClient, borrowerOperations, BORROWER_OPERATIONS_ABI, 'systemParams');
|
|
72
89
|
const systemParamsAddress = requireAddress(systemParamsAddressRaw, 'systemParamsAddress');
|
|
73
|
-
const [ccrRaw, mcrRaw, scrRaw, bcrRaw, minDebtRaw, ethGasCompensationRaw, minAnnualInterestRateRaw,] = await
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
const [ccrRaw, mcrRaw, scrRaw, bcrRaw, minDebtRaw, ethGasCompensationRaw, minAnnualInterestRateRaw,] = await readNoArgsContracts(publicClient, [
|
|
91
|
+
{ address: systemParamsAddress, abi: SYSTEM_PARAMS_ABI, functionName: 'CCR' },
|
|
92
|
+
{ address: systemParamsAddress, abi: SYSTEM_PARAMS_ABI, functionName: 'MCR' },
|
|
93
|
+
{ address: systemParamsAddress, abi: SYSTEM_PARAMS_ABI, functionName: 'SCR' },
|
|
94
|
+
{ address: systemParamsAddress, abi: SYSTEM_PARAMS_ABI, functionName: 'BCR' },
|
|
95
|
+
{ address: systemParamsAddress, abi: SYSTEM_PARAMS_ABI, functionName: 'MIN_DEBT' },
|
|
96
|
+
{ address: systemParamsAddress, abi: SYSTEM_PARAMS_ABI, functionName: 'ETH_GAS_COMPENSATION' },
|
|
97
|
+
{ address: systemParamsAddress, abi: SYSTEM_PARAMS_ABI, functionName: 'MIN_ANNUAL_INTEREST_RATE' },
|
|
81
98
|
]);
|
|
82
99
|
return {
|
|
83
100
|
mcr: requireBigInt(mcrRaw, 'MCR'),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildAddLiquidityTransactionInternal, buildAddLiquidityParamsInternal, buildRemoveLiquidityTransactionInternal, buildRemoveLiquidityParamsInternal, quoteAddLiquidityInternal, quoteRemoveLiquidityInternal, getLPTokenBalanceInternal, } from './basicLiquidity';
|
|
2
|
-
import { buildZapInTransactionInternal, buildZapInParamsInternal, quoteZapInInternal, } from './zapIn';
|
|
3
|
-
import { buildZapOutTransactionInternal, buildZapOutParamsInternal, quoteZapOutInternal, } from './zapOut';
|
|
2
|
+
import { buildZapInTransactionInternal, buildZapInParamsInternal, prepareZapInInternal, quoteZapInInternal, } from './zapIn';
|
|
3
|
+
import { buildZapOutTransactionInternal, buildZapOutParamsInternal, prepareZapOutInternal, quoteZapOutInternal, } from './zapOut';
|
|
4
4
|
export class LiquidityService {
|
|
5
5
|
constructor(publicClient, chainId, poolService, routeService) {
|
|
6
6
|
this.publicClient = publicClient;
|
|
@@ -93,6 +93,9 @@ export class LiquidityService {
|
|
|
93
93
|
async buildZapInParams(input) {
|
|
94
94
|
return buildZapInParamsInternal(this.publicClient, this.chainId, this.poolService, this.routeService, input.poolAddress, input.tokenIn, input.amountIn, input.amountInSplit, input.recipient, input.options);
|
|
95
95
|
}
|
|
96
|
+
async prepareZapIn(input) {
|
|
97
|
+
return prepareZapInInternal(this.publicClient, this.chainId, this.poolService, this.routeService, input.poolAddress, input.tokenIn, input.amountIn, input.amountInSplit, input.recipient, input.owner, input.options);
|
|
98
|
+
}
|
|
96
99
|
/**
|
|
97
100
|
* Builds zap out transaction with approval if needed.
|
|
98
101
|
* Removes liquidity and swaps both tokens to a single output token.
|
|
@@ -111,6 +114,9 @@ export class LiquidityService {
|
|
|
111
114
|
async buildZapOutParams(input) {
|
|
112
115
|
return buildZapOutParamsInternal(this.publicClient, this.chainId, this.poolService, this.routeService, input.poolAddress, input.tokenOut, input.liquidity, input.recipient, input.options);
|
|
113
116
|
}
|
|
117
|
+
async prepareZapOut(input) {
|
|
118
|
+
return prepareZapOutInternal(this.publicClient, this.chainId, this.poolService, this.routeService, input.poolAddress, input.tokenOut, input.liquidity, input.recipient, input.owner, input.options);
|
|
119
|
+
}
|
|
114
120
|
/**
|
|
115
121
|
* Quotes a zap in operation (read-only call).
|
|
116
122
|
* Estimates expected LP tokens and minimum amounts after swaps and slippage.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { encodeFunctionData } from 'viem';
|
|
2
2
|
import { PoolType } from '../../core/types';
|
|
3
|
-
import { ERC20_ABI } from '../../core/abis';
|
|
3
|
+
import { ERC20_ABI, FPMM_ABI } from '../../core/abis';
|
|
4
4
|
import { getContractAddress } from '../../core/constants';
|
|
5
5
|
import { validateAddress } from '../../utils/validation';
|
|
6
|
+
import { multicall } from '../../utils/multicall';
|
|
6
7
|
export function buildApprovalParams(chainId, token, amount) {
|
|
7
8
|
const routerAddress = getContractAddress(chainId, 'Router');
|
|
8
9
|
const data = encodeFunctionData({
|
|
@@ -64,3 +65,28 @@ export function validatePoolTokens(poolToken0, poolToken1, tokenA, tokenB) {
|
|
|
64
65
|
throw new Error('tokenA and tokenB must be different');
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
export async function getPoolSnapshot(publicClient, poolAddress) {
|
|
69
|
+
const results = await multicall(publicClient, [
|
|
70
|
+
{
|
|
71
|
+
address: poolAddress,
|
|
72
|
+
abi: FPMM_ABI,
|
|
73
|
+
functionName: 'getReserves',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
address: poolAddress,
|
|
77
|
+
abi: ERC20_ABI,
|
|
78
|
+
functionName: 'totalSupply',
|
|
79
|
+
args: [],
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
if (results[0].status === 'failure' || results[1].status === 'failure') {
|
|
83
|
+
throw new Error(`Failed to fetch pool snapshot for ${poolAddress}`);
|
|
84
|
+
}
|
|
85
|
+
const [reserve0, reserve1, blockTimestampLast] = results[0].result;
|
|
86
|
+
return {
|
|
87
|
+
reserve0,
|
|
88
|
+
reserve1,
|
|
89
|
+
blockTimestampLast,
|
|
90
|
+
totalSupply: results[1].result,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -33,18 +33,16 @@ export function encodeZapOutCall(tokenOut, liquidity, zapParams, routesA, routes
|
|
|
33
33
|
* @returns Routes from tokenIn to token0 and token1
|
|
34
34
|
*/
|
|
35
35
|
export async function findZapInRoutes(routeService, tokenIn, token0, token1) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
routesB = encodeRoutePath(routeB.path, tokenIn, token1);
|
|
47
|
-
}
|
|
36
|
+
const [routeA, routeB] = await Promise.all([
|
|
37
|
+
tokenIn.toLowerCase() !== token0.toLowerCase()
|
|
38
|
+
? routeService.findRoute(tokenIn, token0)
|
|
39
|
+
: Promise.resolve(null),
|
|
40
|
+
tokenIn.toLowerCase() !== token1.toLowerCase()
|
|
41
|
+
? routeService.findRoute(tokenIn, token1)
|
|
42
|
+
: Promise.resolve(null),
|
|
43
|
+
]);
|
|
44
|
+
const routesA = routeA ? encodeRoutePath(routeA.path, tokenIn, token0) : [];
|
|
45
|
+
const routesB = routeB ? encodeRoutePath(routeB.path, tokenIn, token1) : [];
|
|
48
46
|
return { routesA, routesB };
|
|
49
47
|
}
|
|
50
48
|
/**
|
|
@@ -57,18 +55,16 @@ export async function findZapInRoutes(routeService, tokenIn, token0, token1) {
|
|
|
57
55
|
* @returns Routes from token0 and token1 to tokenOut
|
|
58
56
|
*/
|
|
59
57
|
export async function findZapOutRoutes(routeService, token0, token1, tokenOut) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
routesB = encodeRoutePath(routeB.path, token1, tokenOut);
|
|
71
|
-
}
|
|
58
|
+
const [routeA, routeB] = await Promise.all([
|
|
59
|
+
token0.toLowerCase() !== tokenOut.toLowerCase()
|
|
60
|
+
? routeService.findRoute(token0, tokenOut)
|
|
61
|
+
: Promise.resolve(null),
|
|
62
|
+
token1.toLowerCase() !== tokenOut.toLowerCase()
|
|
63
|
+
? routeService.findRoute(token1, tokenOut)
|
|
64
|
+
: Promise.resolve(null),
|
|
65
|
+
]);
|
|
66
|
+
const routesA = routeA ? encodeRoutePath(routeA.path, token0, tokenOut) : [];
|
|
67
|
+
const routesB = routeB ? encodeRoutePath(routeB.path, token1, tokenOut) : [];
|
|
72
68
|
return { routesA, routesB };
|
|
73
69
|
}
|
|
74
70
|
// ========== CALCULATION FUNCTIONS ==========
|
|
@@ -1,40 +1,64 @@
|
|
|
1
|
-
import { ROUTER_ABI
|
|
1
|
+
import { ROUTER_ABI } from '../../core/abis';
|
|
2
2
|
import { getContractAddress } from '../../core/constants';
|
|
3
3
|
import { validateAddress } from '../../utils/validation';
|
|
4
|
-
import { buildApprovalParams, getAllowance, calculateMinAmount, getPoolInfo } from './liquidityHelpers';
|
|
4
|
+
import { buildApprovalParams, getAllowance, calculateMinAmount, getPoolInfo, getPoolSnapshot } from './liquidityHelpers';
|
|
5
5
|
import { encodeZapInCall, findZapInRoutes, splitAmount, estimateLiquidityFromZapIn, } from './zapHelpers';
|
|
6
6
|
// ========== ZAP IN OPERATIONS ==========
|
|
7
7
|
/**
|
|
8
8
|
* Builds a complete zap in transaction including approval if needed
|
|
9
9
|
*/
|
|
10
10
|
export async function buildZapInTransactionInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, recipient, owner, options) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const ownerAddr = owner;
|
|
17
|
-
const currentAllowance = await getAllowance(publicClient, tokenInAddr, ownerAddr, chainId);
|
|
18
|
-
// Build approval if needed
|
|
19
|
-
const approval = currentAllowance < amountIn
|
|
20
|
-
? { token: tokenIn, amount: amountIn, params: buildApprovalParams(chainId, tokenInAddr, amountIn) }
|
|
21
|
-
: null;
|
|
22
|
-
return { approval, zapIn };
|
|
11
|
+
const prepared = await prepareZapInInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, recipient, owner, options);
|
|
12
|
+
return {
|
|
13
|
+
approval: prepared.approval ?? null,
|
|
14
|
+
zapIn: prepared.details,
|
|
15
|
+
};
|
|
23
16
|
}
|
|
24
17
|
/**
|
|
25
18
|
* Builds zap in transaction parameters without checking approval
|
|
26
19
|
*/
|
|
27
20
|
export async function buildZapInParamsInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, recipient, options) {
|
|
21
|
+
const prepared = await prepareZapInInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, recipient, undefined, options);
|
|
22
|
+
return prepared.details;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Quotes a zap in operation (read-only)
|
|
26
|
+
*/
|
|
27
|
+
export async function quoteZapInInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, options) {
|
|
28
|
+
const prepared = await prepareZapInInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, tokenIn, undefined, options);
|
|
29
|
+
return prepared.quote;
|
|
30
|
+
}
|
|
31
|
+
export async function prepareZapInInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, recipient, owner, options) {
|
|
32
|
+
if (owner) {
|
|
33
|
+
validateAddress(owner, 'owner');
|
|
34
|
+
}
|
|
35
|
+
const [context, currentAllowance] = await Promise.all([
|
|
36
|
+
prepareZapInContextInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, recipient, options),
|
|
37
|
+
owner ? getAllowance(publicClient, tokenIn, owner, chainId) : Promise.resolve(null),
|
|
38
|
+
]);
|
|
39
|
+
const approval = owner && currentAllowance !== null && currentAllowance < amountIn
|
|
40
|
+
? { token: tokenIn, amount: amountIn, params: buildApprovalParams(chainId, tokenIn, amountIn) }
|
|
41
|
+
: owner
|
|
42
|
+
? null
|
|
43
|
+
: undefined;
|
|
44
|
+
return {
|
|
45
|
+
routesA: context.routesA,
|
|
46
|
+
routesB: context.routesB,
|
|
47
|
+
quote: context.quote,
|
|
48
|
+
approval,
|
|
49
|
+
details: context.details,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function prepareZapInContextInternal(publicClient, chainId, poolService, routeService, poolAddress, tokenIn, amountIn, amountInSplit, recipient, options) {
|
|
28
53
|
validateAddress(poolAddress, 'poolAddress');
|
|
29
54
|
validateAddress(tokenIn, 'tokenIn');
|
|
30
55
|
validateAddress(recipient, 'recipient');
|
|
31
|
-
// Get pool info
|
|
32
56
|
const { token0, token1, factoryAddr } = await getPoolInfo(poolService, poolAddress);
|
|
33
|
-
// Split input amount
|
|
34
57
|
const { amountA: amountInA, amountB: amountInB } = splitAmount(amountIn, amountInSplit);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
58
|
+
const [{ routesA, routesB }, poolSnapshot] = await Promise.all([
|
|
59
|
+
findZapInRoutes(routeService, tokenIn, token0, token1),
|
|
60
|
+
getPoolSnapshot(publicClient, poolAddress),
|
|
61
|
+
]);
|
|
38
62
|
const routerAddress = getContractAddress(chainId, 'Router');
|
|
39
63
|
const [amountOutMinA, amountOutMinB, amountAMin, amountBMin] = (await publicClient.readContract({
|
|
40
64
|
address: routerAddress,
|
|
@@ -42,20 +66,18 @@ export async function buildZapInParamsInternal(publicClient, chainId, poolServic
|
|
|
42
66
|
functionName: 'generateZapInParams',
|
|
43
67
|
args: [token0, token1, factoryAddr, amountInA, amountInB, routesA, routesB],
|
|
44
68
|
}));
|
|
45
|
-
// Apply slippage to all minimum amounts
|
|
46
69
|
const finalAmountAMin = calculateMinAmount(amountAMin, options.slippageTolerance);
|
|
47
70
|
const finalAmountBMin = calculateMinAmount(amountBMin, options.slippageTolerance);
|
|
48
71
|
const finalAmountOutMinA = calculateMinAmount(amountOutMinA, options.slippageTolerance);
|
|
49
72
|
const finalAmountOutMinB = calculateMinAmount(amountOutMinB, options.slippageTolerance);
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
const expectedLiquidity = estimateLiquidityFromZapIn(finalAmountOutMinA, finalAmountOutMinB, poolDetails.reserve0, poolDetails.reserve1, totalSupply);
|
|
73
|
+
const expectedLiquidity = estimateLiquidityFromZapIn(finalAmountOutMinA, finalAmountOutMinB, poolSnapshot.reserve0, poolSnapshot.reserve1, poolSnapshot.totalSupply);
|
|
74
|
+
const quote = {
|
|
75
|
+
amountOutFromA: finalAmountOutMinA,
|
|
76
|
+
amountOutFromB: finalAmountOutMinB,
|
|
77
|
+
amountAMin: finalAmountAMin,
|
|
78
|
+
amountBMin: finalAmountBMin,
|
|
79
|
+
estimatedMinLiquidity: expectedLiquidity,
|
|
80
|
+
};
|
|
59
81
|
const zapParams = {
|
|
60
82
|
tokenA: token0,
|
|
61
83
|
tokenB: token1,
|
|
@@ -67,53 +89,24 @@ export async function buildZapInParamsInternal(publicClient, chainId, poolServic
|
|
|
67
89
|
};
|
|
68
90
|
const data = encodeZapInCall(tokenIn, amountInA, amountInB, zapParams, routesA, routesB, recipient);
|
|
69
91
|
return {
|
|
70
|
-
params: {
|
|
71
|
-
to: routerAddress,
|
|
72
|
-
data,
|
|
73
|
-
value: '0',
|
|
74
|
-
},
|
|
75
|
-
poolAddress,
|
|
76
|
-
tokenIn,
|
|
77
|
-
amountIn,
|
|
78
|
-
amountInA,
|
|
79
|
-
amountInB,
|
|
80
92
|
routesA,
|
|
81
93
|
routesB,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
functionName: 'generateZapInParams',
|
|
100
|
-
args: [token0, token1, factoryAddr, amountInA, amountInB, routesA, routesB],
|
|
101
|
-
}));
|
|
102
|
-
const poolDetails = await poolService.getPoolDetails(poolAddress);
|
|
103
|
-
const totalSupply = (await publicClient.readContract({
|
|
104
|
-
address: poolAddress,
|
|
105
|
-
abi: ERC20_ABI,
|
|
106
|
-
functionName: 'totalSupply',
|
|
107
|
-
args: [],
|
|
108
|
-
}));
|
|
109
|
-
const finalAmountOutMinA = calculateMinAmount(amountOutMinA, options.slippageTolerance);
|
|
110
|
-
const finalAmountOutMinB = calculateMinAmount(amountOutMinB, options.slippageTolerance);
|
|
111
|
-
const expectedLiquidity = estimateLiquidityFromZapIn(finalAmountOutMinA, finalAmountOutMinB, poolDetails.reserve0, poolDetails.reserve1, totalSupply);
|
|
112
|
-
return {
|
|
113
|
-
amountOutFromA: finalAmountOutMinA,
|
|
114
|
-
amountOutFromB: finalAmountOutMinB,
|
|
115
|
-
amountAMin: calculateMinAmount(amountAMin, options.slippageTolerance),
|
|
116
|
-
amountBMin: calculateMinAmount(amountBMin, options.slippageTolerance),
|
|
117
|
-
estimatedMinLiquidity: expectedLiquidity,
|
|
94
|
+
quote,
|
|
95
|
+
details: {
|
|
96
|
+
params: {
|
|
97
|
+
to: routerAddress,
|
|
98
|
+
data,
|
|
99
|
+
value: '0',
|
|
100
|
+
},
|
|
101
|
+
poolAddress,
|
|
102
|
+
tokenIn,
|
|
103
|
+
amountIn,
|
|
104
|
+
amountInA,
|
|
105
|
+
amountInB,
|
|
106
|
+
routesA,
|
|
107
|
+
routesB,
|
|
108
|
+
zapParams,
|
|
109
|
+
estimatedMinLiquidity: expectedLiquidity,
|
|
110
|
+
},
|
|
118
111
|
};
|
|
119
112
|
}
|