@velocity-exchange/sdk 0.4.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/README.md +3 -3
  2. package/lib/browser/adminClient.d.ts +52 -0
  3. package/lib/browser/adminClient.js +97 -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 +191 -15
  9. package/lib/browser/idl/velocity.json +192 -16
  10. package/lib/browser/orderSubscriber/OrderSubscriber.d.ts +1 -0
  11. package/lib/browser/orderSubscriber/OrderSubscriber.js +10 -4
  12. package/lib/browser/pyth/pythLazerSubscriber.js +37 -27
  13. package/lib/browser/types.d.ts +6 -0
  14. package/lib/browser/user.d.ts +20 -3
  15. package/lib/browser/user.js +52 -4
  16. package/lib/browser/velocityClient.d.ts +46 -10
  17. package/lib/browser/velocityClient.js +107 -35
  18. package/lib/node/adminClient.d.ts +52 -0
  19. package/lib/node/adminClient.d.ts.map +1 -1
  20. package/lib/node/adminClient.js +97 -0
  21. package/lib/node/config.d.ts.map +1 -1
  22. package/lib/node/config.js +6 -7
  23. package/lib/node/constants/numericConstants.d.ts +1 -1
  24. package/lib/node/constants/numericConstants.d.ts.map +1 -1
  25. package/lib/node/constants/numericConstants.js +1 -1
  26. package/lib/node/decode/user.d.ts.map +1 -1
  27. package/lib/node/decode/user.js +5 -1
  28. package/lib/node/idl/velocity.d.ts +191 -15
  29. package/lib/node/idl/velocity.d.ts.map +1 -1
  30. package/lib/node/idl/velocity.json +192 -16
  31. package/lib/node/orderSubscriber/OrderSubscriber.d.ts +1 -0
  32. package/lib/node/orderSubscriber/OrderSubscriber.d.ts.map +1 -1
  33. package/lib/node/orderSubscriber/OrderSubscriber.js +10 -4
  34. package/lib/node/pyth/pythLazerSubscriber.d.ts.map +1 -1
  35. package/lib/node/pyth/pythLazerSubscriber.js +37 -27
  36. package/lib/node/types.d.ts +6 -0
  37. package/lib/node/types.d.ts.map +1 -1
  38. package/lib/node/user.d.ts +20 -3
  39. package/lib/node/user.d.ts.map +1 -1
  40. package/lib/node/user.js +52 -4
  41. package/lib/node/velocityClient.d.ts +46 -10
  42. package/lib/node/velocityClient.d.ts.map +1 -1
  43. package/lib/node/velocityClient.js +107 -35
  44. package/package.json +7 -3
  45. package/.prettierignore +0 -1
  46. package/CHANGELOG.md +0 -132
  47. package/build-browser.js +0 -58
  48. package/bunfig.toml +0 -3
  49. package/esbuild-shims.js +0 -12
  50. package/scripts/deposit-isolated-positions.ts +0 -110
  51. package/scripts/find-flagged-users.ts +0 -213
  52. package/scripts/grpc-client-test-comparison.ts +0 -372
  53. package/scripts/grpc-multiuser-client-test-comparison.ts +0 -158
  54. package/scripts/postbuild.js +0 -95
  55. package/scripts/single-grpc-client-test.ts +0 -284
  56. package/scripts/withdraw-isolated-positions.ts +0 -174
  57. package/src/accounts/README_WebSocketAccountSubscriberV2.md +0 -95
  58. package/src/accounts/README_WebSocketProgramAccountSubscriberV2.md +0 -135
  59. package/src/accounts/basicUserAccountSubscriber.ts +0 -79
  60. package/src/accounts/basicUserStatsAccountSubscriber.ts +0 -87
  61. package/src/accounts/bulkAccountLoader.ts +0 -358
  62. package/src/accounts/bulkUserStatsSubscription.ts +0 -40
  63. package/src/accounts/bulkUserSubscription.ts +0 -39
  64. package/src/accounts/customizedCadenceBulkAccountLoader.ts +0 -250
  65. package/src/accounts/fetch.ts +0 -200
  66. package/src/accounts/grpcAccountSubscriber.ts +0 -225
  67. package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +0 -73
  68. package/src/accounts/grpcMultiAccountSubscriber.ts +0 -566
  69. package/src/accounts/grpcMultiUserAccountSubscriber.ts +0 -328
  70. package/src/accounts/grpcProgramAccountSubscriber.ts +0 -264
  71. package/src/accounts/grpcUserAccountSubscriber.ts +0 -63
  72. package/src/accounts/grpcUserStatsAccountSubscriber.ts +0 -65
  73. package/src/accounts/grpcVelocityClientAccountSubscriber.ts +0 -257
  74. package/src/accounts/grpcVelocityClientAccountSubscriberV2.ts +0 -941
  75. package/src/accounts/laserProgramAccountSubscriber.ts +0 -266
  76. package/src/accounts/oneShotUserAccountSubscriber.ts +0 -88
  77. package/src/accounts/oneShotUserStatsAccountSubscriber.ts +0 -94
  78. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +0 -223
  79. package/src/accounts/pollingOracleAccountSubscriber.ts +0 -151
  80. package/src/accounts/pollingTokenAccountSubscriber.ts +0 -147
  81. package/src/accounts/pollingUserAccountSubscriber.ts +0 -198
  82. package/src/accounts/pollingUserStatsAccountSubscriber.ts +0 -180
  83. package/src/accounts/pollingVelocityClientAccountSubscriber.ts +0 -774
  84. package/src/accounts/testBulkAccountLoader.ts +0 -59
  85. package/src/accounts/types.ts +0 -428
  86. package/src/accounts/utils.ts +0 -90
  87. package/src/accounts/webSocketAccountSubscriber.ts +0 -304
  88. package/src/accounts/webSocketAccountSubscriberV2.ts +0 -536
  89. package/src/accounts/webSocketInsuranceFundStakeAccountSubscriber.ts +0 -171
  90. package/src/accounts/webSocketProgramAccountSubscriber.ts +0 -244
  91. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +0 -1048
  92. package/src/accounts/webSocketProgramAccountsSubscriberV2.ts +0 -1042
  93. package/src/accounts/webSocketUserAccountSubscriber.ts +0 -139
  94. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +0 -131
  95. package/src/accounts/webSocketVelocityClientAccountSubscriber.ts +0 -865
  96. package/src/accounts/webSocketVelocityClientAccountSubscriberV2.ts +0 -897
  97. package/src/accounts/websocketProgramUserAccountSubscriber.ts +0 -127
  98. package/src/addresses/marketAddresses.ts +0 -28
  99. package/src/addresses/pda.ts +0 -620
  100. package/src/adminClient.ts +0 -8136
  101. package/src/assert/assert.ts +0 -5
  102. package/src/auctionSubscriber/auctionSubscriber.ts +0 -86
  103. package/src/auctionSubscriber/auctionSubscriberGrpc.ts +0 -99
  104. package/src/auctionSubscriber/index.ts +0 -9
  105. package/src/auctionSubscriber/types.ts +0 -30
  106. package/src/bankrun/bankrunConnection.ts +0 -630
  107. package/src/blockhashSubscriber/BlockhashSubscriber.ts +0 -160
  108. package/src/blockhashSubscriber/index.ts +0 -6
  109. package/src/blockhashSubscriber/types.ts +0 -13
  110. package/src/clock/clockSubscriber.ts +0 -146
  111. package/src/config.ts +0 -312
  112. package/src/constants/index.ts +0 -9
  113. package/src/constants/numericConstants.ts +0 -194
  114. package/src/constants/perpMarkets.ts +0 -127
  115. package/src/constants/spotMarkets.ts +0 -104
  116. package/src/constants/txConstants.ts +0 -2
  117. package/src/constituentMap/constituentMap.ts +0 -305
  118. package/src/constituentMap/pollingConstituentAccountSubscriber.ts +0 -99
  119. package/src/constituentMap/webSocketConstituentAccountSubscriber.ts +0 -121
  120. package/src/core/VelocityCore.ts +0 -606
  121. package/src/core/index.ts +0 -12
  122. package/src/core/instructions/deposit.ts +0 -64
  123. package/src/core/instructions/fill.ts +0 -51
  124. package/src/core/instructions/funding.ts +0 -33
  125. package/src/core/instructions/liquidation.ts +0 -55
  126. package/src/core/instructions/orders.ts +0 -84
  127. package/src/core/instructions/perpOrders.ts +0 -312
  128. package/src/core/instructions/settlement.ts +0 -40
  129. package/src/core/instructions/trigger.ts +0 -41
  130. package/src/core/instructions/withdraw.ts +0 -65
  131. package/src/core/remainingAccounts.ts +0 -320
  132. package/src/core/signedMsg.ts +0 -109
  133. package/src/decode/customCoder.ts +0 -148
  134. package/src/decode/user.ts +0 -401
  135. package/src/dlob/DLOB.ts +0 -2793
  136. package/src/dlob/DLOBNode.ts +0 -305
  137. package/src/dlob/DLOBSubscriber.ts +0 -269
  138. package/src/dlob/NodeList.ts +0 -244
  139. package/src/dlob/orderBookLevels.ts +0 -695
  140. package/src/dlob/types.ts +0 -37
  141. package/src/events/eventList.ts +0 -114
  142. package/src/events/eventSubscriber.ts +0 -521
  143. package/src/events/eventsServerLogProvider.ts +0 -189
  144. package/src/events/fetchLogs.ts +0 -219
  145. package/src/events/parse.ts +0 -271
  146. package/src/events/pollingLogProvider.ts +0 -113
  147. package/src/events/sort.ts +0 -51
  148. package/src/events/txEventCache.ts +0 -92
  149. package/src/events/types.ts +0 -337
  150. package/src/events/webSocketLogProvider.ts +0 -152
  151. package/src/factory/bigNum.ts +0 -709
  152. package/src/factory/oracleClient.ts +0 -73
  153. package/src/idl/pyth.json +0 -142
  154. package/src/idl/pyth.ts +0 -97
  155. package/src/idl/pyth_solana_receiver.json +0 -628
  156. package/src/idl/token_faucet.json +0 -229
  157. package/src/idl/token_faucet.ts +0 -197
  158. package/src/idl/velocity.json +0 -23974
  159. package/src/idl/velocity.ts +0 -23980
  160. package/src/index.ts +0 -150
  161. package/src/indicative-quotes/indicativeQuotesSender.ts +0 -233
  162. package/src/isomorphic/README.md +0 -19
  163. package/src/isomorphic/anchor.browser.ts +0 -44
  164. package/src/isomorphic/anchor.node.ts +0 -1
  165. package/src/isomorphic/anchor.ts +0 -1
  166. package/src/isomorphic/anchor29.browser.ts +0 -24
  167. package/src/isomorphic/anchor29.node.ts +0 -1
  168. package/src/isomorphic/anchor29.ts +0 -1
  169. package/src/isomorphic/grpc.browser.ts +0 -4
  170. package/src/isomorphic/grpc.node.ts +0 -130
  171. package/src/isomorphic/grpc.ts +0 -1
  172. package/src/jupiter/jupiterClient.ts +0 -502
  173. package/src/keypair.ts +0 -32
  174. package/src/margin/README.md +0 -138
  175. package/src/marginCalculation.ts +0 -407
  176. package/src/marinade/idl/idl.json +0 -1962
  177. package/src/marinade/index.ts +0 -73
  178. package/src/marinade/types.ts +0 -3925
  179. package/src/math/amm.ts +0 -1758
  180. package/src/math/auction.ts +0 -480
  181. package/src/math/bankruptcy.ts +0 -138
  182. package/src/math/builder.ts +0 -130
  183. package/src/math/conversion.ts +0 -45
  184. package/src/math/exchangeStatus.ts +0 -261
  185. package/src/math/funding.ts +0 -498
  186. package/src/math/insurance.ts +0 -171
  187. package/src/math/liquidation.ts +0 -433
  188. package/src/math/margin.ts +0 -327
  189. package/src/math/market.ts +0 -487
  190. package/src/math/oracles.ts +0 -477
  191. package/src/math/orders.ts +0 -500
  192. package/src/math/position.ts +0 -381
  193. package/src/math/repeg.ts +0 -223
  194. package/src/math/spotBalance.ts +0 -996
  195. package/src/math/spotMarket.ts +0 -115
  196. package/src/math/spotPosition.ts +0 -263
  197. package/src/math/state.ts +0 -71
  198. package/src/math/superStake.ts +0 -602
  199. package/src/math/tiers.ts +0 -73
  200. package/src/math/trade.ts +0 -879
  201. package/src/math/utils.ts +0 -183
  202. package/src/memcmp.ts +0 -279
  203. package/src/oracles/oracleClientCache.ts +0 -41
  204. package/src/oracles/oracleId.ts +0 -99
  205. package/src/oracles/prelaunchOracleClient.ts +0 -63
  206. package/src/oracles/pythClient.ts +0 -125
  207. package/src/oracles/pythLazerClient.ts +0 -141
  208. package/src/oracles/quoteAssetOracleClient.ts +0 -43
  209. package/src/oracles/strictOraclePrice.ts +0 -40
  210. package/src/oracles/types.ts +0 -82
  211. package/src/oracles/utils.ts +0 -45
  212. package/src/orderParams.ts +0 -117
  213. package/src/orderSubscriber/OrderSubscriber.ts +0 -378
  214. package/src/orderSubscriber/PollingSubscription.ts +0 -50
  215. package/src/orderSubscriber/WebsocketSubscription.ts +0 -137
  216. package/src/orderSubscriber/grpcSubscription.ts +0 -155
  217. package/src/orderSubscriber/index.ts +0 -7
  218. package/src/orderSubscriber/types.ts +0 -64
  219. package/src/priorityFee/averageOverSlotsStrategy.ts +0 -17
  220. package/src/priorityFee/averageStrategy.ts +0 -13
  221. package/src/priorityFee/ewmaStrategy.ts +0 -51
  222. package/src/priorityFee/heliusPriorityFeeMethod.ts +0 -68
  223. package/src/priorityFee/index.ts +0 -16
  224. package/src/priorityFee/maxOverSlotsStrategy.ts +0 -18
  225. package/src/priorityFee/maxStrategy.ts +0 -8
  226. package/src/priorityFee/priorityFeeSubscriber.ts +0 -328
  227. package/src/priorityFee/priorityFeeSubscriberMap.ts +0 -134
  228. package/src/priorityFee/solanaPriorityFeeMethod.ts +0 -46
  229. package/src/priorityFee/types.ts +0 -82
  230. package/src/priorityFee/velocityPriorityFeeMethod.ts +0 -53
  231. package/src/pyth/constants.ts +0 -9
  232. package/src/pyth/index.ts +0 -15
  233. package/src/pyth/pythLazerSubscriber.ts +0 -365
  234. package/src/pyth/types.ts +0 -4451
  235. package/src/pyth/utils.ts +0 -13
  236. package/src/slot/SlotSubscriber.ts +0 -126
  237. package/src/slot/SlothashSubscriber.ts +0 -163
  238. package/src/swap/UnifiedSwapClient.ts +0 -315
  239. package/src/swift/grpcSignedMsgUserAccountSubscriber.ts +0 -81
  240. package/src/swift/index.ts +0 -10
  241. package/src/swift/signedMsgUserAccountSubscriber.ts +0 -251
  242. package/src/swift/swiftOrderSubscriber.ts +0 -351
  243. package/src/testClient.ts +0 -42
  244. package/src/titan/titanClient.ts +0 -438
  245. package/src/token/index.ts +0 -13
  246. package/src/tokenFaucet.ts +0 -296
  247. package/src/tx/baseTxSender.ts +0 -651
  248. package/src/tx/blockhashFetcher/baseBlockhashFetcher.ts +0 -31
  249. package/src/tx/blockhashFetcher/cachedBlockhashFetcher.ts +0 -105
  250. package/src/tx/blockhashFetcher/types.ts +0 -9
  251. package/src/tx/fastSingleTxSender.ts +0 -200
  252. package/src/tx/forwardOnlyTxSender.ts +0 -193
  253. package/src/tx/priorityFeeCalculator.ts +0 -117
  254. package/src/tx/reportTransactionError.ts +0 -176
  255. package/src/tx/retryTxSender.ts +0 -177
  256. package/src/tx/txHandler.ts +0 -1009
  257. package/src/tx/txParamProcessor.ts +0 -225
  258. package/src/tx/types.ts +0 -117
  259. package/src/tx/utils.ts +0 -133
  260. package/src/tx/whileValidTxSender.ts +0 -377
  261. package/src/types.ts +0 -2575
  262. package/src/user.ts +0 -5316
  263. package/src/userConfig.ts +0 -38
  264. package/src/userMap/PollingSubscription.ts +0 -61
  265. package/src/userMap/WebsocketSubscription.ts +0 -93
  266. package/src/userMap/grpcSubscription.ts +0 -93
  267. package/src/userMap/referrerMap.ts +0 -360
  268. package/src/userMap/revenueShareEscrowMap.ts +0 -342
  269. package/src/userMap/userMap.ts +0 -784
  270. package/src/userMap/userMapConfig.ts +0 -82
  271. package/src/userMap/userStatsMap.ts +0 -393
  272. package/src/userName.ts +0 -37
  273. package/src/userStats.ts +0 -159
  274. package/src/userStatsConfig.ts +0 -35
  275. package/src/util/TransactionConfirmationManager.ts +0 -333
  276. package/src/util/chainClock.ts +0 -65
  277. package/src/util/computeUnits.ts +0 -101
  278. package/src/util/digest.ts +0 -33
  279. package/src/util/ed25519Utils.ts +0 -95
  280. package/src/util/promiseTimeout.ts +0 -23
  281. package/src/velocityClient.ts +0 -14802
  282. package/src/velocityClientConfig.ts +0 -119
  283. package/src/wallet.ts +0 -91
  284. package/tests/VelocityCore/builder_escrow.test.ts +0 -121
  285. package/tests/VelocityCore/decode.test.ts +0 -16
  286. package/tests/VelocityCore/fill_trigger.test.ts +0 -73
  287. package/tests/VelocityCore/instructions.test.ts +0 -46
  288. package/tests/VelocityCore/pdas.test.ts +0 -30
  289. package/tests/VelocityCore/perp_orders.test.ts +0 -205
  290. package/tests/VelocityCore/remainingAccounts.test.ts +0 -72
  291. package/tests/VelocityCore/settlement_liquidation.test.ts +0 -69
  292. package/tests/accounts/customizedCadenceBulkAccountLoader.test.ts +0 -211
  293. package/tests/amm/test.ts +0 -2402
  294. package/tests/auctions/test.ts +0 -81
  295. package/tests/bn/test.ts +0 -355
  296. package/tests/builder/builderFee.test.ts +0 -42
  297. package/tests/ci/verifyConstants.ts +0 -393
  298. package/tests/decode/test.ts +0 -262
  299. package/tests/decode/userAccountBufferStrings.ts +0 -102
  300. package/tests/dlob/helpers.ts +0 -748
  301. package/tests/dlob/test.ts +0 -6969
  302. package/tests/dlob/tickSizeStandardization.ts +0 -545
  303. package/tests/events/parseLogsForCuUsage.ts +0 -139
  304. package/tests/exchangeStatus/test.ts +0 -45
  305. package/tests/insurance/test.ts +0 -40
  306. package/tests/liquidation/test.ts +0 -125
  307. package/tests/oracles/mmOracleGate.test.ts +0 -379
  308. package/tests/oracles/pythPegSnap.test.ts +0 -76
  309. package/tests/sdkParity/enumParity.test.ts +0 -84
  310. package/tests/sdkParity/marginCategoryFill.test.ts +0 -143
  311. package/tests/sdkParity/memcmpOffsets.test.ts +0 -139
  312. package/tests/spot/test.ts +0 -281
  313. package/tests/tx/TransactionConfirmationManager.test.ts +0 -312
  314. package/tests/tx/cachedBlockhashFetcher.test.ts +0 -96
  315. package/tests/tx/priorityFeeCalculator.ts +0 -77
  316. package/tests/tx/priorityFeeStrategy.ts +0 -95
  317. package/tests/user/bankruptcy.ts +0 -165
  318. package/tests/user/feeAndWithdrawLimits.ts +0 -284
  319. package/tests/user/getMarginCalculation.ts +0 -586
  320. package/tests/user/helpers.ts +0 -184
  321. package/tests/user/liquidations.ts +0 -129
  322. package/tests/user/marginCalculations.test.ts +0 -321
  323. package/tests/user/test.ts +0 -833
  324. package/tsconfig.browser.json +0 -18
  325. 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
- }