curvance 5.1.9 → 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 +249 -65
- 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 +28 -15
- package/dist/classes/CToken.d.ts.map +1 -1
- package/dist/classes/CToken.js +217 -85
- 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.map +1 -1
- package/dist/classes/LendingOptimizer.js +8 -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 -0
- package/dist/classes/index.d.ts.map +1 -1
- package/dist/classes/index.js +1 -0
- package/dist/classes/index.js.map +1 -1
- package/dist/contracts/index.d.ts +248 -248
- 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 +1 -1
- 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 +247 -248
- 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 -4
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
|
|
|
@@ -509,7 +581,7 @@ import {
|
|
|
509
581
|
| `toDecimal(value, decimals)` | `bigint` → `Decimal` |
|
|
510
582
|
| `toBigInt(value, decimals)` | `Decimal` → `bigint` |
|
|
511
583
|
| `getDepositApy(token, opportunities, apyOverrides)` | Total deposit yield (interest + Merkl + native) |
|
|
512
|
-
| `getBorrowCost(token, opportunities)` | Net borrow cost
|
|
584
|
+
| `getBorrowCost(token, opportunities)` | Net borrow cost; may be negative when rewards exceed rate |
|
|
513
585
|
| `getInterestYield(token)` | Lending APY only |
|
|
514
586
|
| `getNativeYield(token, apyOverrides)` | Native yield component |
|
|
515
587
|
| `getMerklDepositIncentives(tokenAddress, opportunities)` | Merkl reward APR for deposits |
|
|
@@ -520,25 +592,58 @@ import {
|
|
|
520
592
|
|
|
521
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.
|
|
522
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
|
+
|
|
523
607
|
```ts
|
|
524
|
-
import {
|
|
608
|
+
import {
|
|
609
|
+
CURVANCE_FEE_BPS,
|
|
610
|
+
flatFeePolicy,
|
|
611
|
+
setupChain,
|
|
612
|
+
} from "curvance"
|
|
613
|
+
|
|
614
|
+
const defaultSetup = await setupChain("monad-mainnet", wallet)
|
|
525
615
|
|
|
526
616
|
const feePolicy = flatFeePolicy({
|
|
527
|
-
|
|
528
|
-
|
|
617
|
+
// Kyber-backed chains require one exact checker-compatible DEX fee.
|
|
618
|
+
bps: CURVANCE_FEE_BPS,
|
|
619
|
+
feeReceiver: defaultSetup.setupConfigSnapshot.feePolicy.feeReceiver,
|
|
529
620
|
chain: "monad-mainnet",
|
|
530
|
-
stableToStableBps: 2n, // optional lower fee for stable↔stable swaps
|
|
531
621
|
})
|
|
532
622
|
|
|
533
623
|
const { markets } = await setupChain("monad-mainnet", wallet, undefined, { feePolicy })
|
|
534
624
|
```
|
|
535
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
|
+
|
|
536
634
|
The SDK automatically returns 0 bps for native ↔ wrapped-native swaps and same-token no-op zaps.
|
|
537
635
|
|
|
538
636
|
```ts
|
|
539
|
-
// FeePolicy interface
|
|
637
|
+
// FeePolicy interface: implement your own
|
|
540
638
|
interface FeePolicy {
|
|
639
|
+
// "any" marks chain-agnostic no-op policies; chain-bound policies must match setupChain.
|
|
640
|
+
chain?: "monad-mainnet" | "arb-sepolia" | "any";
|
|
541
641
|
feeReceiver: address;
|
|
642
|
+
// Required on checker-bound routes when the policy is custom.
|
|
643
|
+
checkerCompatibility?: {
|
|
644
|
+
exactFeeBpsForDexSwaps: bigint;
|
|
645
|
+
feeReceiver: address;
|
|
646
|
+
};
|
|
542
647
|
getFeeBps(ctx: FeePolicyContext): bigint;
|
|
543
648
|
}
|
|
544
649
|
|
|
@@ -560,14 +665,18 @@ interface FeePolicyContext {
|
|
|
560
665
|
```ts
|
|
561
666
|
import { fetchMerklOpportunities, fetchMerklUserRewards, fetchMerklCampaignsBySymbol } from "curvance"
|
|
562
667
|
|
|
563
|
-
//
|
|
564
|
-
const opportunities = await fetchMerklOpportunities()
|
|
668
|
+
// Active opportunities for a production display path (APR, token, type)
|
|
669
|
+
const opportunities = await fetchMerklOpportunities({ chainId: 143 })
|
|
565
670
|
|
|
566
671
|
// Pending rewards for a user
|
|
567
672
|
const rewards = await fetchMerklUserRewards({ wallet: address, chainId: 143 })
|
|
568
673
|
|
|
569
|
-
// Campaigns for a specific token
|
|
570
|
-
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({})
|
|
571
680
|
```
|
|
572
681
|
|
|
573
682
|
### Portfolio snapshots
|
|
@@ -575,9 +684,11 @@ const campaigns = await fetchMerklCampaignsBySymbol({ tokenSymbol: "USDC" })
|
|
|
575
684
|
```ts
|
|
576
685
|
import { takePortfolioSnapshot, snapshotMarket } from "curvance"
|
|
577
686
|
|
|
578
|
-
// Full portfolio across
|
|
687
|
+
// Full portfolio across the current active-chain markets
|
|
579
688
|
const snapshot = await takePortfolioSnapshot(account)
|
|
580
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 })
|
|
581
692
|
|
|
582
693
|
// Single market
|
|
583
694
|
const marketSnapshot = snapshotMarket(market)
|
|
@@ -592,20 +703,26 @@ The `OptimizerReader` reads yield-rebalancing vaults that allocate across market
|
|
|
592
703
|
```ts
|
|
593
704
|
import { ERC20, LendingOptimizer, OptimizerReader } from "curvance"
|
|
594
705
|
|
|
595
|
-
const
|
|
706
|
+
const optimizerReader = new OptimizerReader(optimizerReaderAddress, provider)
|
|
596
707
|
|
|
597
|
-
await
|
|
708
|
+
await optimizerReader.getOptimizerAPY(optimizerAddress)
|
|
598
709
|
// Returns: weighted-average optimizer APY in WAD
|
|
599
710
|
|
|
600
|
-
await
|
|
601
|
-
// Returns: { totalAssets, sharePrice, performanceFee, apy, markets[] }
|
|
711
|
+
await optimizerReader.getOptimizerMarketData(optimizerAddresses)
|
|
712
|
+
// Returns: { totalAssets, sharePrice, performanceFee, apy, markets: [{ address, allocatedAssets, liquidity, allocationCap, allocationCapUtilizationBps }] }
|
|
602
713
|
|
|
603
|
-
await
|
|
714
|
+
await optimizerReader.getOptimizerUserData(optimizerAddresses, account)
|
|
604
715
|
// Returns: user balance and redeemable amounts
|
|
605
716
|
|
|
606
|
-
await
|
|
717
|
+
await optimizerReader.optimalRebalance(optimizerAddress, 100n)
|
|
607
718
|
// Returns: { actions: { cToken, assetsOrBps }[], bounds: { cToken, minBps, maxBps }[] }
|
|
608
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
|
+
|
|
609
726
|
const asset = new ERC20(provider, assetAddress, undefined, undefined, signer)
|
|
610
727
|
const vault = new LendingOptimizer(optimizerAddress, asset, provider, signer)
|
|
611
728
|
|
|
@@ -626,9 +743,25 @@ type USD_WAD = bigint // USD in 1e18 WAD format
|
|
|
626
743
|
type TokenInput = Decimal // human-readable token amount
|
|
627
744
|
type TypeBPS = bigint // basis points (10000 = 100%)
|
|
628
745
|
type ChainRpcPrefix = "monad-mainnet" | "arb-sepolia"
|
|
746
|
+
type ChainEnvironment = "production-mainnet" | "testnet" | "local"
|
|
747
|
+
type curvance_read_provider = JsonRpcProvider
|
|
629
748
|
type curvance_provider = JsonRpcSigner | Wallet | JsonRpcProvider
|
|
630
749
|
type curvance_signer = JsonRpcSigner | Wallet
|
|
631
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
|
+
|
|
632
765
|
// Market categorization
|
|
633
766
|
type MarketCategory = "stablecoin" | "staking" | "restaking" | "yield-stablecoin" | "blue-chip" | "native"
|
|
634
767
|
type CollateralSource = "Renzo" | "Upshift" | "Yuzu" | "Native" | "Circle" | "Fastlane" | "Apriori" | "Mu Digital" | "Kintsu" | "Reservoir"
|
|
@@ -648,7 +781,9 @@ interface Quote {
|
|
|
648
781
|
}
|
|
649
782
|
```
|
|
650
783
|
|
|
651
|
-
|
|
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.
|
|
652
787
|
|
|
653
788
|
## ❯ Constants
|
|
654
789
|
|
|
@@ -666,7 +801,7 @@ DEFAULT_SLIPPAGE_BPS // 100n (1%)
|
|
|
666
801
|
|
|
667
802
|
### Leverage tuning (`LEVERAGE`)
|
|
668
803
|
|
|
669
|
-
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.
|
|
670
805
|
|
|
671
806
|
```ts
|
|
672
807
|
import { LEVERAGE } from 'curvance';
|
|
@@ -682,10 +817,10 @@ LEVERAGE.LEVERAGE_UP_BUFFER_BPS // 10n
|
|
|
682
817
|
// Oracle price drift between snapshot RPC and tx broadcast. NOT amplified
|
|
683
818
|
// by (L-1); the contract's equity-fraction denominator handles amplification.
|
|
684
819
|
|
|
685
|
-
LEVERAGE.DELEVERAGE_OVERHEAD_BPS //
|
|
820
|
+
LEVERAGE.DELEVERAGE_OVERHEAD_BPS // 60n
|
|
686
821
|
// BPS overhead added to full-deleverage swap sizing to absorb DEX impact
|
|
687
822
|
// and oracle drift without leaving dust debt. The contract returns any
|
|
688
|
-
// excess debt token to the user, so economic loss is zero
|
|
823
|
+
// excess debt token to the user, so economic loss is zero, but
|
|
689
824
|
// `checkSlippage` treats the intentional overshoot as equity loss and
|
|
690
825
|
// amplifies it by (L-1), which the contract-slippage expansion compensates.
|
|
691
826
|
|
|
@@ -711,17 +846,67 @@ LEVERAGE.LEVERAGE_UP_VAULT_DRIFT_BPS // 30n
|
|
|
711
846
|
| [ethers v6](https://www.npmjs.com/package/ethers) | Typed contract interactions, providers, and signer handling |
|
|
712
847
|
| [decimal.js](https://www.npmjs.com/package/decimal.js) | Arbitrary-precision math for all token amounts, prices, and rates |
|
|
713
848
|
|
|
714
|
-
## ❯ 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.
|
|
715
904
|
|
|
716
|
-
|
|
717
|
-
`src/retry-provider.ts`, or any RPC-adjacent code:
|
|
905
|
+
### Post-Publish App Rollout Checks
|
|
718
906
|
|
|
719
|
-
|
|
720
|
-
passing. `tests/rpc-config-shape.test.ts` locks the structural invariants
|
|
721
|
-
of `chain_rpc_config` (no known-bad RPCs, no duplicate fallbacks, policy
|
|
722
|
-
fields within sane ranges).
|
|
907
|
+
After publishing or linking a packed SDK artifact into the app repo:
|
|
723
908
|
|
|
724
|
-
|
|
909
|
+
1. **For RPC-adjacent SDK changes, run the app-origin RPC probe.**
|
|
725
910
|
|
|
726
911
|
```bash
|
|
727
912
|
cd path/to/curvance-app
|
|
@@ -743,13 +928,12 @@ Run before every `npm publish` that touches `src/chains/`, `src/setup.ts`,
|
|
|
743
928
|
Deeper-cascade fallbacks (`fallbacks[1]+`) MAY have looser limits if
|
|
744
929
|
documented inline with a comment in `chain_rpc_config`.
|
|
745
930
|
|
|
746
|
-
|
|
931
|
+
2. **Do not add the probe to CI.** The probe fires ~500 requests per run
|
|
747
932
|
across 5-10 public RPCs from a single IP. Running it on every PR would
|
|
748
933
|
trip per-IP rate limits and eventually provoke origin bans from the
|
|
749
|
-
free RPCs we depend on
|
|
750
|
-
(monadinfra 403'ing `staging.curvance.com`) that motivated
|
|
751
|
-
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.
|
|
752
936
|
|
|
753
|
-
|
|
754
|
-
bump `curvance` in `package.json` to the new version
|
|
755
|
-
|
|
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.
|