@mento-protocol/mento-sdk 3.0.0-rc.1 → 3.1.0-beta.0

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.
@@ -1,11 +1,12 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- // Generated on 2026-03-03T20:21:18.047Z
2
+ // Generated on 2026-03-10T12:14:17.694Z
3
3
  /**
4
4
  * Enum of all token symbols across all supported chains
5
5
  */
6
6
  export var TokenSymbol;
7
7
  (function (TokenSymbol) {
8
8
  TokenSymbol["AUDm"] = "AUDm";
9
+ TokenSymbol["AUSD"] = "AUSD";
9
10
  TokenSymbol["BRLm"] = "BRLm";
10
11
  TokenSymbol["CADm"] = "CADm";
11
12
  TokenSymbol["CELO"] = "CELO";
@@ -30,6 +31,29 @@ export var TokenSymbol;
30
31
  * Cached tokens indexed by chain ID
31
32
  */
32
33
  export const cachedTokens = {
34
+ // Chain 143
35
+ 143: [],
36
+ // Chain 10143
37
+ 10143: [
38
+ {
39
+ address: '0x5eCc03111ad2A78F981A108759bc73BAE2AB31bc',
40
+ symbol: TokenSymbol.USDm,
41
+ name: 'Mento Dollar',
42
+ decimals: 18,
43
+ },
44
+ {
45
+ address: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
46
+ symbol: TokenSymbol.USDC,
47
+ name: 'USDC',
48
+ decimals: 6,
49
+ },
50
+ {
51
+ address: '0x502E67D3fE9302A5e4Ec1CFCDdbD6F34F9B9484B',
52
+ symbol: TokenSymbol.AUSD,
53
+ name: 'Mento Mock AUSD',
54
+ decimals: 6,
55
+ }
56
+ ],
33
57
  // Chain 42220
34
58
  42220: [
35
59
  {
@@ -282,6 +306,12 @@ export const cachedTokens = {
282
306
  * Useful for quickly looking up a token address by its symbol on a specific chain
283
307
  */
284
308
  export const TOKEN_ADDRESSES_BY_CHAIN = {
309
+ 143: {},
310
+ 10143: {
311
+ [TokenSymbol.USDm]: '0x5eCc03111ad2A78F981A108759bc73BAE2AB31bc',
312
+ [TokenSymbol.USDC]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
313
+ [TokenSymbol.AUSD]: '0x502E67D3fE9302A5e4Ec1CFCDdbD6F34F9B9484B',
314
+ },
285
315
  42220: {
286
316
  [TokenSymbol.USDm]: '0x765DE816845861e75A25fCA122bb6898B8B1282a',
287
317
  [TokenSymbol.EURm]: '0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73',
@@ -1,6 +1,14 @@
1
1
  import { parseAbi } from 'viem';
2
+ // Legacy Reserve (Celo)
2
3
  export const RESERVE_ABI = parseAbi([
3
4
  'function getTokens() view returns (address[])',
4
5
  'function isToken(address) view returns (bool)',
5
6
  'function isCollateralAsset(address) view returns (bool)',
6
7
  ]);
8
+ // ReserveV2 (Monad)
9
+ export const RESERVE_V2_ABI = parseAbi([
10
+ 'function getStableAssets() view returns (address[])',
11
+ 'function getCollateralAssets() view returns (address[])',
12
+ 'function isStableAsset(address) view returns (bool)',
13
+ 'function isCollateralAsset(address) view returns (bool)',
14
+ ]);
@@ -29,6 +29,22 @@ export const addresses = {
29
29
  ReserveLiquidityStrategy: '0xa0fB8b16ce6AF3634fF9F3f4F40E49E1C1ae4f0B',
30
30
  CDPLiquidityStrategy: '0x4e78BD9565341EAbe99cDC024acB044d9BDcB985',
31
31
  },
32
+ [ChainId.MONAD_TESTNET]: {
33
+ // Oracles & Breakers
34
+ BreakerBox: '0x88869E30609D2C0E4032463D713328C6f541878e',
35
+ MedianDeltaBreaker: '0xf923C884F319b8866F67C5719A80E5cB4D0FAF2c',
36
+ SortedOracles: '0x85ed9ac57827132B8F60938F3165BC139E1F53cd',
37
+ ValueDeltaBreaker: '0xbbD0D093F5F11D16D4456FBd6229c9a3b70B8Aaf',
38
+ // DEX
39
+ Reserve: '0xbCdc1D0b92DfceEaa0FcD0a0D53355F4bF1DB8a7',
40
+ // Stable Tokens
41
+ StableToken: '0x5eCc03111ad2A78F981A108759bc73BAE2AB31bc', // USDm
42
+ // V3
43
+ FPMMFactory: '0x353ED52bF8482027C0e0b9e3c0e5d96A9F680980',
44
+ Router: '0xcf6cD45210b3ffE3cA28379C4683F1e60D0C2CCd',
45
+ ReserveLiquidityStrategy: '0x734bb3251Ec3f1A83f8f2A8609bcEF649D54EbF8',
46
+ },
47
+ [ChainId.MONAD]: {},
32
48
  [ChainId.CELO_SEPOLIA]: {
33
49
  // Oracles & Breakers
34
50
  BreakerBox: '0x578bD46003B9D3fd4c3C3f47c98B329562a6a1dE',
@@ -56,6 +72,17 @@ export const addresses = {
56
72
  CDPLiquidityStrategy: '0x065AE7D4e207C8f4dca112D0B79E668cc7e93e03',
57
73
  },
58
74
  };
75
+ /**
76
+ * Get the address of a contract for a given chain, returning undefined if not found.
77
+ * Use this when the contract may not be deployed on the target chain.
78
+ */
79
+ export function tryGetContractAddress(chainId, contractName) {
80
+ const addressesForChain = addresses[chainId];
81
+ if (!addressesForChain) {
82
+ return undefined;
83
+ }
84
+ return addressesForChain[contractName];
85
+ }
59
86
  /**
60
87
  * Get the address of a contract for a given chain
61
88
  * @param chainId - The chain ID
@@ -64,11 +91,7 @@ export const addresses = {
64
91
  * @throws Error if the address is not found for the given chain
65
92
  */
66
93
  export function getContractAddress(chainId, contractName) {
67
- const addressesForChain = addresses[chainId];
68
- if (!addressesForChain) {
69
- throw new Error(`No addresses found for chain ID ${chainId}`);
70
- }
71
- const address = addressesForChain[contractName];
94
+ const address = tryGetContractAddress(chainId, contractName);
72
95
  if (!address) {
73
96
  throw new Error(`Address not found for contract ${contractName} on chain ID ${chainId}`);
74
97
  }
@@ -2,4 +2,6 @@ export var ChainId;
2
2
  (function (ChainId) {
3
3
  ChainId[ChainId["CELO"] = 42220] = "CELO";
4
4
  ChainId[ChainId["CELO_SEPOLIA"] = 11142220] = "CELO_SEPOLIA";
5
+ ChainId[ChainId["MONAD_TESTNET"] = 10143] = "MONAD_TESTNET";
6
+ ChainId[ChainId["MONAD"] = 143] = "MONAD";
5
7
  })(ChainId || (ChainId = {}));
@@ -1,4 +1,4 @@
1
- import { getContractAddress } from '../../core/constants';
1
+ import { tryGetContractAddress } from '../../core/constants';
2
2
  import { PoolType } from '../../core/types';
3
3
  import { FPMM_FACTORY_ABI, FPMM_ABI, VIRTUAL_POOL_FACTORY_ABI, VIRTUAL_POOL_ABI, BIPOOL_MANAGER_ABI, } from '../../core/abis';
4
4
  import { sortTokenAddresses } from '../../utils/sortUtils';
@@ -6,7 +6,7 @@ import { sortTokenAddresses } from '../../utils/sortUtils';
6
6
  * Fetches all FPMM pools from the FPMM Factory
7
7
  */
8
8
  export async function fetchFPMMPools(publicClient, chainId) {
9
- const fpmmFactoryAddress = getContractAddress(chainId, 'FPMMFactory');
9
+ const fpmmFactoryAddress = tryGetContractAddress(chainId, 'FPMMFactory');
10
10
  if (!fpmmFactoryAddress) {
11
11
  return [];
12
12
  }
@@ -52,8 +52,8 @@ export async function fetchFPMMPools(publicClient, chainId) {
52
52
  * then resolves token pairs and exchange IDs from each pool and BiPoolManager.
53
53
  */
54
54
  export async function fetchVirtualPools(publicClient, chainId) {
55
- const virtualPoolFactoryAddress = getContractAddress(chainId, 'VirtualPoolFactory');
56
- const biPoolManagerAddress = getContractAddress(chainId, 'BiPoolManager');
55
+ const virtualPoolFactoryAddress = tryGetContractAddress(chainId, 'VirtualPoolFactory');
56
+ const biPoolManagerAddress = tryGetContractAddress(chainId, 'BiPoolManager');
57
57
  if (!virtualPoolFactoryAddress || !biPoolManagerAddress) {
58
58
  return [];
59
59
  }
@@ -27,6 +27,7 @@ export class SwapService {
27
27
  * @param options - Swap configuration options (slippage, deadline)
28
28
  * @param route - Optional pre-fetched route for better performance
29
29
  * @returns Combined transaction with approval (if needed) and swap params
30
+ * @throws {Error} 'amountIn must be greater than zero' - if amountIn <= 0
30
31
  * @throws {Error} 'Slippage tolerance cannot be negative' - if slippageTolerance < 0
31
32
  * @throws {Error} 'Slippage tolerance exceeds maximum' - if slippageTolerance > 20%
32
33
  * @throws {Error} 'Deadline must be in the future' - if deadline is not a future timestamp
@@ -54,6 +55,7 @@ export class SwapService {
54
55
  * ```
55
56
  */
56
57
  async buildSwapTransaction(tokenIn, tokenOut, amountIn, recipient, owner, options, route) {
58
+ this.validateAmountIn(amountIn);
57
59
  // Validate all address inputs
58
60
  validateAddress(tokenIn, 'tokenIn');
59
61
  validateAddress(tokenOut, 'tokenOut');
@@ -77,6 +79,7 @@ export class SwapService {
77
79
  * @param options - Swap configuration options (slippage, deadline)
78
80
  * @param route - Optional pre-fetched route for better performance
79
81
  * @returns Detailed swap parameters including transaction data
82
+ * @throws {Error} 'amountIn must be greater than zero' - if amountIn <= 0
80
83
  * @throws {Error} 'Slippage tolerance cannot be negative' - if slippageTolerance < 0
81
84
  * @throws {Error} 'Slippage tolerance exceeds maximum' - if slippageTolerance > 20%
82
85
  * @throws {Error} 'Deadline must be in the future' - if deadline is not a future timestamp
@@ -98,6 +101,7 @@ export class SwapService {
98
101
  * ```
99
102
  */
100
103
  async buildSwapParams(tokenIn, tokenOut, amountIn, recipient, options, route) {
104
+ this.validateAmountIn(amountIn);
101
105
  const deadline = options.deadline;
102
106
  if (deadline <= BigInt(Date.now()) / 1000n) {
103
107
  throw new Error('Deadline must be in the future');
@@ -155,6 +159,15 @@ export class SwapService {
155
159
  args: [owner, routerAddress],
156
160
  }));
157
161
  }
162
+ /**
163
+ * Validates that the input amount is strictly positive.
164
+ * @private
165
+ */
166
+ validateAmountIn(amountIn) {
167
+ if (amountIn <= 0n) {
168
+ throw new Error('amountIn must be greater than zero');
169
+ }
170
+ }
158
171
  /**
159
172
  * Calculates minimum output amount after applying slippage tolerance
160
173
  * @param amountOut - Expected output amount
@@ -1,11 +1,18 @@
1
- import { RESERVE_ABI, BIPOOL_MANAGER_ABI, ERC20_ABI } from '../../core/abis';
2
- import { getContractAddress, RESERVE, BIPOOLMANAGER, } from '../../core/constants';
1
+ import { RESERVE_ABI, RESERVE_V2_ABI, BIPOOL_MANAGER_ABI, ERC20_ABI } from '../../core/abis';
2
+ import { getContractAddress, tryGetContractAddress, ChainId, RESERVE, BIPOOLMANAGER, } from '../../core/constants';
3
3
  import { retryOperation } from '../../utils';
4
+ /**
5
+ * Chains that use ReserveV2 (v3) instead of the legacy Reserve contract.
6
+ */
7
+ const RESERVE_V2_CHAINS = new Set([ChainId.MONAD_TESTNET, ChainId.MONAD]);
4
8
  export class TokenService {
5
9
  constructor(publicClient, chainId) {
6
10
  this.publicClient = publicClient;
7
11
  this.chainId = chainId;
8
12
  }
13
+ isReserveV2() {
14
+ return RESERVE_V2_CHAINS.has(this.chainId);
15
+ }
9
16
  /**
10
17
  * Get token metadata (name, symbol, decimals)
11
18
  * @param address - Token contract address
@@ -52,6 +59,26 @@ export class TokenService {
52
59
  }));
53
60
  return totalSupply.toString();
54
61
  }
62
+ /**
63
+ * Get stable token addresses from the Reserve contract.
64
+ * Uses getStableAssets() on ReserveV2, getTokens() on legacy Reserve.
65
+ */
66
+ async getStableTokenAddresses(reserveAddress) {
67
+ if (this.isReserveV2()) {
68
+ return (await this.publicClient.readContract({
69
+ address: reserveAddress,
70
+ abi: RESERVE_V2_ABI,
71
+ functionName: 'getStableAssets',
72
+ args: [],
73
+ }));
74
+ }
75
+ return (await this.publicClient.readContract({
76
+ address: reserveAddress,
77
+ abi: RESERVE_ABI,
78
+ functionName: 'getTokens',
79
+ args: [],
80
+ }));
81
+ }
55
82
  /**
56
83
  * Get all stable tokens from the Reserve contract.
57
84
  * Returns the actual on-chain ERC20 totalSupply values without adjustments.
@@ -60,12 +87,7 @@ export class TokenService {
60
87
  */
61
88
  async getStableTokens(includeSupply = true) {
62
89
  const reserveAddress = getContractAddress(this.chainId, RESERVE);
63
- const tokenAddresses = (await this.publicClient.readContract({
64
- address: reserveAddress,
65
- abi: RESERVE_ABI,
66
- functionName: 'getTokens',
67
- args: [],
68
- }));
90
+ const tokenAddresses = await this.getStableTokenAddresses(reserveAddress);
69
91
  // Fetch metadata and totalSupply for all tokens concurrently
70
92
  const tokens = await Promise.all(tokenAddresses.map(async (address) => {
71
93
  const [metadata, totalSupply] = await Promise.all([
@@ -81,12 +103,42 @@ export class TokenService {
81
103
  return tokens;
82
104
  }
83
105
  /**
84
- * Get all collateral assets from exchanges
85
- * Filters tokens that are marked as collateral in the Reserve contract
106
+ * Get all collateral assets.
107
+ * On ReserveV2 chains, queries the reserve directly.
108
+ * On legacy chains, discovers collateral via BiPoolManager exchanges.
86
109
  * @returns Array of collateral assets
87
110
  */
88
111
  async getCollateralAssets() {
89
- const biPoolManagerAddress = getContractAddress(this.chainId, BIPOOLMANAGER);
112
+ if (this.isReserveV2()) {
113
+ return this.getCollateralAssetsV2();
114
+ }
115
+ return this.getCollateralAssetsLegacy();
116
+ }
117
+ /**
118
+ * Get collateral assets directly from ReserveV2.
119
+ */
120
+ async getCollateralAssetsV2() {
121
+ const reserveAddress = getContractAddress(this.chainId, RESERVE);
122
+ const collateralAddresses = (await retryOperation(() => this.publicClient.readContract({
123
+ address: reserveAddress,
124
+ abi: RESERVE_V2_ABI,
125
+ functionName: 'getCollateralAssets',
126
+ args: [],
127
+ })));
128
+ const assets = await Promise.all(collateralAddresses.map(async (address) => {
129
+ const metadata = await this.getTokenMetadata(address);
130
+ return { address, ...metadata };
131
+ }));
132
+ return assets;
133
+ }
134
+ /**
135
+ * Get collateral assets from legacy Reserve via BiPoolManager exchanges.
136
+ */
137
+ async getCollateralAssetsLegacy() {
138
+ const biPoolManagerAddress = tryGetContractAddress(this.chainId, BIPOOLMANAGER);
139
+ if (!biPoolManagerAddress) {
140
+ return [];
141
+ }
90
142
  const reserveAddress = getContractAddress(this.chainId, RESERVE);
91
143
  // Get all exchanges to find unique token addresses
92
144
  const exchanges = (await retryOperation(() => this.publicClient.readContract({
@@ -1,6 +1,6 @@
1
1
  import { PoolType } from '../../core/types';
2
2
  import { FPMM_ABI, BROKER_ABI } from '../../core/abis';
3
- import { getContractAddress } from '../../core/constants';
3
+ import { tryGetContractAddress } from '../../core/constants';
4
4
  import { computeLimitId, calculateTradingLimitsV1, calculateTradingLimitsV2, hasConfiguredLimitsV1, hasConfiguredLimitsV2, } from '../../utils/tradingLimits';
5
5
  /**
6
6
  * Service for querying trading limits from the Mento protocol.
@@ -97,7 +97,10 @@ export class TradingLimitsService {
97
97
  * Get trading limits for a specific token in a Virtual pool.
98
98
  */
99
99
  async getVirtualPoolTokenLimits(exchangeId, token) {
100
- const brokerAddr = getContractAddress(this.chainId, 'Broker');
100
+ const brokerAddr = tryGetContractAddress(this.chainId, 'Broker');
101
+ if (!brokerAddr) {
102
+ return [];
103
+ }
101
104
  const limitId = computeLimitId(exchangeId, token);
102
105
  try {
103
106
  // Fetch config and state in parallel
@@ -23,6 +23,47 @@ const celoSepolia = defineChain({
23
23
  },
24
24
  testnet: true,
25
25
  });
26
+ const monadTestnet = defineChain({
27
+ id: 10143,
28
+ name: 'Monad Testnet',
29
+ nativeCurrency: {
30
+ decimals: 18,
31
+ name: 'MON',
32
+ symbol: 'MON',
33
+ },
34
+ rpcUrls: {
35
+ default: {
36
+ http: ['https://testnet-rpc.monad.xyz'],
37
+ },
38
+ },
39
+ blockExplorers: {
40
+ default: {
41
+ name: 'Monad Explorer',
42
+ url: 'https://testnet.monadexplorer.com',
43
+ },
44
+ },
45
+ testnet: true,
46
+ });
47
+ const monad = defineChain({
48
+ id: 143,
49
+ name: 'Monad',
50
+ nativeCurrency: {
51
+ decimals: 18,
52
+ name: 'MON',
53
+ symbol: 'MON',
54
+ },
55
+ rpcUrls: {
56
+ default: {
57
+ http: ['https://rpc.monad.xyz'],
58
+ },
59
+ },
60
+ blockExplorers: {
61
+ default: {
62
+ name: 'Monad Explorer',
63
+ url: 'https://monadvision.com',
64
+ },
65
+ },
66
+ });
26
67
  /**
27
68
  * Get the default RPC URL for a given chain ID
28
69
  * @param chainId - The chain ID
@@ -35,6 +76,10 @@ export function getDefaultRpcUrl(chainId) {
35
76
  return 'https://forno.celo.org';
36
77
  case ChainId.CELO_SEPOLIA:
37
78
  return 'https://forno.celo-sepolia.celo-testnet.org';
79
+ case ChainId.MONAD_TESTNET:
80
+ return 'https://testnet-rpc.monad.xyz';
81
+ case ChainId.MONAD:
82
+ return 'https://rpc.monad.xyz';
38
83
  default:
39
84
  throw new Error(`Unsupported chain ID: ${chainId}`);
40
85
  }
@@ -51,6 +96,10 @@ export function getChainConfig(chainId) {
51
96
  return celo;
52
97
  case ChainId.CELO_SEPOLIA:
53
98
  return celoSepolia;
99
+ case ChainId.MONAD_TESTNET:
100
+ return monadTestnet;
101
+ case ChainId.MONAD:
102
+ return monad;
54
103
  default:
55
104
  throw new Error(`Unsupported chain ID: ${chainId}`);
56
105
  }
@@ -10,7 +10,7 @@ const sortUtils_1 = require("../../utils/sortUtils");
10
10
  * Fetches all FPMM pools from the FPMM Factory
11
11
  */
12
12
  async function fetchFPMMPools(publicClient, chainId) {
13
- const fpmmFactoryAddress = (0, constants_1.getContractAddress)(chainId, 'FPMMFactory');
13
+ const fpmmFactoryAddress = (0, constants_1.tryGetContractAddress)(chainId, 'FPMMFactory');
14
14
  if (!fpmmFactoryAddress) {
15
15
  return [];
16
16
  }
@@ -56,8 +56,8 @@ async function fetchFPMMPools(publicClient, chainId) {
56
56
  * then resolves token pairs and exchange IDs from each pool and BiPoolManager.
57
57
  */
58
58
  async function fetchVirtualPools(publicClient, chainId) {
59
- const virtualPoolFactoryAddress = (0, constants_1.getContractAddress)(chainId, 'VirtualPoolFactory');
60
- const biPoolManagerAddress = (0, constants_1.getContractAddress)(chainId, 'BiPoolManager');
59
+ const virtualPoolFactoryAddress = (0, constants_1.tryGetContractAddress)(chainId, 'VirtualPoolFactory');
60
+ const biPoolManagerAddress = (0, constants_1.tryGetContractAddress)(chainId, 'BiPoolManager');
61
61
  if (!virtualPoolFactoryAddress || !biPoolManagerAddress) {
62
62
  return [];
63
63
  }
@@ -85,6 +85,7 @@ export declare class SwapService {
85
85
  * @param options - Swap configuration options (slippage, deadline)
86
86
  * @param route - Optional pre-fetched route for better performance
87
87
  * @returns Combined transaction with approval (if needed) and swap params
88
+ * @throws {Error} 'amountIn must be greater than zero' - if amountIn <= 0
88
89
  * @throws {Error} 'Slippage tolerance cannot be negative' - if slippageTolerance < 0
89
90
  * @throws {Error} 'Slippage tolerance exceeds maximum' - if slippageTolerance > 20%
90
91
  * @throws {Error} 'Deadline must be in the future' - if deadline is not a future timestamp
@@ -123,6 +124,7 @@ export declare class SwapService {
123
124
  * @param options - Swap configuration options (slippage, deadline)
124
125
  * @param route - Optional pre-fetched route for better performance
125
126
  * @returns Detailed swap parameters including transaction data
127
+ * @throws {Error} 'amountIn must be greater than zero' - if amountIn <= 0
126
128
  * @throws {Error} 'Slippage tolerance cannot be negative' - if slippageTolerance < 0
127
129
  * @throws {Error} 'Slippage tolerance exceeds maximum' - if slippageTolerance > 20%
128
130
  * @throws {Error} 'Deadline must be in the future' - if deadline is not a future timestamp
@@ -154,6 +156,11 @@ export declare class SwapService {
154
156
  * @private
155
157
  */
156
158
  private getAllowance;
159
+ /**
160
+ * Validates that the input amount is strictly positive.
161
+ * @private
162
+ */
163
+ private validateAmountIn;
157
164
  /**
158
165
  * Calculates minimum output amount after applying slippage tolerance
159
166
  * @param amountOut - Expected output amount
@@ -30,6 +30,7 @@ class SwapService {
30
30
  * @param options - Swap configuration options (slippage, deadline)
31
31
  * @param route - Optional pre-fetched route for better performance
32
32
  * @returns Combined transaction with approval (if needed) and swap params
33
+ * @throws {Error} 'amountIn must be greater than zero' - if amountIn <= 0
33
34
  * @throws {Error} 'Slippage tolerance cannot be negative' - if slippageTolerance < 0
34
35
  * @throws {Error} 'Slippage tolerance exceeds maximum' - if slippageTolerance > 20%
35
36
  * @throws {Error} 'Deadline must be in the future' - if deadline is not a future timestamp
@@ -57,6 +58,7 @@ class SwapService {
57
58
  * ```
58
59
  */
59
60
  async buildSwapTransaction(tokenIn, tokenOut, amountIn, recipient, owner, options, route) {
61
+ this.validateAmountIn(amountIn);
60
62
  // Validate all address inputs
61
63
  (0, validation_1.validateAddress)(tokenIn, 'tokenIn');
62
64
  (0, validation_1.validateAddress)(tokenOut, 'tokenOut');
@@ -80,6 +82,7 @@ class SwapService {
80
82
  * @param options - Swap configuration options (slippage, deadline)
81
83
  * @param route - Optional pre-fetched route for better performance
82
84
  * @returns Detailed swap parameters including transaction data
85
+ * @throws {Error} 'amountIn must be greater than zero' - if amountIn <= 0
83
86
  * @throws {Error} 'Slippage tolerance cannot be negative' - if slippageTolerance < 0
84
87
  * @throws {Error} 'Slippage tolerance exceeds maximum' - if slippageTolerance > 20%
85
88
  * @throws {Error} 'Deadline must be in the future' - if deadline is not a future timestamp
@@ -101,6 +104,7 @@ class SwapService {
101
104
  * ```
102
105
  */
103
106
  async buildSwapParams(tokenIn, tokenOut, amountIn, recipient, options, route) {
107
+ this.validateAmountIn(amountIn);
104
108
  const deadline = options.deadline;
105
109
  if (deadline <= BigInt(Date.now()) / 1000n) {
106
110
  throw new Error('Deadline must be in the future');
@@ -158,6 +162,15 @@ class SwapService {
158
162
  args: [owner, routerAddress],
159
163
  }));
160
164
  }
165
+ /**
166
+ * Validates that the input amount is strictly positive.
167
+ * @private
168
+ */
169
+ validateAmountIn(amountIn) {
170
+ if (amountIn <= 0n) {
171
+ throw new Error('amountIn must be greater than zero');
172
+ }
173
+ }
161
174
  /**
162
175
  * Calculates minimum output amount after applying slippage tolerance
163
176
  * @param amountOut - Expected output amount
@@ -4,6 +4,7 @@ export declare class TokenService {
4
4
  private publicClient;
5
5
  private chainId;
6
6
  constructor(publicClient: PublicClient, chainId: number);
7
+ private isReserveV2;
7
8
  /**
8
9
  * Get token metadata (name, symbol, decimals)
9
10
  * @param address - Token contract address
@@ -16,6 +17,11 @@ export declare class TokenService {
16
17
  * @returns Total supply as string
17
18
  */
18
19
  private getTotalSupply;
20
+ /**
21
+ * Get stable token addresses from the Reserve contract.
22
+ * Uses getStableAssets() on ReserveV2, getTokens() on legacy Reserve.
23
+ */
24
+ private getStableTokenAddresses;
19
25
  /**
20
26
  * Get all stable tokens from the Reserve contract.
21
27
  * Returns the actual on-chain ERC20 totalSupply values without adjustments.
@@ -24,10 +30,19 @@ export declare class TokenService {
24
30
  */
25
31
  getStableTokens(includeSupply?: boolean): Promise<StableToken[]>;
26
32
  /**
27
- * Get all collateral assets from exchanges
28
- * Filters tokens that are marked as collateral in the Reserve contract
33
+ * Get all collateral assets.
34
+ * On ReserveV2 chains, queries the reserve directly.
35
+ * On legacy chains, discovers collateral via BiPoolManager exchanges.
29
36
  * @returns Array of collateral assets
30
37
  */
31
38
  getCollateralAssets(): Promise<CollateralAsset[]>;
39
+ /**
40
+ * Get collateral assets directly from ReserveV2.
41
+ */
42
+ private getCollateralAssetsV2;
43
+ /**
44
+ * Get collateral assets from legacy Reserve via BiPoolManager exchanges.
45
+ */
46
+ private getCollateralAssetsLegacy;
32
47
  }
33
48
  //# sourceMappingURL=tokenService.d.ts.map
@@ -4,11 +4,18 @@ exports.TokenService = void 0;
4
4
  const abis_1 = require("../../core/abis");
5
5
  const constants_1 = require("../../core/constants");
6
6
  const utils_1 = require("../../utils");
7
+ /**
8
+ * Chains that use ReserveV2 (v3) instead of the legacy Reserve contract.
9
+ */
10
+ const RESERVE_V2_CHAINS = new Set([constants_1.ChainId.MONAD_TESTNET, constants_1.ChainId.MONAD]);
7
11
  class TokenService {
8
12
  constructor(publicClient, chainId) {
9
13
  this.publicClient = publicClient;
10
14
  this.chainId = chainId;
11
15
  }
16
+ isReserveV2() {
17
+ return RESERVE_V2_CHAINS.has(this.chainId);
18
+ }
12
19
  /**
13
20
  * Get token metadata (name, symbol, decimals)
14
21
  * @param address - Token contract address
@@ -55,6 +62,26 @@ class TokenService {
55
62
  }));
56
63
  return totalSupply.toString();
57
64
  }
65
+ /**
66
+ * Get stable token addresses from the Reserve contract.
67
+ * Uses getStableAssets() on ReserveV2, getTokens() on legacy Reserve.
68
+ */
69
+ async getStableTokenAddresses(reserveAddress) {
70
+ if (this.isReserveV2()) {
71
+ return (await this.publicClient.readContract({
72
+ address: reserveAddress,
73
+ abi: abis_1.RESERVE_V2_ABI,
74
+ functionName: 'getStableAssets',
75
+ args: [],
76
+ }));
77
+ }
78
+ return (await this.publicClient.readContract({
79
+ address: reserveAddress,
80
+ abi: abis_1.RESERVE_ABI,
81
+ functionName: 'getTokens',
82
+ args: [],
83
+ }));
84
+ }
58
85
  /**
59
86
  * Get all stable tokens from the Reserve contract.
60
87
  * Returns the actual on-chain ERC20 totalSupply values without adjustments.
@@ -63,12 +90,7 @@ class TokenService {
63
90
  */
64
91
  async getStableTokens(includeSupply = true) {
65
92
  const reserveAddress = (0, constants_1.getContractAddress)(this.chainId, constants_1.RESERVE);
66
- const tokenAddresses = (await this.publicClient.readContract({
67
- address: reserveAddress,
68
- abi: abis_1.RESERVE_ABI,
69
- functionName: 'getTokens',
70
- args: [],
71
- }));
93
+ const tokenAddresses = await this.getStableTokenAddresses(reserveAddress);
72
94
  // Fetch metadata and totalSupply for all tokens concurrently
73
95
  const tokens = await Promise.all(tokenAddresses.map(async (address) => {
74
96
  const [metadata, totalSupply] = await Promise.all([
@@ -84,12 +106,42 @@ class TokenService {
84
106
  return tokens;
85
107
  }
86
108
  /**
87
- * Get all collateral assets from exchanges
88
- * Filters tokens that are marked as collateral in the Reserve contract
109
+ * Get all collateral assets.
110
+ * On ReserveV2 chains, queries the reserve directly.
111
+ * On legacy chains, discovers collateral via BiPoolManager exchanges.
89
112
  * @returns Array of collateral assets
90
113
  */
91
114
  async getCollateralAssets() {
92
- const biPoolManagerAddress = (0, constants_1.getContractAddress)(this.chainId, constants_1.BIPOOLMANAGER);
115
+ if (this.isReserveV2()) {
116
+ return this.getCollateralAssetsV2();
117
+ }
118
+ return this.getCollateralAssetsLegacy();
119
+ }
120
+ /**
121
+ * Get collateral assets directly from ReserveV2.
122
+ */
123
+ async getCollateralAssetsV2() {
124
+ const reserveAddress = (0, constants_1.getContractAddress)(this.chainId, constants_1.RESERVE);
125
+ const collateralAddresses = (await (0, utils_1.retryOperation)(() => this.publicClient.readContract({
126
+ address: reserveAddress,
127
+ abi: abis_1.RESERVE_V2_ABI,
128
+ functionName: 'getCollateralAssets',
129
+ args: [],
130
+ })));
131
+ const assets = await Promise.all(collateralAddresses.map(async (address) => {
132
+ const metadata = await this.getTokenMetadata(address);
133
+ return { address, ...metadata };
134
+ }));
135
+ return assets;
136
+ }
137
+ /**
138
+ * Get collateral assets from legacy Reserve via BiPoolManager exchanges.
139
+ */
140
+ async getCollateralAssetsLegacy() {
141
+ const biPoolManagerAddress = (0, constants_1.tryGetContractAddress)(this.chainId, constants_1.BIPOOLMANAGER);
142
+ if (!biPoolManagerAddress) {
143
+ return [];
144
+ }
93
145
  const reserveAddress = (0, constants_1.getContractAddress)(this.chainId, constants_1.RESERVE);
94
146
  // Get all exchanges to find unique token addresses
95
147
  const exchanges = (await (0, utils_1.retryOperation)(() => this.publicClient.readContract({
@@ -100,7 +100,10 @@ class TradingLimitsService {
100
100
  * Get trading limits for a specific token in a Virtual pool.
101
101
  */
102
102
  async getVirtualPoolTokenLimits(exchangeId, token) {
103
- const brokerAddr = (0, constants_1.getContractAddress)(this.chainId, 'Broker');
103
+ const brokerAddr = (0, constants_1.tryGetContractAddress)(this.chainId, 'Broker');
104
+ if (!brokerAddr) {
105
+ return [];
106
+ }
104
107
  const limitId = (0, tradingLimits_1.computeLimitId)(exchangeId, token);
105
108
  try {
106
109
  // Fetch config and state in parallel