@mento-protocol/mento-sdk 3.1.0-beta.5 → 3.1.0-beta.6

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 (29) hide show
  1. package/dist/core/abis/index.d.ts +1 -0
  2. package/dist/core/abis/index.js +1 -0
  3. package/dist/core/abis/liquidityStrategy.d.ts +132 -0
  4. package/dist/core/abis/liquidityStrategy.js +10 -0
  5. package/dist/core/constants/addresses.js +1 -0
  6. package/dist/core/types/contractAddresses.d.ts +1 -0
  7. package/dist/core/types/liquidity.d.ts +23 -0
  8. package/dist/core/types/pool.d.ts +64 -1
  9. package/dist/esm/core/abis/index.js +1 -0
  10. package/dist/esm/core/abis/liquidityStrategy.js +6 -0
  11. package/dist/esm/core/constants/addresses.js +1 -0
  12. package/dist/esm/services/liquidity/LiquidityService.js +18 -0
  13. package/dist/esm/services/liquidity/liquidityHelpers.js +9 -6
  14. package/dist/esm/services/liquidity/rebalance.js +59 -0
  15. package/dist/esm/services/pools/PoolService.js +9 -0
  16. package/dist/esm/services/pools/poolDetails.js +35 -35
  17. package/dist/esm/services/pools/rebalancePreview.js +181 -0
  18. package/dist/services/liquidity/LiquidityService.d.ts +16 -1
  19. package/dist/services/liquidity/LiquidityService.js +18 -0
  20. package/dist/services/liquidity/liquidityHelpers.d.ts +2 -2
  21. package/dist/services/liquidity/liquidityHelpers.js +9 -6
  22. package/dist/services/liquidity/rebalance.d.ts +6 -0
  23. package/dist/services/liquidity/rebalance.js +64 -0
  24. package/dist/services/pools/PoolService.d.ts +3 -1
  25. package/dist/services/pools/PoolService.js +9 -0
  26. package/dist/services/pools/poolDetails.js +34 -34
  27. package/dist/services/pools/rebalancePreview.d.ts +5 -0
  28. package/dist/services/pools/rebalancePreview.js +186 -0
  29. package/package.json +1 -1
@@ -18,4 +18,5 @@ export * from './troveNFT';
18
18
  export * from './priceFeed';
19
19
  export * from './addressesRegistry';
20
20
  export * from './systemParams';
21
+ export * from './liquidityStrategy';
21
22
  //# sourceMappingURL=index.d.ts.map
@@ -34,4 +34,5 @@ __exportStar(require("./troveNFT"), exports);
34
34
  __exportStar(require("./priceFeed"), exports);
35
35
  __exportStar(require("./addressesRegistry"), exports);
36
36
  __exportStar(require("./systemParams"), exports);
37
+ __exportStar(require("./liquidityStrategy"), exports);
37
38
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,132 @@
1
+ export declare const LIQUIDITY_STRATEGY_ABI: readonly [{
2
+ readonly name: "poolConfigs";
3
+ readonly type: "function";
4
+ readonly stateMutability: "view";
5
+ readonly inputs: readonly [{
6
+ readonly type: "address";
7
+ }];
8
+ readonly outputs: readonly [{
9
+ readonly type: "bool";
10
+ readonly name: "isToken0Debt";
11
+ }, {
12
+ readonly type: "uint32";
13
+ readonly name: "lastRebalance";
14
+ }, {
15
+ readonly type: "uint32";
16
+ readonly name: "rebalanceCooldown";
17
+ }, {
18
+ readonly type: "address";
19
+ readonly name: "protocolFeeRecipient";
20
+ }, {
21
+ readonly type: "uint64";
22
+ readonly name: "liquiditySourceIncentiveExpansion";
23
+ }, {
24
+ readonly type: "uint64";
25
+ readonly name: "protocolIncentiveExpansion";
26
+ }, {
27
+ readonly type: "uint64";
28
+ readonly name: "liquiditySourceIncentiveContraction";
29
+ }, {
30
+ readonly type: "uint64";
31
+ readonly name: "protocolIncentiveContraction";
32
+ }];
33
+ }, {
34
+ readonly name: "determineAction";
35
+ readonly type: "function";
36
+ readonly stateMutability: "view";
37
+ readonly inputs: readonly [{
38
+ readonly type: "address";
39
+ readonly name: "pool";
40
+ }];
41
+ readonly outputs: readonly [{
42
+ readonly type: "tuple";
43
+ readonly components: readonly [{
44
+ readonly type: "address";
45
+ readonly name: "pool";
46
+ }, {
47
+ readonly type: "tuple";
48
+ readonly components: readonly [{
49
+ readonly type: "uint256";
50
+ readonly name: "reserveNum";
51
+ }, {
52
+ readonly type: "uint256";
53
+ readonly name: "reserveDen";
54
+ }];
55
+ readonly name: "reserves";
56
+ }, {
57
+ readonly type: "tuple";
58
+ readonly components: readonly [{
59
+ readonly type: "uint256";
60
+ readonly name: "oracleNum";
61
+ }, {
62
+ readonly type: "uint256";
63
+ readonly name: "oracleDen";
64
+ }, {
65
+ readonly type: "bool";
66
+ readonly name: "poolPriceAbove";
67
+ }, {
68
+ readonly type: "uint16";
69
+ readonly name: "rebalanceThreshold";
70
+ }];
71
+ readonly name: "prices";
72
+ }, {
73
+ readonly type: "address";
74
+ readonly name: "token0";
75
+ }, {
76
+ readonly type: "address";
77
+ readonly name: "token1";
78
+ }, {
79
+ readonly type: "uint64";
80
+ readonly name: "token0Dec";
81
+ }, {
82
+ readonly type: "uint64";
83
+ readonly name: "token1Dec";
84
+ }, {
85
+ readonly type: "bool";
86
+ readonly name: "isToken0Debt";
87
+ }, {
88
+ readonly type: "tuple";
89
+ readonly components: readonly [{
90
+ readonly type: "uint64";
91
+ readonly name: "liquiditySourceIncentiveExpansion";
92
+ }, {
93
+ readonly type: "uint64";
94
+ readonly name: "protocolIncentiveExpansion";
95
+ }, {
96
+ readonly type: "uint64";
97
+ readonly name: "liquiditySourceIncentiveContraction";
98
+ }, {
99
+ readonly type: "uint64";
100
+ readonly name: "protocolIncentiveContraction";
101
+ }];
102
+ readonly name: "incentives";
103
+ }];
104
+ readonly name: "ctx";
105
+ }, {
106
+ readonly type: "tuple";
107
+ readonly components: readonly [{
108
+ readonly type: "uint8";
109
+ readonly name: "dir";
110
+ }, {
111
+ readonly type: "uint256";
112
+ readonly name: "amount0Out";
113
+ }, {
114
+ readonly type: "uint256";
115
+ readonly name: "amount1Out";
116
+ }, {
117
+ readonly type: "uint256";
118
+ readonly name: "amountOwedToPool";
119
+ }];
120
+ readonly name: "action";
121
+ }];
122
+ }, {
123
+ readonly name: "rebalance";
124
+ readonly type: "function";
125
+ readonly stateMutability: "nonpayable";
126
+ readonly inputs: readonly [{
127
+ readonly type: "address";
128
+ readonly name: "pool";
129
+ }];
130
+ readonly outputs: readonly [];
131
+ }];
132
+ //# sourceMappingURL=liquidityStrategy.d.ts.map
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LIQUIDITY_STRATEGY_ABI = void 0;
4
+ const viem_1 = require("viem");
5
+ exports.LIQUIDITY_STRATEGY_ABI = (0, viem_1.parseAbi)([
6
+ 'function poolConfigs(address) view returns (bool isToken0Debt, uint32 lastRebalance, uint32 rebalanceCooldown, address protocolFeeRecipient, uint64 liquiditySourceIncentiveExpansion, uint64 protocolIncentiveExpansion, uint64 liquiditySourceIncentiveContraction, uint64 protocolIncentiveContraction)',
7
+ 'function determineAction(address pool) view returns ((address pool, (uint256 reserveNum, uint256 reserveDen) reserves, (uint256 oracleNum, uint256 oracleDen, bool poolPriceAbove, uint16 rebalanceThreshold) prices, address token0, address token1, uint64 token0Dec, uint64 token1Dec, bool isToken0Debt, (uint64 liquiditySourceIncentiveExpansion, uint64 protocolIncentiveExpansion, uint64 liquiditySourceIncentiveContraction, uint64 protocolIncentiveContraction) incentives) ctx, (uint8 dir, uint256 amount0Out, uint256 amount1Out, uint256 amountOwedToPool) action)',
8
+ 'function rebalance(address pool)',
9
+ ]);
10
+ //# sourceMappingURL=liquidityStrategy.js.map
@@ -44,6 +44,7 @@ exports.addresses = {
44
44
  FPMMFactory: '0x353ED52bF8482027C0e0b9e3c0e5d96A9F680980',
45
45
  Router: '0xcf6cD45210b3ffE3cA28379C4683F1e60D0C2CCd',
46
46
  ReserveLiquidityStrategy: '0x734bb3251Ec3f1A83f8f2A8609bcEF649D54EbF8',
47
+ OpenLiquidityStrategy: '0xCCd2aD0603a08EBc14D223a983171ef18192e8c9',
47
48
  },
48
49
  [chainId_1.ChainId.MONAD]: {},
49
50
  [chainId_1.ChainId.CELO_SEPOLIA]: {
@@ -30,6 +30,7 @@ export type ContractAddresses = {
30
30
  Router?: string;
31
31
  ReserveLiquidityStrategy?: string;
32
32
  CDPLiquidityStrategy?: string;
33
+ OpenLiquidityStrategy?: string;
33
34
  };
34
35
  /**
35
36
  * Map of chain IDs to their contract addresses.
@@ -1,4 +1,5 @@
1
1
  import { CallParams } from './transaction';
2
+ import { LiquidityStrategyDirection } from './pool';
2
3
  import { RouterRoute } from '../../utils/pathEncoder';
3
4
  export interface LiquidityOptions {
4
5
  slippageTolerance: number;
@@ -114,6 +115,25 @@ export interface ZapOutTransaction {
114
115
  approval: TokenApproval | null;
115
116
  zapOut: ZapOutDetails;
116
117
  }
118
+ export interface RebalanceDetails {
119
+ params: CallParams;
120
+ poolAddress: string;
121
+ strategyAddress: string;
122
+ inputToken: string;
123
+ outputToken: string;
124
+ amountRequired: bigint;
125
+ expectedAmountTransferred: bigint;
126
+ expectedProtocolIncentive: bigint;
127
+ expectedLiquiditySourceIncentive: bigint;
128
+ approvalToken: string;
129
+ approvalSpender: string;
130
+ approvalAmount: bigint;
131
+ direction: LiquidityStrategyDirection;
132
+ }
133
+ export interface RebalanceTransaction {
134
+ approval: TokenApproval | null;
135
+ rebalance: RebalanceDetails;
136
+ }
117
137
  export interface PreparedZapIn {
118
138
  routesA: RouterRoute[];
119
139
  routesB: RouterRoute[];
@@ -168,4 +188,7 @@ export interface ZapOutInput {
168
188
  export interface PrepareZapOutInput extends ZapOutInput {
169
189
  owner?: string;
170
190
  }
191
+ export interface RebalanceInput {
192
+ poolAddress: string;
193
+ }
171
194
  //# sourceMappingURL=liquidity.d.ts.map
@@ -92,9 +92,72 @@ export interface FPMMRebalancing {
92
92
  rebalanceThresholdBelowPercent: number;
93
93
  /** Whether the current price is within rebalancing thresholds (null when pricing unavailable) */
94
94
  inBand: boolean | null;
95
- /** The active liquidity strategy address for this pool, or null if none */
95
+ /** The registered Open Liquidity Strategy address for this pool, or null if none */
96
96
  liquidityStrategy: string | null;
97
97
  }
98
+ export type LiquidityStrategyDirection = 'Expand' | 'Contract';
99
+ export interface LiquidityStrategyRebalanceIncentives {
100
+ liquiditySourceIncentiveExpansion: bigint;
101
+ protocolIncentiveExpansion: bigint;
102
+ liquiditySourceIncentiveContraction: bigint;
103
+ protocolIncentiveContraction: bigint;
104
+ }
105
+ export interface LiquidityStrategyPoolConfig {
106
+ isToken0Debt: boolean;
107
+ lastRebalance: number;
108
+ rebalanceCooldown: number;
109
+ protocolFeeRecipient: string;
110
+ liquiditySourceIncentiveExpansion: bigint;
111
+ protocolIncentiveExpansion: bigint;
112
+ liquiditySourceIncentiveContraction: bigint;
113
+ protocolIncentiveContraction: bigint;
114
+ }
115
+ export interface LiquidityStrategyContext {
116
+ pool: string;
117
+ reserves: {
118
+ reserveNum: bigint;
119
+ reserveDen: bigint;
120
+ };
121
+ prices: {
122
+ oracleNum: bigint;
123
+ oracleDen: bigint;
124
+ poolPriceAbove: boolean;
125
+ rebalanceThreshold: number;
126
+ };
127
+ token0: string;
128
+ token1: string;
129
+ token0Dec: bigint;
130
+ token1Dec: bigint;
131
+ isToken0Debt: boolean;
132
+ incentives: LiquidityStrategyRebalanceIncentives;
133
+ }
134
+ export interface LiquidityStrategyAction {
135
+ dir: LiquidityStrategyDirection;
136
+ amount0Out: bigint;
137
+ amount1Out: bigint;
138
+ amountOwedToPool: bigint;
139
+ }
140
+ export interface PoolRebalanceTokenAmount {
141
+ token: string;
142
+ amount: bigint;
143
+ }
144
+ export interface PoolRebalancePreview {
145
+ poolAddress: string;
146
+ strategyAddress: string;
147
+ direction: LiquidityStrategyDirection;
148
+ config: LiquidityStrategyPoolConfig;
149
+ context: LiquidityStrategyContext;
150
+ action: LiquidityStrategyAction;
151
+ inputToken: string;
152
+ outputToken: string;
153
+ amountRequired: PoolRebalanceTokenAmount;
154
+ amountTransferred: PoolRebalanceTokenAmount;
155
+ protocolIncentive: PoolRebalanceTokenAmount;
156
+ liquiditySourceIncentive: PoolRebalanceTokenAmount;
157
+ approvalToken: string;
158
+ approvalSpender: string;
159
+ approvalAmount: bigint;
160
+ }
98
161
  /**
99
162
  * Enriched details for an FPMM pool including pricing, fees, and rebalancing state
100
163
  */
@@ -18,3 +18,4 @@ export * from './troveNFT';
18
18
  export * from './priceFeed';
19
19
  export * from './addressesRegistry';
20
20
  export * from './systemParams';
21
+ export * from './liquidityStrategy';
@@ -0,0 +1,6 @@
1
+ import { parseAbi } from 'viem';
2
+ export const LIQUIDITY_STRATEGY_ABI = parseAbi([
3
+ 'function poolConfigs(address) view returns (bool isToken0Debt, uint32 lastRebalance, uint32 rebalanceCooldown, address protocolFeeRecipient, uint64 liquiditySourceIncentiveExpansion, uint64 protocolIncentiveExpansion, uint64 liquiditySourceIncentiveContraction, uint64 protocolIncentiveContraction)',
4
+ 'function determineAction(address pool) view returns ((address pool, (uint256 reserveNum, uint256 reserveDen) reserves, (uint256 oracleNum, uint256 oracleDen, bool poolPriceAbove, uint16 rebalanceThreshold) prices, address token0, address token1, uint64 token0Dec, uint64 token1Dec, bool isToken0Debt, (uint64 liquiditySourceIncentiveExpansion, uint64 protocolIncentiveExpansion, uint64 liquiditySourceIncentiveContraction, uint64 protocolIncentiveContraction) incentives) ctx, (uint8 dir, uint256 amount0Out, uint256 amount1Out, uint256 amountOwedToPool) action)',
5
+ 'function rebalance(address pool)',
6
+ ]);
@@ -39,6 +39,7 @@ export const addresses = {
39
39
  FPMMFactory: '0x353ED52bF8482027C0e0b9e3c0e5d96A9F680980',
40
40
  Router: '0xcf6cD45210b3ffE3cA28379C4683F1e60D0C2CCd',
41
41
  ReserveLiquidityStrategy: '0x734bb3251Ec3f1A83f8f2A8609bcEF649D54EbF8',
42
+ OpenLiquidityStrategy: '0xCCd2aD0603a08EBc14D223a983171ef18192e8c9',
42
43
  },
43
44
  [ChainId.MONAD]: {},
44
45
  [ChainId.CELO_SEPOLIA]: {
@@ -1,6 +1,7 @@
1
1
  import { buildAddLiquidityTransactionInternal, buildAddLiquidityParamsInternal, buildRemoveLiquidityTransactionInternal, buildRemoveLiquidityParamsInternal, quoteAddLiquidityInternal, quoteRemoveLiquidityInternal, getLPTokenBalanceInternal, } from './basicLiquidity';
2
2
  import { buildZapInTransactionInternal, buildZapInParamsInternal, prepareZapInInternal, quoteZapInInternal, } from './zapIn';
3
3
  import { buildZapOutTransactionInternal, buildZapOutParamsInternal, prepareZapOutInternal, quoteZapOutInternal, } from './zapOut';
4
+ import { buildRebalanceParamsInternal, buildRebalanceTransactionInternal, } from './rebalance';
4
5
  export class LiquidityService {
5
6
  constructor(publicClient, chainId, poolService, routeService) {
6
7
  this.publicClient = publicClient;
@@ -142,4 +143,21 @@ export class LiquidityService {
142
143
  async quoteZapOut(poolAddress, tokenOut, liquidity, options) {
143
144
  return quoteZapOutInternal(this.publicClient, this.chainId, this.poolService, this.routeService, poolAddress, tokenOut, liquidity, options);
144
145
  }
146
+ /**
147
+ * Builds rebalance transaction parameters without checking approval.
148
+ * Use buildRebalanceTransaction if you need approval handling.
149
+ * @param input - Rebalance parameters
150
+ * @returns Transaction details with encoded call data
151
+ */
152
+ async buildRebalanceParams(input) {
153
+ return buildRebalanceParamsInternal(this.publicClient, this.chainId, this.poolService, input.poolAddress);
154
+ }
155
+ /**
156
+ * Builds a rebalance transaction with ERC20 approval if needed.
157
+ * @param input - Rebalance parameters including owner for allowance checks
158
+ * @returns Transaction with approval (if needed) and rebalance call
159
+ */
160
+ async buildRebalanceTransaction(input) {
161
+ return buildRebalanceTransactionInternal(this.publicClient, this.chainId, this.poolService, input.poolAddress, input.owner);
162
+ }
145
163
  }
@@ -4,22 +4,25 @@ import { ERC20_ABI, FPMM_ABI } from '../../core/abis';
4
4
  import { getContractAddress } from '../../core/constants';
5
5
  import { validateAddress } from '../../utils/validation';
6
6
  import { multicall } from '../../utils/multicall';
7
- export function buildApprovalParams(chainId, token, amount) {
8
- const routerAddress = getContractAddress(chainId, 'Router');
7
+ function getApprovalSpender(chainId, spender) {
8
+ return spender ?? getContractAddress(chainId, 'Router');
9
+ }
10
+ export function buildApprovalParams(chainId, token, amount, spender) {
11
+ const approvalSpender = getApprovalSpender(chainId, spender);
9
12
  const data = encodeFunctionData({
10
13
  abi: ERC20_ABI,
11
14
  functionName: 'approve',
12
- args: [routerAddress, amount],
15
+ args: [approvalSpender, amount],
13
16
  });
14
17
  return { to: token, data, value: '0' };
15
18
  }
16
- export async function getAllowance(publicClient, token, owner, chainId) {
17
- const routerAddress = getContractAddress(chainId, 'Router');
19
+ export async function getAllowance(publicClient, token, owner, chainId, spender) {
20
+ const approvalSpender = getApprovalSpender(chainId, spender);
18
21
  return (await publicClient.readContract({
19
22
  address: token,
20
23
  abi: ERC20_ABI,
21
24
  functionName: 'allowance',
22
- args: [owner, routerAddress],
25
+ args: [owner, approvalSpender],
23
26
  }));
24
27
  }
25
28
  export function calculateMinAmount(amount, slippageTolerance) {
@@ -0,0 +1,59 @@
1
+ import { encodeFunctionData } from 'viem';
2
+ import { LIQUIDITY_STRATEGY_ABI } from '../../core/abis';
3
+ import { validateAddress } from '../../utils/validation';
4
+ import { buildApprovalParams, getAllowance } from './liquidityHelpers';
5
+ function getRebalanceUnavailableError(poolAddress) {
6
+ return new Error(`Pool ${poolAddress} is not currently rebalanceable or does not have a supported liquidity strategy.`);
7
+ }
8
+ function assertRebalanceActionAmounts(poolAddress, amountRequired, amountTransferred) {
9
+ if (amountRequired <= 0n || amountTransferred <= 0n) {
10
+ throw new Error(`Rebalance action for pool ${poolAddress} has zero amounts and cannot be executed.`);
11
+ }
12
+ }
13
+ export async function buildRebalanceParamsInternal(publicClient, chainId, poolService, poolAddress) {
14
+ validateAddress(poolAddress, 'poolAddress');
15
+ const preview = await poolService.getPoolRebalancePreview(poolAddress);
16
+ if (!preview) {
17
+ throw getRebalanceUnavailableError(poolAddress);
18
+ }
19
+ assertRebalanceActionAmounts(poolAddress, preview.amountRequired.amount, preview.amountTransferred.amount);
20
+ const data = encodeFunctionData({
21
+ abi: LIQUIDITY_STRATEGY_ABI,
22
+ functionName: 'rebalance',
23
+ args: [poolAddress],
24
+ });
25
+ return {
26
+ params: {
27
+ to: preview.strategyAddress,
28
+ data,
29
+ value: '0',
30
+ },
31
+ poolAddress,
32
+ strategyAddress: preview.strategyAddress,
33
+ inputToken: preview.inputToken,
34
+ outputToken: preview.outputToken,
35
+ amountRequired: preview.amountRequired.amount,
36
+ expectedAmountTransferred: preview.amountTransferred.amount,
37
+ expectedProtocolIncentive: preview.protocolIncentive.amount,
38
+ expectedLiquiditySourceIncentive: preview.liquiditySourceIncentive.amount,
39
+ approvalToken: preview.approvalToken,
40
+ approvalSpender: preview.approvalSpender,
41
+ approvalAmount: preview.approvalAmount,
42
+ direction: preview.direction,
43
+ };
44
+ }
45
+ export async function buildRebalanceTransactionInternal(publicClient, chainId, poolService, poolAddress, owner) {
46
+ validateAddress(owner, 'owner');
47
+ const rebalance = await buildRebalanceParamsInternal(publicClient, chainId, poolService, poolAddress);
48
+ const approvalToken = rebalance.approvalToken;
49
+ const approvalSpender = rebalance.approvalSpender;
50
+ const currentAllowance = await getAllowance(publicClient, approvalToken, owner, chainId, approvalSpender);
51
+ const approval = currentAllowance < rebalance.approvalAmount
52
+ ? {
53
+ token: rebalance.approvalToken,
54
+ amount: rebalance.approvalAmount,
55
+ params: buildApprovalParams(chainId, approvalToken, rebalance.approvalAmount, approvalSpender),
56
+ }
57
+ : null;
58
+ return { approval, rebalance };
59
+ }
@@ -1,6 +1,7 @@
1
1
  import { PoolType } from '../../core/types';
2
2
  import { fetchFPMMPools, fetchVirtualPools } from './poolDiscovery';
3
3
  import { fetchFPMMPoolDetailsBatch, fetchVirtualPoolDetailsBatch, } from './poolDetails';
4
+ import { fetchPoolRebalancePreview, fetchPoolRebalancePreviewBatch, } from './rebalancePreview';
4
5
  /**
5
6
  * Service for discovering liquidity pools in the Mento protocol.
6
7
  * Aggregates pools from multiple factory contracts (FPMM and VirtualPool).
@@ -104,6 +105,14 @@ export class PoolService {
104
105
  const [details] = await this.getPoolDetailsBatch([poolAddr]);
105
106
  return details;
106
107
  }
108
+ async getPoolRebalancePreview(poolAddr) {
109
+ const details = await this.getPoolDetails(poolAddr);
110
+ return fetchPoolRebalancePreview(this.publicClient, details);
111
+ }
112
+ async getPoolRebalancePreviewBatch(poolAddresses) {
113
+ const details = await this.getPoolDetailsBatch(poolAddresses);
114
+ return fetchPoolRebalancePreviewBatch(this.publicClient, details);
115
+ }
107
116
  async getPoolDetailsBatch(poolAddresses) {
108
117
  const pools = await this.getPools();
109
118
  const targets = poolAddresses
@@ -1,4 +1,4 @@
1
- import { addresses } from '../../core/constants';
1
+ import { tryGetContractAddress } from '../../core/constants';
2
2
  import { FPMM_ABI, VIRTUAL_POOL_ABI } from '../../core/abis';
3
3
  import { getAddress } from 'viem';
4
4
  import { multicall } from '../../utils/multicall';
@@ -15,17 +15,18 @@ export async function fetchFPMMPoolDetailsBatch(publicClient, chainId, pools) {
15
15
  if (pools.length === 0) {
16
16
  return [];
17
17
  }
18
- const knownStrategies = getKnownLiquidityStrategies(chainId);
19
- const contracts = pools.flatMap((pool) => buildFPMMContracts(pool, knownStrategies));
18
+ const openLiquidityStrategy = getOpenLiquidityStrategy(chainId);
19
+ const contracts = pools.flatMap((pool) => buildFPMMContracts(pool, openLiquidityStrategy));
20
20
  const results = await multicall(publicClient, contracts);
21
- const perPoolResultCount = FPMM_FIXED_RESULT_COUNT + knownStrategies.length + 1;
21
+ const strategyCheckCount = openLiquidityStrategy ? 1 : 0;
22
+ const perPoolResultCount = FPMM_FIXED_RESULT_COUNT + strategyCheckCount + 1;
22
23
  return pools.map((pool, index) => {
23
24
  const offset = index * perPoolResultCount;
24
25
  const poolResults = results.slice(offset, offset + perPoolResultCount);
25
- return parseFPMMPoolDetails(pool, knownStrategies, poolResults);
26
+ return parseFPMMPoolDetails(pool, openLiquidityStrategy, poolResults);
26
27
  });
27
28
  }
28
- function buildFPMMContracts(pool, knownStrategies) {
29
+ function buildFPMMContracts(pool, openLiquidityStrategy) {
29
30
  const address = pool.poolAddr;
30
31
  return [
31
32
  { address, abi: FPMM_ABI, functionName: 'getReserves' },
@@ -36,16 +37,18 @@ function buildFPMMContracts(pool, knownStrategies) {
36
37
  { address, abi: FPMM_ABI, functionName: 'rebalanceIncentive' },
37
38
  { address, abi: FPMM_ABI, functionName: 'rebalanceThresholdAbove' },
38
39
  { address, abi: FPMM_ABI, functionName: 'rebalanceThresholdBelow' },
39
- ...knownStrategies.map((strategyAddr) => ({
40
- address,
41
- abi: FPMM_ABI,
42
- functionName: 'liquidityStrategy',
43
- args: [strategyAddr],
44
- })),
40
+ ...(openLiquidityStrategy
41
+ ? [{
42
+ address,
43
+ abi: FPMM_ABI,
44
+ functionName: 'liquidityStrategy',
45
+ args: [openLiquidityStrategy],
46
+ }]
47
+ : []),
45
48
  { address, abi: FPMM_ABI, functionName: 'getRebalancingState' },
46
49
  ];
47
50
  }
48
- function parseFPMMPoolDetails(pool, knownStrategies, results) {
51
+ function parseFPMMPoolDetails(pool, openLiquidityStrategy, results) {
49
52
  try {
50
53
  const reservesRes = results[0];
51
54
  const decimals0Res = results[1];
@@ -79,10 +82,14 @@ function parseFPMMPoolDetails(pool, knownStrategies, results) {
79
82
  const rebalanceIncentiveBps = rebalanceIncentiveRes.result;
80
83
  const thresholdAboveBps = thresholdAboveRes.result;
81
84
  const thresholdBelowBps = thresholdBelowRes.result;
82
- const strategyResults = results.slice(FPMM_FIXED_RESULT_COUNT, FPMM_FIXED_RESULT_COUNT + knownStrategies.length);
83
- const activeIndex = strategyResults.findIndex((result) => result.status === 'success' && result.result === true);
84
- const liquidityStrategy = activeIndex >= 0 ? knownStrategies[activeIndex] : null;
85
- const rebalancingRes = results[FPMM_FIXED_RESULT_COUNT + knownStrategies.length];
85
+ const strategyCheckCount = openLiquidityStrategy ? 1 : 0;
86
+ const openStrategyResult = strategyCheckCount > 0 ? results[FPMM_FIXED_RESULT_COUNT] : null;
87
+ const liquidityStrategy = openLiquidityStrategy &&
88
+ openStrategyResult?.status === 'success' &&
89
+ openStrategyResult.result === true
90
+ ? openLiquidityStrategy
91
+ : null;
92
+ const rebalancingRes = results[FPMM_FIXED_RESULT_COUNT + strategyCheckCount];
86
93
  let pricing = null;
87
94
  let inBand = null;
88
95
  if (rebalancingRes?.status === 'success') {
@@ -187,23 +194,16 @@ function parseVirtualPoolDetails(pool, results) {
187
194
  }
188
195
  }
189
196
  /**
190
- * Returns the known liquidity strategy addresses for the given chain.
197
+ * Returns the configured Open Liquidity Strategy for the given chain.
191
198
  */
192
- function getKnownLiquidityStrategies(chainId) {
193
- const chainAddresses = addresses[chainId];
194
- if (!chainAddresses)
195
- return [];
196
- const strategyCandidates = [
197
- chainAddresses.ReserveLiquidityStrategy,
198
- chainAddresses.CDPLiquidityStrategy,
199
- ].filter((address) => Boolean(address));
200
- // Normalize to checksummed addresses and ignore malformed config values.
201
- return strategyCandidates.flatMap((address) => {
202
- try {
203
- return [getAddress(address)];
204
- }
205
- catch {
206
- return [];
207
- }
208
- });
199
+ function getOpenLiquidityStrategy(chainId) {
200
+ const strategyAddress = tryGetContractAddress(chainId, 'OpenLiquidityStrategy');
201
+ if (!strategyAddress)
202
+ return null;
203
+ try {
204
+ return getAddress(strategyAddress);
205
+ }
206
+ catch {
207
+ return null;
208
+ }
209
209
  }
@@ -0,0 +1,181 @@
1
+ import { getAddress } from 'viem';
2
+ import { LIQUIDITY_STRATEGY_ABI } from '../../core/abis';
3
+ import { multicall } from '../../utils/multicall';
4
+ // Liquidity strategy incentive rates are stored as 18-decimal percentages.
5
+ const FEE_DENOMINATOR = 10n ** 18n;
6
+ function toBigIntValue(value) {
7
+ return typeof value === 'bigint' ? value : BigInt(value);
8
+ }
9
+ function toNumberValue(value) {
10
+ return typeof value === 'number' ? value : Number(value);
11
+ }
12
+ function parseDirection(value) {
13
+ const normalized = toNumberValue(value);
14
+ if (normalized === 0)
15
+ return 'Expand';
16
+ if (normalized === 1)
17
+ return 'Contract';
18
+ throw new Error(`Unsupported liquidity strategy direction: ${normalized}`);
19
+ }
20
+ function parsePoolConfig(raw) {
21
+ return {
22
+ isToken0Debt: raw[0],
23
+ lastRebalance: toNumberValue(raw[1]),
24
+ rebalanceCooldown: toNumberValue(raw[2]),
25
+ protocolFeeRecipient: raw[3],
26
+ liquiditySourceIncentiveExpansion: toBigIntValue(raw[4]),
27
+ protocolIncentiveExpansion: toBigIntValue(raw[5]),
28
+ liquiditySourceIncentiveContraction: toBigIntValue(raw[6]),
29
+ protocolIncentiveContraction: toBigIntValue(raw[7]),
30
+ };
31
+ }
32
+ function parseContext(raw) {
33
+ return {
34
+ pool: raw[0],
35
+ reserves: {
36
+ reserveNum: toBigIntValue(raw[1][0]),
37
+ reserveDen: toBigIntValue(raw[1][1]),
38
+ },
39
+ prices: {
40
+ oracleNum: toBigIntValue(raw[2][0]),
41
+ oracleDen: toBigIntValue(raw[2][1]),
42
+ poolPriceAbove: raw[2][2],
43
+ rebalanceThreshold: toNumberValue(raw[2][3]),
44
+ },
45
+ token0: raw[3],
46
+ token1: raw[4],
47
+ token0Dec: toBigIntValue(raw[5]),
48
+ token1Dec: toBigIntValue(raw[6]),
49
+ isToken0Debt: raw[7],
50
+ incentives: {
51
+ liquiditySourceIncentiveExpansion: toBigIntValue(raw[8][0]),
52
+ protocolIncentiveExpansion: toBigIntValue(raw[8][1]),
53
+ liquiditySourceIncentiveContraction: toBigIntValue(raw[8][2]),
54
+ protocolIncentiveContraction: toBigIntValue(raw[8][3]),
55
+ },
56
+ };
57
+ }
58
+ function parseAction(raw) {
59
+ return {
60
+ dir: parseDirection(raw[0]),
61
+ amount0Out: toBigIntValue(raw[1]),
62
+ amount1Out: toBigIntValue(raw[2]),
63
+ amountOwedToPool: toBigIntValue(raw[3]),
64
+ };
65
+ }
66
+ function isPreviewEligible(detail) {
67
+ return (detail.poolType === 'FPMM' &&
68
+ detail.pricing !== null &&
69
+ detail.rebalancing.inBand === false &&
70
+ !!detail.rebalancing.liquidityStrategy);
71
+ }
72
+ function buildPreview(detail, strategyAddress, config, context, action) {
73
+ const debtToken = context.isToken0Debt ? context.token0 : context.token1;
74
+ const collateralToken = context.isToken0Debt ? context.token1 : context.token0;
75
+ const inputToken = action.dir === 'Expand' ? debtToken : collateralToken;
76
+ const outputToken = action.dir === 'Expand' ? collateralToken : debtToken;
77
+ const amountTransferredValue = action.amount0Out > 0n ? action.amount0Out : action.amount1Out;
78
+ const protocolRate = action.dir === 'Expand'
79
+ ? config.protocolIncentiveExpansion
80
+ : config.protocolIncentiveContraction;
81
+ const liquiditySourceRate = action.dir === 'Expand'
82
+ ? config.liquiditySourceIncentiveExpansion
83
+ : config.liquiditySourceIncentiveContraction;
84
+ const protocolIncentiveAmount = (amountTransferredValue * protocolRate) / FEE_DENOMINATOR;
85
+ const liquiditySourceBase = amountTransferredValue > protocolIncentiveAmount
86
+ ? amountTransferredValue - protocolIncentiveAmount
87
+ : 0n;
88
+ const liquiditySourceIncentiveAmount = (liquiditySourceBase * liquiditySourceRate) / FEE_DENOMINATOR;
89
+ return {
90
+ poolAddress: detail.poolAddr,
91
+ strategyAddress,
92
+ direction: action.dir,
93
+ config,
94
+ context,
95
+ action,
96
+ inputToken,
97
+ outputToken,
98
+ amountRequired: {
99
+ token: inputToken,
100
+ amount: action.amountOwedToPool,
101
+ },
102
+ amountTransferred: {
103
+ token: outputToken,
104
+ amount: amountTransferredValue,
105
+ },
106
+ protocolIncentive: {
107
+ token: outputToken,
108
+ amount: protocolIncentiveAmount,
109
+ },
110
+ liquiditySourceIncentive: {
111
+ token: outputToken,
112
+ amount: liquiditySourceIncentiveAmount,
113
+ },
114
+ approvalToken: inputToken,
115
+ approvalSpender: strategyAddress,
116
+ approvalAmount: action.amountOwedToPool,
117
+ };
118
+ }
119
+ export async function fetchPoolRebalancePreview(publicClient, detail) {
120
+ const [preview] = await fetchPoolRebalancePreviewBatch(publicClient, [detail]);
121
+ return preview;
122
+ }
123
+ export async function fetchPoolRebalancePreviewBatch(publicClient, details) {
124
+ const previews = details.map(() => null);
125
+ const eligibleTargets = details.flatMap((detail, index) => {
126
+ if (!isPreviewEligible(detail))
127
+ return [];
128
+ const strategyAddress = detail.rebalancing.liquidityStrategy;
129
+ if (!strategyAddress)
130
+ return [];
131
+ try {
132
+ return [
133
+ {
134
+ index,
135
+ detail,
136
+ strategyAddress: getAddress(strategyAddress),
137
+ },
138
+ ];
139
+ }
140
+ catch {
141
+ return [];
142
+ }
143
+ });
144
+ if (eligibleTargets.length === 0) {
145
+ return previews;
146
+ }
147
+ const contracts = eligibleTargets.flatMap(({ detail, strategyAddress }) => [
148
+ {
149
+ address: strategyAddress,
150
+ abi: LIQUIDITY_STRATEGY_ABI,
151
+ functionName: 'poolConfigs',
152
+ args: [detail.poolAddr],
153
+ },
154
+ {
155
+ address: strategyAddress,
156
+ abi: LIQUIDITY_STRATEGY_ABI,
157
+ functionName: 'determineAction',
158
+ args: [detail.poolAddr],
159
+ },
160
+ ]);
161
+ const results = await multicall(publicClient, contracts);
162
+ eligibleTargets.forEach((target, targetIndex) => {
163
+ const configResult = results[targetIndex * 2];
164
+ const determineActionResult = results[targetIndex * 2 + 1];
165
+ if (!configResult || !determineActionResult)
166
+ return;
167
+ if (configResult.status === 'failure' || determineActionResult.status === 'failure')
168
+ return;
169
+ try {
170
+ const config = parsePoolConfig(configResult.result);
171
+ const [rawContext, rawAction] = determineActionResult.result;
172
+ const context = parseContext(rawContext);
173
+ const action = parseAction(rawAction);
174
+ previews[target.index] = buildPreview(target.detail, target.strategyAddress, config, context, action);
175
+ }
176
+ catch {
177
+ previews[target.index] = null;
178
+ }
179
+ });
180
+ return previews;
181
+ }
@@ -1,7 +1,7 @@
1
1
  import { PublicClient } from 'viem';
2
2
  import { PoolService } from '../pools';
3
3
  import { RouteService } from '../routes';
4
- import { AddLiquidityInput, RemoveLiquidityInput, ZapInInput, ZapOutInput, PrepareZapInInput, PrepareZapOutInput, AddLiquidityQuote, RemoveLiquidityQuote, AddLiquidityDetails, RemoveLiquidityDetails, AddLiquidityTransaction, RemoveLiquidityTransaction, LPTokenBalance, LiquidityOptions, PreparedZapIn, PreparedZapOut, ZapInQuote, ZapOutQuote, ZapInDetails, ZapOutDetails, ZapInTransaction, ZapOutTransaction } from '../../core/types';
4
+ import { AddLiquidityInput, RemoveLiquidityInput, RebalanceDetails, RebalanceInput, RebalanceTransaction, ZapInInput, ZapOutInput, PrepareZapInInput, PrepareZapOutInput, AddLiquidityQuote, RemoveLiquidityQuote, AddLiquidityDetails, RemoveLiquidityDetails, AddLiquidityTransaction, RemoveLiquidityTransaction, LPTokenBalance, LiquidityOptions, PreparedZapIn, PreparedZapOut, ZapInQuote, ZapOutQuote, ZapInDetails, ZapOutDetails, ZapInTransaction, ZapOutTransaction } from '../../core/types';
5
5
  export declare class LiquidityService {
6
6
  private publicClient;
7
7
  private chainId;
@@ -120,5 +120,20 @@ export declare class LiquidityService {
120
120
  * @returns Expected output amount and minimum amounts after slippage
121
121
  */
122
122
  quoteZapOut(poolAddress: string, tokenOut: string, liquidity: bigint, options: LiquidityOptions): Promise<ZapOutQuote>;
123
+ /**
124
+ * Builds rebalance transaction parameters without checking approval.
125
+ * Use buildRebalanceTransaction if you need approval handling.
126
+ * @param input - Rebalance parameters
127
+ * @returns Transaction details with encoded call data
128
+ */
129
+ buildRebalanceParams(input: RebalanceInput): Promise<RebalanceDetails>;
130
+ /**
131
+ * Builds a rebalance transaction with ERC20 approval if needed.
132
+ * @param input - Rebalance parameters including owner for allowance checks
133
+ * @returns Transaction with approval (if needed) and rebalance call
134
+ */
135
+ buildRebalanceTransaction(input: RebalanceInput & {
136
+ owner: string;
137
+ }): Promise<RebalanceTransaction>;
123
138
  }
124
139
  //# sourceMappingURL=LiquidityService.d.ts.map
@@ -4,6 +4,7 @@ exports.LiquidityService = void 0;
4
4
  const basicLiquidity_1 = require("./basicLiquidity");
5
5
  const zapIn_1 = require("./zapIn");
6
6
  const zapOut_1 = require("./zapOut");
7
+ const rebalance_1 = require("./rebalance");
7
8
  class LiquidityService {
8
9
  constructor(publicClient, chainId, poolService, routeService) {
9
10
  this.publicClient = publicClient;
@@ -145,6 +146,23 @@ class LiquidityService {
145
146
  async quoteZapOut(poolAddress, tokenOut, liquidity, options) {
146
147
  return (0, zapOut_1.quoteZapOutInternal)(this.publicClient, this.chainId, this.poolService, this.routeService, poolAddress, tokenOut, liquidity, options);
147
148
  }
149
+ /**
150
+ * Builds rebalance transaction parameters without checking approval.
151
+ * Use buildRebalanceTransaction if you need approval handling.
152
+ * @param input - Rebalance parameters
153
+ * @returns Transaction details with encoded call data
154
+ */
155
+ async buildRebalanceParams(input) {
156
+ return (0, rebalance_1.buildRebalanceParamsInternal)(this.publicClient, this.chainId, this.poolService, input.poolAddress);
157
+ }
158
+ /**
159
+ * Builds a rebalance transaction with ERC20 approval if needed.
160
+ * @param input - Rebalance parameters including owner for allowance checks
161
+ * @returns Transaction with approval (if needed) and rebalance call
162
+ */
163
+ async buildRebalanceTransaction(input) {
164
+ return (0, rebalance_1.buildRebalanceTransactionInternal)(this.publicClient, this.chainId, this.poolService, input.poolAddress, input.owner);
165
+ }
148
166
  }
149
167
  exports.LiquidityService = LiquidityService;
150
168
  //# sourceMappingURL=LiquidityService.js.map
@@ -1,8 +1,8 @@
1
1
  import { Address, PublicClient } from 'viem';
2
2
  import { PoolService } from '../pools';
3
3
  import { CallParams } from '../../core/types';
4
- export declare function buildApprovalParams(chainId: number, token: Address, amount: bigint): CallParams;
5
- export declare function getAllowance(publicClient: PublicClient, token: Address, owner: Address, chainId: number): Promise<bigint>;
4
+ export declare function buildApprovalParams(chainId: number, token: Address, amount: bigint, spender?: Address): CallParams;
5
+ export declare function getAllowance(publicClient: PublicClient, token: Address, owner: Address, chainId: number, spender?: Address): Promise<bigint>;
6
6
  export declare function calculateMinAmount(amount: bigint, slippageTolerance: number): bigint;
7
7
  export declare function getPoolInfo(poolService: PoolService, poolAddress: string): Promise<{
8
8
  token0: Address;
@@ -12,22 +12,25 @@ const abis_1 = require("../../core/abis");
12
12
  const constants_1 = require("../../core/constants");
13
13
  const validation_1 = require("../../utils/validation");
14
14
  const multicall_1 = require("../../utils/multicall");
15
- function buildApprovalParams(chainId, token, amount) {
16
- const routerAddress = (0, constants_1.getContractAddress)(chainId, 'Router');
15
+ function getApprovalSpender(chainId, spender) {
16
+ return spender ?? (0, constants_1.getContractAddress)(chainId, 'Router');
17
+ }
18
+ function buildApprovalParams(chainId, token, amount, spender) {
19
+ const approvalSpender = getApprovalSpender(chainId, spender);
17
20
  const data = (0, viem_1.encodeFunctionData)({
18
21
  abi: abis_1.ERC20_ABI,
19
22
  functionName: 'approve',
20
- args: [routerAddress, amount],
23
+ args: [approvalSpender, amount],
21
24
  });
22
25
  return { to: token, data, value: '0' };
23
26
  }
24
- async function getAllowance(publicClient, token, owner, chainId) {
25
- const routerAddress = (0, constants_1.getContractAddress)(chainId, 'Router');
27
+ async function getAllowance(publicClient, token, owner, chainId, spender) {
28
+ const approvalSpender = getApprovalSpender(chainId, spender);
26
29
  return (await publicClient.readContract({
27
30
  address: token,
28
31
  abi: abis_1.ERC20_ABI,
29
32
  functionName: 'allowance',
30
- args: [owner, routerAddress],
33
+ args: [owner, approvalSpender],
31
34
  }));
32
35
  }
33
36
  function calculateMinAmount(amount, slippageTolerance) {
@@ -0,0 +1,6 @@
1
+ import { PublicClient } from 'viem';
2
+ import { RebalanceDetails, RebalanceTransaction } from '../../core/types';
3
+ import { PoolService } from '../pools';
4
+ export declare function buildRebalanceParamsInternal(publicClient: PublicClient, chainId: number, poolService: PoolService, poolAddress: string): Promise<RebalanceDetails>;
5
+ export declare function buildRebalanceTransactionInternal(publicClient: PublicClient, chainId: number, poolService: PoolService, poolAddress: string, owner: string): Promise<RebalanceTransaction>;
6
+ //# sourceMappingURL=rebalance.d.ts.map
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRebalanceParamsInternal = buildRebalanceParamsInternal;
4
+ exports.buildRebalanceTransactionInternal = buildRebalanceTransactionInternal;
5
+ const viem_1 = require("viem");
6
+ const abis_1 = require("../../core/abis");
7
+ const validation_1 = require("../../utils/validation");
8
+ const liquidityHelpers_1 = require("./liquidityHelpers");
9
+ function getRebalanceUnavailableError(poolAddress) {
10
+ return new Error(`Pool ${poolAddress} is not currently rebalanceable or does not have a supported liquidity strategy.`);
11
+ }
12
+ function assertRebalanceActionAmounts(poolAddress, amountRequired, amountTransferred) {
13
+ if (amountRequired <= 0n || amountTransferred <= 0n) {
14
+ throw new Error(`Rebalance action for pool ${poolAddress} has zero amounts and cannot be executed.`);
15
+ }
16
+ }
17
+ async function buildRebalanceParamsInternal(publicClient, chainId, poolService, poolAddress) {
18
+ (0, validation_1.validateAddress)(poolAddress, 'poolAddress');
19
+ const preview = await poolService.getPoolRebalancePreview(poolAddress);
20
+ if (!preview) {
21
+ throw getRebalanceUnavailableError(poolAddress);
22
+ }
23
+ assertRebalanceActionAmounts(poolAddress, preview.amountRequired.amount, preview.amountTransferred.amount);
24
+ const data = (0, viem_1.encodeFunctionData)({
25
+ abi: abis_1.LIQUIDITY_STRATEGY_ABI,
26
+ functionName: 'rebalance',
27
+ args: [poolAddress],
28
+ });
29
+ return {
30
+ params: {
31
+ to: preview.strategyAddress,
32
+ data,
33
+ value: '0',
34
+ },
35
+ poolAddress,
36
+ strategyAddress: preview.strategyAddress,
37
+ inputToken: preview.inputToken,
38
+ outputToken: preview.outputToken,
39
+ amountRequired: preview.amountRequired.amount,
40
+ expectedAmountTransferred: preview.amountTransferred.amount,
41
+ expectedProtocolIncentive: preview.protocolIncentive.amount,
42
+ expectedLiquiditySourceIncentive: preview.liquiditySourceIncentive.amount,
43
+ approvalToken: preview.approvalToken,
44
+ approvalSpender: preview.approvalSpender,
45
+ approvalAmount: preview.approvalAmount,
46
+ direction: preview.direction,
47
+ };
48
+ }
49
+ async function buildRebalanceTransactionInternal(publicClient, chainId, poolService, poolAddress, owner) {
50
+ (0, validation_1.validateAddress)(owner, 'owner');
51
+ const rebalance = await buildRebalanceParamsInternal(publicClient, chainId, poolService, poolAddress);
52
+ const approvalToken = rebalance.approvalToken;
53
+ const approvalSpender = rebalance.approvalSpender;
54
+ const currentAllowance = await (0, liquidityHelpers_1.getAllowance)(publicClient, approvalToken, owner, chainId, approvalSpender);
55
+ const approval = currentAllowance < rebalance.approvalAmount
56
+ ? {
57
+ token: rebalance.approvalToken,
58
+ amount: rebalance.approvalAmount,
59
+ params: (0, liquidityHelpers_1.buildApprovalParams)(chainId, approvalToken, rebalance.approvalAmount, approvalSpender),
60
+ }
61
+ : null;
62
+ return { approval, rebalance };
63
+ }
64
+ //# sourceMappingURL=rebalance.js.map
@@ -1,4 +1,4 @@
1
- import { Pool, PoolDetails } from '../../core/types';
1
+ import { Pool, PoolDetails, PoolRebalancePreview } from '../../core/types';
2
2
  import { PublicClient } from 'viem';
3
3
  /**
4
4
  * Result of pool discovery including any warnings from failed factories
@@ -62,6 +62,8 @@ export declare class PoolService {
62
62
  * ```
63
63
  */
64
64
  getPoolDetails(poolAddr: string): Promise<PoolDetails>;
65
+ getPoolRebalancePreview(poolAddr: string): Promise<PoolRebalancePreview | null>;
66
+ getPoolRebalancePreviewBatch(poolAddresses?: string[]): Promise<Array<PoolRebalancePreview | null>>;
65
67
  getPoolDetailsBatch(poolAddresses?: string[]): Promise<PoolDetails[]>;
66
68
  }
67
69
  //# sourceMappingURL=PoolService.d.ts.map
@@ -4,6 +4,7 @@ exports.PoolService = void 0;
4
4
  const types_1 = require("../../core/types");
5
5
  const poolDiscovery_1 = require("./poolDiscovery");
6
6
  const poolDetails_1 = require("./poolDetails");
7
+ const rebalancePreview_1 = require("./rebalancePreview");
7
8
  /**
8
9
  * Service for discovering liquidity pools in the Mento protocol.
9
10
  * Aggregates pools from multiple factory contracts (FPMM and VirtualPool).
@@ -107,6 +108,14 @@ class PoolService {
107
108
  const [details] = await this.getPoolDetailsBatch([poolAddr]);
108
109
  return details;
109
110
  }
111
+ async getPoolRebalancePreview(poolAddr) {
112
+ const details = await this.getPoolDetails(poolAddr);
113
+ return (0, rebalancePreview_1.fetchPoolRebalancePreview)(this.publicClient, details);
114
+ }
115
+ async getPoolRebalancePreviewBatch(poolAddresses) {
116
+ const details = await this.getPoolDetailsBatch(poolAddresses);
117
+ return (0, rebalancePreview_1.fetchPoolRebalancePreviewBatch)(this.publicClient, details);
118
+ }
110
119
  async getPoolDetailsBatch(poolAddresses) {
111
120
  const pools = await this.getPools();
112
121
  const targets = poolAddresses
@@ -21,17 +21,18 @@ async function fetchFPMMPoolDetailsBatch(publicClient, chainId, pools) {
21
21
  if (pools.length === 0) {
22
22
  return [];
23
23
  }
24
- const knownStrategies = getKnownLiquidityStrategies(chainId);
25
- const contracts = pools.flatMap((pool) => buildFPMMContracts(pool, knownStrategies));
24
+ const openLiquidityStrategy = getOpenLiquidityStrategy(chainId);
25
+ const contracts = pools.flatMap((pool) => buildFPMMContracts(pool, openLiquidityStrategy));
26
26
  const results = await (0, multicall_1.multicall)(publicClient, contracts);
27
- const perPoolResultCount = FPMM_FIXED_RESULT_COUNT + knownStrategies.length + 1;
27
+ const strategyCheckCount = openLiquidityStrategy ? 1 : 0;
28
+ const perPoolResultCount = FPMM_FIXED_RESULT_COUNT + strategyCheckCount + 1;
28
29
  return pools.map((pool, index) => {
29
30
  const offset = index * perPoolResultCount;
30
31
  const poolResults = results.slice(offset, offset + perPoolResultCount);
31
- return parseFPMMPoolDetails(pool, knownStrategies, poolResults);
32
+ return parseFPMMPoolDetails(pool, openLiquidityStrategy, poolResults);
32
33
  });
33
34
  }
34
- function buildFPMMContracts(pool, knownStrategies) {
35
+ function buildFPMMContracts(pool, openLiquidityStrategy) {
35
36
  const address = pool.poolAddr;
36
37
  return [
37
38
  { address, abi: abis_1.FPMM_ABI, functionName: 'getReserves' },
@@ -42,16 +43,18 @@ function buildFPMMContracts(pool, knownStrategies) {
42
43
  { address, abi: abis_1.FPMM_ABI, functionName: 'rebalanceIncentive' },
43
44
  { address, abi: abis_1.FPMM_ABI, functionName: 'rebalanceThresholdAbove' },
44
45
  { address, abi: abis_1.FPMM_ABI, functionName: 'rebalanceThresholdBelow' },
45
- ...knownStrategies.map((strategyAddr) => ({
46
- address,
47
- abi: abis_1.FPMM_ABI,
48
- functionName: 'liquidityStrategy',
49
- args: [strategyAddr],
50
- })),
46
+ ...(openLiquidityStrategy
47
+ ? [{
48
+ address,
49
+ abi: abis_1.FPMM_ABI,
50
+ functionName: 'liquidityStrategy',
51
+ args: [openLiquidityStrategy],
52
+ }]
53
+ : []),
51
54
  { address, abi: abis_1.FPMM_ABI, functionName: 'getRebalancingState' },
52
55
  ];
53
56
  }
54
- function parseFPMMPoolDetails(pool, knownStrategies, results) {
57
+ function parseFPMMPoolDetails(pool, openLiquidityStrategy, results) {
55
58
  try {
56
59
  const reservesRes = results[0];
57
60
  const decimals0Res = results[1];
@@ -85,10 +88,14 @@ function parseFPMMPoolDetails(pool, knownStrategies, results) {
85
88
  const rebalanceIncentiveBps = rebalanceIncentiveRes.result;
86
89
  const thresholdAboveBps = thresholdAboveRes.result;
87
90
  const thresholdBelowBps = thresholdBelowRes.result;
88
- const strategyResults = results.slice(FPMM_FIXED_RESULT_COUNT, FPMM_FIXED_RESULT_COUNT + knownStrategies.length);
89
- const activeIndex = strategyResults.findIndex((result) => result.status === 'success' && result.result === true);
90
- const liquidityStrategy = activeIndex >= 0 ? knownStrategies[activeIndex] : null;
91
- const rebalancingRes = results[FPMM_FIXED_RESULT_COUNT + knownStrategies.length];
91
+ const strategyCheckCount = openLiquidityStrategy ? 1 : 0;
92
+ const openStrategyResult = strategyCheckCount > 0 ? results[FPMM_FIXED_RESULT_COUNT] : null;
93
+ const liquidityStrategy = openLiquidityStrategy &&
94
+ openStrategyResult?.status === 'success' &&
95
+ openStrategyResult.result === true
96
+ ? openLiquidityStrategy
97
+ : null;
98
+ const rebalancingRes = results[FPMM_FIXED_RESULT_COUNT + strategyCheckCount];
92
99
  let pricing = null;
93
100
  let inBand = null;
94
101
  if (rebalancingRes?.status === 'success') {
@@ -193,24 +200,17 @@ function parseVirtualPoolDetails(pool, results) {
193
200
  }
194
201
  }
195
202
  /**
196
- * Returns the known liquidity strategy addresses for the given chain.
203
+ * Returns the configured Open Liquidity Strategy for the given chain.
197
204
  */
198
- function getKnownLiquidityStrategies(chainId) {
199
- const chainAddresses = constants_1.addresses[chainId];
200
- if (!chainAddresses)
201
- return [];
202
- const strategyCandidates = [
203
- chainAddresses.ReserveLiquidityStrategy,
204
- chainAddresses.CDPLiquidityStrategy,
205
- ].filter((address) => Boolean(address));
206
- // Normalize to checksummed addresses and ignore malformed config values.
207
- return strategyCandidates.flatMap((address) => {
208
- try {
209
- return [(0, viem_1.getAddress)(address)];
210
- }
211
- catch {
212
- return [];
213
- }
214
- });
205
+ function getOpenLiquidityStrategy(chainId) {
206
+ const strategyAddress = (0, constants_1.tryGetContractAddress)(chainId, 'OpenLiquidityStrategy');
207
+ if (!strategyAddress)
208
+ return null;
209
+ try {
210
+ return (0, viem_1.getAddress)(strategyAddress);
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
215
  }
216
216
  //# sourceMappingURL=poolDetails.js.map
@@ -0,0 +1,5 @@
1
+ import { type PublicClient } from 'viem';
2
+ import type { PoolDetails, PoolRebalancePreview } from '../../core/types';
3
+ export declare function fetchPoolRebalancePreview(publicClient: PublicClient, detail: PoolDetails): Promise<PoolRebalancePreview | null>;
4
+ export declare function fetchPoolRebalancePreviewBatch(publicClient: PublicClient, details: PoolDetails[]): Promise<Array<PoolRebalancePreview | null>>;
5
+ //# sourceMappingURL=rebalancePreview.d.ts.map
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchPoolRebalancePreview = fetchPoolRebalancePreview;
4
+ exports.fetchPoolRebalancePreviewBatch = fetchPoolRebalancePreviewBatch;
5
+ const viem_1 = require("viem");
6
+ const abis_1 = require("../../core/abis");
7
+ const multicall_1 = require("../../utils/multicall");
8
+ // Liquidity strategy incentive rates are stored as 18-decimal percentages.
9
+ const FEE_DENOMINATOR = 10n ** 18n;
10
+ function toBigIntValue(value) {
11
+ return typeof value === 'bigint' ? value : BigInt(value);
12
+ }
13
+ function toNumberValue(value) {
14
+ return typeof value === 'number' ? value : Number(value);
15
+ }
16
+ function parseDirection(value) {
17
+ const normalized = toNumberValue(value);
18
+ if (normalized === 0)
19
+ return 'Expand';
20
+ if (normalized === 1)
21
+ return 'Contract';
22
+ throw new Error(`Unsupported liquidity strategy direction: ${normalized}`);
23
+ }
24
+ function parsePoolConfig(raw) {
25
+ return {
26
+ isToken0Debt: raw[0],
27
+ lastRebalance: toNumberValue(raw[1]),
28
+ rebalanceCooldown: toNumberValue(raw[2]),
29
+ protocolFeeRecipient: raw[3],
30
+ liquiditySourceIncentiveExpansion: toBigIntValue(raw[4]),
31
+ protocolIncentiveExpansion: toBigIntValue(raw[5]),
32
+ liquiditySourceIncentiveContraction: toBigIntValue(raw[6]),
33
+ protocolIncentiveContraction: toBigIntValue(raw[7]),
34
+ };
35
+ }
36
+ function parseContext(raw) {
37
+ return {
38
+ pool: raw[0],
39
+ reserves: {
40
+ reserveNum: toBigIntValue(raw[1][0]),
41
+ reserveDen: toBigIntValue(raw[1][1]),
42
+ },
43
+ prices: {
44
+ oracleNum: toBigIntValue(raw[2][0]),
45
+ oracleDen: toBigIntValue(raw[2][1]),
46
+ poolPriceAbove: raw[2][2],
47
+ rebalanceThreshold: toNumberValue(raw[2][3]),
48
+ },
49
+ token0: raw[3],
50
+ token1: raw[4],
51
+ token0Dec: toBigIntValue(raw[5]),
52
+ token1Dec: toBigIntValue(raw[6]),
53
+ isToken0Debt: raw[7],
54
+ incentives: {
55
+ liquiditySourceIncentiveExpansion: toBigIntValue(raw[8][0]),
56
+ protocolIncentiveExpansion: toBigIntValue(raw[8][1]),
57
+ liquiditySourceIncentiveContraction: toBigIntValue(raw[8][2]),
58
+ protocolIncentiveContraction: toBigIntValue(raw[8][3]),
59
+ },
60
+ };
61
+ }
62
+ function parseAction(raw) {
63
+ return {
64
+ dir: parseDirection(raw[0]),
65
+ amount0Out: toBigIntValue(raw[1]),
66
+ amount1Out: toBigIntValue(raw[2]),
67
+ amountOwedToPool: toBigIntValue(raw[3]),
68
+ };
69
+ }
70
+ function isPreviewEligible(detail) {
71
+ return (detail.poolType === 'FPMM' &&
72
+ detail.pricing !== null &&
73
+ detail.rebalancing.inBand === false &&
74
+ !!detail.rebalancing.liquidityStrategy);
75
+ }
76
+ function buildPreview(detail, strategyAddress, config, context, action) {
77
+ const debtToken = context.isToken0Debt ? context.token0 : context.token1;
78
+ const collateralToken = context.isToken0Debt ? context.token1 : context.token0;
79
+ const inputToken = action.dir === 'Expand' ? debtToken : collateralToken;
80
+ const outputToken = action.dir === 'Expand' ? collateralToken : debtToken;
81
+ const amountTransferredValue = action.amount0Out > 0n ? action.amount0Out : action.amount1Out;
82
+ const protocolRate = action.dir === 'Expand'
83
+ ? config.protocolIncentiveExpansion
84
+ : config.protocolIncentiveContraction;
85
+ const liquiditySourceRate = action.dir === 'Expand'
86
+ ? config.liquiditySourceIncentiveExpansion
87
+ : config.liquiditySourceIncentiveContraction;
88
+ const protocolIncentiveAmount = (amountTransferredValue * protocolRate) / FEE_DENOMINATOR;
89
+ const liquiditySourceBase = amountTransferredValue > protocolIncentiveAmount
90
+ ? amountTransferredValue - protocolIncentiveAmount
91
+ : 0n;
92
+ const liquiditySourceIncentiveAmount = (liquiditySourceBase * liquiditySourceRate) / FEE_DENOMINATOR;
93
+ return {
94
+ poolAddress: detail.poolAddr,
95
+ strategyAddress,
96
+ direction: action.dir,
97
+ config,
98
+ context,
99
+ action,
100
+ inputToken,
101
+ outputToken,
102
+ amountRequired: {
103
+ token: inputToken,
104
+ amount: action.amountOwedToPool,
105
+ },
106
+ amountTransferred: {
107
+ token: outputToken,
108
+ amount: amountTransferredValue,
109
+ },
110
+ protocolIncentive: {
111
+ token: outputToken,
112
+ amount: protocolIncentiveAmount,
113
+ },
114
+ liquiditySourceIncentive: {
115
+ token: outputToken,
116
+ amount: liquiditySourceIncentiveAmount,
117
+ },
118
+ approvalToken: inputToken,
119
+ approvalSpender: strategyAddress,
120
+ approvalAmount: action.amountOwedToPool,
121
+ };
122
+ }
123
+ async function fetchPoolRebalancePreview(publicClient, detail) {
124
+ const [preview] = await fetchPoolRebalancePreviewBatch(publicClient, [detail]);
125
+ return preview;
126
+ }
127
+ async function fetchPoolRebalancePreviewBatch(publicClient, details) {
128
+ const previews = details.map(() => null);
129
+ const eligibleTargets = details.flatMap((detail, index) => {
130
+ if (!isPreviewEligible(detail))
131
+ return [];
132
+ const strategyAddress = detail.rebalancing.liquidityStrategy;
133
+ if (!strategyAddress)
134
+ return [];
135
+ try {
136
+ return [
137
+ {
138
+ index,
139
+ detail,
140
+ strategyAddress: (0, viem_1.getAddress)(strategyAddress),
141
+ },
142
+ ];
143
+ }
144
+ catch {
145
+ return [];
146
+ }
147
+ });
148
+ if (eligibleTargets.length === 0) {
149
+ return previews;
150
+ }
151
+ const contracts = eligibleTargets.flatMap(({ detail, strategyAddress }) => [
152
+ {
153
+ address: strategyAddress,
154
+ abi: abis_1.LIQUIDITY_STRATEGY_ABI,
155
+ functionName: 'poolConfigs',
156
+ args: [detail.poolAddr],
157
+ },
158
+ {
159
+ address: strategyAddress,
160
+ abi: abis_1.LIQUIDITY_STRATEGY_ABI,
161
+ functionName: 'determineAction',
162
+ args: [detail.poolAddr],
163
+ },
164
+ ]);
165
+ const results = await (0, multicall_1.multicall)(publicClient, contracts);
166
+ eligibleTargets.forEach((target, targetIndex) => {
167
+ const configResult = results[targetIndex * 2];
168
+ const determineActionResult = results[targetIndex * 2 + 1];
169
+ if (!configResult || !determineActionResult)
170
+ return;
171
+ if (configResult.status === 'failure' || determineActionResult.status === 'failure')
172
+ return;
173
+ try {
174
+ const config = parsePoolConfig(configResult.result);
175
+ const [rawContext, rawAction] = determineActionResult.result;
176
+ const context = parseContext(rawContext);
177
+ const action = parseAction(rawAction);
178
+ previews[target.index] = buildPreview(target.detail, target.strategyAddress, config, context, action);
179
+ }
180
+ catch {
181
+ previews[target.index] = null;
182
+ }
183
+ });
184
+ return previews;
185
+ }
186
+ //# sourceMappingURL=rebalancePreview.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mento-protocol/mento-sdk",
3
3
  "description": "Official SDK for interacting with the Mento Protocol",
4
- "version": "3.1.0-beta.5",
4
+ "version": "3.1.0-beta.6",
5
5
  "license": "MIT",
6
6
  "author": "Mento Labs",
7
7
  "keywords": [