curvance 5.1.8 → 5.2.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.
- package/README.md +251 -70
- package/dist/abis/OptimizerReader.json +292 -1
- package/dist/chains/arbitrum.d.ts.map +1 -1
- package/dist/chains/arbitrum.js +14 -1
- package/dist/chains/arbitrum.js.map +1 -1
- package/dist/chains/index.d.ts +19 -0
- package/dist/chains/index.d.ts.map +1 -1
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/monad.d.ts.map +1 -1
- package/dist/chains/monad.js +25 -2
- package/dist/chains/monad.js.map +1 -1
- package/dist/chains/services.d.ts +8 -0
- package/dist/chains/services.d.ts.map +1 -0
- package/dist/chains/services.js +9 -0
- package/dist/chains/services.js.map +1 -0
- package/dist/classes/Api.d.ts +7 -2
- package/dist/classes/Api.d.ts.map +1 -1
- package/dist/classes/Api.js +60 -24
- package/dist/classes/Api.js.map +1 -1
- package/dist/classes/BorrowableCToken.d.ts.map +1 -1
- package/dist/classes/BorrowableCToken.js +7 -2
- package/dist/classes/BorrowableCToken.js.map +1 -1
- package/dist/classes/CToken.d.ts +36 -29
- package/dist/classes/CToken.d.ts.map +1 -1
- package/dist/classes/CToken.js +225 -166
- package/dist/classes/CToken.js.map +1 -1
- package/dist/classes/DexAggregators/IDexAgg.d.ts +8 -0
- package/dist/classes/DexAggregators/IDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.d.ts +5 -2
- package/dist/classes/DexAggregators/KyberSwap.d.ts.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.js +41 -19
- package/dist/classes/DexAggregators/KyberSwap.js.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts +7 -4
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.js +62 -16
- package/dist/classes/DexAggregators/MultiDexAgg.js.map +1 -1
- package/dist/classes/DexAggregators/helpers.d.ts +1 -1
- package/dist/classes/DexAggregators/helpers.d.ts.map +1 -1
- package/dist/classes/DexAggregators/helpers.js +1 -1
- package/dist/classes/DexAggregators/helpers.js.map +1 -1
- package/dist/classes/DexAggregators/index.d.ts +0 -1
- package/dist/classes/DexAggregators/index.d.ts.map +1 -1
- package/dist/classes/DexAggregators/index.js +0 -1
- package/dist/classes/DexAggregators/index.js.map +1 -1
- package/dist/classes/LendingOptimizer.d.ts +7 -0
- package/dist/classes/LendingOptimizer.d.ts.map +1 -1
- package/dist/classes/LendingOptimizer.js +12 -2
- package/dist/classes/LendingOptimizer.js.map +1 -1
- package/dist/classes/Market.d.ts +5 -0
- package/dist/classes/Market.d.ts.map +1 -1
- package/dist/classes/Market.js +129 -30
- package/dist/classes/Market.js.map +1 -1
- package/dist/classes/NativeToken.d.ts +5 -2
- package/dist/classes/NativeToken.d.ts.map +1 -1
- package/dist/classes/NativeToken.js +5 -5
- package/dist/classes/NativeToken.js.map +1 -1
- package/dist/classes/OptimizerReader.d.ts +44 -4
- package/dist/classes/OptimizerReader.d.ts.map +1 -1
- package/dist/classes/OptimizerReader.js +133 -62
- package/dist/classes/OptimizerReader.js.map +1 -1
- package/dist/classes/PositionManager.d.ts +1 -0
- package/dist/classes/PositionManager.d.ts.map +1 -1
- package/dist/classes/PositionManager.js +25 -0
- package/dist/classes/PositionManager.js.map +1 -1
- package/dist/classes/ProtocolReader.d.ts +3 -0
- package/dist/classes/ProtocolReader.d.ts.map +1 -1
- package/dist/classes/ProtocolReader.js +34 -0
- package/dist/classes/ProtocolReader.js.map +1 -1
- package/dist/classes/Zapper.d.ts +4 -1
- package/dist/classes/Zapper.d.ts.map +1 -1
- package/dist/classes/Zapper.js +34 -9
- package/dist/classes/Zapper.js.map +1 -1
- package/dist/classes/index.d.ts +1 -1
- package/dist/classes/index.d.ts.map +1 -1
- package/dist/classes/index.js +1 -1
- package/dist/classes/index.js.map +1 -1
- package/dist/contracts/index.d.ts +249 -245
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +3 -2
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/monad-mainnet.json +4 -0
- package/dist/feePolicy.d.ts +29 -26
- package/dist/feePolicy.d.ts.map +1 -1
- package/dist/feePolicy.js +43 -34
- package/dist/feePolicy.js.map +1 -1
- package/dist/format/index.d.ts +5 -0
- package/dist/format/index.d.ts.map +1 -1
- package/dist/format/index.js +28 -0
- package/dist/format/index.js.map +1 -1
- package/dist/helpers.d.ts +248 -245
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +5 -5
- package/dist/helpers.js.map +1 -1
- package/dist/immutability.d.ts +6 -0
- package/dist/immutability.d.ts.map +1 -0
- package/dist/immutability.js +25 -0
- package/dist/immutability.js.map +1 -0
- package/dist/integrations/merkl.d.ts +9 -2
- package/dist/integrations/merkl.d.ts.map +1 -1
- package/dist/integrations/merkl.js +219 -11
- package/dist/integrations/merkl.js.map +1 -1
- package/dist/integrations/snapshot.d.ts +3 -0
- package/dist/integrations/snapshot.d.ts.map +1 -1
- package/dist/integrations/snapshot.js +47 -3
- package/dist/integrations/snapshot.js.map +1 -1
- package/dist/setup.d.ts +13 -3
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +101 -7
- package/dist/setup.js.map +1 -1
- package/package.json +7 -5
- package/dist/classes/Redstone.d.ts +0 -15
- package/dist/classes/Redstone.d.ts.map +0 -1
- package/dist/classes/Redstone.js +0 -56
- package/dist/classes/Redstone.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<img src="https://pbs.twimg.com/profile_banners/1445781144125857796/1773687595/1500x500" alt="Curvance"/>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
A TypeScript SDK for interacting with the Curvance protocol.
|
|
5
|
+
A TypeScript SDK for interacting with the Curvance protocol. It uses ethers v6 and a setup-bound cache model: `setupChain()` loads market state up front, snapshots the chain configuration it used, and returns markets whose reads are synchronous until an explicit refresh runs.
|
|
6
6
|
|
|
7
7
|
## ❯ Install
|
|
8
8
|
|
|
@@ -14,10 +14,21 @@ $ npm install --save curvance
|
|
|
14
14
|
|
|
15
15
|
Chain identifiers use Alchemy-style prefixes:
|
|
16
16
|
|
|
17
|
-
| Chain | Identifier |
|
|
18
|
-
|
|
19
|
-
| Monad Mainnet | `monad-mainnet` |
|
|
20
|
-
| Arbitrum Sepolia | `arb-sepolia` |
|
|
17
|
+
| Chain | Identifier | Support |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Monad Mainnet | `monad-mainnet` | Production mainnet; setup/read, rewards, Kyber-backed simple zaps/leverage where configured |
|
|
20
|
+
| Arbitrum Sepolia | `arb-sepolia` | Testnet read/setup surface; DEX routes fail closed through `UnsupportedDexAgg` |
|
|
21
|
+
|
|
22
|
+
Adding a production chain is an explicit SDK release task. A chain is not
|
|
23
|
+
supported just because a wallet or app can switch to it. The SDK needs:
|
|
24
|
+
|
|
25
|
+
- a `chain_config` entry and `chain_rpc_config` entry with matching `chainId`
|
|
26
|
+
- a contract manifest under `src/contracts`
|
|
27
|
+
- Curvance API service aliases or an explicit disabled state
|
|
28
|
+
- DEX service config, fee/checker policy, or `UnsupportedDexAgg`
|
|
29
|
+
- native/wrapped-native and vault metadata in chain config
|
|
30
|
+
- route-matrix tests for advertised zap/leverage support
|
|
31
|
+
- fork or live-read proof once deployments exist
|
|
21
32
|
|
|
22
33
|
## ❯ Quick Start
|
|
23
34
|
|
|
@@ -26,7 +37,8 @@ import { setupChain } from "curvance";
|
|
|
26
37
|
import { ethers } from "ethers";
|
|
27
38
|
|
|
28
39
|
const wallet = new ethers.Wallet(privateKey, provider);
|
|
29
|
-
const { markets, reader, dexAgg, global_milestone } =
|
|
40
|
+
const { chain, chainId, setupConfigSnapshot, markets, reader, dexAgg, global_milestone } =
|
|
41
|
+
await setupChain("monad-mainnet", wallet);
|
|
30
42
|
```
|
|
31
43
|
|
|
32
44
|
`setupChain` signature:
|
|
@@ -37,11 +49,14 @@ setupChain(
|
|
|
37
49
|
provider: curvance_provider | null = null, // signer (wallet) OR read-only provider; null → SDK default
|
|
38
50
|
api_url: string = "https://api.curvance.com",
|
|
39
51
|
options: {
|
|
40
|
-
feePolicy?: FeePolicy; //
|
|
52
|
+
feePolicy?: FeePolicy; // default is setup-resolved; Kyber chains require checker-compatible policy
|
|
41
53
|
account?: address | null; // user address for user-specific reads without a signer
|
|
42
54
|
readProvider?: curvance_read_provider | null; // explicit override for read transport
|
|
43
55
|
} = {}
|
|
44
56
|
): Promise<{
|
|
57
|
+
chain: ChainRpcPrefix,
|
|
58
|
+
chainId: number,
|
|
59
|
+
setupConfigSnapshot: Readonly<SetupConfigSnapshot>, // includes chain asset metadata and service policies
|
|
45
60
|
markets: Market[],
|
|
46
61
|
reader: ProtocolReader,
|
|
47
62
|
dexAgg: IDexAgg,
|
|
@@ -49,6 +64,26 @@ setupChain(
|
|
|
49
64
|
}>
|
|
50
65
|
```
|
|
51
66
|
|
|
67
|
+
`setupChain()` still publishes a single active setup for compatibility helpers
|
|
68
|
+
such as `getActiveUserMarkets()` and snapshot calls without explicit `markets`.
|
|
69
|
+
Multichain-safe code should pass explicit `markets`, `reader`, provider,
|
|
70
|
+
account, or setup context instead of relying on the latest singleton.
|
|
71
|
+
|
|
72
|
+
### Architecture contract
|
|
73
|
+
|
|
74
|
+
The current SDK architecture is result-bound. A `setupChain(...)` result owns
|
|
75
|
+
the chain context that produced it, and downstream objects should keep using
|
|
76
|
+
that context even if another chain boots later.
|
|
77
|
+
|
|
78
|
+
| Layer | Contract |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Setup snapshot | `setupConfigSnapshot` contains chain id, environment, cloned/frozen asset metadata, cloned/frozen external service policy, contract addresses, read transport, signer/account, API URL, and fee policy. |
|
|
81
|
+
| Returned markets | `Market` and `CToken` instances keep the setup snapshot and reader they were created with. Explicit returned-result calls stay on that chain after the module singleton moves. |
|
|
82
|
+
| Compatibility globals | `setup_config` and `all_markets` exist for single-active-chain consumers. Treat no-argument helpers as compatibility paths, not multichain-safe state. |
|
|
83
|
+
| External services | Curvance API reward/native-yield slugs and Kyber API/router/chain aliases live in chain config and are cloned into the setup snapshot. Helper code should read the snapshot, not mutable exported config. |
|
|
84
|
+
| DEX routing | Markets receive a setup-bound `dexAgg` after boot. `CToken` route discovery and zap/leverage execution do not fall back to `chain_config.dexAgg`; unsupported or manually constructed markets fail closed unless a market-bound adapter is attached. |
|
|
85
|
+
| Fee/checker policy | Kyber-backed chains validate checker-compatible fee policy during setup. The current checker requires `CURVANCE_FEE_BPS` and the setup-resolved DAO receiver before routes are advertised or markets finish booting. |
|
|
86
|
+
|
|
52
87
|
### RPC routing
|
|
53
88
|
|
|
54
89
|
- **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.
|
|
@@ -69,7 +104,7 @@ setupChain(
|
|
|
69
104
|
for (const market of markets) {
|
|
70
105
|
console.log(`${market.name} | deposits: ${market.totalDeposits} | debt: ${market.totalDebt}`);
|
|
71
106
|
for (const token of market.tokens) {
|
|
72
|
-
console.log(` ${token.symbol} | price: ${token.getPrice()} | apy: ${token.getApy(true)}%`);
|
|
107
|
+
console.log(` ${token.symbol} | price: ${token.getPrice(true)} | apy: ${token.getApy(true)}%`);
|
|
73
108
|
}
|
|
74
109
|
}
|
|
75
110
|
```
|
|
@@ -85,7 +120,7 @@ for (const market of markets) {
|
|
|
85
120
|
| `ethers.JsonRpcProvider` | Read-only or custom RPC |
|
|
86
121
|
| `null` | SDK constructs a provider from chain config |
|
|
87
122
|
|
|
88
|
-
`curvance_signer` = `JsonRpcSigner | Wallet
|
|
123
|
+
`curvance_signer` = `JsonRpcSigner | Wallet`, required for write operations (deposit, borrow, etc.)
|
|
89
124
|
|
|
90
125
|
## ❯ Markets
|
|
91
126
|
|
|
@@ -113,7 +148,7 @@ market.userDebt // total outstanding debt
|
|
|
113
148
|
market.userMaxDebt // maximum allowable debt
|
|
114
149
|
market.userRemainingCredit // available borrow capacity (with 0.1% buffer)
|
|
115
150
|
market.userCollateral // posted collateral (in shares)
|
|
116
|
-
market.positionHealth // health factor
|
|
151
|
+
market.positionHealth // health factor; null means infinite (no debt)
|
|
117
152
|
market.userNet // deposits - debt
|
|
118
153
|
```
|
|
119
154
|
|
|
@@ -166,8 +201,8 @@ token.mintPaused
|
|
|
166
201
|
### Prices & conversions
|
|
167
202
|
|
|
168
203
|
```ts
|
|
169
|
-
token.getPrice() //
|
|
170
|
-
token.getPrice(true) //
|
|
204
|
+
token.getPrice() // share price (USD, Decimal)
|
|
205
|
+
token.getPrice(true) // asset price
|
|
171
206
|
token.convertTokensToUsd(amount) // TokenInput → USD
|
|
172
207
|
token.convertUsdToTokens(usd) // USD → TokenInput
|
|
173
208
|
token.convertTokenInputToShares(amount) // user input → shares
|
|
@@ -306,29 +341,60 @@ const zapper = token.getZapper('simple')
|
|
|
306
341
|
const positionManager = token.getPositionManager('simple')
|
|
307
342
|
```
|
|
308
343
|
|
|
344
|
+
Prefer `token.getZapper(...)` so the zapper carries the token's setup-bound DEX aggregator. Direct construction must use the same setup result as the CToken it will operate on:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
import { Zapper } from "curvance"
|
|
348
|
+
|
|
349
|
+
const setup = await setupChain("monad-mainnet", wallet)
|
|
350
|
+
const token = setup.markets[0].tokens[0]
|
|
351
|
+
const zapperAddress = token.getPluginAddress("simple", "zapper")
|
|
352
|
+
if (zapperAddress == null) throw new Error("Simple zapper is not configured")
|
|
353
|
+
|
|
354
|
+
const zapper = new Zapper(
|
|
355
|
+
zapperAddress,
|
|
356
|
+
wallet,
|
|
357
|
+
"simple",
|
|
358
|
+
setup.setupConfigSnapshot,
|
|
359
|
+
setup.dexAgg,
|
|
360
|
+
)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
A direct `Zapper` built without the setup-bound adapter throws. A `Zapper`
|
|
364
|
+
from one setup result also refuses to build calldata for a CToken from another
|
|
365
|
+
setup result.
|
|
366
|
+
|
|
309
367
|
## ❯ Zapping (Swap + Deposit)
|
|
310
368
|
|
|
311
|
-
Zap deposits allow depositing
|
|
369
|
+
Zap deposits allow depositing another token by swapping to the required underlying through the setup-bound DEX aggregator.
|
|
370
|
+
|
|
371
|
+
`token.getDepositTokens(search?)` is the route-discovery entrypoint. It always
|
|
372
|
+
includes the direct deposit asset and then adds native, vault, and simple-swap
|
|
373
|
+
routes only when the token and chain can execute them. DEX-sourced simple
|
|
374
|
+
routes require a market-bound executable adapter; unsupported DEX chains expose
|
|
375
|
+
readable markets but no simple zap or simple leverage routes.
|
|
312
376
|
|
|
313
377
|
```ts
|
|
314
378
|
// Native token (MON) → deposit
|
|
315
379
|
await token.approvePlugin('native-simple', 'zapper')
|
|
316
|
-
await
|
|
380
|
+
await token.depositAsCollateral(amount, 'native-simple')
|
|
317
381
|
|
|
318
382
|
// Any ERC20 → swap → deposit
|
|
319
383
|
await token.approvePlugin('simple', 'zapper')
|
|
320
|
-
|
|
321
|
-
await token.depositAsCollateral(amount, {
|
|
384
|
+
const simpleZap = {
|
|
322
385
|
type: 'simple',
|
|
323
386
|
inputToken: inputTokenAddress,
|
|
324
387
|
slippage: new Decimal(0.01) // 1%
|
|
325
|
-
}
|
|
388
|
+
} as const
|
|
389
|
+
await token.approveZapAsset(simpleZap, amount)
|
|
390
|
+
await token.depositAsCollateral(amount, simpleZap)
|
|
326
391
|
```
|
|
327
392
|
|
|
328
393
|
Check approval status for a zap before executing:
|
|
329
394
|
|
|
330
395
|
```ts
|
|
331
|
-
const
|
|
396
|
+
const rawZapAmount = toBigInt(amount, inputTokenDecimals)
|
|
397
|
+
const approved = await token.isZapAssetApproved(instructions, rawZapAmount)
|
|
332
398
|
if (!approved) await token.approveZapAsset(instructions, amount)
|
|
333
399
|
```
|
|
334
400
|
|
|
@@ -338,11 +404,14 @@ Leverage uses the PositionManager plugin to atomically borrow and swap into the
|
|
|
338
404
|
|
|
339
405
|
```ts
|
|
340
406
|
// One-step: deposit collateral + leverage
|
|
341
|
-
|
|
342
|
-
|
|
407
|
+
const positionManager = collateralToken.getPluginAddress('simple', 'positionManager')
|
|
408
|
+
if (positionManager == null) throw new Error("Simple position manager is not configured")
|
|
409
|
+
|
|
410
|
+
await collateralToken.approveUnderlying(amount, positionManager)
|
|
343
411
|
await collateralToken.depositAndLeverage(amount, borrowToken, targetLeverage, 'simple', slippage)
|
|
344
412
|
|
|
345
413
|
// Separate: deposit first, then leverage
|
|
414
|
+
await collateralToken.approveUnderlying(amount)
|
|
346
415
|
await collateralToken.depositAsCollateral(amount)
|
|
347
416
|
await collateralToken.leverageUp(borrowToken, new Decimal(3), 'simple', new Decimal(0.005))
|
|
348
417
|
|
|
@@ -371,7 +440,7 @@ await market.previewPositionHealthDeposit(ctoken, amount)
|
|
|
371
440
|
await market.previewPositionHealthRedeem(ctoken, amount)
|
|
372
441
|
await market.previewPositionHealthBorrow(borrowToken, amount)
|
|
373
442
|
await market.previewPositionHealthRepay(borrowToken, amount)
|
|
374
|
-
await market.previewPositionHealthLeverageUp(depositCToken,
|
|
443
|
+
await market.previewPositionHealthLeverageUp(depositCToken, borrowCToken, newLeverage, depositAmount)
|
|
375
444
|
await market.previewPositionHealthLeverageDown(depositCToken, borrowCToken, newLeverage, currentLeverage)
|
|
376
445
|
|
|
377
446
|
// Generic preview
|
|
@@ -386,7 +455,7 @@ const health = await market.previewPositionHealthBorrow(borrowToken, new Decimal
|
|
|
386
455
|
if (health === null) {
|
|
387
456
|
// remains solvent with infinite health
|
|
388
457
|
} else if (health.lt(0.1)) {
|
|
389
|
-
console.warn("Would drop to 10% health
|
|
458
|
+
console.warn("Would drop to 10% health - too risky")
|
|
390
459
|
}
|
|
391
460
|
```
|
|
392
461
|
|
|
@@ -402,7 +471,10 @@ await market.multiHoldExpiresAt(markets) // cooldown across multiple markets
|
|
|
402
471
|
|
|
403
472
|
## ❯ Format Utilities
|
|
404
473
|
|
|
405
|
-
Pure calculation helpers for building UI or simulating outcomes.
|
|
474
|
+
Pure calculation helpers for building UI or simulating outcomes. Amount and
|
|
475
|
+
leverage helpers primarily use `Decimal`; validation, health-status, slippage,
|
|
476
|
+
and normalization helpers also expose `number`, `bigint`, string, and structured
|
|
477
|
+
result types where those are the safer UI boundary.
|
|
406
478
|
|
|
407
479
|
### Leverage math
|
|
408
480
|
|
|
@@ -431,13 +503,13 @@ import { amplifyContractSlippage, toContractSwapSlippage } from "curvance"
|
|
|
431
503
|
// user's raw `slippage` budget (reserved for variable DEX impact + drift).
|
|
432
504
|
amplifyContractSlippage(baseSlippageBps, leverageDelta, bpsToAmplify)
|
|
433
505
|
|
|
434
|
-
// Used by DEX aggregator adapters
|
|
506
|
+
// Used by DEX aggregator adapters in `quoteAction` to
|
|
435
507
|
// compute the WAD-BPS slippage tolerance for the `Swap.slippage` struct
|
|
436
|
-
// field consumed by on-chain `_swapSafe`. When
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
508
|
+
// field consumed by on-chain `_swapSafe`. When an adapter's fee model is
|
|
509
|
+
// represented as value loss in the swap calldata (for example Kyber's
|
|
510
|
+
// currency_in fee), pass `feeBps` so `_swapSafe`
|
|
511
|
+
// does not treat deterministic fee loss as user slippage. Adapters whose
|
|
512
|
+
// fees are not observable as swap value loss should omit `feeBps` / pass 0n.
|
|
441
513
|
toContractSwapSlippage(userSlippageBps, feeBps?)
|
|
442
514
|
```
|
|
443
515
|
|
|
@@ -491,7 +563,6 @@ amounts.normalizeCurrencyAmounts({ amount, currencyView, tokenDecimals, price })
|
|
|
491
563
|
import {
|
|
492
564
|
getContractAddresses,
|
|
493
565
|
contractSetup,
|
|
494
|
-
handleTransactionWithOracles,
|
|
495
566
|
toDecimal, toBigInt,
|
|
496
567
|
getDepositApy, getBorrowCost,
|
|
497
568
|
getInterestYield, getNativeYield,
|
|
@@ -507,11 +578,10 @@ import {
|
|
|
507
578
|
|---|---|
|
|
508
579
|
| `getContractAddresses(chain)` | All contract addresses for a chain |
|
|
509
580
|
| `contractSetup(provider, address, abi)` | Create a typed contract instance |
|
|
510
|
-
| `handleTransactionWithOracles(...)` | Wraps a tx in a Redstone multicall when a pull oracle price write is required |
|
|
511
581
|
| `toDecimal(value, decimals)` | `bigint` → `Decimal` |
|
|
512
582
|
| `toBigInt(value, decimals)` | `Decimal` → `bigint` |
|
|
513
583
|
| `getDepositApy(token, opportunities, apyOverrides)` | Total deposit yield (interest + Merkl + native) |
|
|
514
|
-
| `getBorrowCost(token, opportunities)` | Net borrow cost
|
|
584
|
+
| `getBorrowCost(token, opportunities)` | Net borrow cost; may be negative when rewards exceed rate |
|
|
515
585
|
| `getInterestYield(token)` | Lending APY only |
|
|
516
586
|
| `getNativeYield(token, apyOverrides)` | Native yield component |
|
|
517
587
|
| `getMerklDepositIncentives(tokenAddress, opportunities)` | Merkl reward APR for deposits |
|
|
@@ -522,25 +592,58 @@ import {
|
|
|
522
592
|
|
|
523
593
|
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.
|
|
524
594
|
|
|
595
|
+
For standard Curvance app/front-end usage, omit `options.feePolicy` and let
|
|
596
|
+
`setupChain()` build the default Curvance fee policy:
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
const { markets } = await setupChain("monad-mainnet", wallet)
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
The default policy charges `CURVANCE_FEE_BPS` and resolves the fee receiver from
|
|
603
|
+
`CentralRegistry.daoAddress()` once during setup. App consumers should not
|
|
604
|
+
hardcode or pin a DAO fee receiver locally. Pass `options.feePolicy` only for an
|
|
605
|
+
intentional custom integration override.
|
|
606
|
+
|
|
525
607
|
```ts
|
|
526
|
-
import {
|
|
608
|
+
import {
|
|
609
|
+
CURVANCE_FEE_BPS,
|
|
610
|
+
flatFeePolicy,
|
|
611
|
+
setupChain,
|
|
612
|
+
} from "curvance"
|
|
613
|
+
|
|
614
|
+
const defaultSetup = await setupChain("monad-mainnet", wallet)
|
|
527
615
|
|
|
528
616
|
const feePolicy = flatFeePolicy({
|
|
529
|
-
|
|
530
|
-
|
|
617
|
+
// Kyber-backed chains require one exact checker-compatible DEX fee.
|
|
618
|
+
bps: CURVANCE_FEE_BPS,
|
|
619
|
+
feeReceiver: defaultSetup.setupConfigSnapshot.feePolicy.feeReceiver,
|
|
531
620
|
chain: "monad-mainnet",
|
|
532
|
-
stableToStableBps: 2n, // optional lower fee for stable↔stable swaps
|
|
533
621
|
})
|
|
534
622
|
|
|
535
623
|
const { markets } = await setupChain("monad-mainnet", wallet, undefined, { feePolicy })
|
|
536
624
|
```
|
|
537
625
|
|
|
626
|
+
On Kyber-backed chains, setup validates explicit policies before rewards or
|
|
627
|
+
markets boot. A zero-fee policy, wrong BPS value, or wrong receiver rejects
|
|
628
|
+
with a checker-policy error. Context-dependent lower tiers such as
|
|
629
|
+
`stableToStableBps` are not valid for checker-bound Kyber routes because the
|
|
630
|
+
on-chain checker enforces one exact BPS value and DAO receiver. On
|
|
631
|
+
unsupported-Dex chains such as `arb-sepolia`, `NO_FEE_POLICY` remains valid and
|
|
632
|
+
setup skips the DAO lookup because no DEX route can execute there.
|
|
633
|
+
|
|
538
634
|
The SDK automatically returns 0 bps for native ↔ wrapped-native swaps and same-token no-op zaps.
|
|
539
635
|
|
|
540
636
|
```ts
|
|
541
|
-
// FeePolicy interface
|
|
637
|
+
// FeePolicy interface: implement your own
|
|
542
638
|
interface FeePolicy {
|
|
639
|
+
// "any" marks chain-agnostic no-op policies; chain-bound policies must match setupChain.
|
|
640
|
+
chain?: "monad-mainnet" | "arb-sepolia" | "any";
|
|
543
641
|
feeReceiver: address;
|
|
642
|
+
// Required on checker-bound routes when the policy is custom.
|
|
643
|
+
checkerCompatibility?: {
|
|
644
|
+
exactFeeBpsForDexSwaps: bigint;
|
|
645
|
+
feeReceiver: address;
|
|
646
|
+
};
|
|
544
647
|
getFeeBps(ctx: FeePolicyContext): bigint;
|
|
545
648
|
}
|
|
546
649
|
|
|
@@ -562,14 +665,18 @@ interface FeePolicyContext {
|
|
|
562
665
|
```ts
|
|
563
666
|
import { fetchMerklOpportunities, fetchMerklUserRewards, fetchMerklCampaignsBySymbol } from "curvance"
|
|
564
667
|
|
|
565
|
-
//
|
|
566
|
-
const opportunities = await fetchMerklOpportunities()
|
|
668
|
+
// Active opportunities for a production display path (APR, token, type)
|
|
669
|
+
const opportunities = await fetchMerklOpportunities({ chainId: 143 })
|
|
567
670
|
|
|
568
671
|
// Pending rewards for a user
|
|
569
672
|
const rewards = await fetchMerklUserRewards({ wallet: address, chainId: 143 })
|
|
570
673
|
|
|
571
|
-
// Campaigns for a specific token
|
|
572
|
-
const campaigns = await fetchMerklCampaignsBySymbol({ tokenSymbol: "USDC" })
|
|
674
|
+
// Campaigns for a specific token on one chain
|
|
675
|
+
const campaigns = await fetchMerklCampaignsBySymbol({ tokenSymbol: "USDC", chainId: 143 })
|
|
676
|
+
|
|
677
|
+
// Chainless Merkl calls are all-chain utilities. Filter explicitly before
|
|
678
|
+
// using them in production multichain display paths.
|
|
679
|
+
const allChainOpportunities = await fetchMerklOpportunities({})
|
|
573
680
|
```
|
|
574
681
|
|
|
575
682
|
### Portfolio snapshots
|
|
@@ -577,9 +684,11 @@ const campaigns = await fetchMerklCampaignsBySymbol({ tokenSymbol: "USDC" })
|
|
|
577
684
|
```ts
|
|
578
685
|
import { takePortfolioSnapshot, snapshotMarket } from "curvance"
|
|
579
686
|
|
|
580
|
-
// Full portfolio across
|
|
687
|
+
// Full portfolio across the current active-chain markets
|
|
581
688
|
const snapshot = await takePortfolioSnapshot(account)
|
|
582
689
|
// Returns: { account, chain, timestamp, totalDepositsUSD, totalDebtUSD, netUSD, dailyEarnings, dailyCost, markets[] }
|
|
690
|
+
// Each market row includes { chain, chainId }. Mixed-chain snapshots require:
|
|
691
|
+
// takePortfolioSnapshot(account, { markets, allowMixedChains: true })
|
|
583
692
|
|
|
584
693
|
// Single market
|
|
585
694
|
const marketSnapshot = snapshotMarket(market)
|
|
@@ -594,20 +703,26 @@ The `OptimizerReader` reads yield-rebalancing vaults that allocate across market
|
|
|
594
703
|
```ts
|
|
595
704
|
import { ERC20, LendingOptimizer, OptimizerReader } from "curvance"
|
|
596
705
|
|
|
597
|
-
const
|
|
706
|
+
const optimizerReader = new OptimizerReader(optimizerReaderAddress, provider)
|
|
598
707
|
|
|
599
|
-
await
|
|
708
|
+
await optimizerReader.getOptimizerAPY(optimizerAddress)
|
|
600
709
|
// Returns: weighted-average optimizer APY in WAD
|
|
601
710
|
|
|
602
|
-
await
|
|
603
|
-
// Returns: { totalAssets, sharePrice, performanceFee, apy, markets[] }
|
|
711
|
+
await optimizerReader.getOptimizerMarketData(optimizerAddresses)
|
|
712
|
+
// Returns: { totalAssets, sharePrice, performanceFee, apy, markets: [{ address, allocatedAssets, liquidity, allocationCap, allocationCapUtilizationBps }] }
|
|
604
713
|
|
|
605
|
-
await
|
|
714
|
+
await optimizerReader.getOptimizerUserData(optimizerAddresses, account)
|
|
606
715
|
// Returns: user balance and redeemable amounts
|
|
607
716
|
|
|
608
|
-
await
|
|
717
|
+
await optimizerReader.optimalRebalance(optimizerAddress, 100n)
|
|
609
718
|
// Returns: { actions: { cToken, assetsOrBps }[], bounds: { cToken, minBps, maxBps }[] }
|
|
610
719
|
|
|
720
|
+
await optimizerReader.optimalRebalanceAt(optimizerAddress, 100n, timestamp)
|
|
721
|
+
// Returns the rebalance plan projected at a specific timestamp
|
|
722
|
+
|
|
723
|
+
await optimizerReader.isBad(optimizerAddress)
|
|
724
|
+
// Returns bad cToken markets for the optimizer
|
|
725
|
+
|
|
611
726
|
const asset = new ERC20(provider, assetAddress, undefined, undefined, signer)
|
|
612
727
|
const vault = new LendingOptimizer(optimizerAddress, asset, provider, signer)
|
|
613
728
|
|
|
@@ -628,9 +743,25 @@ type USD_WAD = bigint // USD in 1e18 WAD format
|
|
|
628
743
|
type TokenInput = Decimal // human-readable token amount
|
|
629
744
|
type TypeBPS = bigint // basis points (10000 = 100%)
|
|
630
745
|
type ChainRpcPrefix = "monad-mainnet" | "arb-sepolia"
|
|
746
|
+
type ChainEnvironment = "production-mainnet" | "testnet" | "local"
|
|
747
|
+
type curvance_read_provider = JsonRpcProvider
|
|
631
748
|
type curvance_provider = JsonRpcSigner | Wallet | JsonRpcProvider
|
|
632
749
|
type curvance_signer = JsonRpcSigner | Wallet
|
|
633
750
|
|
|
751
|
+
interface SetupConfigSnapshot {
|
|
752
|
+
chain: ChainRpcPrefix
|
|
753
|
+
chainId: number
|
|
754
|
+
environment: ChainEnvironment
|
|
755
|
+
assets: Readonly<ChainAssetConfig>
|
|
756
|
+
services: Readonly<ChainServiceConfig>
|
|
757
|
+
contracts: Readonly<Record<string, unknown>>
|
|
758
|
+
readProvider: curvance_read_provider
|
|
759
|
+
signer: curvance_signer | null
|
|
760
|
+
account: address | null
|
|
761
|
+
api_url: string
|
|
762
|
+
feePolicy: FeePolicy
|
|
763
|
+
}
|
|
764
|
+
|
|
634
765
|
// Market categorization
|
|
635
766
|
type MarketCategory = "stablecoin" | "staking" | "restaking" | "yield-stablecoin" | "blue-chip" | "native"
|
|
636
767
|
type CollateralSource = "Renzo" | "Upshift" | "Yuzu" | "Native" | "Circle" | "Fastlane" | "Apriori" | "Mu Digital" | "Kintsu" | "Reservoir"
|
|
@@ -650,7 +781,9 @@ interface Quote {
|
|
|
650
781
|
}
|
|
651
782
|
```
|
|
652
783
|
|
|
653
|
-
|
|
784
|
+
Core monetary, token, share, health, APY, and fixed-point values use `bigint`
|
|
785
|
+
or `Decimal`. Backend API DTOs may expose raw `number` fields before SDK
|
|
786
|
+
normalization; do not use those DTO fields as contract-scale values.
|
|
654
787
|
|
|
655
788
|
## ❯ Constants
|
|
656
789
|
|
|
@@ -668,7 +801,7 @@ DEFAULT_SLIPPAGE_BPS // 100n (1%)
|
|
|
668
801
|
|
|
669
802
|
### Leverage tuning (`LEVERAGE`)
|
|
670
803
|
|
|
671
|
-
Exposed tuning block used by leverage preview / mutation paths. Values are considered tunable across releases
|
|
804
|
+
Exposed tuning block used by leverage preview / mutation paths. Values are considered tunable across releases. SDK consumers pinning against specific values opt into the coupling.
|
|
672
805
|
|
|
673
806
|
```ts
|
|
674
807
|
import { LEVERAGE } from 'curvance';
|
|
@@ -676,18 +809,18 @@ import { LEVERAGE } from 'curvance';
|
|
|
676
809
|
LEVERAGE.MAX_LEVERAGE_FACTOR // Decimal(0.98)
|
|
677
810
|
// Cap applied to the theoretical max leverage span. Reserves ~2% of the
|
|
678
811
|
// equity-fraction slippage budget for deterministic loss channels
|
|
679
|
-
// (CURVANCE_FEE_BPS, pool-fee variance,
|
|
812
|
+
// (CURVANCE_FEE_BPS, pool-fee variance, oracle drift, share rounding)
|
|
680
813
|
// that would otherwise push post-op LTV above collRatio at the boundary.
|
|
681
814
|
|
|
682
815
|
LEVERAGE.LEVERAGE_UP_BUFFER_BPS // 10n
|
|
683
816
|
// Flat BPS buffer added to leverage-up slippage for share-rounding + fresh
|
|
684
|
-
//
|
|
817
|
+
// Oracle price drift between snapshot RPC and tx broadcast. NOT amplified
|
|
685
818
|
// by (L-1); the contract's equity-fraction denominator handles amplification.
|
|
686
819
|
|
|
687
|
-
LEVERAGE.DELEVERAGE_OVERHEAD_BPS //
|
|
820
|
+
LEVERAGE.DELEVERAGE_OVERHEAD_BPS // 60n
|
|
688
821
|
// BPS overhead added to full-deleverage swap sizing to absorb DEX impact
|
|
689
822
|
// and oracle drift without leaving dust debt. The contract returns any
|
|
690
|
-
// excess debt token to the user, so economic loss is zero
|
|
823
|
+
// excess debt token to the user, so economic loss is zero, but
|
|
691
824
|
// `checkSlippage` treats the intentional overshoot as equity loss and
|
|
692
825
|
// amplifies it by (L-1), which the contract-slippage expansion compensates.
|
|
693
826
|
|
|
@@ -712,19 +845,68 @@ LEVERAGE.LEVERAGE_UP_VAULT_DRIFT_BPS // 30n
|
|
|
712
845
|
|---|---|
|
|
713
846
|
| [ethers v6](https://www.npmjs.com/package/ethers) | Typed contract interactions, providers, and signer handling |
|
|
714
847
|
| [decimal.js](https://www.npmjs.com/package/decimal.js) | Arbitrary-precision math for all token amounts, prices, and rates |
|
|
715
|
-
| [@redstone-finance/sdk](https://www.npmjs.com/package/@redstone-finance/sdk) | Price feed writes bundled into multicalls for pull-oracle adaptors |
|
|
716
848
|
|
|
717
|
-
## ❯ Pre-Publish Checklist
|
|
849
|
+
## ❯ SDK Pre-Publish Checklist
|
|
850
|
+
|
|
851
|
+
Run before every SDK `npm publish`:
|
|
852
|
+
|
|
853
|
+
1. **Typecheck, build, and deterministic transport gate green.**
|
|
854
|
+
|
|
855
|
+
```bash
|
|
856
|
+
node node_modules/typescript/bin/tsc --noEmit
|
|
857
|
+
npm run build
|
|
858
|
+
npm run test:transport
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
`npm test` is an alias for `test:transport`. `tests/rpc-config-shape.test.ts`
|
|
862
|
+
locks the structural invariants of `chain_rpc_config` (no known-bad RPCs,
|
|
863
|
+
no duplicate fallbacks, policy fields within sane ranges).
|
|
864
|
+
|
|
865
|
+
2. **Fork gate green, or explicitly classified as pending.** `npm run test:fork`
|
|
866
|
+
is the live fork/write gate. If it skips because `TEST_RPC`, deployer keys,
|
|
867
|
+
or a generated fixture are missing, the SDK can be called
|
|
868
|
+
deterministic/package covered, but not fork-covered.
|
|
869
|
+
|
|
870
|
+
3. **Package artifact smoke green.**
|
|
871
|
+
|
|
872
|
+
```bash
|
|
873
|
+
npm run test:dist-smoke
|
|
874
|
+
npm pack --dry-run --json
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
`prepack` and `prepublishOnly` rebuild `dist`, and `test:dist-smoke`
|
|
878
|
+
imports the packed package root. Package consumers load the artifact, so
|
|
879
|
+
source-green or build-green alone is not package-boundary proof.
|
|
880
|
+
The dry-run package should contain `README.md`, `package.json`, and `dist/**`;
|
|
881
|
+
source files and tests should not be published.
|
|
882
|
+
|
|
883
|
+
4. **Workspace hygiene clean.**
|
|
884
|
+
|
|
885
|
+
```bash
|
|
886
|
+
git diff --check
|
|
887
|
+
git status --short
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Confirm new imported production files are tracked. This matters because
|
|
891
|
+
dirty-tree tests can pass while a clean package checkout cannot import an
|
|
892
|
+
untracked source file.
|
|
893
|
+
|
|
894
|
+
### SDK gate interpretation
|
|
895
|
+
|
|
896
|
+
- `test:transport`, `test:all`, `test:dist-smoke`, `npm pack --dry-run --json`,
|
|
897
|
+
and `git diff --check` green means the SDK is deterministic/package covered.
|
|
898
|
+
- `test:fork` must execute against a local Anvil-compatible fork before calling
|
|
899
|
+
the SDK fork-covered. A command that exits 0 after skip messages is not live
|
|
900
|
+
fork proof.
|
|
901
|
+
- App build, app Cypress/Vitest, and app RPC-origin probes are downstream
|
|
902
|
+
adoption checks. Run them after publishing or linking the packed SDK into the
|
|
903
|
+
app repo; they are not part of the SDK-only publish gate.
|
|
718
904
|
|
|
719
|
-
|
|
720
|
-
`src/retry-provider.ts`, or any RPC-adjacent code:
|
|
905
|
+
### Post-Publish App Rollout Checks
|
|
721
906
|
|
|
722
|
-
|
|
723
|
-
passing. `tests/rpc-config-shape.test.ts` locks the structural invariants
|
|
724
|
-
of `chain_rpc_config` (no known-bad RPCs, no duplicate fallbacks, policy
|
|
725
|
-
fields within sane ranges).
|
|
907
|
+
After publishing or linking a packed SDK artifact into the app repo:
|
|
726
908
|
|
|
727
|
-
|
|
909
|
+
1. **For RPC-adjacent SDK changes, run the app-origin RPC probe.**
|
|
728
910
|
|
|
729
911
|
```bash
|
|
730
912
|
cd path/to/curvance-app
|
|
@@ -746,13 +928,12 @@ Run before every `npm publish` that touches `src/chains/`, `src/setup.ts`,
|
|
|
746
928
|
Deeper-cascade fallbacks (`fallbacks[1]+`) MAY have looser limits if
|
|
747
929
|
documented inline with a comment in `chain_rpc_config`.
|
|
748
930
|
|
|
749
|
-
|
|
931
|
+
2. **Do not add the probe to CI.** The probe fires ~500 requests per run
|
|
750
932
|
across 5-10 public RPCs from a single IP. Running it on every PR would
|
|
751
933
|
trip per-IP rate limits and eventually provoke origin bans from the
|
|
752
|
-
free RPCs we depend on
|
|
753
|
-
(monadinfra 403'ing `staging.curvance.com`) that motivated
|
|
754
|
-
this probe.
|
|
934
|
+
free RPCs we depend on. That recreates the exact failure mode
|
|
935
|
+
(monadinfra 403'ing `staging.curvance.com`) that motivated this probe.
|
|
755
936
|
|
|
756
|
-
|
|
757
|
-
bump `curvance` in `package.json` to the new version
|
|
758
|
-
|
|
937
|
+
3. **App rollout workflow.** Version bump -> `npm publish` -> in app repo,
|
|
938
|
+
bump `curvance` in `package.json` to the new version -> `yarn install`
|
|
939
|
+
-> commit `yarn.lock` -> deploy.
|