@hobba-io/core 0.1.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 (190) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +87 -0
  3. package/dist/amountUtils.d.ts +15 -0
  4. package/dist/amountUtils.d.ts.map +1 -0
  5. package/dist/amountUtils.js +59 -0
  6. package/dist/amountUtils.js.map +1 -0
  7. package/dist/constants.d.ts +148 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +231 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/errors.d.ts +7 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +29 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/executeAction.d.ts +36 -0
  16. package/dist/executeAction.d.ts.map +1 -0
  17. package/dist/executeAction.js +606 -0
  18. package/dist/executeAction.js.map +1 -0
  19. package/dist/executeDepositBorrow.d.ts +49 -0
  20. package/dist/executeDepositBorrow.d.ts.map +1 -0
  21. package/dist/executeDepositBorrow.js +444 -0
  22. package/dist/executeDepositBorrow.js.map +1 -0
  23. package/dist/idl/hobba.json +5072 -0
  24. package/dist/idl.d.ts +783 -0
  25. package/dist/idl.d.ts.map +1 -0
  26. package/dist/idl.js +7 -0
  27. package/dist/idl.js.map +1 -0
  28. package/dist/index.d.ts +17 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +22 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/juplend/accountResolvers.d.ts +106 -0
  33. package/dist/juplend/accountResolvers.d.ts.map +1 -0
  34. package/dist/juplend/accountResolvers.js +148 -0
  35. package/dist/juplend/accountResolvers.js.map +1 -0
  36. package/dist/juplend/operateRemaining.d.ts +11 -0
  37. package/dist/juplend/operateRemaining.d.ts.map +1 -0
  38. package/dist/juplend/operateRemaining.js +46 -0
  39. package/dist/juplend/operateRemaining.js.map +1 -0
  40. package/dist/juplend/positionInit.d.ts +34 -0
  41. package/dist/juplend/positionInit.d.ts.map +1 -0
  42. package/dist/juplend/positionInit.js +56 -0
  43. package/dist/juplend/positionInit.js.map +1 -0
  44. package/dist/juplend/positionParser.d.ts +8 -0
  45. package/dist/juplend/positionParser.d.ts.map +1 -0
  46. package/dist/juplend/positionParser.js +30 -0
  47. package/dist/juplend/positionParser.js.map +1 -0
  48. package/dist/juplend/sdkApi.d.ts +39 -0
  49. package/dist/juplend/sdkApi.d.ts.map +1 -0
  50. package/dist/juplend/sdkApi.js +68 -0
  51. package/dist/juplend/sdkApi.js.map +1 -0
  52. package/dist/juplend/tickMath.d.ts +19 -0
  53. package/dist/juplend/tickMath.d.ts.map +1 -0
  54. package/dist/juplend/tickMath.js +91 -0
  55. package/dist/juplend/tickMath.js.map +1 -0
  56. package/dist/juplend/tickUtils.d.ts +20 -0
  57. package/dist/juplend/tickUtils.d.ts.map +1 -0
  58. package/dist/juplend/tickUtils.js +115 -0
  59. package/dist/juplend/tickUtils.js.map +1 -0
  60. package/dist/juplend/vaultConfigParser.d.ts +25 -0
  61. package/dist/juplend/vaultConfigParser.d.ts.map +1 -0
  62. package/dist/juplend/vaultConfigParser.js +38 -0
  63. package/dist/juplend/vaultConfigParser.js.map +1 -0
  64. package/dist/kamino/accountResolvers.d.ts +110 -0
  65. package/dist/kamino/accountResolvers.d.ts.map +1 -0
  66. package/dist/kamino/accountResolvers.js +220 -0
  67. package/dist/kamino/accountResolvers.js.map +1 -0
  68. package/dist/kamino/obligationParser.d.ts +13 -0
  69. package/dist/kamino/obligationParser.d.ts.map +1 -0
  70. package/dist/kamino/obligationParser.js +92 -0
  71. package/dist/kamino/obligationParser.js.map +1 -0
  72. package/dist/kamino/refreshInstructions.d.ts +4 -0
  73. package/dist/kamino/refreshInstructions.d.ts.map +1 -0
  74. package/dist/kamino/refreshInstructions.js +32 -0
  75. package/dist/kamino/refreshInstructions.js.map +1 -0
  76. package/dist/limits.d.ts +87 -0
  77. package/dist/limits.d.ts.map +1 -0
  78. package/dist/limits.js +174 -0
  79. package/dist/limits.js.map +1 -0
  80. package/dist/marketConfig.d.ts +35 -0
  81. package/dist/marketConfig.d.ts.map +1 -0
  82. package/dist/marketConfig.js +85 -0
  83. package/dist/marketConfig.js.map +1 -0
  84. package/dist/oracle/scopePrice.d.ts +7 -0
  85. package/dist/oracle/scopePrice.d.ts.map +1 -0
  86. package/dist/oracle/scopePrice.js +31 -0
  87. package/dist/oracle/scopePrice.js.map +1 -0
  88. package/dist/pdas.d.ts +33 -0
  89. package/dist/pdas.d.ts.map +1 -0
  90. package/dist/pdas.js +116 -0
  91. package/dist/pdas.js.map +1 -0
  92. package/dist/position.d.ts +45 -0
  93. package/dist/position.d.ts.map +1 -0
  94. package/dist/position.js +68 -0
  95. package/dist/position.js.map +1 -0
  96. package/dist/positionMetrics.d.ts +108 -0
  97. package/dist/positionMetrics.d.ts.map +1 -0
  98. package/dist/positionMetrics.js +238 -0
  99. package/dist/positionMetrics.js.map +1 -0
  100. package/dist/program.d.ts +12 -0
  101. package/dist/program.d.ts.map +1 -0
  102. package/dist/program.js +24 -0
  103. package/dist/program.js.map +1 -0
  104. package/dist/quote.d.ts +90 -0
  105. package/dist/quote.d.ts.map +1 -0
  106. package/dist/quote.js +198 -0
  107. package/dist/quote.js.map +1 -0
  108. package/dist/rates/borrowRates.d.ts +44 -0
  109. package/dist/rates/borrowRates.d.ts.map +1 -0
  110. package/dist/rates/borrowRates.js +229 -0
  111. package/dist/rates/borrowRates.js.map +1 -0
  112. package/dist/rates/collateralInfo.d.ts +21 -0
  113. package/dist/rates/collateralInfo.d.ts.map +1 -0
  114. package/dist/rates/collateralInfo.js +104 -0
  115. package/dist/rates/collateralInfo.js.map +1 -0
  116. package/dist/rates/constants.d.ts +22 -0
  117. package/dist/rates/constants.d.ts.map +1 -0
  118. package/dist/rates/constants.js +70 -0
  119. package/dist/rates/constants.js.map +1 -0
  120. package/dist/rates/index.d.ts +11 -0
  121. package/dist/rates/index.d.ts.map +1 -0
  122. package/dist/rates/index.js +11 -0
  123. package/dist/rates/index.js.map +1 -0
  124. package/dist/rates/kaminoApi.d.ts +24 -0
  125. package/dist/rates/kaminoApi.d.ts.map +1 -0
  126. package/dist/rates/kaminoApi.js +98 -0
  127. package/dist/rates/kaminoApi.js.map +1 -0
  128. package/dist/rates/supplyRates.d.ts +23 -0
  129. package/dist/rates/supplyRates.d.ts.map +1 -0
  130. package/dist/rates/supplyRates.js +67 -0
  131. package/dist/rates/supplyRates.js.map +1 -0
  132. package/dist/transactionBuilder.d.ts +5 -0
  133. package/dist/transactionBuilder.d.ts.map +1 -0
  134. package/dist/transactionBuilder.js +287 -0
  135. package/dist/transactionBuilder.js.map +1 -0
  136. package/dist/txHelpers.d.ts +16 -0
  137. package/dist/txHelpers.d.ts.map +1 -0
  138. package/dist/txHelpers.js +82 -0
  139. package/dist/txHelpers.js.map +1 -0
  140. package/dist/userState.d.ts +21 -0
  141. package/dist/userState.d.ts.map +1 -0
  142. package/dist/userState.js +60 -0
  143. package/dist/userState.js.map +1 -0
  144. package/dist/vault/vaultAccounts.d.ts +82 -0
  145. package/dist/vault/vaultAccounts.d.ts.map +1 -0
  146. package/dist/vault/vaultAccounts.js +275 -0
  147. package/dist/vault/vaultAccounts.js.map +1 -0
  148. package/dist/wallet.d.ts +12 -0
  149. package/dist/wallet.d.ts.map +1 -0
  150. package/dist/wallet.js +2 -0
  151. package/dist/wallet.js.map +1 -0
  152. package/package.json +63 -0
  153. package/src/amountUtils.ts +69 -0
  154. package/src/constants.ts +263 -0
  155. package/src/errors.ts +33 -0
  156. package/src/executeAction.ts +884 -0
  157. package/src/executeDepositBorrow.ts +677 -0
  158. package/src/idl/hobba.json +5072 -0
  159. package/src/idl.ts +7 -0
  160. package/src/index.ts +21 -0
  161. package/src/juplend/accountResolvers.ts +207 -0
  162. package/src/juplend/operateRemaining.ts +81 -0
  163. package/src/juplend/positionInit.ts +86 -0
  164. package/src/juplend/positionParser.ts +41 -0
  165. package/src/juplend/sdkApi.ts +112 -0
  166. package/src/juplend/tickMath.ts +82 -0
  167. package/src/juplend/tickUtils.ts +163 -0
  168. package/src/juplend/vaultConfigParser.ts +45 -0
  169. package/src/kamino/accountResolvers.ts +299 -0
  170. package/src/kamino/obligationParser.ts +123 -0
  171. package/src/kamino/refreshInstructions.ts +45 -0
  172. package/src/limits.ts +222 -0
  173. package/src/marketConfig.ts +139 -0
  174. package/src/oracle/scopePrice.ts +31 -0
  175. package/src/pdas.ts +226 -0
  176. package/src/position.ts +131 -0
  177. package/src/positionMetrics.ts +387 -0
  178. package/src/program.ts +30 -0
  179. package/src/quote.ts +310 -0
  180. package/src/rates/borrowRates.ts +270 -0
  181. package/src/rates/collateralInfo.ts +136 -0
  182. package/src/rates/constants.ts +87 -0
  183. package/src/rates/index.ts +38 -0
  184. package/src/rates/kaminoApi.ts +134 -0
  185. package/src/rates/supplyRates.ts +102 -0
  186. package/src/transactionBuilder.ts +324 -0
  187. package/src/txHelpers.ts +107 -0
  188. package/src/userState.ts +91 -0
  189. package/src/vault/vaultAccounts.ts +365 -0
  190. package/src/wallet.ts +12 -0
package/src/limits.ts ADDED
@@ -0,0 +1,222 @@
1
+ import { Connection, PublicKey } from "@solana/web3.js";
2
+ import { getAssociatedTokenAddress } from "@solana/spl-token";
3
+ import { SOL_MINT, USDC_MINT } from "./constants";
4
+ import { collateralDecimals, collateralLabel } from "./amountUtils";
5
+ import { getPosition, type Lender } from "./position";
6
+
7
+ /** Protocol input rules, shared by the app, the widget and /api/widget/config. */
8
+ export const LIMITS_CONFIG = {
9
+ /** Users can borrow up to this % of collateral value. */
10
+ maxUserLtvPct: 50,
11
+ /** Minimum deposits in UI units, first-deposit vs top-up. */
12
+ minDepositUi: {
13
+ SOL: { first: 1, topUp: 0.1 },
14
+ cbBTC: { first: 0.001, topUp: 0.0001 },
15
+ } as Record<string, { first: number; topUp: number }>,
16
+ /** SOL held back from "Max" for rents + fees (lamports). */
17
+ solReserveLamports: { first: 50_000_000, topUp: 5_000_000 },
18
+ /** JupLend deposit rounding shave, base units per collateral. */
19
+ juplendDepositBufferBase: { SOL: 3_000_000, cbBTC: 1 } as Record<string, number>,
20
+ /**
21
+ * JupLend max-repay dust shave, micro-USDC. JupLend's operate rejects a
22
+ * repay that leaves 0 < debt < its min-debt floor (error 6025
23
+ * VAULT_USER_DEBT_TOO_LOW): a 0.002 USDC residual was observed failing on
24
+ * staging while ~0.01 passed, so leave 0.02 to clear the floor plus the
25
+ * interest accrued between quote and execution.
26
+ */
27
+ juplendRepayShaveMicro: 20_000,
28
+ /**
29
+ * klend balance-for-accrued-debt drift divisor applied to max withdraw
30
+ * (observed refresh-time BFAD drift up to ~0.43% on SOL).
31
+ */
32
+ kaminoWithdrawDriftFactor: 1.006,
33
+ /**
34
+ * JupLend max-withdraw margin. Hobba's 50% user-LTV cap is client-side
35
+ * policy (JupLend's on-chain ceiling is its 75-80% liquidation LTV), so
36
+ * only a hair of headroom is needed for interest accrued between quote
37
+ * and execution — lands ~49.99% instead of stranding 0.6% of the
38
+ * withdrawable amount like the klend factor would.
39
+ */
40
+ juplendWithdrawDriftFactor: 1.0005,
41
+ /**
42
+ * Collateral reserve for the post-unwind residual on max withdraw when the
43
+ * operator has capital deployed. prepare_withdraw redeems vault shares to
44
+ * repay the operator debt as collateral leaves, but redemption rounding,
45
+ * vault fees and the repay dust leave a small residual — withdrawing ALL
46
+ * collateral against it is infinite LTV and the lender rejects the tx
47
+ * (observed: JupLend 6011 VAULT_POSITION_ABOVE_CF). Reserve enough
48
+ * collateral to hold the residual at ≤ 50% LTV: residual is estimated as
49
+ * max(floorUsd, fracOfOperatorDebt × operator debt).
50
+ */
51
+ unwindResidualReserve: { floorUsd: 0.1, fracOfOperatorDebt: 0.002 },
52
+ /** Fallback liquidation LTVs (%) when /api/rates is unavailable. */
53
+ fallbackLiquidationLtvPct: {
54
+ kamino: { SOL: 75, cbBTC: 80 },
55
+ juplend: { SOL: 75, cbBTC: 80 },
56
+ } as Record<Lender, Record<string, number>>,
57
+ };
58
+
59
+ export interface HobbaLimits {
60
+ lender: Lender;
61
+ /** micro-USDC — respects the 50% max user LTV. */
62
+ maxBorrowUsdc: string;
63
+ /** collateral base units — keeps LTV ≤ 50%, drift-adjusted. */
64
+ maxWithdraw: string;
65
+ /** micro-USDC — min(wallet USDC, debt), minus the JupLend dust shave. */
66
+ maxRepayUsdc: string;
67
+ /** collateral base units — first-deposit vs top-up aware. */
68
+ minDeposit: string;
69
+ /** lamports held back from Max when depositing SOL. */
70
+ solReserve: string;
71
+ /** JupLend rounding shave in collateral base units; "0" on Kamino. */
72
+ depositBuffer: string;
73
+ maxUserLtvPct: number;
74
+ /** % — per active lender + collateral. */
75
+ liquidationLtvPct: number;
76
+ }
77
+
78
+ async function fetchCollateralPriceUsd(base: string, label: string): Promise<number> {
79
+ const route = label === "SOL" ? "sol-price" : "btc-price";
80
+ const res = await fetch(`${base}/api/${route}`);
81
+ if (!res.ok) throw new Error(`${route} fetch failed: ${res.status}`);
82
+ const data = await res.json();
83
+ const price = Number(data.price);
84
+ if (!Number.isFinite(price) || price <= 0) throw new Error(`${route} returned no price`);
85
+ return price;
86
+ }
87
+
88
+ async function fetchLiquidationLtvPct(
89
+ base: string,
90
+ lender: Lender,
91
+ label: string,
92
+ ): Promise<number> {
93
+ try {
94
+ const res = await fetch(`${base}/api/rates`);
95
+ if (!res.ok) throw new Error(String(res.status));
96
+ const data = await res.json();
97
+ const table = lender === "juplend" ? data.juplendCollateralLtvs : data.collateralLtvs;
98
+ const pct = Number(table?.[label]?.liquidationThresholdPct);
99
+ if (Number.isFinite(pct) && pct > 0) return pct;
100
+ } catch {
101
+ /* fall through to constants */
102
+ }
103
+ return LIMITS_CONFIG.fallbackLiquidationLtvPct[lender][label] ?? 80;
104
+ }
105
+
106
+ async function fetchTokenBalanceBase(
107
+ connection: Connection,
108
+ owner: PublicKey,
109
+ mint: PublicKey,
110
+ ): Promise<number> {
111
+ try {
112
+ const ata = await getAssociatedTokenAddress(mint, owner);
113
+ const bal = await connection.getTokenAccountBalance(ata);
114
+ return Number(bal.value.amount);
115
+ } catch {
116
+ return 0;
117
+ }
118
+ }
119
+
120
+ export interface GetLimitsParams {
121
+ connection: Connection;
122
+ owner: PublicKey;
123
+ collateralMint: PublicKey;
124
+ apiBaseUrl?: string;
125
+ }
126
+
127
+ /**
128
+ * Single quote for input bounds so integrator math can't drift from protocol
129
+ * rules (SOL rent reserves, JupLend rounding shaves, klend accrual drift).
130
+ * Mirrors the app's CalculatorSection / ActionModal / max-withdraw logic.
131
+ */
132
+ export async function getLimits(params: GetLimitsParams): Promise<HobbaLimits> {
133
+ const { connection, owner, collateralMint } = params;
134
+ const base = (params.apiBaseUrl ?? "").replace(/\/+$/, "");
135
+ const label = collateralLabel(collateralMint);
136
+ const decimals = collateralDecimals(collateralMint);
137
+ const isSol = collateralMint.equals(SOL_MINT);
138
+
139
+ const position = await getPosition({ connection, owner, collateralMint, apiBaseUrl: base });
140
+ const [price, liquidationLtvPct] = await Promise.all([
141
+ fetchCollateralPriceUsd(base, label),
142
+ fetchLiquidationLtvPct(base, position.lender, label),
143
+ ]);
144
+
145
+ const collateralBase = Number(position.collateral);
146
+ const userDebtMicro = Number(position.userDebtUsdc);
147
+ const collateralUsd = (collateralBase / 10 ** decimals) * price;
148
+ const maxLtv = LIMITS_CONFIG.maxUserLtvPct / 100;
149
+
150
+ // Max borrow: 50% of collateral value minus current user debt.
151
+ const maxBorrowMicro = Math.max(0, Math.floor(collateralUsd * maxLtv * 1e6 - userDebtMicro));
152
+
153
+ // Max withdraw: keep post-withdraw LTV ≤ 50%. With debt, shave a per-lender
154
+ // drift margin so the quote survives until submission: klend needs real
155
+ // headroom for refresh-time BFAD drift, JupLend only accrual dust. When the
156
+ // operator has capital deployed, additionally reserve collateral for the
157
+ // post-unwind residual (see LIMITS_CONFIG.unwindResidualReserve) so a max
158
+ // withdraw can't strand residual debt against ~zero collateral.
159
+ const operatorDebtUsd = Number(position.depositedToVault) / 1e6;
160
+ const unwindResidualUsd =
161
+ operatorDebtUsd > 0
162
+ ? Math.max(
163
+ LIMITS_CONFIG.unwindResidualReserve.floorUsd,
164
+ operatorDebtUsd * LIMITS_CONFIG.unwindResidualReserve.fracOfOperatorDebt,
165
+ )
166
+ : 0;
167
+ const effectiveDebtUsd = userDebtMicro / 1e6 + unwindResidualUsd;
168
+ let maxWithdrawBase: number;
169
+ if (effectiveDebtUsd > 0 && price > 0) {
170
+ const driftFactor =
171
+ position.lender === "juplend"
172
+ ? LIMITS_CONFIG.juplendWithdrawDriftFactor
173
+ : LIMITS_CONFIG.kaminoWithdrawDriftFactor;
174
+ const minCollateralUsd = effectiveDebtUsd / maxLtv;
175
+ const maxWithdrawUsd = Math.max(0, collateralUsd - minCollateralUsd);
176
+ maxWithdrawBase = Math.floor(
177
+ ((maxWithdrawUsd / price) * 10 ** decimals) / driftFactor,
178
+ );
179
+ maxWithdrawBase = Math.min(maxWithdrawBase, collateralBase);
180
+ } else {
181
+ maxWithdrawBase = collateralBase;
182
+ }
183
+
184
+ // Max repay: wallet USDC capped at debt; JupLend leaves rounding dust.
185
+ const walletUsdcMicro = await fetchTokenBalanceBase(connection, owner, USDC_MINT);
186
+ let maxRepayMicro: number;
187
+ if (position.lender === "juplend") {
188
+ // Cap at debt - shave so the residual clears JupLend's min-debt floor
189
+ // (VAULT_USER_DEBT_TOO_LOW) whether the wallet or the debt is the binding
190
+ // limit; the extra micro off the wallet cap guards balance rounding.
191
+ const targetMicro = Math.max(0, userDebtMicro - LIMITS_CONFIG.juplendRepayShaveMicro);
192
+ maxRepayMicro = Math.max(0, Math.min(walletUsdcMicro - 1, targetMicro));
193
+ } else {
194
+ maxRepayMicro = Math.min(walletUsdcMicro, userDebtMicro);
195
+ }
196
+
197
+ const minDepositUi = position.exists
198
+ ? LIMITS_CONFIG.minDepositUi[label]?.topUp
199
+ : LIMITS_CONFIG.minDepositUi[label]?.first;
200
+ const minDepositBase = Math.round((minDepositUi ?? 0) * 10 ** decimals);
201
+
202
+ const solReserveLamports = isSol
203
+ ? position.exists
204
+ ? LIMITS_CONFIG.solReserveLamports.topUp
205
+ : LIMITS_CONFIG.solReserveLamports.first
206
+ : 0;
207
+
208
+ const depositBufferBase =
209
+ position.lender === "juplend" ? (LIMITS_CONFIG.juplendDepositBufferBase[label] ?? 0) : 0;
210
+
211
+ return {
212
+ lender: position.lender,
213
+ maxBorrowUsdc: String(maxBorrowMicro),
214
+ maxWithdraw: String(maxWithdrawBase),
215
+ maxRepayUsdc: String(maxRepayMicro),
216
+ minDeposit: String(minDepositBase),
217
+ solReserve: String(solReserveLamports),
218
+ depositBuffer: String(depositBufferBase),
219
+ maxUserLtvPct: LIMITS_CONFIG.maxUserLtvPct,
220
+ liquidationLtvPct,
221
+ };
222
+ }
@@ -0,0 +1,139 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import {
3
+ MAIN_MARKET,
4
+ XSTOCKS_MARKET,
5
+ CBBTC_MINT,
6
+ SOL_MINT,
7
+ CBBTC_RESERVE,
8
+ CBBTC_LIQUIDITY_SUPPLY,
9
+ CBBTC_COLLATERAL_MINT,
10
+ CBBTC_COLLATERAL_SUPPLY,
11
+ CBBTC_RESERVE_FARM_COLLATERAL,
12
+ SOL_RESERVE,
13
+ SOL_LIQUIDITY_SUPPLY,
14
+ SOL_COLLATERAL_MINT,
15
+ SOL_COLLATERAL_SUPPLY,
16
+ SOL_RESERVE_FARM_COLLATERAL,
17
+ USDC_RESERVE,
18
+ USDC_LIQUIDITY_SUPPLY,
19
+ USDC_FEE_VAULT,
20
+ SCOPE_PRICES,
21
+ XSTOCKS_CBBTC_RESERVE,
22
+ XSTOCKS_USDC_RESERVE,
23
+ XSTOCKS_CBBTC_LIQUIDITY_SUPPLY,
24
+ XSTOCKS_CBBTC_COLLATERAL_MINT,
25
+ XSTOCKS_CBBTC_COLLATERAL_SUPPLY,
26
+ XSTOCKS_USDC_LIQUIDITY_SUPPLY,
27
+ XSTOCKS_USDC_FEE_VAULT,
28
+ XSTOCKS_USDC_RESERVE_FARM_DEBT,
29
+ } from "./constants";
30
+ import { deriveMarketAuthority } from "./pdas";
31
+
32
+ export interface KaminoMarketConfig {
33
+ collateralMint: PublicKey;
34
+ market: PublicKey;
35
+ marketAuthority: PublicKey;
36
+ collateralReserve: PublicKey;
37
+ collateralLiquiditySupply: PublicKey;
38
+ collateralCTokenMint: PublicKey;
39
+ collateralCTokenSupply: PublicKey;
40
+ collateralFarmState: PublicKey | null;
41
+ usdcReserve: PublicKey;
42
+ usdcLiquiditySupply: PublicKey;
43
+ usdcFeeVault: PublicKey;
44
+ usdcReserveFarmDebt: PublicKey | null;
45
+ scopePrices: PublicKey;
46
+ }
47
+
48
+ const [mainMarketAuthority] = deriveMarketAuthority(MAIN_MARKET);
49
+
50
+ export const MAIN_MARKET_CONFIG: KaminoMarketConfig = {
51
+ collateralMint: CBBTC_MINT,
52
+ market: MAIN_MARKET,
53
+ marketAuthority: mainMarketAuthority,
54
+ collateralReserve: CBBTC_RESERVE,
55
+ collateralLiquiditySupply: CBBTC_LIQUIDITY_SUPPLY,
56
+ collateralCTokenMint: CBBTC_COLLATERAL_MINT,
57
+ collateralCTokenSupply: CBBTC_COLLATERAL_SUPPLY,
58
+ collateralFarmState: CBBTC_RESERVE_FARM_COLLATERAL,
59
+ usdcReserve: USDC_RESERVE,
60
+ usdcLiquiditySupply: USDC_LIQUIDITY_SUPPLY,
61
+ usdcFeeVault: USDC_FEE_VAULT,
62
+ usdcReserveFarmDebt: null,
63
+ scopePrices: SCOPE_PRICES,
64
+ };
65
+
66
+ export const SOL_MAIN_MARKET_CONFIG: KaminoMarketConfig = {
67
+ collateralMint: SOL_MINT,
68
+ market: MAIN_MARKET,
69
+ marketAuthority: mainMarketAuthority,
70
+ collateralReserve: SOL_RESERVE,
71
+ collateralLiquiditySupply: SOL_LIQUIDITY_SUPPLY,
72
+ collateralCTokenMint: SOL_COLLATERAL_MINT,
73
+ collateralCTokenSupply: SOL_COLLATERAL_SUPPLY,
74
+ collateralFarmState: SOL_RESERVE_FARM_COLLATERAL,
75
+ usdcReserve: USDC_RESERVE,
76
+ usdcLiquiditySupply: USDC_LIQUIDITY_SUPPLY,
77
+ usdcFeeVault: USDC_FEE_VAULT,
78
+ usdcReserveFarmDebt: null,
79
+ scopePrices: SCOPE_PRICES,
80
+ };
81
+
82
+ const [xstocksMarketAuthority] = deriveMarketAuthority(XSTOCKS_MARKET);
83
+
84
+ export const XSTOCKS_MARKET_CONFIG: KaminoMarketConfig = {
85
+ collateralMint: CBBTC_MINT,
86
+ market: XSTOCKS_MARKET,
87
+ marketAuthority: xstocksMarketAuthority,
88
+ collateralReserve: XSTOCKS_CBBTC_RESERVE,
89
+ collateralLiquiditySupply: XSTOCKS_CBBTC_LIQUIDITY_SUPPLY,
90
+ collateralCTokenMint: XSTOCKS_CBBTC_COLLATERAL_MINT,
91
+ collateralCTokenSupply: XSTOCKS_CBBTC_COLLATERAL_SUPPLY,
92
+ collateralFarmState: null,
93
+ usdcReserve: XSTOCKS_USDC_RESERVE,
94
+ usdcLiquiditySupply: XSTOCKS_USDC_LIQUIDITY_SUPPLY,
95
+ usdcFeeVault: XSTOCKS_USDC_FEE_VAULT,
96
+ usdcReserveFarmDebt: XSTOCKS_USDC_RESERVE_FARM_DEBT,
97
+ scopePrices: SCOPE_PRICES,
98
+ };
99
+
100
+ export async function resolveXStocksConfig(): Promise<KaminoMarketConfig> {
101
+ return XSTOCKS_MARKET_CONFIG;
102
+ }
103
+
104
+ /**
105
+ * Resolve market config for a given market address.
106
+ * Main Market uses hardcoded addresses; XStocks fetches from mainnet.
107
+ */
108
+ export async function resolveMarketConfig(
109
+ market: PublicKey
110
+ ): Promise<KaminoMarketConfig | null> {
111
+ if (market.equals(MAIN_MARKET)) return MAIN_MARKET_CONFIG;
112
+ if (market.equals(XSTOCKS_MARKET)) return resolveXStocksConfig();
113
+ return null;
114
+ }
115
+
116
+ /**
117
+ * Resolve market config for a given collateral mint on Main Market.
118
+ */
119
+ export function resolveMainMarketConfigByMint(
120
+ collateralMint: PublicKey
121
+ ): KaminoMarketConfig {
122
+ if (collateralMint.equals(SOL_MINT)) return SOL_MAIN_MARKET_CONFIG;
123
+ return MAIN_MARKET_CONFIG;
124
+ }
125
+
126
+ /**
127
+ * Resolve the best Kamino market config based on the rates API response.
128
+ * Returns the config for whichever Kamino market has the lowest borrow APY.
129
+ */
130
+ export async function resolveBestKaminoConfig(
131
+ bestMarketName: string | null
132
+ ): Promise<KaminoMarketConfig> {
133
+ if (bestMarketName === "Kamino xStocks") {
134
+ const xstocks = await resolveXStocksConfig();
135
+ if (xstocks) return xstocks;
136
+ }
137
+ // Default to Main Market
138
+ return MAIN_MARKET_CONFIG;
139
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Kamino Scope oracle price decoding, shared by /api/btc-price and
3
+ * /api/sol-price (and any script needing a Scope-chain USD price).
4
+ * Server-leaning (decodeReserve lazy-requires klend-sdk) — import via the
5
+ * subpath `@hobba/core/oracle/scopePrice`, not core's index.
6
+ */
7
+ import { decodeReserve } from "../rates/borrowRates";
8
+
9
+ // Scope oracle layout: 8 disc + 32 oracleMappings + 512×56 prices
10
+ export const SCOPE_PRICES_OFFSET = 40; // 8 + 32
11
+ export const SCOPE_ENTRY_SIZE = 56; // DatedPrice: 8 value + 8 exp + ...
12
+
13
+ /** Scope price-chain token indices configured on a klend reserve. */
14
+ export function getReservePriceChain(reserveData: Buffer): number[] {
15
+ const reserve = decodeReserve(reserveData);
16
+ return reserve.config.tokenInfo.scopeConfiguration.priceChain.map((x: any) => Number(x));
17
+ }
18
+
19
+ /** Walk the price chain through the Scope prices account, multiplying hops. */
20
+ export function readScopePrice(scopeData: Buffer, priceChain: number[]): number {
21
+ let price = 1.0;
22
+ for (const tokenIndex of priceChain) {
23
+ if (tokenIndex === 65535) break;
24
+ const entryOffset = SCOPE_PRICES_OFFSET + tokenIndex * SCOPE_ENTRY_SIZE;
25
+ if (entryOffset + 16 > scopeData.length) break;
26
+ const value = Number(scopeData.readBigUInt64LE(entryOffset));
27
+ const exp = Number(scopeData.readBigUInt64LE(entryOffset + 8));
28
+ price *= value * Math.pow(10, -exp);
29
+ }
30
+ return price;
31
+ }
package/src/pdas.ts ADDED
@@ -0,0 +1,226 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { getAssociatedTokenAddress } from "@solana/spl-token";
3
+ import BN from "bn.js";
4
+ import {
5
+ PROGRAM_ID,
6
+ KAMINO_LENDING_PROGRAM_ID,
7
+ KVAULT_PROGRAM_ID,
8
+ FARMS_PROGRAM,
9
+ JUPLEND_PROGRAM,
10
+ METAPLEX_PROGRAM,
11
+ USDC_MINT,
12
+ ALLEZ_USDC_SHARES_MINT,
13
+ USDC_PRIME_SHARES_MINT,
14
+ ROCKAWAY_RWA_SHARES_MINT,
15
+ STAR_USDC_MINT,
16
+ JUPLEND_MAX_TICK,
17
+ } from "./constants";
18
+
19
+ export function deriveUserStatePda(owner: PublicKey, collateralMint: PublicKey): [PublicKey, number] {
20
+ return PublicKey.findProgramAddressSync(
21
+ [Buffer.from("user_state"), owner.toBuffer(), collateralMint.toBuffer()],
22
+ PROGRAM_ID
23
+ );
24
+ }
25
+
26
+ export function deriveAllowlistEntryPda(owner: PublicKey): [PublicKey, number] {
27
+ return PublicKey.findProgramAddressSync(
28
+ [Buffer.from("allowlist"), owner.toBuffer()],
29
+ PROGRAM_ID
30
+ );
31
+ }
32
+
33
+ export function deriveProgramConfigPda(): [PublicKey, number] {
34
+ return PublicKey.findProgramAddressSync(
35
+ [Buffer.from("program_config")],
36
+ PROGRAM_ID
37
+ );
38
+ }
39
+
40
+ export function deriveDepositConfigPda(collateralMint: PublicKey): [PublicKey, number] {
41
+ return PublicKey.findProgramAddressSync(
42
+ [Buffer.from("deposit_config"), collateralMint.toBuffer()],
43
+ PROGRAM_ID
44
+ );
45
+ }
46
+
47
+ export function deriveOriginConfigPda(origin: number): [PublicKey, number] {
48
+ return PublicKey.findProgramAddressSync(
49
+ [Buffer.from("origin_config"), Buffer.from([origin])],
50
+ PROGRAM_ID
51
+ );
52
+ }
53
+
54
+ export function deriveUserOriginPda(owner: PublicKey): [PublicKey, number] {
55
+ return PublicKey.findProgramAddressSync(
56
+ [Buffer.from("user_origin"), owner.toBuffer()],
57
+ PROGRAM_ID
58
+ );
59
+ }
60
+
61
+ export function deriveObligationPda(
62
+ userStatePda: PublicKey,
63
+ market: PublicKey
64
+ ): [PublicKey, number] {
65
+ return PublicKey.findProgramAddressSync(
66
+ [
67
+ Buffer.from([0]),
68
+ Buffer.from([0]),
69
+ userStatePda.toBuffer(),
70
+ market.toBuffer(),
71
+ PublicKey.default.toBuffer(),
72
+ PublicKey.default.toBuffer(),
73
+ ],
74
+ KAMINO_LENDING_PROGRAM_ID
75
+ );
76
+ }
77
+
78
+ export function deriveUserMetadataPda(userStatePda: PublicKey): [PublicKey, number] {
79
+ return PublicKey.findProgramAddressSync(
80
+ [Buffer.from("user_meta"), userStatePda.toBuffer()],
81
+ KAMINO_LENDING_PROGRAM_ID
82
+ );
83
+ }
84
+
85
+ export function deriveMarketAuthority(market: PublicKey): [PublicKey, number] {
86
+ return PublicKey.findProgramAddressSync(
87
+ [Buffer.from("lma"), market.toBuffer()],
88
+ KAMINO_LENDING_PROGRAM_ID
89
+ );
90
+ }
91
+
92
+ export function deriveFarmUserStatePda(
93
+ farmState: PublicKey,
94
+ obligation: PublicKey
95
+ ): [PublicKey, number] {
96
+ return PublicKey.findProgramAddressSync(
97
+ [Buffer.from("user"), farmState.toBuffer(), obligation.toBuffer()],
98
+ FARMS_PROGRAM
99
+ );
100
+ }
101
+
102
+ export function deriveKvaultEventAuthority(): [PublicKey, number] {
103
+ return PublicKey.findProgramAddressSync(
104
+ [Buffer.from("__event_authority")],
105
+ KVAULT_PROGRAM_ID
106
+ );
107
+ }
108
+
109
+ export function deriveKvaultGlobalConfig(): [PublicKey, number] {
110
+ return PublicKey.findProgramAddressSync(
111
+ [Buffer.from("global_config")],
112
+ KVAULT_PROGRAM_ID
113
+ );
114
+ }
115
+
116
+ // JupLend PDAs
117
+ export function deriveJuplendPositionPda(
118
+ vaultId: number,
119
+ positionId: number
120
+ ): [PublicKey, number] {
121
+ const vaultIdBytes = Buffer.alloc(2);
122
+ vaultIdBytes.writeUInt16LE(vaultId);
123
+ const positionIdBytes = Buffer.alloc(4);
124
+ positionIdBytes.writeUInt32LE(positionId);
125
+ return PublicKey.findProgramAddressSync(
126
+ [Buffer.from("position"), vaultIdBytes, positionIdBytes],
127
+ JUPLEND_PROGRAM
128
+ );
129
+ }
130
+
131
+ export function deriveJuplendPositionMintPda(
132
+ vaultId: number,
133
+ positionId: number
134
+ ): [PublicKey, number] {
135
+ const vaultIdBytes = Buffer.alloc(2);
136
+ vaultIdBytes.writeUInt16LE(vaultId);
137
+ const positionIdBytes = Buffer.alloc(4);
138
+ positionIdBytes.writeUInt32LE(positionId);
139
+ return PublicKey.findProgramAddressSync(
140
+ [Buffer.from("position_mint"), vaultIdBytes, positionIdBytes],
141
+ JUPLEND_PROGRAM
142
+ );
143
+ }
144
+
145
+ export function deriveJuplendTickPda(vaultId: number, tick: number): PublicKey {
146
+ const [pda] = PublicKey.findProgramAddressSync(
147
+ [
148
+ Buffer.from("tick"),
149
+ new BN(vaultId).toArrayLike(Buffer, "le", 2),
150
+ new BN(tick + JUPLEND_MAX_TICK).toArrayLike(Buffer, "le", 4),
151
+ ],
152
+ JUPLEND_PROGRAM
153
+ );
154
+ return pda;
155
+ }
156
+
157
+ export function deriveJuplendTickIdLiquidationPda(
158
+ vaultId: number,
159
+ tick: number,
160
+ totalIds: number = 0
161
+ ): PublicKey {
162
+ const [pda] = PublicKey.findProgramAddressSync(
163
+ [
164
+ Buffer.from("tick_id_liquidation"),
165
+ new BN(vaultId).toArrayLike(Buffer, "le", 2),
166
+ new BN(tick + JUPLEND_MAX_TICK).toArrayLike(Buffer, "le", 4),
167
+ new BN(Math.floor((totalIds + 2) / 3)).toArrayLike(Buffer, "le", 4),
168
+ ],
169
+ JUPLEND_PROGRAM
170
+ );
171
+ return pda;
172
+ }
173
+
174
+ export function derivePositionMetadataPda(positionMint: PublicKey): [PublicKey, number] {
175
+ return PublicKey.findProgramAddressSync(
176
+ [Buffer.from("metadata"), METAPLEX_PROGRAM.toBuffer(), positionMint.toBuffer()],
177
+ METAPLEX_PROGRAM
178
+ );
179
+ }
180
+
181
+ // Derive all PDA token accounts for a user
182
+ export async function deriveUserTokenAccounts(
183
+ owner: PublicKey,
184
+ userStatePda: PublicKey,
185
+ collateralMint: PublicKey
186
+ ) {
187
+ const [userCollateralAta, pdaCollateralAta, userUsdcAta, pdaUsdcAta, pdaSharesAta, pdaPrimeSharesAta, pdaRockawaySharesAta, pdaPerenaSharesAta] =
188
+ await Promise.all([
189
+ getAssociatedTokenAddress(collateralMint, owner),
190
+ getAssociatedTokenAddress(collateralMint, userStatePda, true),
191
+ getAssociatedTokenAddress(USDC_MINT, owner),
192
+ getAssociatedTokenAddress(USDC_MINT, userStatePda, true),
193
+ getAssociatedTokenAddress(ALLEZ_USDC_SHARES_MINT, userStatePda, true),
194
+ getAssociatedTokenAddress(USDC_PRIME_SHARES_MINT, userStatePda, true),
195
+ getAssociatedTokenAddress(ROCKAWAY_RWA_SHARES_MINT, userStatePda, true),
196
+ getAssociatedTokenAddress(STAR_USDC_MINT, userStatePda, true),
197
+ ]);
198
+
199
+ return {
200
+ userCollateralAta,
201
+ pdaCollateralAta,
202
+ userUsdcAta,
203
+ pdaUsdcAta,
204
+ pdaSharesAta,
205
+ pdaPrimeSharesAta,
206
+ pdaRockawaySharesAta,
207
+ pdaPerenaSharesAta,
208
+ };
209
+ }
210
+
211
+ // Derive farm PDAs for any market, given its farm addresses
212
+ export function deriveFarmPdas(
213
+ obligationPda: PublicKey,
214
+ collateralFarmState: PublicKey | null,
215
+ usdcReserveFarmDebt: PublicKey | null
216
+ ) {
217
+ const obligationFarmUserStatePda = collateralFarmState
218
+ ? deriveFarmUserStatePda(collateralFarmState, obligationPda)[0]
219
+ : KAMINO_LENDING_PROGRAM_ID;
220
+
221
+ const obligationFarmDebtUserStatePda = usdcReserveFarmDebt
222
+ ? deriveFarmUserStatePda(usdcReserveFarmDebt, obligationPda)[0]
223
+ : KAMINO_LENDING_PROGRAM_ID;
224
+
225
+ return { obligationFarmUserStatePda, obligationFarmDebtUserStatePda };
226
+ }