curvance 4.0.4 → 4.1.1

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 (125) hide show
  1. package/README.md +649 -59
  2. package/dist/chains/arb-sepolia.json +44 -0
  3. package/dist/chains/arbitrum.d.ts.map +1 -1
  4. package/dist/chains/arbitrum.js +4 -2
  5. package/dist/chains/arbitrum.js.map +1 -1
  6. package/dist/chains/index.d.ts +4 -0
  7. package/dist/chains/index.d.ts.map +1 -1
  8. package/dist/chains/index.js +15 -0
  9. package/dist/chains/index.js.map +1 -1
  10. package/dist/chains/monad-mainnet.json +26 -1
  11. package/dist/chains/monad.d.ts.map +1 -1
  12. package/dist/chains/monad.js +4 -2
  13. package/dist/chains/monad.js.map +1 -1
  14. package/dist/chains/rpc.d.ts +57 -0
  15. package/dist/chains/rpc.d.ts.map +1 -0
  16. package/dist/chains/rpc.js +67 -0
  17. package/dist/chains/rpc.js.map +1 -0
  18. package/dist/classes/Api.d.ts +4 -3
  19. package/dist/classes/Api.d.ts.map +1 -1
  20. package/dist/classes/Api.js +7 -7
  21. package/dist/classes/Api.js.map +1 -1
  22. package/dist/classes/BorrowableCToken.d.ts +3 -2
  23. package/dist/classes/BorrowableCToken.d.ts.map +1 -1
  24. package/dist/classes/BorrowableCToken.js +6 -5
  25. package/dist/classes/BorrowableCToken.js.map +1 -1
  26. package/dist/classes/CToken.d.ts +11 -3
  27. package/dist/classes/CToken.d.ts.map +1 -1
  28. package/dist/classes/CToken.js +102 -108
  29. package/dist/classes/CToken.js.map +1 -1
  30. package/dist/classes/Calldata.d.ts +2 -2
  31. package/dist/classes/Calldata.d.ts.map +1 -1
  32. package/dist/classes/Calldata.js +2 -2
  33. package/dist/classes/Calldata.js.map +1 -1
  34. package/dist/classes/DexAggregators/IDexAgg.d.ts +2 -2
  35. package/dist/classes/DexAggregators/IDexAgg.d.ts.map +1 -1
  36. package/dist/classes/DexAggregators/Kuru.d.ts +2 -2
  37. package/dist/classes/DexAggregators/Kuru.d.ts.map +1 -1
  38. package/dist/classes/DexAggregators/Kuru.js +3 -4
  39. package/dist/classes/DexAggregators/Kuru.js.map +1 -1
  40. package/dist/classes/DexAggregators/KuruMainnet.d.ts +1 -0
  41. package/dist/classes/DexAggregators/KuruMainnet.d.ts.map +1 -0
  42. package/dist/classes/DexAggregators/KuruMainnet.js +228 -0
  43. package/dist/classes/DexAggregators/KuruMainnet.js.map +1 -0
  44. package/dist/classes/DexAggregators/KyberSwap.d.ts +2 -2
  45. package/dist/classes/DexAggregators/KyberSwap.d.ts.map +1 -1
  46. package/dist/classes/DexAggregators/KyberSwap.js +22 -13
  47. package/dist/classes/DexAggregators/KyberSwap.js.map +1 -1
  48. package/dist/classes/DexAggregators/MultiDexAgg.d.ts +2 -2
  49. package/dist/classes/DexAggregators/MultiDexAgg.d.ts.map +1 -1
  50. package/dist/classes/DexAggregators/MultiDexAgg.js +3 -3
  51. package/dist/classes/DexAggregators/MultiDexAgg.js.map +1 -1
  52. package/dist/classes/ERC20.d.ts +5 -3
  53. package/dist/classes/ERC20.d.ts.map +1 -1
  54. package/dist/classes/ERC20.js +20 -14
  55. package/dist/classes/ERC20.js.map +1 -1
  56. package/dist/classes/ERC4626.d.ts.map +1 -1
  57. package/dist/classes/ERC4626.js +3 -1
  58. package/dist/classes/ERC4626.js.map +1 -1
  59. package/dist/classes/Kuru.d.ts +59 -0
  60. package/dist/classes/Kuru.d.ts.map +1 -0
  61. package/dist/classes/Kuru.js +167 -0
  62. package/dist/classes/Kuru.js.map +1 -0
  63. package/dist/classes/KuruMainnet.d.ts +59 -0
  64. package/dist/classes/KuruMainnet.d.ts.map +1 -0
  65. package/dist/classes/KuruMainnet.js +167 -0
  66. package/dist/classes/KuruMainnet.js.map +1 -0
  67. package/dist/classes/Market.d.ts +13 -4
  68. package/dist/classes/Market.d.ts.map +1 -1
  69. package/dist/classes/Market.js +87 -32
  70. package/dist/classes/Market.js.map +1 -1
  71. package/dist/classes/NativeToken.d.ts +6 -3
  72. package/dist/classes/NativeToken.d.ts.map +1 -1
  73. package/dist/classes/NativeToken.js +11 -16
  74. package/dist/classes/NativeToken.js.map +1 -1
  75. package/dist/classes/OptimizerReader.d.ts +3 -3
  76. package/dist/classes/OptimizerReader.d.ts.map +1 -1
  77. package/dist/classes/OptimizerReader.js +1 -1
  78. package/dist/classes/OptimizerReader.js.map +1 -1
  79. package/dist/classes/OracleManager.d.ts +3 -3
  80. package/dist/classes/OracleManager.d.ts.map +1 -1
  81. package/dist/classes/OracleManager.js +1 -1
  82. package/dist/classes/OracleManager.js.map +1 -1
  83. package/dist/classes/PositionManager.d.ts +2 -2
  84. package/dist/classes/PositionManager.d.ts.map +1 -1
  85. package/dist/classes/PositionManager.js +4 -4
  86. package/dist/classes/PositionManager.js.map +1 -1
  87. package/dist/classes/ProtocolReader.d.ts +23 -8
  88. package/dist/classes/ProtocolReader.d.ts.map +1 -1
  89. package/dist/classes/ProtocolReader.js +197 -64
  90. package/dist/classes/ProtocolReader.js.map +1 -1
  91. package/dist/classes/Redstone.d.ts.map +1 -1
  92. package/dist/classes/Redstone.js +1 -2
  93. package/dist/classes/Redstone.js.map +1 -1
  94. package/dist/classes/Zapper.d.ts +4 -2
  95. package/dist/classes/Zapper.d.ts.map +1 -1
  96. package/dist/classes/Zapper.js +14 -13
  97. package/dist/classes/Zapper.js.map +1 -1
  98. package/dist/classes/index.d.ts +1 -1
  99. package/dist/classes/index.d.ts.map +1 -1
  100. package/dist/classes/index.js +6 -1
  101. package/dist/classes/index.js.map +1 -1
  102. package/dist/contracts/monad-mainnet.json +1 -1
  103. package/dist/helpers.d.ts +3 -1
  104. package/dist/helpers.d.ts.map +1 -1
  105. package/dist/helpers.js +34 -4
  106. package/dist/helpers.js.map +1 -1
  107. package/dist/integrations/snapshot.d.ts.map +1 -1
  108. package/dist/integrations/snapshot.js +4 -18
  109. package/dist/integrations/snapshot.js.map +1 -1
  110. package/dist/retry-provider.d.ts +83 -6
  111. package/dist/retry-provider.d.ts.map +1 -1
  112. package/dist/retry-provider.js +538 -68
  113. package/dist/retry-provider.js.map +1 -1
  114. package/dist/setup.d.ts +14 -3
  115. package/dist/setup.d.ts.map +1 -1
  116. package/dist/setup.js +67 -20
  117. package/dist/setup.js.map +1 -1
  118. package/dist/snapshot.d.ts +53 -0
  119. package/dist/snapshot.d.ts.map +1 -0
  120. package/dist/snapshot.js +103 -0
  121. package/dist/snapshot.js.map +1 -0
  122. package/dist/types.d.ts +2 -1
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/types.js.map +1 -1
  125. package/package.json +5 -2
package/README.md CHANGED
@@ -1,28 +1,8 @@
1
1
  <p style="text-align: center;width:100%">
2
- <img src="https://pbs.twimg.com/profile_banners/1445781144125857796/1752160592" alt="Curvance"/>
2
+ <img src="https://pbs.twimg.com/profile_banners/1445781144125857796/1773687595/1500x500" alt="Curvance"/>
3
3
  </p>
4
4
 
5
- Features
6
- - **Efficient RPC Usage:** Preloads all market data with minimal calls.
7
- - **Typed Contracts:** Uses ethers.js for safe, typed blockchain interactions.
8
- - **Price Feeds:** Integrates Redstone for on-chain price updates.
9
- - **Decimal Support:** Handles BigInt and floating-point math with decimal.js.
10
- - **Flexible Providers:** Works with multiple providers:
11
- - `ethers.Wallet` - For CLI/Local wallet connections
12
- - `ethers.JsonRpcSigner` - For browser interactions
13
- - `ethers.JsonRpcProvider` - For a user configured RPC
14
- - `null` - We setup a JsonRpcProvider if we configured one for you
15
- - **Property conversions:** For example getting a users asset balance can optionally be returned in USD or token amount
16
- - **Contract addresses:** Use this package to pull in curvance contracts & have the latest contract addresses (especially useful on testnet)
17
-
18
- Dependencies:
19
- - [Redstone](https://www.npmjs.com/package/@redstone-finance/sdk): Used to attach price updates in a multicall for some actions.
20
- - [Decimals](https://www.npmjs.com/package/decimal.js): Any floating point path being done with BigInt is done with Decimals.
21
- - [ethers.js](https://www.npmjs.com/package/ethers): All signers passed into the protocol are using ether.js typed signers.
22
-
23
- Notes:
24
- - All values are returned in either BigInt or [Decimals](https://www.npmjs.com/package/decimal.js)
25
- - We use [alchemy](https://dashboard.alchemy.com/apps) chain prefixing for exaple: `eth-mainnet` or `arb-sepolia` to represents chains
5
+ A TypeScript SDK for interacting with the Curvance protocol. Built on ethers v6 with a bulk-loaded cache model — `setupChain()` preloads all market data in 1–3 RPC calls, and all subsequent reads are synchronous from cache.
26
6
 
27
7
  ## ❯ Install
28
8
 
@@ -30,54 +10,664 @@ Notes:
30
10
  $ npm install --save curvance
31
11
  ```
32
12
 
33
- ## ❯ Usage
13
+ ## ❯ Supported Chains
34
14
 
35
- ### Grab general information
36
- This is very RPC efficient as it uses 1-3 RPC call's to setup all the data you need. This is the main way to use the SDK as ALL the data is pre-setup for you. All you need to do is traverse the markets.
37
- ```js
38
- const { markets, reader, faucet } = await setupChain("monad-testnet", wallet);
15
+ Chain identifiers use Alchemy-style prefixes:
16
+
17
+ | Chain | Identifier |
18
+ |---|---|
19
+ | Monad Mainnet | `monad-mainnet` |
20
+ | Arbitrum Sepolia | `arb-sepolia` |
21
+
22
+ ## ❯ Quick Start
23
+
24
+ ```ts
25
+ import { setupChain } from "curvance";
26
+ import { ethers } from "ethers";
27
+
28
+ const wallet = new ethers.Wallet(privateKey, provider);
29
+ const { markets, reader, dexAgg, global_milestone } = await setupChain("monad-mainnet", wallet);
30
+ ```
31
+
32
+ `setupChain` signature:
33
+
34
+ ```ts
35
+ setupChain(
36
+ chain: ChainRpcPrefix,
37
+ provider: curvance_provider | null = null, // signer (wallet) OR read-only provider; null → SDK default
38
+ approval_protection: boolean = false, // revoke-before-approve pattern
39
+ api_url: string = "https://api.curvance.com",
40
+ options: {
41
+ feePolicy?: FeePolicy; // zap/leverage fee routing (default: NO_FEE_POLICY)
42
+ account?: address | null; // user address for user-specific reads without a signer
43
+ readProvider?: curvance_read_provider | null; // explicit override for read transport
44
+ } = {}
45
+ ): Promise<{
46
+ markets: Market[],
47
+ reader: ProtocolReader,
48
+ dexAgg: IDexAgg,
49
+ global_milestone: MilestoneResponse | null
50
+ }>
39
51
  ```
40
52
 
41
- You can then explore the data pretty easily like so:
42
- ```js
43
- let count = 0;
44
- console.log(`Market summaries in USD:`);
45
- for(const market of markets) {
46
- console.log(`[${count}] tvl: ${market.tvl.toFixed(18)} | totalDebt: ${market.totalDebt.toFixed(18)} | totalCollateral: ${market.totalCollateral.toFixed(18)}`);
47
- for(const token of market.tokens) {
48
- console.log(`\tToken: ${token.symbol} | Price: ${token.getPrice()} | Amount: ${token.getTvl(false)}`);
53
+ ### RPC routing
54
+
55
+ - **Wallet connected** (signer with a `.provider`) → the wallet's own provider is the **primary** read source; the chain's configured RPC + fallbacks absorb wallet RPC failures. This distributes read load across users' wallet RPCs and respects whichever endpoint each user chose.
56
+ - **Signerless / public view** → the chain's configured RPC is primary; chain fallbacks serve as backup.
57
+ - **Explicit `options.readProvider`** → wins over all of the above. Use when you want deterministic read transport (e.g. fork testing).
58
+ - **Writes** always route through the connected signer; they never use the chain RPC or fallbacks.
59
+
60
+ ### Explore markets
61
+
62
+ ```ts
63
+ for (const market of markets) {
64
+ console.log(`${market.name} | tvl: ${market.tvl} | debt: ${market.totalDebt}`);
65
+ for (const token of market.tokens) {
66
+ console.log(` ${token.symbol} | price: ${token.getPrice()} | apy: ${token.getApy(true)}%`);
49
67
  }
50
- count++;
51
68
  }
52
69
  ```
53
70
 
54
- ### Grab individaul classes
55
- ```js
56
- const test_1 = new ERC20(signer, `0x123`);
57
- await test_1.approve(someGuy, BigInt(50e18));
71
+ ## Providers
72
+
73
+ `curvance_provider` accepts any ethers v6 provider or signer. All providers are automatically wrapped with retry logic (exponential backoff for rate limits and 5xx errors).
74
+
75
+ | Type | Use case |
76
+ |---|---|
77
+ | `ethers.Wallet` | CLI / server-side with private key |
78
+ | `ethers.JsonRpcSigner` | Browser wallet (MetaMask, etc.) |
79
+ | `ethers.JsonRpcProvider` | Read-only or custom RPC |
80
+ | `null` | SDK constructs a provider from chain config |
81
+
82
+ `curvance_signer` = `JsonRpcSigner | Wallet` — required for write operations (deposit, borrow, etc.)
83
+
84
+ ## ❯ Markets
85
+
86
+ `Market` is the top-level container. Each market groups related collateral and borrow tokens and tracks the user's aggregate position.
87
+
88
+ ### Market properties
89
+
90
+ ```ts
91
+ market.name // market name
92
+ market.address // market contract address
93
+ market.tvl // total value locked (USD, Decimal)
94
+ market.totalDebt // total outstanding debt (USD, Decimal)
95
+ market.totalCollateral // total posted collateral (USD, Decimal)
96
+ market.cooldownLength // hold period between actions (20 min)
97
+ market.hasBorrowing() // whether this market supports borrowing
98
+ market.highestApy() // best supply APY across all tokens
99
+ market.ltv // LTV range {min, max} or single value
100
+ ```
101
+
102
+ ### User position (all in USD as `Decimal`)
103
+
104
+ ```ts
105
+ market.userDeposits // total deposits
106
+ market.userDebt // total outstanding debt
107
+ market.userMaxDebt // maximum allowable debt
108
+ market.userRemainingCredit // available borrow capacity (with 0.1% buffer)
109
+ market.userCollateral // posted collateral (in shares)
110
+ market.positionHealth // health factor — null means infinite (no debt)
111
+ market.userNet // deposits - debt
112
+ ```
113
+
114
+ ### Rate tracking
115
+
116
+ ```ts
117
+ // rateType: 'day' | 'week' | 'month' | 'year'
118
+ market.getUserDepositsChange('week') // projected earnings
119
+ market.getUserDebtChange('week') // projected interest cost
120
+ market.getUserNetChange('week') // net projected change
121
+ ```
122
+
123
+ ### Data refresh
124
+
125
+ ```ts
126
+ await market.reloadMarketData() // refresh rates, prices, utilization
127
+ await market.reloadUserData(account) // refresh user balances and position
128
+ ```
129
+
130
+ ## ❯ Tokens (CToken / BorrowableCToken)
131
+
132
+ Tokens within a market are either `CToken` (collateral/supply side) or `BorrowableCToken` (extends `CToken` with borrow/repay). Access them via `market.tokens` or `market.getBorrowableCTokens()`.
133
+
134
+ ### Token metadata
135
+
136
+ ```ts
137
+ token.symbol
138
+ token.name
139
+ token.decimals
140
+ token.asset // underlying ERC20 address
141
+ token.isBorrowable // whether this token can be borrowed
142
+ token.isVault // whether underlying is an ERC4626 vault
143
+ token.isNativeVault // native token vault (e.g. shMON)
144
+ token.canZap // supports zap deposits
145
+ token.canLeverage // supports leverage
146
+ token.maxLeverage // max allowed leverage (Decimal)
147
+ ```
148
+
149
+ ### Market state
150
+
151
+ ```ts
152
+ token.exchangeRate // current share-to-asset rate
153
+ token.totalAssets // total assets held (bigint)
154
+ token.totalSupply // total shares outstanding (bigint)
155
+ token.borrowPaused
156
+ token.collateralizationPaused
157
+ token.mintPaused
158
+ ```
159
+
160
+ ### Prices & conversions
161
+
162
+ ```ts
163
+ token.getPrice() // asset price (USD, Decimal)
164
+ token.getPrice(true) // share price
165
+ token.convertTokensToUsd(amount) // TokenInput → USD
166
+ token.convertUsdToTokens(usd) // USD → TokenInput
167
+ token.convertTokenInputToShares(amount) // user input → shares
168
+ token.virtualConvertToAssets(shares) // shares → assets (cached, no RPC)
169
+ token.virtualConvertToShares(assets) // assets → shares (cached, no RPC)
170
+ ```
171
+
172
+ ### User balances
173
+
174
+ ```ts
175
+ token.getUserShareBalance(inUSD) // cToken balance
176
+ token.getUserAssetBalance(inUSD) // underlying asset balance
177
+ token.getUserUnderlyingBalance(inUSD) // underlying token balance
178
+ token.getUserCollateral(inUSD) // posted collateral
179
+ token.getUserDebt(inUSD) // outstanding debt (borrow tokens)
180
+ ```
181
+
182
+ ### Market totals & caps
183
+
184
+ ```ts
185
+ token.getTvl(inUSD)
186
+ token.getTotalCollateral(inUSD)
187
+ token.getCollateralCap(inUSD) // remaining collateral capacity
188
+ token.getDebtCap(inUSD) // remaining debt capacity
189
+ token.getRemainingCollateral(formatted)
190
+ token.getRemainingDebt(formatted)
191
+ ```
192
+
193
+ ### Collateral parameters
194
+
195
+ ```ts
196
+ token.getCollRatio(inBPS) // collateralization ratio
197
+ token.getCollReqSoft(inBPS) // soft liquidation threshold
198
+ token.getCollReqHard(inBPS) // hard liquidation threshold
199
+ token.getLiqIncBase(inBPS) // liquidation incentive base
200
+ token.getLiqIncMin(inBPS) // liquidation incentive min
201
+ token.getLiqIncMax(inBPS) // liquidation incentive max
202
+ token.liquidationPrice // oracle liquidation price (null = infinite)
203
+ ```
204
+
205
+ ### APY & rates
206
+
207
+ ```ts
208
+ token.getApy(asPercentage) // supply APY
209
+ token.getTotalSupplyRate() // supply APY + incentives + native yield
210
+ token.getBorrowRate(inPercentage) // borrow APY
211
+ token.getTotalBorrowRate() // borrow APY minus incentive rewards
212
+
213
+ // BorrowableCToken only
214
+ token.getLiquidity(inUSD) // available liquidity to borrow
215
+ token.getUtilizationRate(inPercentage)
216
+ token.getPredictedBorrowRate(inPercentage)
217
+ token.getMaxBorrowable() // max amount given credit
218
+ ```
219
+
220
+ ### Position snapshot & preview
221
+
222
+ ```ts
223
+ token.getSnapshot(account) // position snapshot for an account
224
+ token.maxRedemption(inShares, bufferTime) // max redeemable amount
225
+ token.simulateDeposit(amount) // preview deposit without executing
226
+ token.simulateDepositAsCollateral(amount)
227
+ ```
228
+
229
+ ## ❯ Core Operations
230
+
231
+ All amounts are `Decimal` (human-readable token units) unless noted.
232
+
233
+ ### Approvals
234
+
235
+ ```ts
236
+ await token.approveUnderlying(amount, target) // approve underlying asset spend
237
+ await token.approve(amount, spender) // approve cToken itself
238
+ await token.getAllowance(contract, underlying) // check allowance
239
+ ```
240
+
241
+ ### Deposit & Withdraw
242
+
243
+ ```ts
244
+ // Deposit as supplier (earns yield, cannot be used as collateral)
245
+ await token.deposit(amount, zap?, receiver?)
246
+
247
+ // Deposit as collateral (enables borrowing against it)
248
+ await token.depositAsCollateral(amount, zapInstructions?, receiver?)
249
+
250
+ // Withdraw
251
+ await token.redeem(amount) // by asset amount
252
+ await token.redeemShares(amount) // by share amount
253
+ await token.redeemCollateral(amount, receiver?, owner?)
254
+
255
+ // Manage posted collateral
256
+ await token.postCollateral(amount) // post unposted balance as collateral
257
+ await token.removeCollateral(amount, removeAll?)
258
+ ```
259
+
260
+ ### Borrow & Repay (`BorrowableCToken` only)
261
+
262
+ ```ts
263
+ await borrowToken.borrow(amount, receiver?)
264
+ await borrowToken.repay(amount)
265
+
266
+ // Previews
267
+ const impact = await borrowToken.hypotheticalBorrowOf(amount) // on-chain health preview
268
+ await borrowToken.fetchDebt(inUSD)
269
+ await borrowToken.debtBalance(account)
270
+ ```
271
+
272
+ ### Interest rate model
273
+
274
+ ```ts
275
+ await borrowToken.fetchBorrowRate()
276
+ await borrowToken.fetchSupplyRate()
277
+ await borrowToken.fetchUtilizationRate()
278
+ await borrowToken.fetchPredictedBorrowRate()
279
+ await borrowToken.fetchUtilizationRateChange(assets, direction)
280
+ borrowToken.borrowChange(amount, rateType) // interest accrual over time period
281
+ ```
282
+
283
+ ## ❯ Plugins (Zappers & Position Managers)
284
+
285
+ Zapper and PositionManager contracts must be approved before first use.
286
+
287
+ ```ts
288
+ // Check and approve a plugin
289
+ const approved = await token.isPluginApproved('simple', 'zapper')
290
+ if (!approved) await token.approvePlugin('simple', 'zapper')
291
+
292
+ // Plugin types
293
+ // ZapperTypes: 'none' | 'native-vault' | 'vault' | 'simple' | 'native-simple'
294
+ // PositionManagerTypes: 'native-vault' | 'simple' | 'vault'
295
+
296
+ const zapper = token.getZapper('simple')
297
+ const positionManager = token.getPositionManager('simple')
298
+ ```
299
+
300
+ ## ❯ Zapping (Swap + Deposit)
301
+
302
+ Zap deposits allow depositing any token by swapping to the required underlying via the DEX aggregator.
303
+
304
+ ```ts
305
+ // Native token (MON) → deposit
306
+ await token.approvePlugin('native-simple', 'zapper')
307
+ await zapper.nativeZap(ctoken, amount, collateralize)
308
+
309
+ // Any ERC20 → swap → deposit
310
+ await token.approvePlugin('simple', 'zapper')
311
+ await token.approveUnderlying(amount)
312
+ await token.depositAsCollateral(amount, {
313
+ type: 'simple',
314
+ inputToken: inputTokenAddress,
315
+ slippage: new Decimal(0.01) // 1%
316
+ })
317
+ ```
318
+
319
+ Check approval status for a zap before executing:
320
+
321
+ ```ts
322
+ const approved = await token.isZapAssetApproved(instructions, amount)
323
+ if (!approved) await token.approveZapAsset(instructions, amount)
324
+ ```
325
+
326
+ ## ❯ Leverage & Deleverage
327
+
328
+ Leverage uses the PositionManager plugin to atomically borrow and swap into the collateral token.
329
+
330
+ ```ts
331
+ // One-step: deposit collateral + leverage
332
+ await collateralToken.approveUnderlying(amount)
333
+ await collateralToken.approvePlugin('simple', 'positionManager')
334
+ await collateralToken.depositAndLeverage(amount, borrowToken, targetLeverage, 'simple', slippage)
335
+
336
+ // Separate: deposit first, then leverage
337
+ await collateralToken.depositAsCollateral(amount)
338
+ await collateralToken.leverageUp(borrowToken, new Decimal(3), 'simple', new Decimal(0.005))
339
+
340
+ // Reduce leverage
341
+ await collateralToken.leverageDown(borrowToken, currentLeverage, targetLeverage, 'simple', slippage)
342
+
343
+ // Check current leverage
344
+ collateralToken.getLeverage() // Decimal | null (null if no debt)
345
+ ```
346
+
347
+ ### Leverage previews (via ProtocolReader)
348
+
349
+ ```ts
350
+ const preview = await reader.hypotheticalLeverageOf(account, depositCToken, borrowCToken, depositAmount)
351
+ // Returns: { currentLeverage, adjustMaxLeverage, maxLeverage, maxDebtBorrowable }
352
+ ```
353
+
354
+ ## ❯ Health & Position Previews
355
+
356
+ Preview position health before executing any action. Returns a `Decimal` percentage (0–1) or `null` (infinite / no debt).
357
+
358
+ ```ts
359
+ // Individual action previews
360
+ await market.previewPositionHealthDeposit(ctoken, amount)
361
+ await market.previewPositionHealthRedeem(ctoken, amount)
362
+ await market.previewPositionHealthBorrow(borrowToken, amount)
363
+ await market.previewPositionHealthRepay(borrowToken, amount)
364
+ await market.previewPositionHealthLeverageUp(depositCToken, depositAmount, borrowCToken, borrowAmount)
365
+ await market.previewPositionHealthLeverageDown(depositCToken, borrowCToken, newLeverage, currentLeverage)
366
+
367
+ // Generic preview
368
+ await market.previewPositionHealth(depositCToken, borrowCToken, isDeposit, collateralAmt, isRepay, debtAmt, bufferTime)
369
+
370
+ // Projected earnings/cost impact
371
+ await market.previewAssetImpact(user, collateralCToken, debtCToken, depositAmount, borrowAmount, rateType)
372
+ ```
373
+
374
+ ```ts
375
+ const health = await market.previewPositionHealthBorrow(borrowToken, new Decimal(1000))
376
+ if (health === null) {
377
+ // remains solvent with infinite health
378
+ } else if (health.lt(0.1)) {
379
+ console.warn("Would drop to 10% health — too risky")
380
+ }
381
+ ```
382
+
383
+ ## ❯ Cooldowns
384
+
385
+ Curvance enforces a 20-minute hold period between certain actions.
386
+
387
+ ```ts
388
+ market.cooldown // Date | null (current cooldown expiry)
389
+ await market.expiresAt(account) // fetch cooldown expiry from chain
390
+ await market.multiHoldExpiresAt(markets) // cooldown across multiple markets
391
+ ```
392
+
393
+ ## ❯ Format Utilities
394
+
395
+ Pure calculation helpers for building UI or simulating outcomes. All accept and return `Decimal`.
396
+
397
+ ### Leverage math
398
+
399
+ ```ts
400
+ import { leverage } from "curvance"
401
+
402
+ leverage.calculateBorrowAmount(depositUsd, leverageMultiplier)
403
+ leverage.calculateLeverageRatio(totalValue, debtAmount)
404
+ leverage.calculateDeleverageAmount(currentLeverage, targetLeverage, totalValue)
405
+ leverage.calculatePositionSize(tokenAmount, leverageMultiplier)
406
+ leverage.validateLeverageInput(input) // checks balance, min deposit, max leverage, liquidity
407
+ leverage.checkLeverageAmountBelowMinimum(input) // $10.10 minimum borrow
408
+ leverage.checkBorrowExceedsLiquidity(borrowAmount, availableLiquidity)
409
+ ```
410
+
411
+ ### Borrow math
412
+
413
+ ```ts
414
+ import { borrow } from "curvance"
415
+
416
+ borrow.calculateMaxBorrow(remainingCredit, remainingDebt, availableLiquidity)
417
+ borrow.calculateMaxRepay(userBalance, userDebt)
418
+ borrow.validateRepayRemainder(currentDebtUsd, repayAmountUsd) // enforces $10 minimum remainder
419
+ borrow.calculateDebtPreview(currentDebt, amount, isRepaying)
420
+ borrow.convertAmountByCurrencyView(amount, price, currencyView) // USD ↔ token view
421
+ ```
422
+
423
+ ### Collateral math
424
+
425
+ ```ts
426
+ import { collateral } from "curvance"
427
+
428
+ collateral.calculateExchangeRate(assetBalance, shareBalance)
429
+ collateral.calculateCollateralBreakdown(assetBalance, shares, exchangeRate)
430
+ collateral.calculateNewCollateral(currentCollateral, amount, action)
431
+ ```
432
+
433
+ ### Health display
434
+
435
+ ```ts
436
+ import { health } from "curvance"
437
+
438
+ health.getHealthStatus(percentageValue) // 'Danger' | 'Caution' | 'Healthy'
439
+ health.healthFactorToPercentage(rawFactor)
440
+ health.formatHealthFactorPercentage(value)
441
+ health.formatHealthFactor(value) // handles infinity
442
+ health.getLiquidityStatus(ratio) // 'green' | 'yellow' | 'red'
443
+ ```
444
+
445
+ ### Amount formatting
446
+
447
+ ```ts
448
+ import { amounts } from "curvance"
449
+
450
+ amounts.clampUsdDustAmount(value) // zero out sub-$0.01 amounts
451
+ amounts.normalizeAmountString(value, maxFractionDigits, roundingMode)
452
+ amounts.normalizeCurrencyAmounts({ amount, currencyView, tokenDecimals, price })
453
+ ```
454
+
455
+ ## ❯ Helpers & Utilities
456
+
457
+ ```ts
458
+ import {
459
+ getContractAddresses,
460
+ contractSetup,
461
+ handleTransactionWithOracles,
462
+ toDecimal, toBigInt,
463
+ getDepositApy, getBorrowCost,
464
+ getInterestYield, getNativeYield,
465
+ getMerklDepositIncentives, getMerklBorrowIncentives,
466
+ getRateSeconds,
467
+ WAD, WAD_DECIMAL, BPS, RAY,
468
+ UINT256_MAX, EMPTY_ADDRESS, NATIVE_ADDRESS,
469
+ DEFAULT_SLIPPAGE_BPS,
470
+ } from "curvance"
471
+ ```
472
+
473
+ | Helper | Description |
474
+ |---|---|
475
+ | `getContractAddresses(chain)` | All contract addresses for a chain |
476
+ | `contractSetup(provider, address, abi)` | Create a typed contract instance |
477
+ | `handleTransactionWithOracles(...)` | Wraps a tx in a Redstone multicall when a pull oracle price write is required |
478
+ | `toDecimal(value, decimals)` | `bigint` → `Decimal` |
479
+ | `toBigInt(value, decimals)` | `Decimal` → `bigint` |
480
+ | `getDepositApy(token, opportunities, apyOverrides)` | Total deposit yield (interest + Merkl + native) |
481
+ | `getBorrowCost(token, opportunities)` | Net borrow cost — may be negative when rewards exceed rate |
482
+ | `getInterestYield(token)` | Lending APY only |
483
+ | `getNativeYield(token, apyOverrides)` | Native yield component |
484
+ | `getMerklDepositIncentives(tokenAddress, opportunities)` | Merkl reward APR for deposits |
485
+ | `getMerklBorrowIncentives(tokenAddress, opportunities)` | Merkl reward APR for borrows |
486
+ | `getRateSeconds(rateType)` | Convert `'year' \| 'month' \| 'week' \| 'day'` → seconds |
487
+
488
+ ## ❯ Fee Policy
489
+
490
+ The SDK supports configurable fees applied at the DEX aggregator layer for swaps. Fees are denominated in BPS of the swap input and charged on leverage, deleverage, deposit+leverage, and zap operations.
491
+
492
+ ```ts
493
+ import { flatFeePolicy, NO_FEE_POLICY } from "curvance"
494
+
495
+ const feePolicy = flatFeePolicy({
496
+ bps: 10n, // 0.1% default fee
497
+ feeReceiver: "0xYourAddress",
498
+ chain: "monad-mainnet",
499
+ stableToStableBps: 2n, // optional lower fee for stable↔stable swaps
500
+ })
501
+
502
+ const { markets } = await setupChain("monad-mainnet", wallet, false, undefined, { feePolicy })
503
+ ```
504
+
505
+ The SDK automatically returns 0 bps for native ↔ wrapped-native swaps and same-token no-op zaps.
506
+
507
+ ```ts
508
+ // FeePolicy interface — implement your own
509
+ interface FeePolicy {
510
+ feeReceiver: address;
511
+ getFeeBps(ctx: FeePolicyContext): bigint;
512
+ }
513
+
514
+ // Context passed to getFeeBps
515
+ interface FeePolicyContext {
516
+ operation: 'leverage-up' | 'leverage-down' | 'deposit-and-leverage' | 'zap';
517
+ inputToken: address;
518
+ outputToken: address;
519
+ inputAmount: bigint;
520
+ currentLeverage: Decimal | null;
521
+ targetLeverage: Decimal | null;
522
+ }
523
+ ```
524
+
525
+ ## ❯ Integrations
526
+
527
+ ### Merkl rewards
528
+
529
+ ```ts
530
+ import { fetchMerklOpportunities, fetchMerklUserRewards, fetchMerklCampaignsBySymbol } from "curvance"
531
+
532
+ // All active opportunities (APR, token, type)
533
+ const opportunities = await fetchMerklOpportunities()
534
+
535
+ // Pending rewards for a user
536
+ const rewards = await fetchMerklUserRewards({ wallet: address, chainId: 143 })
537
+
538
+ // Campaigns for a specific token
539
+ const campaigns = await fetchMerklCampaignsBySymbol({ tokenSymbol: "USDC" })
540
+ ```
541
+
542
+ ### Portfolio snapshots
543
+
544
+ ```ts
545
+ import { takePortfolioSnapshot, snapshotMarket } from "curvance"
546
+
547
+ // Full portfolio across all markets
548
+ const snapshot = await takePortfolioSnapshot(account)
549
+ // Returns: { account, chain, timestamp, totalDepositsUSD, totalDebtUSD, netUSD, dailyEarnings, dailyCost, markets[] }
550
+
551
+ // Single market
552
+ const marketSnapshot = snapshotMarket(market)
553
+ ```
554
+
555
+ ## ❯ Optimizer
556
+
557
+ The `OptimizerReader` reads yield-rebalancing vaults that allocate across markets.
558
+
559
+ ```ts
560
+ import { OptimizerReader } from "curvance"
561
+
562
+ const optimizer = new OptimizerReader(provider)
563
+
564
+ await optimizer.getOptimizerMarketData(optimizerAddresses)
565
+ // Returns: { totalAssets, sharePrice, performanceFee, markets[] }
566
+
567
+ await optimizer.getOptimizerUserData(optimizerAddresses, account)
568
+ // Returns: user balance and redeemable amounts
569
+
570
+ await optimizer.optimalDeposit(optimizer, assets) // best market to deposit into
571
+ await optimizer.optimalWithdrawal(optimizer, assets) // best market to withdraw from
572
+ await optimizer.optimalRebalance(optimizer) // suggested reallocations: { cToken, assets }[]
573
+ ```
574
+
575
+ ## ❯ TypeScript Types
576
+
577
+ ```ts
578
+ // Primitives
579
+ type address = `0x${string}` // checksummed Ethereum address
580
+ type bytes = `0x${string}` // hex-encoded calldata
581
+ type Percentage = Decimal // 0–1, e.g. 0.7 = 70%
582
+ type USD = Decimal // human-readable USD (1.0 = $1)
583
+ type USD_WAD = bigint // USD in 1e18 WAD format
584
+ type TokenInput = Decimal // human-readable token amount
585
+ type TypeBPS = bigint // basis points (10000 = 100%)
586
+ type ChainRpcPrefix = "monad-mainnet" | "arb-sepolia"
587
+ type curvance_provider = JsonRpcSigner | Wallet | JsonRpcProvider
588
+ type curvance_signer = JsonRpcSigner | Wallet
589
+
590
+ // Market categorization
591
+ type MarketCategory = "stablecoin" | "staking" | "restaking" | "yield-stablecoin" | "blue-chip" | "native"
592
+ type CollateralSource = "Renzo" | "Upshift" | "Yuzu" | "Native" | "Circle" | "Fastlane" | "Apriori" | "Mu Digital" | "Kintsu" | "Reservoir"
593
+
594
+ // Operations
595
+ type ZapperTypes = 'none' | 'native-vault' | 'vault' | 'simple' | 'native-simple'
596
+ type PositionManagerTypes = 'native-vault' | 'simple' | 'vault'
597
+ type ChangeRate = 'year' | 'month' | 'week' | 'day'
598
+
599
+ // DEX
600
+ interface Quote {
601
+ to: address
602
+ calldata: bytes
603
+ min_out: bigint
604
+ out: bigint
605
+ }
58
606
  ```
59
607
 
60
- Some of these classes use preloaded cache to prevent RPC calls for example
61
- ```js
62
- const test_1 = new ERC20(signer, `0x123`);
63
- console.log(test_1.name); // Attempts to use cache for name, so this returns undefined
64
- const name = await test_1.fetchName();
65
- console.log(name); // My Test Token
66
- console.log(test_1.name); // My Test Token
608
+ All numeric return values are `bigint` or `Decimal` never plain JS `number`.
609
+
610
+ ## Constants
611
+
612
+ ```ts
613
+ WAD // 1_000_000_000_000_000_000n (1e18)
614
+ BPS // 10_000n
615
+ RAY // 1_000_000_000_000_000_000_000_000_000n (1e27)
616
+ WAD_SQUARED // 1e36n
617
+ WAD_DECIMAL // Decimal('1e18')
618
+ UINT256_MAX
619
+ EMPTY_ADDRESS // '0x0000000000000000000000000000000000000000'
620
+ NATIVE_ADDRESS // canonical native token address
621
+ DEFAULT_SLIPPAGE_BPS // 100n (1%)
67
622
  ```
68
623
 
69
- Note: Protocol reader will populate things like `test_1.name` for underlying assets from the first preload RPC call and wont need to be fetched.
624
+ ## Dependencies
625
+
626
+ | Package | Purpose |
627
+ |---|---|
628
+ | [ethers v6](https://www.npmjs.com/package/ethers) | Typed contract interactions, providers, and signer handling |
629
+ | [decimal.js](https://www.npmjs.com/package/decimal.js) | Arbitrary-precision math for all token amounts, prices, and rates |
630
+ | [@redstone-finance/sdk](https://www.npmjs.com/package/@redstone-finance/sdk) | Price feed writes bundled into multicalls for pull-oracle adaptors |
631
+
632
+ ## ❯ Pre-Publish Checklist
633
+
634
+ Run before every `npm publish` that touches `src/chains/`, `src/setup.ts`,
635
+ `src/retry-provider.ts`, or any RPC-adjacent code:
636
+
637
+ 1. **Unit tests green.** `npm test` — must show all `test:transport` tests
638
+ passing. `tests/rpc-config-shape.test.ts` locks the structural invariants
639
+ of `chain_rpc_config` (no known-bad RPCs, no duplicate fallbacks, policy
640
+ fields within sane ranges).
641
+
642
+ 2. **Live RPC probe against both app origins.** In the app repo:
643
+
644
+ ```bash
645
+ cd path/to/curvance-app
646
+ RPC_PROBE_YES=1 node scripts/rpc-probe.mjs
647
+ ```
648
+
649
+ The probe hits `staging.curvance.com` and `app.curvance.com` origins
650
+ against every URL in `chain_rpc_config`. Required thresholds for the
651
+ **primary** and **first fallback** on each chain:
652
+
653
+ - CORS preflight returns `204` or `200` with a matching `Access-Control-Allow-Origin` header
654
+ - Correctness call returns a valid `chainId` matching the chain
655
+ - Latency `p95 ≤ 500ms`
656
+ - 50-concurrent-burst: `≥ 45/50` successful (allow 10% for transient blips)
657
+
658
+ Any primary or first-fallback failing these thresholds **blocks the
659
+ publish**. Demote to a later fallback position or replace.
70
660
 
661
+ Deeper-cascade fallbacks (`fallbacks[1]+`) MAY have looser limits if
662
+ documented inline with a comment in `chain_rpc_config`.
71
663
 
72
- ### Helpers
73
- - `getContractAddresses` - Grab the contracts addresses for a given chain
74
- - `AdaptorTypes` - Adaptor identifier enums
75
- - `WAD` - WAD amount
76
- - `WAD_DECIMAL` - WAD amount as Decimal.js type
77
- - `contractSetup` - Used to initialize contract & attach typescript interface
78
- - `handleTransactionWithOracles` - Depending on what adaptor is being used to execute the function we choose to run a multi-call that will write-price on-chain with the given function -- but only if the adaptor is the type of adaptor that requires this (pull oracle)
664
+ 3. **Do not add the probe to CI.** The probe fires ~500 requests per run
665
+ across 5-10 public RPCs from a single IP. Running it on every PR would
666
+ trip per-IP rate limits and eventually provoke origin bans from the
667
+ free RPCs we depend on — recreating the exact failure mode
668
+ (monadinfra 403'ing `staging.curvance.com`) that motivated building
669
+ this probe.
79
670
 
80
- ```js
81
- const contracts = getContractAddresses('monad-testnet');
82
- console.log(contracts.ProtocolReader);
83
- ```
671
+ 4. **Republish workflow.** Version bump → `npm publish` → in app repo,
672
+ bump `curvance` in `package.json` to the new version → `yarn install`
673
+ → commit `yarn.lock` → deploy.