@velocity-exchange/sdk 0.4.0 → 0.6.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.
Files changed (323) hide show
  1. package/README.md +3 -3
  2. package/lib/browser/adminClient.d.ts +35 -0
  3. package/lib/browser/adminClient.js +65 -0
  4. package/lib/browser/config.js +6 -7
  5. package/lib/browser/constants/numericConstants.d.ts +1 -1
  6. package/lib/browser/constants/numericConstants.js +1 -1
  7. package/lib/browser/decode/user.js +5 -1
  8. package/lib/browser/idl/velocity.d.ts +146 -3
  9. package/lib/browser/idl/velocity.json +147 -4
  10. package/lib/browser/orderSubscriber/OrderSubscriber.js +3 -0
  11. package/lib/browser/pyth/pythLazerSubscriber.js +37 -27
  12. package/lib/browser/types.d.ts +4 -0
  13. package/lib/browser/user.d.ts +15 -0
  14. package/lib/browser/user.js +37 -1
  15. package/lib/browser/velocityClient.d.ts +46 -10
  16. package/lib/browser/velocityClient.js +107 -35
  17. package/lib/node/adminClient.d.ts +35 -0
  18. package/lib/node/adminClient.d.ts.map +1 -1
  19. package/lib/node/adminClient.js +65 -0
  20. package/lib/node/config.d.ts.map +1 -1
  21. package/lib/node/config.js +6 -7
  22. package/lib/node/constants/numericConstants.d.ts +1 -1
  23. package/lib/node/constants/numericConstants.d.ts.map +1 -1
  24. package/lib/node/constants/numericConstants.js +1 -1
  25. package/lib/node/decode/user.d.ts.map +1 -1
  26. package/lib/node/decode/user.js +5 -1
  27. package/lib/node/idl/velocity.d.ts +146 -3
  28. package/lib/node/idl/velocity.d.ts.map +1 -1
  29. package/lib/node/idl/velocity.json +147 -4
  30. package/lib/node/orderSubscriber/OrderSubscriber.d.ts.map +1 -1
  31. package/lib/node/orderSubscriber/OrderSubscriber.js +3 -0
  32. package/lib/node/pyth/pythLazerSubscriber.d.ts.map +1 -1
  33. package/lib/node/pyth/pythLazerSubscriber.js +37 -27
  34. package/lib/node/types.d.ts +4 -0
  35. package/lib/node/types.d.ts.map +1 -1
  36. package/lib/node/user.d.ts +15 -0
  37. package/lib/node/user.d.ts.map +1 -1
  38. package/lib/node/user.js +37 -1
  39. package/lib/node/velocityClient.d.ts +46 -10
  40. package/lib/node/velocityClient.d.ts.map +1 -1
  41. package/lib/node/velocityClient.js +107 -35
  42. package/package.json +7 -3
  43. package/.prettierignore +0 -1
  44. package/CHANGELOG.md +0 -132
  45. package/build-browser.js +0 -58
  46. package/bunfig.toml +0 -3
  47. package/esbuild-shims.js +0 -12
  48. package/scripts/deposit-isolated-positions.ts +0 -110
  49. package/scripts/find-flagged-users.ts +0 -213
  50. package/scripts/grpc-client-test-comparison.ts +0 -372
  51. package/scripts/grpc-multiuser-client-test-comparison.ts +0 -158
  52. package/scripts/postbuild.js +0 -95
  53. package/scripts/single-grpc-client-test.ts +0 -284
  54. package/scripts/withdraw-isolated-positions.ts +0 -174
  55. package/src/accounts/README_WebSocketAccountSubscriberV2.md +0 -95
  56. package/src/accounts/README_WebSocketProgramAccountSubscriberV2.md +0 -135
  57. package/src/accounts/basicUserAccountSubscriber.ts +0 -79
  58. package/src/accounts/basicUserStatsAccountSubscriber.ts +0 -87
  59. package/src/accounts/bulkAccountLoader.ts +0 -358
  60. package/src/accounts/bulkUserStatsSubscription.ts +0 -40
  61. package/src/accounts/bulkUserSubscription.ts +0 -39
  62. package/src/accounts/customizedCadenceBulkAccountLoader.ts +0 -250
  63. package/src/accounts/fetch.ts +0 -200
  64. package/src/accounts/grpcAccountSubscriber.ts +0 -225
  65. package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +0 -73
  66. package/src/accounts/grpcMultiAccountSubscriber.ts +0 -566
  67. package/src/accounts/grpcMultiUserAccountSubscriber.ts +0 -328
  68. package/src/accounts/grpcProgramAccountSubscriber.ts +0 -264
  69. package/src/accounts/grpcUserAccountSubscriber.ts +0 -63
  70. package/src/accounts/grpcUserStatsAccountSubscriber.ts +0 -65
  71. package/src/accounts/grpcVelocityClientAccountSubscriber.ts +0 -257
  72. package/src/accounts/grpcVelocityClientAccountSubscriberV2.ts +0 -941
  73. package/src/accounts/laserProgramAccountSubscriber.ts +0 -266
  74. package/src/accounts/oneShotUserAccountSubscriber.ts +0 -88
  75. package/src/accounts/oneShotUserStatsAccountSubscriber.ts +0 -94
  76. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +0 -223
  77. package/src/accounts/pollingOracleAccountSubscriber.ts +0 -151
  78. package/src/accounts/pollingTokenAccountSubscriber.ts +0 -147
  79. package/src/accounts/pollingUserAccountSubscriber.ts +0 -198
  80. package/src/accounts/pollingUserStatsAccountSubscriber.ts +0 -180
  81. package/src/accounts/pollingVelocityClientAccountSubscriber.ts +0 -774
  82. package/src/accounts/testBulkAccountLoader.ts +0 -59
  83. package/src/accounts/types.ts +0 -428
  84. package/src/accounts/utils.ts +0 -90
  85. package/src/accounts/webSocketAccountSubscriber.ts +0 -304
  86. package/src/accounts/webSocketAccountSubscriberV2.ts +0 -536
  87. package/src/accounts/webSocketInsuranceFundStakeAccountSubscriber.ts +0 -171
  88. package/src/accounts/webSocketProgramAccountSubscriber.ts +0 -244
  89. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +0 -1048
  90. package/src/accounts/webSocketProgramAccountsSubscriberV2.ts +0 -1042
  91. package/src/accounts/webSocketUserAccountSubscriber.ts +0 -139
  92. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +0 -131
  93. package/src/accounts/webSocketVelocityClientAccountSubscriber.ts +0 -865
  94. package/src/accounts/webSocketVelocityClientAccountSubscriberV2.ts +0 -897
  95. package/src/accounts/websocketProgramUserAccountSubscriber.ts +0 -127
  96. package/src/addresses/marketAddresses.ts +0 -28
  97. package/src/addresses/pda.ts +0 -620
  98. package/src/adminClient.ts +0 -8136
  99. package/src/assert/assert.ts +0 -5
  100. package/src/auctionSubscriber/auctionSubscriber.ts +0 -86
  101. package/src/auctionSubscriber/auctionSubscriberGrpc.ts +0 -99
  102. package/src/auctionSubscriber/index.ts +0 -9
  103. package/src/auctionSubscriber/types.ts +0 -30
  104. package/src/bankrun/bankrunConnection.ts +0 -630
  105. package/src/blockhashSubscriber/BlockhashSubscriber.ts +0 -160
  106. package/src/blockhashSubscriber/index.ts +0 -6
  107. package/src/blockhashSubscriber/types.ts +0 -13
  108. package/src/clock/clockSubscriber.ts +0 -146
  109. package/src/config.ts +0 -312
  110. package/src/constants/index.ts +0 -9
  111. package/src/constants/numericConstants.ts +0 -194
  112. package/src/constants/perpMarkets.ts +0 -127
  113. package/src/constants/spotMarkets.ts +0 -104
  114. package/src/constants/txConstants.ts +0 -2
  115. package/src/constituentMap/constituentMap.ts +0 -305
  116. package/src/constituentMap/pollingConstituentAccountSubscriber.ts +0 -99
  117. package/src/constituentMap/webSocketConstituentAccountSubscriber.ts +0 -121
  118. package/src/core/VelocityCore.ts +0 -606
  119. package/src/core/index.ts +0 -12
  120. package/src/core/instructions/deposit.ts +0 -64
  121. package/src/core/instructions/fill.ts +0 -51
  122. package/src/core/instructions/funding.ts +0 -33
  123. package/src/core/instructions/liquidation.ts +0 -55
  124. package/src/core/instructions/orders.ts +0 -84
  125. package/src/core/instructions/perpOrders.ts +0 -312
  126. package/src/core/instructions/settlement.ts +0 -40
  127. package/src/core/instructions/trigger.ts +0 -41
  128. package/src/core/instructions/withdraw.ts +0 -65
  129. package/src/core/remainingAccounts.ts +0 -320
  130. package/src/core/signedMsg.ts +0 -109
  131. package/src/decode/customCoder.ts +0 -148
  132. package/src/decode/user.ts +0 -401
  133. package/src/dlob/DLOB.ts +0 -2793
  134. package/src/dlob/DLOBNode.ts +0 -305
  135. package/src/dlob/DLOBSubscriber.ts +0 -269
  136. package/src/dlob/NodeList.ts +0 -244
  137. package/src/dlob/orderBookLevels.ts +0 -695
  138. package/src/dlob/types.ts +0 -37
  139. package/src/events/eventList.ts +0 -114
  140. package/src/events/eventSubscriber.ts +0 -521
  141. package/src/events/eventsServerLogProvider.ts +0 -189
  142. package/src/events/fetchLogs.ts +0 -219
  143. package/src/events/parse.ts +0 -271
  144. package/src/events/pollingLogProvider.ts +0 -113
  145. package/src/events/sort.ts +0 -51
  146. package/src/events/txEventCache.ts +0 -92
  147. package/src/events/types.ts +0 -337
  148. package/src/events/webSocketLogProvider.ts +0 -152
  149. package/src/factory/bigNum.ts +0 -709
  150. package/src/factory/oracleClient.ts +0 -73
  151. package/src/idl/pyth.json +0 -142
  152. package/src/idl/pyth.ts +0 -97
  153. package/src/idl/pyth_solana_receiver.json +0 -628
  154. package/src/idl/token_faucet.json +0 -229
  155. package/src/idl/token_faucet.ts +0 -197
  156. package/src/idl/velocity.json +0 -23974
  157. package/src/idl/velocity.ts +0 -23980
  158. package/src/index.ts +0 -150
  159. package/src/indicative-quotes/indicativeQuotesSender.ts +0 -233
  160. package/src/isomorphic/README.md +0 -19
  161. package/src/isomorphic/anchor.browser.ts +0 -44
  162. package/src/isomorphic/anchor.node.ts +0 -1
  163. package/src/isomorphic/anchor.ts +0 -1
  164. package/src/isomorphic/anchor29.browser.ts +0 -24
  165. package/src/isomorphic/anchor29.node.ts +0 -1
  166. package/src/isomorphic/anchor29.ts +0 -1
  167. package/src/isomorphic/grpc.browser.ts +0 -4
  168. package/src/isomorphic/grpc.node.ts +0 -130
  169. package/src/isomorphic/grpc.ts +0 -1
  170. package/src/jupiter/jupiterClient.ts +0 -502
  171. package/src/keypair.ts +0 -32
  172. package/src/margin/README.md +0 -138
  173. package/src/marginCalculation.ts +0 -407
  174. package/src/marinade/idl/idl.json +0 -1962
  175. package/src/marinade/index.ts +0 -73
  176. package/src/marinade/types.ts +0 -3925
  177. package/src/math/amm.ts +0 -1758
  178. package/src/math/auction.ts +0 -480
  179. package/src/math/bankruptcy.ts +0 -138
  180. package/src/math/builder.ts +0 -130
  181. package/src/math/conversion.ts +0 -45
  182. package/src/math/exchangeStatus.ts +0 -261
  183. package/src/math/funding.ts +0 -498
  184. package/src/math/insurance.ts +0 -171
  185. package/src/math/liquidation.ts +0 -433
  186. package/src/math/margin.ts +0 -327
  187. package/src/math/market.ts +0 -487
  188. package/src/math/oracles.ts +0 -477
  189. package/src/math/orders.ts +0 -500
  190. package/src/math/position.ts +0 -381
  191. package/src/math/repeg.ts +0 -223
  192. package/src/math/spotBalance.ts +0 -996
  193. package/src/math/spotMarket.ts +0 -115
  194. package/src/math/spotPosition.ts +0 -263
  195. package/src/math/state.ts +0 -71
  196. package/src/math/superStake.ts +0 -602
  197. package/src/math/tiers.ts +0 -73
  198. package/src/math/trade.ts +0 -879
  199. package/src/math/utils.ts +0 -183
  200. package/src/memcmp.ts +0 -279
  201. package/src/oracles/oracleClientCache.ts +0 -41
  202. package/src/oracles/oracleId.ts +0 -99
  203. package/src/oracles/prelaunchOracleClient.ts +0 -63
  204. package/src/oracles/pythClient.ts +0 -125
  205. package/src/oracles/pythLazerClient.ts +0 -141
  206. package/src/oracles/quoteAssetOracleClient.ts +0 -43
  207. package/src/oracles/strictOraclePrice.ts +0 -40
  208. package/src/oracles/types.ts +0 -82
  209. package/src/oracles/utils.ts +0 -45
  210. package/src/orderParams.ts +0 -117
  211. package/src/orderSubscriber/OrderSubscriber.ts +0 -378
  212. package/src/orderSubscriber/PollingSubscription.ts +0 -50
  213. package/src/orderSubscriber/WebsocketSubscription.ts +0 -137
  214. package/src/orderSubscriber/grpcSubscription.ts +0 -155
  215. package/src/orderSubscriber/index.ts +0 -7
  216. package/src/orderSubscriber/types.ts +0 -64
  217. package/src/priorityFee/averageOverSlotsStrategy.ts +0 -17
  218. package/src/priorityFee/averageStrategy.ts +0 -13
  219. package/src/priorityFee/ewmaStrategy.ts +0 -51
  220. package/src/priorityFee/heliusPriorityFeeMethod.ts +0 -68
  221. package/src/priorityFee/index.ts +0 -16
  222. package/src/priorityFee/maxOverSlotsStrategy.ts +0 -18
  223. package/src/priorityFee/maxStrategy.ts +0 -8
  224. package/src/priorityFee/priorityFeeSubscriber.ts +0 -328
  225. package/src/priorityFee/priorityFeeSubscriberMap.ts +0 -134
  226. package/src/priorityFee/solanaPriorityFeeMethod.ts +0 -46
  227. package/src/priorityFee/types.ts +0 -82
  228. package/src/priorityFee/velocityPriorityFeeMethod.ts +0 -53
  229. package/src/pyth/constants.ts +0 -9
  230. package/src/pyth/index.ts +0 -15
  231. package/src/pyth/pythLazerSubscriber.ts +0 -365
  232. package/src/pyth/types.ts +0 -4451
  233. package/src/pyth/utils.ts +0 -13
  234. package/src/slot/SlotSubscriber.ts +0 -126
  235. package/src/slot/SlothashSubscriber.ts +0 -163
  236. package/src/swap/UnifiedSwapClient.ts +0 -315
  237. package/src/swift/grpcSignedMsgUserAccountSubscriber.ts +0 -81
  238. package/src/swift/index.ts +0 -10
  239. package/src/swift/signedMsgUserAccountSubscriber.ts +0 -251
  240. package/src/swift/swiftOrderSubscriber.ts +0 -351
  241. package/src/testClient.ts +0 -42
  242. package/src/titan/titanClient.ts +0 -438
  243. package/src/token/index.ts +0 -13
  244. package/src/tokenFaucet.ts +0 -296
  245. package/src/tx/baseTxSender.ts +0 -651
  246. package/src/tx/blockhashFetcher/baseBlockhashFetcher.ts +0 -31
  247. package/src/tx/blockhashFetcher/cachedBlockhashFetcher.ts +0 -105
  248. package/src/tx/blockhashFetcher/types.ts +0 -9
  249. package/src/tx/fastSingleTxSender.ts +0 -200
  250. package/src/tx/forwardOnlyTxSender.ts +0 -193
  251. package/src/tx/priorityFeeCalculator.ts +0 -117
  252. package/src/tx/reportTransactionError.ts +0 -176
  253. package/src/tx/retryTxSender.ts +0 -177
  254. package/src/tx/txHandler.ts +0 -1009
  255. package/src/tx/txParamProcessor.ts +0 -225
  256. package/src/tx/types.ts +0 -117
  257. package/src/tx/utils.ts +0 -133
  258. package/src/tx/whileValidTxSender.ts +0 -377
  259. package/src/types.ts +0 -2575
  260. package/src/user.ts +0 -5316
  261. package/src/userConfig.ts +0 -38
  262. package/src/userMap/PollingSubscription.ts +0 -61
  263. package/src/userMap/WebsocketSubscription.ts +0 -93
  264. package/src/userMap/grpcSubscription.ts +0 -93
  265. package/src/userMap/referrerMap.ts +0 -360
  266. package/src/userMap/revenueShareEscrowMap.ts +0 -342
  267. package/src/userMap/userMap.ts +0 -784
  268. package/src/userMap/userMapConfig.ts +0 -82
  269. package/src/userMap/userStatsMap.ts +0 -393
  270. package/src/userName.ts +0 -37
  271. package/src/userStats.ts +0 -159
  272. package/src/userStatsConfig.ts +0 -35
  273. package/src/util/TransactionConfirmationManager.ts +0 -333
  274. package/src/util/chainClock.ts +0 -65
  275. package/src/util/computeUnits.ts +0 -101
  276. package/src/util/digest.ts +0 -33
  277. package/src/util/ed25519Utils.ts +0 -95
  278. package/src/util/promiseTimeout.ts +0 -23
  279. package/src/velocityClient.ts +0 -14802
  280. package/src/velocityClientConfig.ts +0 -119
  281. package/src/wallet.ts +0 -91
  282. package/tests/VelocityCore/builder_escrow.test.ts +0 -121
  283. package/tests/VelocityCore/decode.test.ts +0 -16
  284. package/tests/VelocityCore/fill_trigger.test.ts +0 -73
  285. package/tests/VelocityCore/instructions.test.ts +0 -46
  286. package/tests/VelocityCore/pdas.test.ts +0 -30
  287. package/tests/VelocityCore/perp_orders.test.ts +0 -205
  288. package/tests/VelocityCore/remainingAccounts.test.ts +0 -72
  289. package/tests/VelocityCore/settlement_liquidation.test.ts +0 -69
  290. package/tests/accounts/customizedCadenceBulkAccountLoader.test.ts +0 -211
  291. package/tests/amm/test.ts +0 -2402
  292. package/tests/auctions/test.ts +0 -81
  293. package/tests/bn/test.ts +0 -355
  294. package/tests/builder/builderFee.test.ts +0 -42
  295. package/tests/ci/verifyConstants.ts +0 -393
  296. package/tests/decode/test.ts +0 -262
  297. package/tests/decode/userAccountBufferStrings.ts +0 -102
  298. package/tests/dlob/helpers.ts +0 -748
  299. package/tests/dlob/test.ts +0 -6969
  300. package/tests/dlob/tickSizeStandardization.ts +0 -545
  301. package/tests/events/parseLogsForCuUsage.ts +0 -139
  302. package/tests/exchangeStatus/test.ts +0 -45
  303. package/tests/insurance/test.ts +0 -40
  304. package/tests/liquidation/test.ts +0 -125
  305. package/tests/oracles/mmOracleGate.test.ts +0 -379
  306. package/tests/oracles/pythPegSnap.test.ts +0 -76
  307. package/tests/sdkParity/enumParity.test.ts +0 -84
  308. package/tests/sdkParity/marginCategoryFill.test.ts +0 -143
  309. package/tests/sdkParity/memcmpOffsets.test.ts +0 -139
  310. package/tests/spot/test.ts +0 -281
  311. package/tests/tx/TransactionConfirmationManager.test.ts +0 -312
  312. package/tests/tx/cachedBlockhashFetcher.test.ts +0 -96
  313. package/tests/tx/priorityFeeCalculator.ts +0 -77
  314. package/tests/tx/priorityFeeStrategy.ts +0 -95
  315. package/tests/user/bankruptcy.ts +0 -165
  316. package/tests/user/feeAndWithdrawLimits.ts +0 -284
  317. package/tests/user/getMarginCalculation.ts +0 -586
  318. package/tests/user/helpers.ts +0 -184
  319. package/tests/user/liquidations.ts +0 -129
  320. package/tests/user/marginCalculations.test.ts +0 -321
  321. package/tests/user/test.ts +0 -833
  322. package/tsconfig.browser.json +0 -18
  323. package/tsconfig.json +0 -36
@@ -1,996 +0,0 @@
1
- import {
2
- SpotMarketAccount,
3
- SpotBalanceType,
4
- isVariant,
5
- MarginCategory,
6
- } from '../types';
7
- import { BN } from '../isomorphic/anchor';
8
- import {
9
- SPOT_MARKET_UTILIZATION_PRECISION,
10
- ONE,
11
- TEN,
12
- ZERO,
13
- SPOT_MARKET_RATE_PRECISION,
14
- SPOT_MARKET_WEIGHT_PRECISION,
15
- ONE_YEAR,
16
- AMM_RESERVE_PRECISION,
17
- QUOTE_SPOT_MARKET_INDEX,
18
- } from '../constants/numericConstants';
19
- import {
20
- calculateSizeDiscountAssetWeight,
21
- calculateSizePremiumLiabilityWeight,
22
- } from './margin';
23
- import { OraclePriceData } from '../oracles/types';
24
- import { PERCENTAGE_PRECISION } from '../constants/numericConstants';
25
- import { divCeil } from './utils';
26
- import { StrictOraclePrice } from '../oracles/strictOraclePrice';
27
-
28
- // BN's `.div()` truncates toward zero; the program uses `safe_div_floor` when
29
- // the numerator is negative (get_token_value / get_strict_token_value), so a
30
- // negative dividend must round toward -infinity here to match.
31
- function divFloor(a: BN, b: BN): BN {
32
- const quotient = a.div(b);
33
- const remainder = a.mod(b);
34
- if (!remainder.isZero() && a.isNeg() !== b.isNeg()) {
35
- return quotient.sub(ONE);
36
- }
37
- return quotient;
38
- }
39
-
40
- /**
41
- * Calculates the balance of a given token amount including any accumulated interest. This
42
- * is the same as `SpotPosition.scaledBalance`.
43
- *
44
- * @param {BN} tokenAmount - the amount of tokens
45
- * @param {SpotMarketAccount} spotMarket - the spot market account
46
- * @param {SpotBalanceType} balanceType - the balance type ('deposit' or 'borrow')
47
- * @param {boolean} [roundUp] - override the default rounding direction (program's `round_up`);
48
- * defaults to rounding up for borrows only. Callers reducing a deposit balance while the
49
- * funds are leaving Velocity (e.g. a withdrawal) should pass `true` to match `is_leaving_velocity`.
50
- * @return {BN} the calculated balance, scaled by `SPOT_MARKET_BALANCE_PRECISION`
51
- */
52
- export function getBalance(
53
- tokenAmount: BN,
54
- spotMarket: SpotMarketAccount,
55
- balanceType: SpotBalanceType,
56
- roundUp?: boolean
57
- ): BN {
58
- const precisionIncrease = TEN.pow(new BN(19 - spotMarket.decimals));
59
-
60
- const cumulativeInterest = isVariant(balanceType, 'deposit')
61
- ? spotMarket.cumulativeDepositInterest
62
- : spotMarket.cumulativeBorrowInterest;
63
-
64
- let balance = tokenAmount.mul(precisionIncrease).div(cumulativeInterest);
65
-
66
- const shouldRoundUp = roundUp ?? isVariant(balanceType, 'borrow');
67
- if (!balance.eq(ZERO) && shouldRoundUp) {
68
- balance = balance.add(ONE);
69
- }
70
-
71
- return balance;
72
- }
73
-
74
- /**
75
- * Calculates the spot token amount including any accumulated interest.
76
- *
77
- * @param {BN} balanceAmount - The balance amount, typically from `SpotPosition.scaledBalance`
78
- * @param {SpotMarketAccount} spotMarket - The spot market account details
79
- * @param {SpotBalanceType} balanceType - The balance type to be used for calculation
80
- * @returns {BN} The calculated token amount, scaled by `SpotMarketConfig.precision`
81
- */
82
- export function getTokenAmount(
83
- balanceAmount: BN,
84
- spotMarket: SpotMarketAccount,
85
- balanceType: SpotBalanceType
86
- ): BN {
87
- const precisionDecrease = TEN.pow(new BN(19 - spotMarket.decimals));
88
- if (isVariant(balanceType, 'deposit')) {
89
- return balanceAmount
90
- .mul(spotMarket.cumulativeDepositInterest)
91
- .div(precisionDecrease);
92
- } else {
93
- return divCeil(
94
- balanceAmount.mul(spotMarket.cumulativeBorrowInterest),
95
- precisionDecrease
96
- );
97
- }
98
- }
99
-
100
- /**
101
- * Returns the signed (positive for deposit,negative for borrow) token amount based on the balance type.
102
- *
103
- * @param {BN} tokenAmount - The token amount to convert (from `getTokenAmount`)
104
- * @param {SpotBalanceType} balanceType - The balance type to determine the sign of the token amount.
105
- * @returns {BN} - The signed token amount, scaled by `SpotMarketConfig.precision`
106
- */
107
- export function getSignedTokenAmount(
108
- tokenAmount: BN,
109
- balanceType: SpotBalanceType
110
- ): BN {
111
- if (isVariant(balanceType, 'deposit')) {
112
- return tokenAmount;
113
- } else {
114
- return tokenAmount.abs().neg();
115
- }
116
- }
117
-
118
- /**
119
- * Calculates the value of a given token amount using the worst of the provided oracle price and its TWAP.
120
- *
121
- * @param {BN} tokenAmount - The amount of tokens to calculate the value for (from `getTokenAmount`)
122
- * @param {number} spotDecimals - The number of decimals in the token.
123
- * @param {StrictOraclePrice} strictOraclePrice - Contains oracle price and 5min twap.
124
- * @return {BN} The calculated value of the given token amount, scaled by `PRICE_PRECISION`
125
- */
126
- export function getStrictTokenValue(
127
- tokenAmount: BN,
128
- spotDecimals: number,
129
- strictOraclePrice: StrictOraclePrice
130
- ): BN {
131
- if (tokenAmount.eq(ZERO)) {
132
- return ZERO;
133
- }
134
-
135
- let price;
136
- if (tokenAmount.gte(ZERO)) {
137
- price = strictOraclePrice.min();
138
- } else {
139
- price = strictOraclePrice.max();
140
- }
141
-
142
- const precisionDecrease = TEN.pow(new BN(spotDecimals));
143
- const tokenWithPrice = tokenAmount.mul(price);
144
-
145
- if (tokenWithPrice.isNeg()) {
146
- return divFloor(tokenWithPrice, precisionDecrease);
147
- }
148
- return tokenWithPrice.div(precisionDecrease);
149
- }
150
-
151
- /**
152
- * Calculates the value of a given token amount in relation to an oracle price data
153
- *
154
- * @param {BN} tokenAmount - The amount of tokens to calculate the value for (from `getTokenAmount`)
155
- * @param {number} spotDecimals - The number of decimal places of the token.
156
- * @param {OraclePriceData} oraclePriceData - The oracle price data (typically a token/USD oracle).
157
- * @return {BN} The value of the token based on the oracle, scaled by `PRICE_PRECISION`
158
- */
159
- export function getTokenValue(
160
- tokenAmount: BN,
161
- spotDecimals: number,
162
- oraclePriceData: Pick<OraclePriceData, 'price'>
163
- ): BN {
164
- if (tokenAmount.eq(ZERO)) {
165
- return ZERO;
166
- }
167
-
168
- const precisionDecrease = TEN.pow(new BN(spotDecimals));
169
- const tokenWithOraclePrice = tokenAmount.mul(oraclePriceData.price);
170
-
171
- if (tokenWithOraclePrice.isNeg()) {
172
- return divFloor(tokenWithOraclePrice, precisionDecrease);
173
- }
174
- return tokenWithOraclePrice.div(precisionDecrease);
175
- }
176
-
177
- /**
178
- * Calculates the collateral (asset) weight applied to a spot deposit balance, mirroring
179
- * `SpotMarket::get_asset_weight`'s `Initial`/`Maintenance` branches (there is no SDK
180
- * equivalent of the on-chain `Fill` branch, which averages initial and maintenance).
181
- * Size is first rescaled into `AMM_RESERVE_PRECISION` before the IMF size-discount is applied,
182
- * so larger positions receive a lower (more conservative) weight.
183
- *
184
- * @param {BN} balanceAmount - The deposit token amount, scaled by the spot market's token decimals
185
- * @param {BN} oraclePrice - The oracle price, PRICE_PRECISION (1e6); only used for the `Initial`
186
- * scaled-weight lookup (`calculateScaledInitialAssetWeight`)
187
- * @param {SpotMarketAccount} spotMarket - The spot market account
188
- * @param {MarginCategory | undefined} marginCategory - `'Initial'`, `'Maintenance'`, `'Fill'`
189
- * (the integer-averaged midpoint of scaled-initial and maintenance weights), or `undefined`
190
- * (defaults to the scaled initial weight, used for e.g. UI display outside a margin check)
191
- * @return {BN} The asset weight, scaled by `SPOT_MARKET_WEIGHT_PRECISION` (1e4, i.e. 10000 = 100%)
192
- */
193
- export function calculateAssetWeight(
194
- balanceAmount: BN,
195
- oraclePrice: BN,
196
- spotMarket: SpotMarketAccount,
197
- marginCategory: MarginCategory | undefined
198
- ): BN {
199
- const sizePrecision = TEN.pow(new BN(spotMarket.decimals));
200
- let sizeInAmmReservePrecision;
201
- if (sizePrecision.gt(AMM_RESERVE_PRECISION)) {
202
- sizeInAmmReservePrecision = balanceAmount.div(
203
- sizePrecision.div(AMM_RESERVE_PRECISION)
204
- );
205
- } else {
206
- sizeInAmmReservePrecision = balanceAmount
207
- .mul(AMM_RESERVE_PRECISION)
208
- .div(sizePrecision);
209
- }
210
-
211
- let assetWeight;
212
-
213
- switch (marginCategory) {
214
- case 'Initial':
215
- assetWeight = calculateSizeDiscountAssetWeight(
216
- sizeInAmmReservePrecision,
217
- new BN(spotMarket.imfFactor),
218
- calculateScaledInitialAssetWeight(spotMarket, oraclePrice)
219
- );
220
- break;
221
- case 'Fill':
222
- // mirrors SpotMarket::get_asset_weight's Fill branch:
223
- // (scaled_initial_asset_weight + maintenance_asset_weight) / 2 (integer division)
224
- assetWeight = calculateSizeDiscountAssetWeight(
225
- sizeInAmmReservePrecision,
226
- new BN(spotMarket.imfFactor),
227
- calculateScaledInitialAssetWeight(spotMarket, oraclePrice)
228
- .add(new BN(spotMarket.maintenanceAssetWeight))
229
- .divn(2)
230
- );
231
- break;
232
- case 'Maintenance':
233
- assetWeight = calculateSizeDiscountAssetWeight(
234
- sizeInAmmReservePrecision,
235
- new BN(spotMarket.imfFactor),
236
- new BN(spotMarket.maintenanceAssetWeight)
237
- );
238
- break;
239
- default:
240
- assetWeight = calculateScaledInitialAssetWeight(spotMarket, oraclePrice);
241
- break;
242
- }
243
-
244
- return assetWeight;
245
- }
246
-
247
- /**
248
- * Calculates the initial asset weight after applying the market's optional deposit-value
249
- * scaling, mirroring `SpotMarket::get_scaled_initial_asset_weight`. When
250
- * `scaleInitialAssetWeightStart` is set and total deposit value exceeds it, the weight is
251
- * scaled down proportionally (`initialAssetWeight * scaleInitialAssetWeightStart / depositsValue`)
252
- * so the market's collateral usefulness degrades as its deposits grow past the configured cap.
253
- *
254
- * @param {SpotMarketAccount} spotMarket - The spot market account
255
- * @param {BN} oraclePrice - The oracle price, PRICE_PRECISION (1e6), used to value total deposits
256
- * @return {BN} The (possibly scaled) initial asset weight, `SPOT_MARKET_WEIGHT_PRECISION` (1e4)
257
- */
258
- export function calculateScaledInitialAssetWeight(
259
- spotMarket: SpotMarketAccount,
260
- oraclePrice: BN
261
- ): BN {
262
- if (spotMarket.scaleInitialAssetWeightStart.eq(ZERO)) {
263
- return new BN(spotMarket.initialAssetWeight);
264
- }
265
-
266
- const deposits = getTokenAmount(
267
- spotMarket.depositBalance,
268
- spotMarket,
269
- SpotBalanceType.DEPOSIT
270
- );
271
- const depositsValue = getTokenValue(deposits, spotMarket.decimals, {
272
- price: oraclePrice,
273
- });
274
-
275
- if (depositsValue.lt(spotMarket.scaleInitialAssetWeightStart)) {
276
- return new BN(spotMarket.initialAssetWeight);
277
- } else {
278
- return new BN(spotMarket.initialAssetWeight)
279
- .mul(spotMarket.scaleInitialAssetWeightStart)
280
- .div(depositsValue);
281
- }
282
- }
283
-
284
- /**
285
- * Calculates the liability (borrow) weight applied to a spot borrow balance, mirroring
286
- * `SpotMarket::get_liability_weight`'s `Initial`/`Maintenance` branches. Size is rescaled into
287
- * `AMM_RESERVE_PRECISION` before the IMF size-premium is applied, so larger borrows receive a
288
- * higher (more conservative) weight.
289
- *
290
- * @param {BN} size - The borrow token amount, scaled by the spot market's token decimals
291
- * @param {SpotMarketAccount} spotMarket - The spot market account
292
- * @param {MarginCategory | undefined} marginCategory - `'Initial'`, `'Maintenance'`, `'Fill'`
293
- * (the integer-averaged midpoint of initial and maintenance liability weights), or
294
- * `undefined` (defaults to `initialLiabilityWeight` with no size premium applied)
295
- * @return {BN} The liability weight, scaled by `SPOT_MARKET_WEIGHT_PRECISION` (1e4, i.e. 10000 = 100%)
296
- */
297
- export function calculateLiabilityWeight(
298
- size: BN,
299
- spotMarket: SpotMarketAccount,
300
- marginCategory: MarginCategory | undefined
301
- ): BN {
302
- const sizePrecision = TEN.pow(new BN(spotMarket.decimals));
303
- let sizeInAmmReservePrecision;
304
- if (sizePrecision.gt(AMM_RESERVE_PRECISION)) {
305
- sizeInAmmReservePrecision = size.div(
306
- sizePrecision.div(AMM_RESERVE_PRECISION)
307
- );
308
- } else {
309
- sizeInAmmReservePrecision = size
310
- .mul(AMM_RESERVE_PRECISION)
311
- .div(sizePrecision);
312
- }
313
-
314
- let liabilityWeight;
315
-
316
- switch (marginCategory) {
317
- case 'Initial':
318
- liabilityWeight = calculateSizePremiumLiabilityWeight(
319
- sizeInAmmReservePrecision,
320
- new BN(spotMarket.imfFactor),
321
- new BN(spotMarket.initialLiabilityWeight),
322
- SPOT_MARKET_WEIGHT_PRECISION
323
- );
324
- break;
325
- case 'Fill':
326
- // mirrors SpotMarket::get_liability_weight's Fill branch:
327
- // (initial_liability_weight + maintenance_liability_weight) / 2 (integer division)
328
- liabilityWeight = calculateSizePremiumLiabilityWeight(
329
- sizeInAmmReservePrecision,
330
- new BN(spotMarket.imfFactor),
331
- new BN(spotMarket.initialLiabilityWeight)
332
- .add(new BN(spotMarket.maintenanceLiabilityWeight))
333
- .divn(2),
334
- SPOT_MARKET_WEIGHT_PRECISION
335
- );
336
- break;
337
- case 'Maintenance':
338
- liabilityWeight = calculateSizePremiumLiabilityWeight(
339
- sizeInAmmReservePrecision,
340
- new BN(spotMarket.imfFactor),
341
- new BN(spotMarket.maintenanceLiabilityWeight),
342
- SPOT_MARKET_WEIGHT_PRECISION
343
- );
344
- break;
345
- default:
346
- liabilityWeight = new BN(spotMarket.initialLiabilityWeight);
347
- break;
348
- }
349
-
350
- return liabilityWeight;
351
- }
352
-
353
- /**
354
- * Calculates a spot market's utilization (borrows / deposits), mirroring
355
- * `calculate_utilization`. Returns `SPOT_MARKET_UTILIZATION_PRECISION` (100% utilization) if
356
- * there are borrows but no deposits, and zero if both are zero.
357
- *
358
- * @param {SpotMarketAccount} bank - The spot market account
359
- * @param {BN} [delta] - Optional hypothetical change in token amount, scaled by the market's
360
- * token decimals: a positive delta is added to deposits, a negative delta (its absolute
361
- * value) is added to borrows. Defaults to zero (current on-chain utilization).
362
- * @return {BN} Utilization, scaled by `SPOT_MARKET_UTILIZATION_PRECISION` (1e6, i.e. 1e6 = 100%)
363
- */
364
- export function calculateUtilization(
365
- bank: SpotMarketAccount,
366
- delta = ZERO
367
- ): BN {
368
- let tokenDepositAmount = getTokenAmount(
369
- bank.depositBalance,
370
- bank,
371
- SpotBalanceType.DEPOSIT
372
- );
373
- let tokenBorrowAmount = getTokenAmount(
374
- bank.borrowBalance,
375
- bank,
376
- SpotBalanceType.BORROW
377
- );
378
-
379
- if (delta.gt(ZERO)) {
380
- tokenDepositAmount = tokenDepositAmount.add(delta);
381
- } else if (delta.lt(ZERO)) {
382
- tokenBorrowAmount = tokenBorrowAmount.add(delta.abs());
383
- }
384
-
385
- let utilization: BN;
386
- if (tokenBorrowAmount.eq(ZERO) && tokenDepositAmount.eq(ZERO)) {
387
- utilization = ZERO;
388
- } else if (tokenDepositAmount.eq(ZERO)) {
389
- utilization = SPOT_MARKET_UTILIZATION_PRECISION;
390
- } else {
391
- utilization = tokenBorrowAmount
392
- .mul(SPOT_MARKET_UTILIZATION_PRECISION)
393
- .div(tokenDepositAmount);
394
- }
395
-
396
- return utilization;
397
- }
398
-
399
- /**
400
- * SDK-only helper (no direct on-chain counterpart) that inverts `calculateInterestRate`'s
401
- * utilization curve to find how much more can be borrowed before the borrow rate would reach
402
- * `targetBorrowRate`. Useful for UI "available to borrow at rate X" displays.
403
- *
404
- * @param {SpotMarketAccount} spotMarketAccount - The spot market account
405
- * @param {BN} targetBorrowRate - The target annualized borrow rate, `SPOT_MARKET_RATE_PRECISION` (1e6)
406
- * @returns {{ totalCapacity: BN; remainingCapacity: BN }} Both scaled by the market's token
407
- * decimals. `totalCapacity` is the total borrow amount implied by the target utilization;
408
- * `remainingCapacity` is `totalCapacity` minus current borrows (zero if the market's current
409
- * borrow rate already meets or exceeds the target), additionally capped by
410
- * `maxTokenBorrowsFraction` of `maxTokenDeposits` when that cap is configured (>0)
411
- */
412
- export function calculateSpotMarketBorrowCapacity(
413
- spotMarketAccount: SpotMarketAccount,
414
- targetBorrowRate: BN
415
- ): { totalCapacity: BN; remainingCapacity: BN } {
416
- const currentBorrowRate = calculateBorrowRate(spotMarketAccount);
417
-
418
- const tokenDepositAmount = getTokenAmount(
419
- spotMarketAccount.depositBalance,
420
- spotMarketAccount,
421
- SpotBalanceType.DEPOSIT
422
- );
423
-
424
- const tokenBorrowAmount = getTokenAmount(
425
- spotMarketAccount.borrowBalance,
426
- spotMarketAccount,
427
- SpotBalanceType.BORROW
428
- );
429
-
430
- let targetUtilization;
431
- // target utilization past mid point
432
- if (targetBorrowRate.gte(new BN(spotMarketAccount.optimalBorrowRate))) {
433
- const borrowRateSlope = new BN(
434
- spotMarketAccount.maxBorrowRate - spotMarketAccount.optimalBorrowRate
435
- )
436
- .mul(SPOT_MARKET_UTILIZATION_PRECISION)
437
- .div(
438
- SPOT_MARKET_UTILIZATION_PRECISION.sub(
439
- new BN(spotMarketAccount.optimalUtilization)
440
- )
441
- );
442
-
443
- const surplusTargetUtilization = targetBorrowRate
444
- .sub(new BN(spotMarketAccount.optimalBorrowRate))
445
- .mul(SPOT_MARKET_UTILIZATION_PRECISION)
446
- .div(borrowRateSlope);
447
-
448
- targetUtilization = surplusTargetUtilization.add(
449
- new BN(spotMarketAccount.optimalUtilization)
450
- );
451
- } else {
452
- const borrowRateSlope = new BN(spotMarketAccount.optimalBorrowRate)
453
- .mul(SPOT_MARKET_UTILIZATION_PRECISION)
454
- .div(new BN(spotMarketAccount.optimalUtilization));
455
-
456
- targetUtilization = targetBorrowRate
457
- .mul(SPOT_MARKET_UTILIZATION_PRECISION)
458
- .div(borrowRateSlope);
459
- }
460
-
461
- const totalCapacity = tokenDepositAmount
462
- .mul(targetUtilization)
463
- .div(SPOT_MARKET_UTILIZATION_PRECISION);
464
-
465
- let remainingCapacity;
466
- if (currentBorrowRate.gte(targetBorrowRate)) {
467
- remainingCapacity = ZERO;
468
- } else {
469
- remainingCapacity = BN.max(ZERO, totalCapacity.sub(tokenBorrowAmount));
470
- }
471
-
472
- if (spotMarketAccount.maxTokenBorrowsFraction > 0) {
473
- const maxTokenBorrows = spotMarketAccount.maxTokenDeposits
474
- .mul(new BN(spotMarketAccount.maxTokenBorrowsFraction))
475
- .divn(10000);
476
-
477
- remainingCapacity = BN.min(
478
- remainingCapacity,
479
- BN.max(ZERO, maxTokenBorrows.sub(tokenBorrowAmount))
480
- );
481
- }
482
-
483
- return { totalCapacity, remainingCapacity };
484
- }
485
-
486
- /**
487
- * Calculates the annualized borrow interest rate for a spot market, mirroring
488
- * `calculate_borrow_rate` / the underlying utilization curve. Below `optimalUtilization` the
489
- * rate ramps linearly from 0 to `optimalBorrowRate`; above it, the rate ramps through a fixed
490
- * piecewise schedule (85/90/95/99/99.5/100% utilization breakpoints) from `optimalBorrowRate`
491
- * up to `maxBorrowRate`. The result is floored at `minBorrowRate / 200` (i.e. `minBorrowRate`
492
- * is in units of half-percentage-points of `PERCENTAGE_PRECISION`).
493
- *
494
- * @param {SpotMarketAccount} bank - The spot market account
495
- * @param {BN} [delta] - Optional hypothetical change in token amount passed through to
496
- * `calculateUtilization` (ignored if `currentUtilization` is provided)
497
- * @param {BN} [currentUtilization] - Precomputed utilization, `SPOT_MARKET_UTILIZATION_PRECISION`
498
- * (1e6); if omitted it is derived from `bank` and `delta`
499
- * @return {BN} Annualized borrow rate, scaled by `SPOT_MARKET_RATE_PRECISION` (1e6)
500
- */
501
- export function calculateInterestRate(
502
- bank: SpotMarketAccount,
503
- delta = ZERO,
504
- currentUtilization?: BN
505
- ): BN {
506
- // todo: ensure both a delta and current util aren't pass?
507
- const utilization = currentUtilization ?? calculateUtilization(bank, delta);
508
-
509
- const optimalUtil = new BN(bank.optimalUtilization);
510
- const optimalRate = new BN(bank.optimalBorrowRate);
511
- const maxRate = new BN(bank.maxBorrowRate);
512
- const minRate = new BN(bank.minBorrowRate).mul(
513
- PERCENTAGE_PRECISION.divn(200)
514
- );
515
-
516
- const weightsDivisor = new BN(1000);
517
- const segments: [BN, BN][] = [
518
- [new BN(850_000), new BN(50)],
519
- [new BN(900_000), new BN(100)],
520
- [new BN(950_000), new BN(150)],
521
- [new BN(990_000), new BN(200)],
522
- [new BN(995_000), new BN(250)],
523
- [SPOT_MARKET_UTILIZATION_PRECISION, new BN(250)],
524
- ];
525
-
526
- let rate: BN;
527
- if (utilization.lte(optimalUtil)) {
528
- // below optimal: linear ramp from 0 to optimalRate
529
- const slope = optimalRate
530
- .mul(SPOT_MARKET_UTILIZATION_PRECISION)
531
- .div(optimalUtil);
532
- rate = utilization.mul(slope).div(SPOT_MARKET_UTILIZATION_PRECISION);
533
- } else {
534
- // above optimal: piecewise segments
535
- const totalExtraRate = maxRate.sub(optimalRate);
536
-
537
- rate = optimalRate.clone();
538
- let prevUtil = optimalUtil.clone();
539
-
540
- for (const [bp, weight] of segments) {
541
- const segmentEnd = bp.gt(SPOT_MARKET_UTILIZATION_PRECISION)
542
- ? SPOT_MARKET_UTILIZATION_PRECISION
543
- : bp;
544
- const segmentRange = segmentEnd.sub(prevUtil);
545
-
546
- const segmentRateTotal = totalExtraRate.mul(weight).div(weightsDivisor);
547
-
548
- if (utilization.lte(segmentEnd)) {
549
- const partialUtil = utilization.sub(prevUtil);
550
- const partialRate = segmentRateTotal.mul(partialUtil).div(segmentRange);
551
- rate = rate.add(partialRate);
552
- break;
553
- } else {
554
- rate = rate.add(segmentRateTotal);
555
- prevUtil = segmentEnd;
556
- }
557
- }
558
- }
559
-
560
- return BN.max(minRate, rate);
561
- }
562
-
563
- /**
564
- * Calculates the annualized deposit interest rate for a spot market, mirroring
565
- * `calculate_deposit_rate` (velocity-rs). Lenders receive the borrow rate net of the insurance
566
- * fund and protocol fee carveouts (`ifFeeFactor` + `protocolFeeFactor`, both `PERCENTAGE_PRECISION`),
567
- * scaled down by utilization since only borrowed deposits earn interest.
568
- *
569
- * @param {SpotMarketAccount} bank - The spot market account
570
- * @param {BN} [delta] - Optional hypothetical change in token amount; positive adds to deposits,
571
- * negative adds to borrows (see `calculateUtilization`)
572
- * @param {BN} [currentUtilization] - Precomputed utilization, `SPOT_MARKET_UTILIZATION_PRECISION`
573
- * (1e6); if omitted it is derived from `bank` and `delta`
574
- * @return {BN} Annualized deposit rate, scaled by `SPOT_MARKET_RATE_PRECISION` (1e6)
575
- */
576
- export function calculateDepositRate(
577
- bank: SpotMarketAccount,
578
- delta = ZERO,
579
- currentUtilization?: BN
580
- ): BN {
581
- // positive delta => adding to deposit
582
- // negative delta => adding to borrow
583
-
584
- const utilization = currentUtilization ?? calculateUtilization(bank, delta);
585
- const borrowRate = calculateBorrowRate(bank, delta, utilization);
586
- const depositRate = borrowRate
587
- .mul(
588
- PERCENTAGE_PRECISION.sub(
589
- new BN(bank.insuranceFund.ifFeeFactor + bank.protocolFeeFactor)
590
- )
591
- )
592
- .mul(utilization)
593
- .div(SPOT_MARKET_UTILIZATION_PRECISION)
594
- .div(PERCENTAGE_PRECISION);
595
- return depositRate;
596
- }
597
-
598
- /**
599
- * Alias for `calculateInterestRate` (annualized borrow rate).
600
- *
601
- * @param {SpotMarketAccount} bank - The spot market account
602
- * @param {BN} [delta] - Optional hypothetical change in token amount (see `calculateUtilization`)
603
- * @param {BN} [currentUtilization] - Precomputed utilization, `SPOT_MARKET_UTILIZATION_PRECISION` (1e6)
604
- * @return {BN} Annualized borrow rate, scaled by `SPOT_MARKET_RATE_PRECISION` (1e6)
605
- */
606
- export function calculateBorrowRate(
607
- bank: SpotMarketAccount,
608
- delta = ZERO,
609
- currentUtilization?: BN
610
- ): BN {
611
- return calculateInterestRate(bank, delta, currentUtilization);
612
- }
613
-
614
- /**
615
- * Projects the cumulative interest multipliers that would accrue between `spotMarket.lastInterestTs`
616
- * and `now` at the market's current interest rate, mirroring the gross amounts computed by
617
- * `calculate_accumulated_interest`. This is a point-in-time estimate for display purposes only —
618
- * the actual on-chain update (`update_spot_market_cumulative_interest`) re-derives the rate from
619
- * utilization at settlement time (same as this function calling `calculateInterestRate(bank)` with
620
- * no delta), and only runs at all if `deposit_interest > 0 && borrow_interest > 1`. Borrow interest
621
- * is always rounded up by 1 (added unconditionally), matching the program's lender-favoring
622
- * rounding, and is credited to `cumulativeBorrowInterest` in full. **`depositInterest` here is the
623
- * gross pre-carveout amount** — on-chain, `insuranceFund.ifFeeFactor` and `protocolFeeFactor`
624
- * (both `IF_FACTOR_PRECISION`) are each cut from it first (to `revenuePool` and `protocolFeePool`
625
- * respectively) and only the remainder is what actually gets added to `cumulativeDepositInterest`;
626
- * this function does not replicate that split, so it overstates the deposit-side increment
627
- * whenever either factor is non-zero.
628
- *
629
- * @param {SpotMarketAccount} bank - The spot market account
630
- * @param {BN} now - The timestamp (unix seconds) to project interest up to
631
- * @return {{ borrowInterest: BN; depositInterest: BN }} `borrowInterest` is the exact amount added
632
- * to `cumulativeBorrowInterest`; `depositInterest` is the gross pre-carveout amount, not
633
- * necessarily what's added to `cumulativeDepositInterest` (see above). Both in the same
634
- * fixed-point units as those cumulative fields (`SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION`)
635
- */
636
- export function calculateInterestAccumulated(
637
- bank: SpotMarketAccount,
638
- now: BN
639
- ): { borrowInterest: BN; depositInterest: BN } {
640
- const interestRate = calculateInterestRate(bank);
641
-
642
- const timeSinceLastUpdate = now.sub(bank.lastInterestTs);
643
-
644
- const modifiedBorrowRate = interestRate.mul(timeSinceLastUpdate);
645
-
646
- const utilization = calculateUtilization(bank);
647
-
648
- const modifiedDepositRate = modifiedBorrowRate
649
- .mul(utilization)
650
- .div(SPOT_MARKET_UTILIZATION_PRECISION);
651
-
652
- const borrowInterest = bank.cumulativeBorrowInterest
653
- .mul(modifiedBorrowRate)
654
- .div(ONE_YEAR)
655
- .div(SPOT_MARKET_RATE_PRECISION)
656
- .add(ONE);
657
- const depositInterest = bank.cumulativeDepositInterest
658
- .mul(modifiedDepositRate)
659
- .div(ONE_YEAR)
660
- .div(SPOT_MARKET_RATE_PRECISION);
661
-
662
- return { borrowInterest, depositInterest };
663
- }
664
-
665
- /**
666
- * Calculates the minimum deposit / maximum borrow token amounts that keep the market's
667
- * utilization from exceeding a "max withdraw utilization" ceiling, mirroring
668
- * `calculate_token_utilization_limits`. The ceiling is `max(optimalUtilization,
669
- * utilizationTwap + (100% - utilizationTwap) / 2)` — i.e. it allows utilization to rise, but
670
- * only up to halfway from the TWAP to 100%. Deposit sizes already below
671
- * `withdrawGuardThreshold` are never blocked (the min-deposit result is capped so it can't
672
- * exceed `depositTokenAmount - withdrawGuardThreshold`), and borrows below the guard threshold
673
- * are never blocked either (the max-borrow result is floored at `withdrawGuardThreshold`).
674
- *
675
- * @param {BN} depositTokenAmount - Current total deposit token amount, market's token decimals
676
- * @param {BN} borrowTokenAmount - Current total borrow token amount, market's token decimals
677
- * @param {SpotMarketAccount} spotMarket - The spot market account
678
- * @return {{ minDepositTokensForUtilization: BN; maxBorrowTokensForUtilization: BN }} Both
679
- * scaled by the market's token decimals
680
- */
681
- export function calculateTokenUtilizationLimits(
682
- depositTokenAmount: BN,
683
- borrowTokenAmount: BN,
684
- spotMarket: SpotMarketAccount
685
- ): {
686
- minDepositTokensForUtilization: BN;
687
- maxBorrowTokensForUtilization: BN;
688
- } {
689
- // Calculates the allowable minimum deposit and maximum borrow amounts for immediate withdrawal based on market utilization.
690
- // First, it determines a maximum withdrawal utilization from the market's target and historic utilization.
691
- // Then, it deduces corresponding deposit/borrow amounts.
692
- // Note: For deposit sizes below the guard threshold, withdrawals aren't blocked.
693
-
694
- const maxWithdrawUtilization = BN.max(
695
- new BN(spotMarket.optimalUtilization),
696
- spotMarket.utilizationTwap.add(
697
- SPOT_MARKET_UTILIZATION_PRECISION.sub(spotMarket.utilizationTwap).div(
698
- new BN(2)
699
- )
700
- )
701
- );
702
-
703
- let minDepositTokensForUtilization = borrowTokenAmount
704
- .mul(SPOT_MARKET_UTILIZATION_PRECISION)
705
- .div(maxWithdrawUtilization);
706
-
707
- // don't block withdraws for deposit sizes below guard threshold
708
- minDepositTokensForUtilization = BN.min(
709
- minDepositTokensForUtilization,
710
- depositTokenAmount.sub(spotMarket.withdrawGuardThreshold)
711
- );
712
-
713
- let maxBorrowTokensForUtilization = maxWithdrawUtilization
714
- .mul(depositTokenAmount)
715
- .div(SPOT_MARKET_UTILIZATION_PRECISION);
716
-
717
- maxBorrowTokensForUtilization = BN.max(
718
- spotMarket.withdrawGuardThreshold,
719
- maxBorrowTokensForUtilization
720
- );
721
-
722
- return {
723
- minDepositTokensForUtilization,
724
- maxBorrowTokensForUtilization,
725
- };
726
- }
727
-
728
- /**
729
- * Estimates the current immediate withdraw/borrow limits for a spot market, mirroring the
730
- * on-chain `check_withdraw_limits` / `get_max_withdraw_for_market_with_token_amount` guard
731
- * (combining `calculate_min_deposit_token_amount`, `calculate_max_borrow_token_amount`, and
732
- * `calculateTokenUtilizationLimits`). Because the SDK cannot force an on-chain TWAP update
733
- * before reading it, this projects a "live" 24h deposit/borrow TWAP by weighting the stored
734
- * TWAP and the current amount by `sinceStart`/`sinceLast` (the same weighted-average shape as
735
- * `update_spot_market_twap_stats`, without its rounding bias term) before deriving limits, so
736
- * the result approximates what the on-chain TWAP would be if updated at `now`.
737
- *
738
- * Deposit/borrow TWAP friction bands differ by pool: the main pool (`poolId === 0`) targets
739
- * ~30-92.5% utilization (borrow ceiling is `lesserDepositAmount` clamped between 1/3 and
740
- * 13/14 of itself, floored around the live borrow TWAP + 1/5), isolated pools (`poolId !== 0`)
741
- * target ~50-95% (clamped between 1/2 and 19/20, floored around the live borrow TWAP + 1/3).
742
- * `lesserDepositAmount` is `min(currentDepositAmount, live deposit TWAP)` — using the smaller of
743
- * the two keeps the borrow ceiling conservative whether deposits are rising or falling.
744
- * `borrowLimit` is additionally zeroed for `assetTier === 'protected'` markets, and both limits
745
- * are clamped by `maxTokenBorrowsFraction` of `maxTokenDeposits` when that cap is configured.
746
- *
747
- * @param {SpotMarketAccount} spotMarket - The spot market account
748
- * @param {BN} now - The timestamp (unix seconds) to project the live TWAP up to
749
- * @return {{ borrowLimit: BN; withdrawLimit: BN; minDepositAmount: BN; maxBorrowAmount: BN;
750
- * currentDepositAmount: BN; currentBorrowAmount: BN }} All values scaled by the market's token
751
- * decimals. `withdrawLimit`/`borrowLimit` are floored at zero (a market already past its
752
- * min-deposit/max-borrow bound reports zero remaining room rather than negative)
753
- */
754
- export function calculateWithdrawLimit(
755
- spotMarket: SpotMarketAccount,
756
- now: BN
757
- ): {
758
- borrowLimit: BN;
759
- withdrawLimit: BN;
760
- minDepositAmount: BN;
761
- maxBorrowAmount: BN;
762
- currentDepositAmount: BN;
763
- currentBorrowAmount: BN;
764
- } {
765
- const marketDepositTokenAmount = getTokenAmount(
766
- spotMarket.depositBalance,
767
- spotMarket,
768
- SpotBalanceType.DEPOSIT
769
- );
770
- const marketBorrowTokenAmount = getTokenAmount(
771
- spotMarket.borrowBalance,
772
- spotMarket,
773
- SpotBalanceType.BORROW
774
- );
775
-
776
- const twentyFourHours = new BN(60 * 60 * 24);
777
- const sinceLast = now.sub(spotMarket.lastTwapTs);
778
- const sinceStart = BN.max(ZERO, twentyFourHours.sub(sinceLast));
779
- const borrowTokenTwapLive = spotMarket.borrowTokenTwap
780
- .mul(sinceStart)
781
- .add(marketBorrowTokenAmount.mul(sinceLast))
782
- .div(sinceLast.add(sinceStart));
783
-
784
- const depositTokenTwapLive = spotMarket.depositTokenTwap
785
- .mul(sinceStart)
786
- .add(marketDepositTokenAmount.mul(sinceLast))
787
- .div(sinceLast.add(sinceStart));
788
-
789
- const lesserDepositAmount = BN.min(
790
- marketDepositTokenAmount,
791
- depositTokenTwapLive
792
- );
793
- let maxBorrowTokensTwap;
794
-
795
- if (spotMarket.poolId == 0) {
796
- maxBorrowTokensTwap = BN.max(
797
- spotMarket.withdrawGuardThreshold,
798
- BN.min(
799
- BN.max(
800
- lesserDepositAmount.div(new BN(3)),
801
- borrowTokenTwapLive.add(lesserDepositAmount.div(new BN(5)))
802
- ),
803
- lesserDepositAmount.sub(lesserDepositAmount.div(new BN(14)))
804
- )
805
- ); // main pool between ~30-92.5% utilization with friction on twap in 20% increments
806
- } else {
807
- maxBorrowTokensTwap = BN.max(
808
- spotMarket.withdrawGuardThreshold,
809
- BN.min(
810
- BN.max(
811
- lesserDepositAmount.div(new BN(2)),
812
- borrowTokenTwapLive.add(lesserDepositAmount.div(new BN(3)))
813
- ),
814
- lesserDepositAmount.sub(lesserDepositAmount.div(new BN(20)))
815
- )
816
- ); // isolated pools between 50-95% utilization with friction on twap in 33% increments
817
- }
818
-
819
- const minDepositTokensTwap = depositTokenTwapLive.sub(
820
- BN.max(
821
- depositTokenTwapLive.div(new BN(4)),
822
- BN.min(spotMarket.withdrawGuardThreshold, depositTokenTwapLive)
823
- )
824
- );
825
-
826
- const { minDepositTokensForUtilization, maxBorrowTokensForUtilization } =
827
- calculateTokenUtilizationLimits(
828
- marketDepositTokenAmount,
829
- marketBorrowTokenAmount,
830
- spotMarket
831
- );
832
-
833
- const minDepositTokens = BN.max(
834
- minDepositTokensForUtilization,
835
- minDepositTokensTwap
836
- );
837
-
838
- let maxBorrowTokens = BN.min(
839
- maxBorrowTokensForUtilization,
840
- maxBorrowTokensTwap
841
- );
842
-
843
- const withdrawLimit = BN.max(
844
- marketDepositTokenAmount.sub(minDepositTokens),
845
- ZERO
846
- );
847
-
848
- let borrowLimit = maxBorrowTokens.sub(marketBorrowTokenAmount);
849
-
850
- borrowLimit = BN.min(
851
- borrowLimit,
852
- marketDepositTokenAmount.sub(marketBorrowTokenAmount)
853
- );
854
-
855
- if (spotMarket.maxTokenBorrowsFraction > 0) {
856
- const maxTokenBorrowsByFraction = spotMarket.maxTokenDeposits
857
- .mul(new BN(spotMarket.maxTokenBorrowsFraction))
858
- .divn(10000);
859
-
860
- const trueMaxBorrowTokensAvailable = maxTokenBorrowsByFraction.sub(
861
- marketBorrowTokenAmount
862
- );
863
-
864
- maxBorrowTokens = BN.min(maxBorrowTokens, trueMaxBorrowTokensAvailable);
865
-
866
- borrowLimit = BN.min(borrowLimit, maxBorrowTokens);
867
- }
868
-
869
- if (withdrawLimit.eq(ZERO) || isVariant(spotMarket.assetTier, 'protected')) {
870
- borrowLimit = ZERO;
871
- }
872
-
873
- return {
874
- borrowLimit,
875
- withdrawLimit,
876
- maxBorrowAmount: maxBorrowTokens,
877
- minDepositAmount: minDepositTokens,
878
- currentDepositAmount: marketDepositTokenAmount,
879
- currentBorrowAmount: marketBorrowTokenAmount,
880
- };
881
- }
882
-
883
- /**
884
- * Calculates the margin-weighted value of a spot deposit, mirroring the asset-side of the
885
- * program's collateral valuation (`get_strict_token_value` + `get_asset_weight`). Uses the
886
- * worst of the oracle's live price and its 5min TWAP (via `strictOraclePrice`) so a favorable
887
- * price spike can't be used to over-value collateral.
888
- *
889
- * @param {BN} tokenAmount - The deposit token amount, scaled by `spotMarketAccount.decimals`
890
- * @param {StrictOraclePrice} strictOraclePrice - Live oracle price + 5min TWAP, PRICE_PRECISION (1e6)
891
- * @param {SpotMarketAccount} spotMarketAccount - The spot market account
892
- * @param {number} maxMarginRatio - The user's custom max margin ratio (0 if unset), in
893
- * `SPOT_MARKET_WEIGHT_PRECISION` (1e4) units; only applied when `marginCategory === 'Initial'`
894
- * and the market isn't the quote spot market, capping the weight at
895
- * `SPOT_MARKET_WEIGHT_PRECISION - maxMarginRatio`
896
- * @param {MarginCategory} [marginCategory] - When omitted, returns the unweighted (100%) value
897
- * @return {BN} The (optionally weighted) asset value, scaled by `PRICE_PRECISION` (1e6)
898
- */
899
- export function getSpotAssetValue(
900
- tokenAmount: BN,
901
- strictOraclePrice: StrictOraclePrice,
902
- spotMarketAccount: SpotMarketAccount,
903
- maxMarginRatio: number,
904
- marginCategory?: MarginCategory
905
- ): BN {
906
- let assetValue = getStrictTokenValue(
907
- tokenAmount,
908
- spotMarketAccount.decimals,
909
- strictOraclePrice
910
- );
911
-
912
- if (marginCategory !== undefined) {
913
- let weight = calculateAssetWeight(
914
- tokenAmount,
915
- strictOraclePrice.current,
916
- spotMarketAccount,
917
- marginCategory
918
- );
919
-
920
- if (
921
- marginCategory === 'Initial' &&
922
- spotMarketAccount.marketIndex !== QUOTE_SPOT_MARKET_INDEX
923
- ) {
924
- const userCustomAssetWeight = BN.max(
925
- ZERO,
926
- SPOT_MARKET_WEIGHT_PRECISION.sub(new BN(maxMarginRatio))
927
- );
928
- weight = BN.min(weight, userCustomAssetWeight);
929
- }
930
-
931
- assetValue = assetValue.mul(weight).div(SPOT_MARKET_WEIGHT_PRECISION);
932
- }
933
-
934
- return assetValue;
935
- }
936
-
937
- /**
938
- * Calculates the margin-weighted value of a spot borrow, mirroring the liability-side of the
939
- * program's collateral valuation (`get_strict_token_value` + `get_liability_weight`). Uses the
940
- * worst of the oracle's live price and its 5min TWAP (via `strictOraclePrice`) so a favorable
941
- * price dip can't be used to under-value a liability.
942
- *
943
- * @param {BN} tokenAmount - The borrow token amount (positive), scaled by `spotMarketAccount.decimals`
944
- * @param {StrictOraclePrice} strictOraclePrice - Live oracle price + 5min TWAP, PRICE_PRECISION (1e6)
945
- * @param {SpotMarketAccount} spotMarketAccount - The spot market account
946
- * @param {number} maxMarginRatio - The user's custom max margin ratio (0 if unset),
947
- * `SPOT_MARKET_WEIGHT_PRECISION` (1e4) units; only applied when `marginCategory === 'Initial'`
948
- * and the market isn't the quote spot market, flooring the weight at
949
- * `SPOT_MARKET_WEIGHT_PRECISION + maxMarginRatio`
950
- * @param {MarginCategory} [marginCategory] - When omitted, returns the unweighted (100%) value
951
- * @param {BN} [liquidationBuffer] - Extra weight added on top (`SPOT_MARKET_WEIGHT_PRECISION`
952
- * units) to make maintenance margin checks stricter during liquidation eligibility checks
953
- * @return {BN} The (optionally weighted) liability value, scaled by `PRICE_PRECISION` (1e6)
954
- */
955
- export function getSpotLiabilityValue(
956
- tokenAmount: BN,
957
- strictOraclePrice: StrictOraclePrice,
958
- spotMarketAccount: SpotMarketAccount,
959
- maxMarginRatio: number,
960
- marginCategory?: MarginCategory,
961
- liquidationBuffer?: BN
962
- ): BN {
963
- let liabilityValue = getStrictTokenValue(
964
- tokenAmount,
965
- spotMarketAccount.decimals,
966
- strictOraclePrice
967
- );
968
-
969
- if (marginCategory !== undefined) {
970
- let weight = calculateLiabilityWeight(
971
- tokenAmount,
972
- spotMarketAccount,
973
- marginCategory
974
- );
975
-
976
- if (
977
- marginCategory === 'Initial' &&
978
- spotMarketAccount.marketIndex !== QUOTE_SPOT_MARKET_INDEX
979
- ) {
980
- weight = BN.max(
981
- weight,
982
- SPOT_MARKET_WEIGHT_PRECISION.add(new BN(maxMarginRatio))
983
- );
984
- }
985
-
986
- if (liquidationBuffer !== undefined) {
987
- weight = weight.add(liquidationBuffer);
988
- }
989
-
990
- liabilityValue = liabilityValue
991
- .mul(weight)
992
- .div(SPOT_MARKET_WEIGHT_PRECISION);
993
- }
994
-
995
- return liabilityValue;
996
- }