@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
@@ -0,0 +1,136 @@
1
+ import { Connection, PublicKey } from "@solana/web3.js";
2
+ import {
3
+ decodeReserve,
4
+ computeKaminoSupplyRate,
5
+ computeJuplendSupplyRate,
6
+ getCollateralLtvInfo,
7
+ getJuplendCollateralLtvInfo,
8
+ } from "./borrowRates";
9
+ import {
10
+ deriveJuplendVaultConfigPda,
11
+ JUPLEND_VAULT_ID,
12
+ JUPLEND_SOL_VAULT_ID,
13
+ JUPLEND_LIQUIDITY_PROGRAM,
14
+ SOL_MINT,
15
+ CBBTC_MINT,
16
+ } from "../constants";
17
+
18
+ // Collateral reserve addresses for supply APY (Kamino Main Market)
19
+ const COLLATERAL_RESERVES: Record<string, string> = {
20
+ cbBTC: "37Jk2zkz23vkAYBT66HM2gaqJuNg2nYLsCreQAVt5MWK",
21
+ SOL: "d4A2prbA2whesmvHaL88BH6Ewn5N4bTSU2Ze8P6Bc4Q",
22
+ };
23
+
24
+ export interface CollateralRatesInfo {
25
+ collateralSupplyAPYs: Record<string, number>;
26
+ collateralLtvs: Record<string, { loanToValuePct: number; liquidationThresholdPct: number }>;
27
+ juplendCollateralSupplyAPYs: Record<string, number>;
28
+ juplendCollateralRewardsAPYs: Record<string, number>;
29
+ juplendCollateralLtvs: Record<string, { loanToValuePct: number; liquidationThresholdPct: number }>;
30
+ }
31
+
32
+ /**
33
+ * Per-collateral supply APYs + LTV thresholds for both lenders. Shared by
34
+ * /api/rates and /api/market-data so widget integrators get liquidation LTVs
35
+ * from the same source the app uses.
36
+ */
37
+ export async function computeCollateralRatesInfo(
38
+ connection: Connection,
39
+ recentSlotDurationMs: number,
40
+ ): Promise<CollateralRatesInfo> {
41
+ // Kamino collateral info: supply APY + LTV thresholds per asset
42
+ const collateralSupplyAPYs: Record<string, number> = {};
43
+ const collateralLtvs: CollateralRatesInfo["collateralLtvs"] = {};
44
+ await Promise.all(
45
+ Object.entries(COLLATERAL_RESERVES).map(async ([asset, reserveAddr]) => {
46
+ try {
47
+ const info = await connection.getAccountInfo(new PublicKey(reserveAddr));
48
+ if (info) {
49
+ const reserve = decodeReserve(info.data as Buffer);
50
+ const { supplyAPY } = computeKaminoSupplyRate(reserve, recentSlotDurationMs);
51
+ collateralSupplyAPYs[asset] = supplyAPY;
52
+ collateralLtvs[asset] = getCollateralLtvInfo(reserve);
53
+ }
54
+ } catch (e: any) {
55
+ console.error(`Error computing ${asset} supply APY:`, e.message);
56
+ }
57
+ })
58
+ );
59
+
60
+ // JupLend collateral supply APYs — the lender rate on the SOL/cbBTC liquidity
61
+ // reserve, used when a position is deposited into JupLend rather than Kamino.
62
+ const juplendCollateralSupplyAPYs: Record<string, number> = {};
63
+ const juplendCollateralMints: Array<[string, PublicKey]> = [
64
+ ["SOL", SOL_MINT],
65
+ ["cbBTC", CBBTC_MINT],
66
+ ];
67
+ await Promise.all(
68
+ juplendCollateralMints.map(async ([asset, mint]) => {
69
+ try {
70
+ const reservePda = PublicKey.findProgramAddressSync(
71
+ [Buffer.from("reserve"), mint.toBuffer()],
72
+ JUPLEND_LIQUIDITY_PROGRAM
73
+ )[0];
74
+ const info = await connection.getAccountInfo(reservePda);
75
+ if (info) {
76
+ const { supplyAPY } = computeJuplendSupplyRate(info.data as Buffer);
77
+ juplendCollateralSupplyAPYs[asset] = supplyAPY;
78
+ }
79
+ } catch (e: any) {
80
+ console.error(`Error computing JupLend ${asset} supply APY:`, e.message);
81
+ }
82
+ })
83
+ );
84
+
85
+ // JupLend rewards boost (vault supplyRate minus base supplyRateLiquidity)
86
+ const juplendCollateralRewardsAPYs: Record<string, number> = {};
87
+ try {
88
+ const vaultsRes = await fetch("https://lite-api.jup.ag/lend/v1/borrow/vaults", {
89
+ headers: { accept: "application/json" },
90
+ signal: AbortSignal.timeout(8000),
91
+ });
92
+ if (vaultsRes.ok) {
93
+ const vaults: any[] = await vaultsRes.json();
94
+ const juplendVaultIds: Array<[string, number]> = [
95
+ ["SOL", JUPLEND_SOL_VAULT_ID],
96
+ ["cbBTC", JUPLEND_VAULT_ID],
97
+ ];
98
+ for (const [asset, vaultId] of juplendVaultIds) {
99
+ const v = vaults.find((x) => x?.id === vaultId);
100
+ if (!v) continue;
101
+ // API rates are 1e2-scaled (e.g. "531" => 5.31%).
102
+ const rewards = Math.max(0, Number(v.supplyRate) - Number(v.supplyRateLiquidity)) / 100;
103
+ if (rewards > 0) juplendCollateralRewardsAPYs[asset] = rewards;
104
+ }
105
+ }
106
+ } catch (e: any) {
107
+ console.error("Error fetching JupLend rewards:", e.message);
108
+ }
109
+
110
+ // JupLend collateral LTVs (per-vault liquidationThreshold from vault_config)
111
+ const juplendCollateralLtvs: CollateralRatesInfo["juplendCollateralLtvs"] = {};
112
+ const juplendVaultConfigs: Array<[string, PublicKey]> = [
113
+ ["cbBTC", deriveJuplendVaultConfigPda(JUPLEND_VAULT_ID)],
114
+ ["SOL", deriveJuplendVaultConfigPda(JUPLEND_SOL_VAULT_ID)],
115
+ ];
116
+ await Promise.all(
117
+ juplendVaultConfigs.map(async ([asset, addr]) => {
118
+ try {
119
+ const info = await connection.getAccountInfo(addr);
120
+ if (info) {
121
+ juplendCollateralLtvs[asset] = getJuplendCollateralLtvInfo(info.data as Buffer);
122
+ }
123
+ } catch (e: any) {
124
+ console.error(`Error fetching JupLend ${asset} vault config:`, e.message);
125
+ }
126
+ })
127
+ );
128
+
129
+ return {
130
+ collateralSupplyAPYs,
131
+ collateralLtvs,
132
+ juplendCollateralSupplyAPYs,
133
+ juplendCollateralRewardsAPYs,
134
+ juplendCollateralLtvs,
135
+ };
136
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Shared vault-to-reserves mappings and borrow market definitions.
3
+ * Single source of truth used by /api/rates, /api/supply-rates, and /api/market-data.
4
+ */
5
+
6
+ export interface VaultReserveDef {
7
+ name: string;
8
+ id: string; // 'allez' | 'prime' | 'rockawayRWA'
9
+ vault: string;
10
+ sharesMint: string;
11
+ reserves: string[];
12
+ }
13
+
14
+ export interface BorrowMarketDef {
15
+ name: string;
16
+ provider: "kamino" | "juplend";
17
+ usdcReserve: string;
18
+ cbbtcReserve?: string;
19
+ }
20
+
21
+ export const VAULT_RESERVE_MAP: VaultReserveDef[] = [
22
+ {
23
+ name: "Allez USDC",
24
+ id: "allez",
25
+ vault: "A1USdzqDHmw5oz97AkqAGLxEQZfFjASZFuy4T6Qdvnpo",
26
+ sharesMint: "BDh5h1uqhdL3pdCcTB1AEc2dyRsorDx9fF8Y72H1XZu8",
27
+ reserves: [
28
+ "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59", // Main Market
29
+ "Ga4rZytCpq1unD4DbEJ5bkHeUz9g3oh9AAFEi6vSauXp", // Altcoin
30
+ "9FRZvAsjDJ6WM8BJ2S45h9PoDCLAq8DNY9zZDX7MyGzT", // JLP
31
+ "9TD2TSv4pENb8VwfbVYg25jvym7HN6iuAR6pFNSrKjqQ", // Market 4
32
+ "F22tnLsbv66vEU2GZRc7coaqZsr8UcBbyp9V2kqWAiWK", // Market 5
33
+ "Atj6UREVWa7WxbF2EMKNyfmYUY1U1txughe2gjhcPDCo", // Market 6
34
+ "9GJ9GBRwCp4pHmWrQ43L5xpc9Vykg7jnfwcFGN8FoHYu", // Market 7
35
+ "ALnjBHECcdBsSwgVJ6A8pFEZ15qxE5jJQriCXSKT3Bte", // Market 8
36
+ "4QKFoFDzNFnvfkzVazABbCEfMwd3y1pZqUVzmpnkCphj", // Market 9
37
+ "AYL4LMc4ZCVyq3Z7XPJGWDM4H9PiWjqXAAuuHBEGVR2Z", // Market 10
38
+ "CxvpximovDBSPXhPj4VXechMWEV5G2DtuKwa9v9Hswrc", // Market 11
39
+ ],
40
+ },
41
+ {
42
+ name: "Prime USDC",
43
+ id: "prime",
44
+ vault: "HDsayqAsDWy3QvANGqh2yNraqcD8Fnjgh73Mhb3WRS5E",
45
+ sharesMint: "7D8C5pDFxug58L9zkwK7bCiDg4kD4AygzbcZUmf5usHS",
46
+ reserves: [
47
+ "Ga4rZytCpq1unD4DbEJ5bkHeUz9g3oh9AAFEi6vSauXp", // Altcoin
48
+ "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59", // Main Market
49
+ "9FRZvAsjDJ6WM8BJ2S45h9PoDCLAq8DNY9zZDX7MyGzT", // JLP
50
+ ],
51
+ },
52
+ {
53
+ name: "RockawayRWA USDC",
54
+ id: "rockawayRWA",
55
+ vault: "DWSXb18xZApz29vnQpgR2m6MynCT7PznaXt7Ut7M7KaP",
56
+ sharesMint: "DgHN3q3dSYAchNX7V3D4aYiTWMx8RHTgHbfPiwiqBkE9",
57
+ reserves: [
58
+ "6XdN3zXeoYKgfSeZb8h1LpiEkUXRJ3CbimE5FJ35XFBP", // RWA
59
+ "4QKFoFDzNFnvfkzVazABbCEfMwd3y1pZqUVzmpnkCphj", // Market 9
60
+ "AYL4LMc4ZCVyq3Z7XPJGWDM4H9PiWjqXAAuuHBEGVR2Z", // Market 10
61
+ "9GJ9GBRwCp4pHmWrQ43L5xpc9Vykg7jnfwcFGN8FoHYu", // Market 7
62
+ ],
63
+ },
64
+ ];
65
+
66
+ export const BORROW_MARKET_DEFS: BorrowMarketDef[] = [
67
+ {
68
+ name: "Kamino Main Market",
69
+ provider: "kamino",
70
+ usdcReserve: "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59",
71
+ cbbtcReserve: "37Jk2zkz23vkAYBT66HM2gaqJuNg2nYLsCreQAVt5MWK",
72
+ },
73
+ {
74
+ name: "Kamino xStocks",
75
+ provider: "kamino",
76
+ usdcReserve: "97zoywd8mPZsGTg8q1wdD2Wgkdrs2tqusp1Qqcxbyj7E",
77
+ cbbtcReserve: "5AWpVYJvNASoUM8toSDQRcVyA9dvoUuFj5qNx9v32bjj",
78
+ },
79
+ {
80
+ name: "JupLend",
81
+ provider: "juplend",
82
+ usdcReserve: "94vK29npVbyRHXH63rRcTiSr26SFhrQTzbpNJuhQEDu",
83
+ },
84
+ ];
85
+
86
+ export const JUPLEND_USDC_RESERVE = "94vK29npVbyRHXH63rRcTiSr26SFhrQTzbpNJuhQEDu";
87
+ export const JUPLEND_VAULT_STATE_ADDR = "HnSMEmypWgJ1a6JNnBycccsBeQQvqNKphgSMquCaFiy9";
@@ -0,0 +1,38 @@
1
+ // Server-leaning rate math shared by the app's API routes, the rebalancer and
2
+ // scripts. Deliberately NOT re-exported from @hobba/core's index: decodeReserve
3
+ // lazy-requires @kamino-finance/klend-sdk (heavy, effectively server-only), so
4
+ // consumers import via the subpath (`@hobba/core/rates`) to keep it out of
5
+ // browser/widget bundles.
6
+ export {
7
+ decodeReserve,
8
+ computeKaminoBorrowRate,
9
+ computeKaminoSupplyRate,
10
+ computeJuplendBorrowRate,
11
+ computeJuplendSupplyRate,
12
+ getCollateralInfo,
13
+ getCollateralLtvInfo,
14
+ getJuplendCollateral,
15
+ getJuplendCollateralLtvInfo,
16
+ getRecentSlotDurationMs,
17
+ } from "./borrowRates";
18
+ export type { BorrowRateResult, CollateralInfo, CollateralLtvInfo, CurvePoint } from "./borrowRates";
19
+
20
+ export {
21
+ computeSupplyRate,
22
+ computeWeightedVaultAPY,
23
+ } from "./supplyRates";
24
+ export type { SupplyRateResult, VaultAPYResult } from "./supplyRates";
25
+
26
+ export {
27
+ VAULT_RESERVE_MAP,
28
+ BORROW_MARKET_DEFS,
29
+ JUPLEND_USDC_RESERVE,
30
+ JUPLEND_VAULT_STATE_ADDR,
31
+ } from "./constants";
32
+ export type { VaultReserveDef, BorrowMarketDef } from "./constants";
33
+
34
+ export { fetchKaminoVaultAPYs, fetchPerenaVaultAPY } from "./kaminoApi";
35
+ export type { KaminoVaultMetrics } from "./kaminoApi";
36
+
37
+ export { computeCollateralRatesInfo } from "./collateralInfo";
38
+ export type { CollateralRatesInfo } from "./collateralInfo";
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Fetch vault APYs from Kamino's public API.
3
+ * This includes base lending yield + farming rewards + incentives,
4
+ * matching what the Kamino UI displays.
5
+ */
6
+
7
+ import { Connection, PublicKey } from "@solana/web3.js";
8
+ import { VAULT_RESERVE_MAP } from "./constants";
9
+
10
+ const KAMINO_API = "https://api.kamino.finance";
11
+
12
+ export interface KaminoVaultMetrics {
13
+ name: string;
14
+ id: string;
15
+ vault: string;
16
+ supplyAPY: number; // total APY (base + rewards) as percentage
17
+ baseAPY: number; // base lending APY as percentage
18
+ rewardsAPY: number; // farming + incentives APY as percentage
19
+ // True = excluded from rebalancer's vault selection (won't switch INTO it,
20
+ // won't pick as bestVault). Users with existing shares can still switch OUT.
21
+ isDisabled: boolean;
22
+ }
23
+
24
+ interface KaminoMetricsResponse {
25
+ apy?: string | number;
26
+ apyFarmRewards?: string | number;
27
+ apyIncentives?: string | number;
28
+ apyReservesIncentives?: string | number;
29
+ }
30
+
31
+ export async function fetchKaminoVaultAPYs(): Promise<KaminoVaultMetrics[]> {
32
+ const results = await Promise.all(
33
+ VAULT_RESERVE_MAP.map(async (v) => {
34
+ try {
35
+ const resp = await fetch(`${KAMINO_API}/kvaults/${v.vault}/metrics`, { cache: 'no-store' });
36
+ if (!resp.ok) {
37
+ return fallback(v);
38
+ }
39
+ const data: KaminoMetricsResponse = await resp.json();
40
+
41
+ const apy = toNum(data.apy);
42
+ const farmRewards = toNum(data.apyFarmRewards);
43
+ const incentives = toNum(data.apyIncentives);
44
+ const reserveIncentives = toNum(data.apyReservesIncentives);
45
+ const rewardsAPY = farmRewards + incentives + reserveIncentives;
46
+ // Kamino's API `apy` excludes reserveIncentives; the website includes them
47
+ const totalAPY = apy + reserveIncentives;
48
+
49
+ return {
50
+ name: v.name,
51
+ id: v.id,
52
+ vault: v.vault,
53
+ supplyAPY: totalAPY * 100,
54
+ baseAPY: apy * 100,
55
+ rewardsAPY: rewardsAPY * 100,
56
+ isDisabled: true,
57
+ };
58
+ } catch {
59
+ return fallback(v);
60
+ }
61
+ })
62
+ );
63
+
64
+ return results;
65
+ }
66
+
67
+ function toNum(val: string | number | undefined): number {
68
+ if (val === undefined || val === null) return 0;
69
+ const n = typeof val === "string" ? parseFloat(val) : val;
70
+ return isNaN(n) ? 0 : n;
71
+ }
72
+
73
+ function fallback(v: { name: string; id: string; vault: string }): KaminoVaultMetrics {
74
+ return { name: v.name, id: v.id, vault: v.vault, supplyAPY: 0, baseAPY: 0, rewardsAPY: 0, isDisabled: true };
75
+ }
76
+
77
+ // --- Perena vault ---
78
+
79
+ const PERENA_VAULT_STATE_ADDRESS = new PublicKey("sM6P4mh53CnG4faN4Fo3seY7wMSAiHdy8o6gKjwQF7A");
80
+ const PERENA_TOTAL_SHARES_OFFSET = 200;
81
+ const PERENA_PRICE_PER_SHARE_OFFSET = 352;
82
+
83
+ // Known reference point from the Perena USD* historical price chart:
84
+ // 2026-06-25 02:00 CEST (UTC+2) = 2026-06-25 00:00:00 UTC, price per share 1.0744.
85
+ // (Verified against on-chain state: offset 352 read 1.07609 on 2026-07-01.)
86
+ const PERENA_REF_PRICE = 1.0744;
87
+ const PERENA_REF_TIMESTAMP_MS = Date.UTC(2026, 5, 25, 0, 0, 0);
88
+
89
+ /**
90
+ * Fetch Perena vault APY by reading on-chain state.
91
+ * Share price is a 6-decimal fixed-point u64 (e.g. 1055691 = 1.055691 USDC per star*).
92
+ * APY is annualized from share price appreciation between a known reference point
93
+ * and the current on-chain price.
94
+ */
95
+ export async function fetchPerenaVaultAPY(connection: Connection): Promise<KaminoVaultMetrics> {
96
+ const id = "perena";
97
+ const name = "Perena USDC";
98
+ const vault = PERENA_VAULT_STATE_ADDRESS.toBase58();
99
+
100
+ try {
101
+ const accountInfo = await connection.getAccountInfo(PERENA_VAULT_STATE_ADDRESS);
102
+ if (!accountInfo || !accountInfo.data) {
103
+ return { name, id, vault, supplyAPY: 0, baseAPY: 0, rewardsAPY: 0, isDisabled: false };
104
+ }
105
+
106
+ const data = accountInfo.data;
107
+
108
+ // Read total shares (u64 LE at offset 200)
109
+ const _totalShares = data.readBigUInt64LE(PERENA_TOTAL_SHARES_OFFSET);
110
+
111
+ // Read price per share (u64 LE at offset 352, 6-decimal fixed point)
112
+ const rawPrice = data.readBigUInt64LE(PERENA_PRICE_PER_SHARE_OFFSET);
113
+ const currentPrice = Number(rawPrice) / 1_000_000;
114
+
115
+ // Annualize the share-price appreciation since the reference point.
116
+ const elapsedDays = (Date.now() - PERENA_REF_TIMESTAMP_MS) / (1000 * 60 * 60 * 24);
117
+ const apy =
118
+ elapsedDays > 0 && currentPrice > PERENA_REF_PRICE
119
+ ? ((currentPrice / PERENA_REF_PRICE - 1) * (365 / elapsedDays)) * 100
120
+ : 0;
121
+
122
+ return {
123
+ name,
124
+ id,
125
+ vault,
126
+ supplyAPY: apy,
127
+ baseAPY: apy,
128
+ rewardsAPY: 0,
129
+ isDisabled: false,
130
+ };
131
+ } catch {
132
+ return { name, id, vault, supplyAPY: 0, baseAPY: 0, rewardsAPY: 0, isDisabled: false };
133
+ }
134
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Shared supply rate computation functions.
3
+ * Extracted from /api/supply-rates/route.ts to be reused by /api/market-data and the rebalancer.
4
+ */
5
+
6
+ import { decodeReserve, CurvePoint } from "./borrowRates";
7
+
8
+ const SF_SCALE = BigInt("1152921504606846976"); // 2^60
9
+ const SLOTS_PER_YEAR = 63072000;
10
+ const SLOTS_PER_SECOND = 2;
11
+ const DEFAULT_SLOT_DURATION_MS = 400;
12
+
13
+ export interface SupplyRateResult {
14
+ totalDeposited: number;
15
+ utilization: number; // percentage (0–100)
16
+ supplyAPY: number; // percentage
17
+ }
18
+
19
+ export interface VaultAPYResult {
20
+ name: string;
21
+ supplyAPY: number; // percentage
22
+ utilization?: number; // percentage
23
+ totalDeposited?: number;
24
+ numReserves: number;
25
+ }
26
+
27
+ export function computeSupplyRate(reserve: any, recentSlotDurationMs = DEFAULT_SLOT_DURATION_MS): SupplyRateResult {
28
+ const availableAmount = Number(reserve.liquidity.availableAmount);
29
+ const borrowedAmount = Number(BigInt(reserve.liquidity.borrowedAmountSf.toString()) / SF_SCALE);
30
+ const protocolFees = Number(BigInt(reserve.liquidity.accumulatedProtocolFeesSf.toString()) / SF_SCALE);
31
+ const referrerFees = Number(BigInt(reserve.liquidity.accumulatedReferrerFeesSf.toString()) / SF_SCALE);
32
+ const pendingFees = Number(BigInt(reserve.liquidity.pendingReferrerFeesSf.toString()) / SF_SCALE);
33
+
34
+ const totalSupply = availableAmount + borrowedAmount - protocolFees - referrerFees - pendingFees;
35
+ const utilization = totalSupply > 0 ? borrowedAmount / totalSupply : 0;
36
+
37
+ const curve: CurvePoint[] = reserve.config.borrowRateCurve.points;
38
+ const utilBps = utilization * 10000;
39
+ let borrowRateBps = 0;
40
+ for (let i = 1; i < curve.length; i++) {
41
+ if (curve[i].utilizationRateBps === 0 && curve[i].borrowRateBps === 0 && i > 1) break;
42
+ if (utilBps <= curve[i].utilizationRateBps) {
43
+ const x0 = curve[i - 1].utilizationRateBps;
44
+ const x1 = curve[i].utilizationRateBps;
45
+ const y0 = curve[i - 1].borrowRateBps;
46
+ const y1 = curve[i].borrowRateBps;
47
+ borrowRateBps = y0 + ((utilBps - x0) * (y1 - y0)) / (x1 - x0);
48
+ break;
49
+ }
50
+ }
51
+
52
+ const slotAdjustment = 1000 / SLOTS_PER_SECOND / recentSlotDurationMs;
53
+ const adjustedBorrowRate = (borrowRateBps / 10000) * slotAdjustment;
54
+ const hostFixedRate = (reserve.config.hostFixedInterestRateBps / 10000) * slotAdjustment;
55
+ const borrowAPR = adjustedBorrowRate + hostFixedRate;
56
+
57
+ const protocolTakeRate = reserve.config.protocolTakeRatePct / 100;
58
+ const supplyAPR = borrowAPR * utilization * (1 - protocolTakeRate);
59
+ const supplyAPY = Math.pow(1 + supplyAPR / SLOTS_PER_YEAR, SLOTS_PER_YEAR) - 1;
60
+
61
+ const mintDecimals = Number(reserve.liquidity.mintDecimals);
62
+ const divisor = Math.pow(10, mintDecimals);
63
+
64
+ return {
65
+ totalDeposited: totalSupply / divisor,
66
+ utilization: utilization * 100,
67
+ supplyAPY: supplyAPY * 100,
68
+ };
69
+ }
70
+
71
+ export function computeWeightedVaultAPY(
72
+ reserveAddresses: string[],
73
+ rateMap: Map<string, SupplyRateResult>,
74
+ ): VaultAPYResult & { supplyAPY: number } {
75
+ const reserveRates = reserveAddresses
76
+ .map((addr) => rateMap.get(addr))
77
+ .filter((r): r is SupplyRateResult => r != null && r.totalDeposited > 0);
78
+
79
+ if (reserveRates.length === 0) {
80
+ return { name: "", supplyAPY: 0, totalDeposited: 0, numReserves: 0 };
81
+ }
82
+
83
+ const totalWeight = reserveRates.reduce((s, r) => s + r.totalDeposited, 0);
84
+ const weightedAPY = reserveRates.reduce(
85
+ (s, r) => s + r.supplyAPY * (r.totalDeposited / totalWeight),
86
+ 0
87
+ );
88
+ const avgUtilization = reserveRates.reduce(
89
+ (s, r) => s + r.utilization * (r.totalDeposited / totalWeight),
90
+ 0
91
+ );
92
+
93
+ return {
94
+ name: "",
95
+ supplyAPY: weightedAPY,
96
+ utilization: avgUtilization,
97
+ totalDeposited: totalWeight,
98
+ numReserves: reserveRates.length,
99
+ };
100
+ }
101
+
102
+ export { decodeReserve };