@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.
- package/LICENSE +15 -0
- package/README.md +87 -0
- package/dist/amountUtils.d.ts +15 -0
- package/dist/amountUtils.d.ts.map +1 -0
- package/dist/amountUtils.js +59 -0
- package/dist/amountUtils.js.map +1 -0
- package/dist/constants.d.ts +148 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +231 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/executeAction.d.ts +36 -0
- package/dist/executeAction.d.ts.map +1 -0
- package/dist/executeAction.js +606 -0
- package/dist/executeAction.js.map +1 -0
- package/dist/executeDepositBorrow.d.ts +49 -0
- package/dist/executeDepositBorrow.d.ts.map +1 -0
- package/dist/executeDepositBorrow.js +444 -0
- package/dist/executeDepositBorrow.js.map +1 -0
- package/dist/idl/hobba.json +5072 -0
- package/dist/idl.d.ts +783 -0
- package/dist/idl.d.ts.map +1 -0
- package/dist/idl.js +7 -0
- package/dist/idl.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/juplend/accountResolvers.d.ts +106 -0
- package/dist/juplend/accountResolvers.d.ts.map +1 -0
- package/dist/juplend/accountResolvers.js +148 -0
- package/dist/juplend/accountResolvers.js.map +1 -0
- package/dist/juplend/operateRemaining.d.ts +11 -0
- package/dist/juplend/operateRemaining.d.ts.map +1 -0
- package/dist/juplend/operateRemaining.js +46 -0
- package/dist/juplend/operateRemaining.js.map +1 -0
- package/dist/juplend/positionInit.d.ts +34 -0
- package/dist/juplend/positionInit.d.ts.map +1 -0
- package/dist/juplend/positionInit.js +56 -0
- package/dist/juplend/positionInit.js.map +1 -0
- package/dist/juplend/positionParser.d.ts +8 -0
- package/dist/juplend/positionParser.d.ts.map +1 -0
- package/dist/juplend/positionParser.js +30 -0
- package/dist/juplend/positionParser.js.map +1 -0
- package/dist/juplend/sdkApi.d.ts +39 -0
- package/dist/juplend/sdkApi.d.ts.map +1 -0
- package/dist/juplend/sdkApi.js +68 -0
- package/dist/juplend/sdkApi.js.map +1 -0
- package/dist/juplend/tickMath.d.ts +19 -0
- package/dist/juplend/tickMath.d.ts.map +1 -0
- package/dist/juplend/tickMath.js +91 -0
- package/dist/juplend/tickMath.js.map +1 -0
- package/dist/juplend/tickUtils.d.ts +20 -0
- package/dist/juplend/tickUtils.d.ts.map +1 -0
- package/dist/juplend/tickUtils.js +115 -0
- package/dist/juplend/tickUtils.js.map +1 -0
- package/dist/juplend/vaultConfigParser.d.ts +25 -0
- package/dist/juplend/vaultConfigParser.d.ts.map +1 -0
- package/dist/juplend/vaultConfigParser.js +38 -0
- package/dist/juplend/vaultConfigParser.js.map +1 -0
- package/dist/kamino/accountResolvers.d.ts +110 -0
- package/dist/kamino/accountResolvers.d.ts.map +1 -0
- package/dist/kamino/accountResolvers.js +220 -0
- package/dist/kamino/accountResolvers.js.map +1 -0
- package/dist/kamino/obligationParser.d.ts +13 -0
- package/dist/kamino/obligationParser.d.ts.map +1 -0
- package/dist/kamino/obligationParser.js +92 -0
- package/dist/kamino/obligationParser.js.map +1 -0
- package/dist/kamino/refreshInstructions.d.ts +4 -0
- package/dist/kamino/refreshInstructions.d.ts.map +1 -0
- package/dist/kamino/refreshInstructions.js +32 -0
- package/dist/kamino/refreshInstructions.js.map +1 -0
- package/dist/limits.d.ts +87 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +174 -0
- package/dist/limits.js.map +1 -0
- package/dist/marketConfig.d.ts +35 -0
- package/dist/marketConfig.d.ts.map +1 -0
- package/dist/marketConfig.js +85 -0
- package/dist/marketConfig.js.map +1 -0
- package/dist/oracle/scopePrice.d.ts +7 -0
- package/dist/oracle/scopePrice.d.ts.map +1 -0
- package/dist/oracle/scopePrice.js +31 -0
- package/dist/oracle/scopePrice.js.map +1 -0
- package/dist/pdas.d.ts +33 -0
- package/dist/pdas.d.ts.map +1 -0
- package/dist/pdas.js +116 -0
- package/dist/pdas.js.map +1 -0
- package/dist/position.d.ts +45 -0
- package/dist/position.d.ts.map +1 -0
- package/dist/position.js +68 -0
- package/dist/position.js.map +1 -0
- package/dist/positionMetrics.d.ts +108 -0
- package/dist/positionMetrics.d.ts.map +1 -0
- package/dist/positionMetrics.js +238 -0
- package/dist/positionMetrics.js.map +1 -0
- package/dist/program.d.ts +12 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +24 -0
- package/dist/program.js.map +1 -0
- package/dist/quote.d.ts +90 -0
- package/dist/quote.d.ts.map +1 -0
- package/dist/quote.js +198 -0
- package/dist/quote.js.map +1 -0
- package/dist/rates/borrowRates.d.ts +44 -0
- package/dist/rates/borrowRates.d.ts.map +1 -0
- package/dist/rates/borrowRates.js +229 -0
- package/dist/rates/borrowRates.js.map +1 -0
- package/dist/rates/collateralInfo.d.ts +21 -0
- package/dist/rates/collateralInfo.d.ts.map +1 -0
- package/dist/rates/collateralInfo.js +104 -0
- package/dist/rates/collateralInfo.js.map +1 -0
- package/dist/rates/constants.d.ts +22 -0
- package/dist/rates/constants.d.ts.map +1 -0
- package/dist/rates/constants.js +70 -0
- package/dist/rates/constants.js.map +1 -0
- package/dist/rates/index.d.ts +11 -0
- package/dist/rates/index.d.ts.map +1 -0
- package/dist/rates/index.js +11 -0
- package/dist/rates/index.js.map +1 -0
- package/dist/rates/kaminoApi.d.ts +24 -0
- package/dist/rates/kaminoApi.d.ts.map +1 -0
- package/dist/rates/kaminoApi.js +98 -0
- package/dist/rates/kaminoApi.js.map +1 -0
- package/dist/rates/supplyRates.d.ts +23 -0
- package/dist/rates/supplyRates.d.ts.map +1 -0
- package/dist/rates/supplyRates.js +67 -0
- package/dist/rates/supplyRates.js.map +1 -0
- package/dist/transactionBuilder.d.ts +5 -0
- package/dist/transactionBuilder.d.ts.map +1 -0
- package/dist/transactionBuilder.js +287 -0
- package/dist/transactionBuilder.js.map +1 -0
- package/dist/txHelpers.d.ts +16 -0
- package/dist/txHelpers.d.ts.map +1 -0
- package/dist/txHelpers.js +82 -0
- package/dist/txHelpers.js.map +1 -0
- package/dist/userState.d.ts +21 -0
- package/dist/userState.d.ts.map +1 -0
- package/dist/userState.js +60 -0
- package/dist/userState.js.map +1 -0
- package/dist/vault/vaultAccounts.d.ts +82 -0
- package/dist/vault/vaultAccounts.d.ts.map +1 -0
- package/dist/vault/vaultAccounts.js +275 -0
- package/dist/vault/vaultAccounts.js.map +1 -0
- package/dist/wallet.d.ts +12 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +2 -0
- package/dist/wallet.js.map +1 -0
- package/package.json +63 -0
- package/src/amountUtils.ts +69 -0
- package/src/constants.ts +263 -0
- package/src/errors.ts +33 -0
- package/src/executeAction.ts +884 -0
- package/src/executeDepositBorrow.ts +677 -0
- package/src/idl/hobba.json +5072 -0
- package/src/idl.ts +7 -0
- package/src/index.ts +21 -0
- package/src/juplend/accountResolvers.ts +207 -0
- package/src/juplend/operateRemaining.ts +81 -0
- package/src/juplend/positionInit.ts +86 -0
- package/src/juplend/positionParser.ts +41 -0
- package/src/juplend/sdkApi.ts +112 -0
- package/src/juplend/tickMath.ts +82 -0
- package/src/juplend/tickUtils.ts +163 -0
- package/src/juplend/vaultConfigParser.ts +45 -0
- package/src/kamino/accountResolvers.ts +299 -0
- package/src/kamino/obligationParser.ts +123 -0
- package/src/kamino/refreshInstructions.ts +45 -0
- package/src/limits.ts +222 -0
- package/src/marketConfig.ts +139 -0
- package/src/oracle/scopePrice.ts +31 -0
- package/src/pdas.ts +226 -0
- package/src/position.ts +131 -0
- package/src/positionMetrics.ts +387 -0
- package/src/program.ts +30 -0
- package/src/quote.ts +310 -0
- package/src/rates/borrowRates.ts +270 -0
- package/src/rates/collateralInfo.ts +136 -0
- package/src/rates/constants.ts +87 -0
- package/src/rates/index.ts +38 -0
- package/src/rates/kaminoApi.ts +134 -0
- package/src/rates/supplyRates.ts +102 -0
- package/src/transactionBuilder.ts +324 -0
- package/src/txHelpers.ts +107 -0
- package/src/userState.ts +91 -0
- package/src/vault/vaultAccounts.ts +365 -0
- 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 };
|