@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,163 @@
1
+ import {
2
+ Connection,
3
+ PublicKey,
4
+ SystemProgram,
5
+ TransactionInstruction,
6
+ } from "@solana/web3.js";
7
+ import {
8
+ JUPLEND_PROGRAM,
9
+ JUPLEND_VAULT_ID,
10
+ deriveJuplendVaultConfigPda,
11
+ } from "../constants";
12
+ import {
13
+ deriveJuplendTickPda,
14
+ deriveJuplendTickIdLiquidationPda,
15
+ } from "../pdas";
16
+
17
+ // InitTick discriminator: [22, 13, 62, 141, 73, 89, 178, 29]
18
+ const INIT_TICK_DISC = Buffer.from([22, 13, 62, 141, 73, 89, 178, 29]);
19
+ // InitTickIdLiquidation discriminator: [221, 173, 234, 108, 69, 217, 237, 101]
20
+ const INIT_TICK_ID_LIQ_DISC = Buffer.from([221, 173, 234, 108, 69, 217, 237, 101]);
21
+
22
+ export function buildInitTickIx(
23
+ vaultId: number,
24
+ tick: number,
25
+ payer: PublicKey
26
+ ): TransactionInstruction {
27
+ const tickPda = deriveJuplendTickPda(vaultId, tick);
28
+ const vaultConfig = deriveJuplendVaultConfigPda(vaultId);
29
+ const data = Buffer.alloc(14);
30
+ data.set(INIT_TICK_DISC, 0);
31
+ data.writeUInt16LE(vaultId, 8);
32
+ data.writeInt32LE(tick, 10);
33
+ return new TransactionInstruction({
34
+ programId: JUPLEND_PROGRAM,
35
+ keys: [
36
+ { pubkey: payer, isSigner: true, isWritable: true },
37
+ { pubkey: vaultConfig, isSigner: false, isWritable: false },
38
+ { pubkey: tickPda, isSigner: false, isWritable: true },
39
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
40
+ ],
41
+ data,
42
+ });
43
+ }
44
+
45
+ export function buildInitTickIdLiquidationIx(
46
+ vaultId: number,
47
+ tick: number,
48
+ totalIds: number,
49
+ payer: PublicKey
50
+ ): TransactionInstruction {
51
+ const tickPda = deriveJuplendTickPda(vaultId, tick);
52
+ const tickIdPda = deriveJuplendTickIdLiquidationPda(vaultId, tick, totalIds);
53
+ const data = Buffer.alloc(18);
54
+ data.set(INIT_TICK_ID_LIQ_DISC, 0);
55
+ data.writeUInt16LE(vaultId, 8);
56
+ data.writeInt32LE(tick, 10);
57
+ data.writeUInt32LE(totalIds, 14);
58
+ // initTickIdLiquidation: [signer, tickData (existing), tickIdLiquidation (new), systemProgram]
59
+ // Slot 1 is tickData (NOT vaultConfig — initTick is the sibling that uses
60
+ // vaultConfig). Matches the production /api/juplend-operate builder.
61
+ return new TransactionInstruction({
62
+ programId: JUPLEND_PROGRAM,
63
+ keys: [
64
+ { pubkey: payer, isSigner: true, isWritable: true },
65
+ { pubkey: tickPda, isSigner: false, isWritable: false },
66
+ { pubkey: tickIdPda, isSigner: false, isWritable: true },
67
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
68
+ ],
69
+ data,
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Reverse-derive a tick value from its PDA. Searches outward from `startTick`
75
+ * (usually the position's current tick, < 50 iterations) before falling back
76
+ * to the full ±16383 range.
77
+ */
78
+ export function tickValueFromPda(
79
+ pda: PublicKey,
80
+ vaultId: number,
81
+ startTick: number = 0,
82
+ maxTick: number = 16383,
83
+ ): number | null {
84
+ for (let delta = 0; delta <= 1000; delta++) {
85
+ for (const tick of delta === 0 ? [startTick] : [startTick + delta, startTick - delta]) {
86
+ if (tick < -maxTick || tick > maxTick) continue;
87
+ if (deriveJuplendTickPda(vaultId, tick).equals(pda)) return tick;
88
+ }
89
+ }
90
+ // Fallback: full range (should rarely hit this)
91
+ for (let tick = -maxTick; tick <= maxTick; tick++) {
92
+ if (deriveJuplendTickPda(vaultId, tick).equals(pda)) return tick;
93
+ }
94
+ return null;
95
+ }
96
+
97
+ export interface TickAccounts {
98
+ currentTickPda: PublicKey;
99
+ finalTickPda: PublicKey;
100
+ currentTickIdPda: PublicKey;
101
+ finalTickIdPda: PublicKey;
102
+ initIxs: TransactionInstruction[];
103
+ }
104
+
105
+ // Derive tick + tickId PDAs for current and final ticks, check existence, return init instructions
106
+ export async function resolveTickAccounts(
107
+ connection: Connection,
108
+ payer: PublicKey,
109
+ currentTick: number,
110
+ finalTick: number,
111
+ vaultId: number = JUPLEND_VAULT_ID
112
+ ): Promise<TickAccounts> {
113
+ const currentTickPda = deriveJuplendTickPda(vaultId, currentTick);
114
+ const currentTickIdPda = deriveJuplendTickIdLiquidationPda(vaultId, currentTick);
115
+ const finalTickPda = deriveJuplendTickPda(vaultId, finalTick);
116
+ const finalTickIdPda = deriveJuplendTickIdLiquidationPda(vaultId, finalTick);
117
+
118
+ const initIxs: TransactionInstruction[] = [];
119
+ const checked = new Set<string>();
120
+
121
+ for (const { tickPda, tickIdPda, tick } of [
122
+ { tickPda: currentTickPda, tickIdPda: currentTickIdPda, tick: currentTick },
123
+ { tickPda: finalTickPda, tickIdPda: finalTickIdPda, tick: finalTick },
124
+ ]) {
125
+ const key = tickPda.toBase58();
126
+ if (checked.has(key)) continue;
127
+ checked.add(key);
128
+
129
+ const [tickInfo, tickIdInfo] = await Promise.all([
130
+ connection.getAccountInfo(tickPda).catch(() => null),
131
+ connection.getAccountInfo(tickIdPda).catch(() => null),
132
+ ]);
133
+
134
+ if (!tickInfo) {
135
+ initIxs.push(buildInitTickIx(vaultId, tick, payer));
136
+ }
137
+ if (!tickIdInfo) {
138
+ initIxs.push(buildInitTickIdLiquidationIx(vaultId, tick, 0, payer));
139
+ }
140
+ }
141
+
142
+ return { currentTickPda, finalTickPda, currentTickIdPda, finalTickIdPda, initIxs };
143
+ }
144
+
145
+ // Parse expected tick from VaultTickMismatch error
146
+ export function parseTickMismatch(errorMsg: string): number | null {
147
+ const match = errorMsg.match(/Tick mismatch: expected (-?\d+) but got (-?\d+)/);
148
+ return match ? parseInt(match[1]) : null;
149
+ }
150
+
151
+ export function isTickError(errorMsg: string): boolean {
152
+ return (
153
+ errorMsg.includes("VaultTickMismatch") ||
154
+ errorMsg.includes("Tick mismatch") ||
155
+ errorMsg.includes("VaultInvalidTick") ||
156
+ errorMsg.includes("VAULT_INVALID_TICK") ||
157
+ errorMsg.includes("VaultTickHasDebtNotFound") ||
158
+ errorMsg.includes("VAULT_TICK_HAS_DEBT_NOT_FOUND") ||
159
+ errorMsg.includes("0x1792") || // VaultTickMismatch
160
+ errorMsg.includes("0x179a") || // VaultInvalidTick
161
+ errorMsg.includes("0x1791") // VaultTickHasDebtNotFound
162
+ );
163
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * JupLend VaultConfig byte-layout knowledge, centralized. Browser-safe.
3
+ * (The LTV/liquidation-threshold fields at the head of the same struct are
4
+ * decoded by rates/borrowRates.getJuplendCollateralLtvInfo.)
5
+ */
6
+ import { PublicKey } from "@solana/web3.js";
7
+ import BN from "bn.js";
8
+
9
+ /**
10
+ * VaultConfig is bytemuck-packed; `oracle` lives 26 bytes after the 8-byte
11
+ * Anchor discriminator (sum of the preceding 9× u16 / i16 fields).
12
+ */
13
+ export const JUPLEND_VAULT_CONFIG_ORACLE_OFFSET = 8 + 2 * 9;
14
+
15
+ /** Oracle pubkey configured on a JupLend vault_config account. */
16
+ export function parseVaultConfigOracle(vaultConfigData: Buffer): PublicKey {
17
+ if (vaultConfigData.length < JUPLEND_VAULT_CONFIG_ORACLE_OFFSET + 32) {
18
+ throw new Error("vault_config account too short");
19
+ }
20
+ return new PublicKey(
21
+ vaultConfigData.subarray(
22
+ JUPLEND_VAULT_CONFIG_ORACLE_OFFSET,
23
+ JUPLEND_VAULT_CONFIG_ORACLE_OFFSET + 32,
24
+ ),
25
+ );
26
+ }
27
+
28
+ /** Supply token decimals per vault (determines exchange rate divisor). */
29
+ export const SUPPLY_DECIMALS_BY_VAULT: Record<number, number> = {
30
+ 1: 9, // SOL (wSOL) — 9 decimals
31
+ 11: 8, // cbBTC — 8 decimals
32
+ };
33
+
34
+ /**
35
+ * Convert JupLend shares to native token base units. Exchange prices are
36
+ * scaled by 10^12; the divisor folds in the token-decimal difference vs SOL:
37
+ * SOL (9 decimals): 10^(12 + 0) = 10^12
38
+ * cbBTC (8 decimals): 10^(12 + 1) = 10^13
39
+ * USDC (6 decimals): 10^(12 + 3) = 10^15
40
+ */
41
+ export function sharesToBaseUnits(shares: BN, exchangePrice: BN, tokenDecimals: number): BN {
42
+ if (!exchangePrice.gt(new BN(0))) return new BN(0);
43
+ const divisor = new BN(10).pow(new BN(12 + (9 - tokenDecimals)));
44
+ return shares.mul(exchangePrice).div(divisor);
45
+ }
@@ -0,0 +1,299 @@
1
+ import { PublicKey, SystemProgram, SYSVAR_INSTRUCTIONS_PUBKEY, SYSVAR_RENT_PUBKEY, AccountMeta } from "@solana/web3.js";
2
+ import { TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from "@solana/spl-token";
3
+ import {
4
+ KAMINO_LENDING_PROGRAM_ID,
5
+ FARMS_PROGRAM,
6
+ ATA_PROGRAM_ID,
7
+ USDC_MINT,
8
+ SCOPE_PRICES,
9
+ } from "../constants";
10
+ import { deriveDepositConfigPda, deriveOriginConfigPda, deriveProgramConfigPda, deriveUserOriginPda } from "../pdas";
11
+ import { KaminoMarketConfig } from "../marketConfig";
12
+
13
+ interface UserPdas {
14
+ userStatePda: PublicKey;
15
+ allowlistEntryPda: PublicKey;
16
+ obligationPda: PublicKey;
17
+ userMetadataPda: PublicKey;
18
+ obligationFarmUserStatePda: PublicKey;
19
+ obligationFarmDebtUserStatePda: PublicKey;
20
+ userCollateralAta: PublicKey;
21
+ userUsdcAta: PublicKey;
22
+ pdaCollateralAta: PublicKey;
23
+ pdaUsdcAta: PublicKey;
24
+ pdaSharesAta: PublicKey;
25
+ }
26
+
27
+ const [programConfigPda] = deriveProgramConfigPda();
28
+
29
+ export function getInitializeUserAccounts(owner: PublicKey, pdas: UserPdas, mc: KaminoMarketConfig, origin: number = 0) {
30
+ return {
31
+ owner,
32
+ userState: pdas.userStatePda,
33
+ collateralMint: mc.collateralMint,
34
+ systemProgram: SystemProgram.programId,
35
+ programConfig: programConfigPda,
36
+ allowlistEntry: pdas.allowlistEntryPda,
37
+ depositConfig: deriveDepositConfigPda(mc.collateralMint)[0],
38
+ // Per-wallet origin registration; created on the wallet's first init,
39
+ // later inits keep the stored origin (the origin arg is then ignored).
40
+ userOrigin: deriveUserOriginPda(owner)[0],
41
+ // Seeded by the `origin` instruction arg — always pass explicitly rather
42
+ // than relying on Anchor's IDL auto-resolution.
43
+ originConfig: deriveOriginConfigPda(origin)[0],
44
+ };
45
+ }
46
+
47
+ /// Returns 7 Kamino initialize_user lender accounts as AccountMeta[] for remaining_accounts.
48
+ export function getKaminoInitializeUserLenderAccounts(
49
+ pdas: UserPdas,
50
+ mc: KaminoMarketConfig,
51
+ ): AccountMeta[] {
52
+ return [
53
+ { pubkey: pdas.obligationPda, isSigner: false, isWritable: true },
54
+ { pubkey: mc.market, isSigner: false, isWritable: false },
55
+ { pubkey: PublicKey.default, isSigner: false, isWritable: false }, // seed1
56
+ { pubkey: PublicKey.default, isSigner: false, isWritable: false }, // seed2
57
+ { pubkey: pdas.userMetadataPda, isSigner: false, isWritable: true },
58
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
59
+ { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
60
+ ];
61
+ }
62
+
63
+ export function getDepositAccounts(owner: PublicKey, pdas: UserPdas, mc: KaminoMarketConfig) {
64
+ return {
65
+ owner,
66
+ userState: pdas.userStatePda,
67
+ userSourceLiquidity: pdas.userCollateralAta,
68
+ pdaSourceLiquidity: pdas.pdaCollateralAta,
69
+ supplyTokenMint: mc.collateralMint,
70
+ tokenProgram: TOKEN_PROGRAM_ID,
71
+ associatedTokenProgram: ATA_PROGRAM_ID,
72
+ systemProgram: SystemProgram.programId,
73
+ programConfig: programConfigPda,
74
+ allowlistEntry: pdas.allowlistEntryPda,
75
+ collateralMint: mc.collateralMint,
76
+ };
77
+ }
78
+
79
+ /// Returns 15 Kamino deposit lender accounts as AccountMeta[] for remaining_accounts.
80
+ export function getKaminoDepositLenderAccounts(
81
+ pdas: UserPdas,
82
+ mc: KaminoMarketConfig,
83
+ ): AccountMeta[] {
84
+ const hasFarms = !!mc.collateralFarmState;
85
+ return [
86
+ { pubkey: pdas.obligationPda, isSigner: false, isWritable: true },
87
+ { pubkey: mc.market, isSigner: false, isWritable: false },
88
+ { pubkey: mc.marketAuthority, isSigner: false, isWritable: false },
89
+ { pubkey: mc.collateralReserve, isSigner: false, isWritable: true },
90
+ { pubkey: mc.collateralLiquiditySupply, isSigner: false, isWritable: true },
91
+ { pubkey: mc.collateralCTokenMint, isSigner: false, isWritable: true },
92
+ { pubkey: mc.collateralCTokenSupply, isSigner: false, isWritable: true },
93
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // placeholder_user_destination_collateral
94
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, // collateral_token_program
95
+ { pubkey: pdas.obligationFarmUserStatePda, isSigner: false, isWritable: hasFarms },
96
+ { pubkey: mc.collateralFarmState ?? KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: hasFarms },
97
+ { pubkey: FARMS_PROGRAM, isSigner: false, isWritable: false },
98
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
99
+ { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
100
+ { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
101
+ ];
102
+ }
103
+
104
+ export function getBorrowAccounts(
105
+ owner: PublicKey,
106
+ pdas: UserPdas,
107
+ mc: KaminoMarketConfig,
108
+ hasVaultShares: boolean,
109
+ vaultAccounts?: {
110
+ vault: PublicKey;
111
+ sharesMint: PublicKey;
112
+ sharesAta: PublicKey;
113
+ }
114
+ ) {
115
+ const va = vaultAccounts;
116
+ // Canonical ATA of the placeholder shares mint (the wallet pubkey), so the
117
+ // program's pda_shares_ata == ATA(user_state, shares_mint) check holds when no vault is used.
118
+ const placeholderSharesAta = getAssociatedTokenAddressSync(owner, pdas.userStatePda, true);
119
+ return {
120
+ owner,
121
+ userState: pdas.userStatePda,
122
+ collateralMint: mc.collateralMint,
123
+ pdaUsdcAta: pdas.pdaUsdcAta,
124
+ userUsdcAta: pdas.userUsdcAta,
125
+ tokenProgram: TOKEN_PROGRAM_ID,
126
+ systemProgram: SystemProgram.programId,
127
+ vault: hasVaultShares && va ? va.vault : owner,
128
+ sharesMint: hasVaultShares && va ? va.sharesMint : owner,
129
+ pdaSharesAta: hasVaultShares && va ? va.sharesAta : placeholderSharesAta,
130
+ sharesTokenProgram: hasVaultShares ? TOKEN_PROGRAM_ID : SystemProgram.programId,
131
+ usdcMint: USDC_MINT,
132
+ programConfig: programConfigPda,
133
+ allowlistEntry: pdas.allowlistEntryPda,
134
+ };
135
+ }
136
+
137
+ /// Returns 15 Kamino lender accounts as AccountMeta[] for remaining_accounts.
138
+ export function getKaminoBorrowLenderAccounts(
139
+ pdas: UserPdas,
140
+ mc: KaminoMarketConfig,
141
+ hasVaultShares: boolean,
142
+ ): AccountMeta[] {
143
+ const hasFarms = !!mc.usdcReserveFarmDebt;
144
+ return [
145
+ { pubkey: pdas.obligationPda, isSigner: false, isWritable: true },
146
+ { pubkey: mc.market, isSigner: false, isWritable: false },
147
+ { pubkey: mc.marketAuthority, isSigner: false, isWritable: false },
148
+ { pubkey: mc.usdcReserve, isSigner: false, isWritable: true },
149
+ { pubkey: mc.usdcLiquiditySupply, isSigner: false, isWritable: true },
150
+ { pubkey: mc.usdcFeeVault, isSigner: false, isWritable: true },
151
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false }, // referrer_token_state
152
+ { pubkey: pdas.obligationFarmDebtUserStatePda, isSigner: false, isWritable: hasFarms },
153
+ { pubkey: mc.usdcReserveFarmDebt ?? KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: hasFarms },
154
+ { pubkey: FARMS_PROGRAM, isSigner: false, isWritable: false },
155
+ { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
156
+ { pubkey: hasVaultShares ? SCOPE_PRICES : SystemProgram.programId, isSigner: false, isWritable: false },
157
+ { pubkey: mc.collateralReserve, isSigner: false, isWritable: true },
158
+ { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
159
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false }, // kamino_program
160
+ ];
161
+ }
162
+
163
+ export function getRepayAccounts(owner: PublicKey, pdas: UserPdas, mc: KaminoMarketConfig) {
164
+ return {
165
+ owner,
166
+ userState: pdas.userStatePda,
167
+ userUsdcAta: pdas.userUsdcAta,
168
+ pdaUsdcAta: pdas.pdaUsdcAta,
169
+ tokenProgram: TOKEN_PROGRAM_ID,
170
+ programConfig: programConfigPda,
171
+ allowlistEntry: pdas.allowlistEntryPda,
172
+ collateralMint: mc.collateralMint,
173
+ };
174
+ }
175
+
176
+ /// Returns 11 Kamino repay lender accounts as AccountMeta[] for remaining_accounts.
177
+ export function getKaminoRepayLenderAccounts(
178
+ pdas: UserPdas,
179
+ mc: KaminoMarketConfig,
180
+ ): AccountMeta[] {
181
+ const hasFarms = !!mc.usdcReserveFarmDebt;
182
+ return [
183
+ { pubkey: pdas.obligationPda, isSigner: false, isWritable: true },
184
+ { pubkey: mc.market, isSigner: false, isWritable: false },
185
+ { pubkey: mc.marketAuthority, isSigner: false, isWritable: false },
186
+ { pubkey: mc.usdcReserve, isSigner: false, isWritable: true },
187
+ { pubkey: USDC_MINT, isSigner: false, isWritable: false },
188
+ { pubkey: mc.usdcLiquiditySupply, isSigner: false, isWritable: true },
189
+ { pubkey: pdas.obligationFarmDebtUserStatePda, isSigner: false, isWritable: hasFarms },
190
+ { pubkey: mc.usdcReserveFarmDebt ?? KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: hasFarms },
191
+ { pubkey: FARMS_PROGRAM, isSigner: false, isWritable: false },
192
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
193
+ { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
194
+ ];
195
+ }
196
+
197
+ export function getWithdrawAccounts(owner: PublicKey, pdas: UserPdas, mc: KaminoMarketConfig) {
198
+ return {
199
+ owner,
200
+ userState: pdas.userStatePda,
201
+ pdaDestinationAta: pdas.pdaCollateralAta,
202
+ userDestinationAta: pdas.userCollateralAta,
203
+ tokenProgram: TOKEN_PROGRAM_ID,
204
+ programConfig: programConfigPda,
205
+ allowlistEntry: pdas.allowlistEntryPda,
206
+ collateralMint: mc.collateralMint,
207
+ };
208
+ }
209
+
210
+ /// Returns 15 Kamino withdraw lender accounts as AccountMeta[] for remaining_accounts.
211
+ export function getKaminoWithdrawLenderAccounts(
212
+ pdas: UserPdas,
213
+ mc: KaminoMarketConfig,
214
+ ): AccountMeta[] {
215
+ const hasFarms = !!mc.collateralFarmState;
216
+ return [
217
+ { pubkey: pdas.obligationPda, isSigner: false, isWritable: true },
218
+ { pubkey: mc.market, isSigner: false, isWritable: false },
219
+ { pubkey: mc.marketAuthority, isSigner: false, isWritable: false },
220
+ { pubkey: mc.collateralReserve, isSigner: false, isWritable: true },
221
+ { pubkey: mc.collateralMint, isSigner: false, isWritable: false },
222
+ { pubkey: mc.collateralCTokenSupply, isSigner: false, isWritable: true },
223
+ { pubkey: mc.collateralCTokenMint, isSigner: false, isWritable: true },
224
+ { pubkey: mc.collateralLiquiditySupply, isSigner: false, isWritable: true },
225
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // placeholder_user_destination_collateral
226
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, // collateral_token_program
227
+ { pubkey: pdas.obligationFarmUserStatePda, isSigner: false, isWritable: hasFarms },
228
+ { pubkey: mc.collateralFarmState ?? KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: hasFarms },
229
+ { pubkey: FARMS_PROGRAM, isSigner: false, isWritable: false },
230
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
231
+ { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
232
+ ];
233
+ }
234
+
235
+ export function getPrepareWithdrawAccounts(
236
+ owner: PublicKey,
237
+ pdas: UserPdas,
238
+ mc: KaminoMarketConfig,
239
+ vaultAccounts: {
240
+ vault: PublicKey;
241
+ sharesMint: PublicKey;
242
+ sharesAta: PublicKey;
243
+ }
244
+ ) {
245
+ const va = vaultAccounts;
246
+ return {
247
+ owner,
248
+ userState: pdas.userStatePda,
249
+ collateralMint: mc.collateralMint,
250
+ programConfig: programConfigPda,
251
+ allowlistEntry: pdas.allowlistEntryPda,
252
+ pdaUsdcAta: pdas.pdaUsdcAta,
253
+ vault: va.vault,
254
+ sharesMint: va.sharesMint,
255
+ pdaSharesAta: va.sharesAta,
256
+ sharesTokenProgram: TOKEN_PROGRAM_ID,
257
+ usdcMint: USDC_MINT,
258
+ tokenProgram: TOKEN_PROGRAM_ID,
259
+ };
260
+ }
261
+
262
+ /// Returns 12 Kamino prepare_withdraw lender accounts as AccountMeta[] for remaining_accounts.
263
+ export function getKaminoPrepareWithdrawLenderAccounts(
264
+ pdas: UserPdas,
265
+ mc: KaminoMarketConfig,
266
+ ): AccountMeta[] {
267
+ const hasFarms = !!mc.usdcReserveFarmDebt;
268
+ return [
269
+ { pubkey: pdas.obligationPda, isSigner: false, isWritable: true },
270
+ { pubkey: mc.market, isSigner: false, isWritable: false },
271
+ { pubkey: mc.marketAuthority, isSigner: false, isWritable: false },
272
+ { pubkey: mc.usdcReserve, isSigner: false, isWritable: true },
273
+ { pubkey: mc.usdcLiquiditySupply, isSigner: false, isWritable: true },
274
+ { pubkey: pdas.obligationFarmDebtUserStatePda, isSigner: false, isWritable: hasFarms },
275
+ { pubkey: mc.usdcReserveFarmDebt ?? KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: hasFarms },
276
+ { pubkey: FARMS_PROGRAM, isSigner: false, isWritable: false },
277
+ { pubkey: SCOPE_PRICES, isSigner: false, isWritable: false },
278
+ { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
279
+ { pubkey: mc.collateralReserve, isSigner: false, isWritable: true },
280
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false }, // kamino_program
281
+ ];
282
+ }
283
+
284
+ // Fix farm accounts to be writable when they are real addresses
285
+ export function fixFarmWritable(
286
+ ixKeys: { pubkey: PublicKey; isSigner: boolean; isWritable: boolean }[],
287
+ farmUserState: PublicKey,
288
+ farmState: PublicKey | null
289
+ ) {
290
+ for (const key of ixKeys) {
291
+ if (
292
+ key.pubkey.equals(farmUserState) ||
293
+ (farmState && key.pubkey.equals(farmState))
294
+ ) {
295
+ key.isWritable = true;
296
+ }
297
+ }
298
+ }
299
+
@@ -0,0 +1,123 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import BN from "bn.js";
3
+ import { BORROWS_OFFSET, BORROW_ENTRY_SIZE, BORROWED_AMOUNT_SF_OFFSET } from "../constants";
4
+
5
+ const FRACTION_SCALE = new BN(2).pow(new BN(60));
6
+
7
+ // Deposits in obligation: offset 96, each entry 136 bytes, depositedAmount at +32 (u64 LE)
8
+ // Layout: 8 disc + 8 tag + 16 lastUpdate + 32 lendingMarket + 32 owner = 96
9
+ // Entry: 32 depositReserve + 8 depositedAmount + 16 marketValueSf + 8 borrowedAgainst + 72 padding = 136
10
+ const DEPOSITS_OFFSET = 96;
11
+ const DEPOSIT_ENTRY_SIZE = 136;
12
+ const DEPOSITED_AMOUNT_OFFSET = 32;
13
+
14
+ export function parseObligationDebt(data: Buffer): number {
15
+ let totalBorrowed = new BN(0);
16
+
17
+ for (let i = 0; i < 5; i++) {
18
+ const entryOffset = BORROWS_OFFSET + i * BORROW_ENTRY_SIZE;
19
+ if (entryOffset + 32 > data.length) break;
20
+
21
+ const reserveKey = new PublicKey(data.slice(entryOffset, entryOffset + 32));
22
+ if (reserveKey.equals(PublicKey.default)) continue;
23
+
24
+ const sfOffset = entryOffset + BORROWED_AMOUNT_SF_OFFSET;
25
+ if (sfOffset + 16 > data.length) continue;
26
+
27
+ const borrowedAmountSf = new BN(data.slice(sfOffset, sfOffset + 16), "le");
28
+ const borrowedAmount = borrowedAmountSf.div(FRACTION_SCALE);
29
+ totalBorrowed = totalBorrowed.add(borrowedAmount);
30
+ }
31
+
32
+ return totalBorrowed.toNumber();
33
+ }
34
+
35
+ export function parseObligationCTokens(data: Buffer): number {
36
+ let totalCTokens = new BN(0);
37
+
38
+ for (let i = 0; i < 8; i++) {
39
+ const entryOffset = DEPOSITS_OFFSET + i * DEPOSIT_ENTRY_SIZE;
40
+ if (entryOffset + 32 > data.length) break;
41
+
42
+ const reserveKey = new PublicKey(data.slice(entryOffset, entryOffset + 32));
43
+ if (reserveKey.equals(PublicKey.default)) continue;
44
+
45
+ const amtOffset = entryOffset + DEPOSITED_AMOUNT_OFFSET;
46
+ if (amtOffset + 8 > data.length) continue;
47
+ const depositedAmount = new BN(data.slice(amtOffset, amtOffset + 8), "le");
48
+ totalCTokens = totalCTokens.add(depositedAmount);
49
+ }
50
+
51
+ return totalCTokens.toNumber();
52
+ }
53
+
54
+ // Reserve layout offsets (traced from klend-sdk borsh struct definitions).
55
+ // 8 disc + 8 version + 16 lastUpdate + 32 lendingMarket + 32 farmCollateral + 32 farmDebt = 128
56
+ // ReserveLiquidity at 128: 32 mint + 32 supplyVault + 32 feeVault = 96 bytes before availableAmount
57
+ const RESERVE_AVAILABLE_AMOUNT_OFFSET = 224; // u64 LE (128 + 96)
58
+ const RESERVE_BORROWED_AMOUNT_SF_OFFSET = 232; // u128 LE (128 + 96 + 8)
59
+ // ReserveCollateral starts at 2560 (128 + 1232 liquidity + 1200 liquidityPadding)
60
+ // mintPubkey: 32 bytes, then mintTotalSupply
61
+ const RESERVE_CTOKENS_TOTAL_SUPPLY_OFFSET = 2592; // u64 LE (2560 + 32)
62
+
63
+ /**
64
+ * Convert cToken amount to underlying token amount using reserve exchange rate.
65
+ * underlying = cTokens * (available + borrowed_sf / 2^60) / cTokenSupply
66
+ */
67
+ export function cTokensToUnderlying(cTokens: number, reserveData: Buffer): number {
68
+ const availableAmount = new BN(
69
+ reserveData.slice(RESERVE_AVAILABLE_AMOUNT_OFFSET, RESERVE_AVAILABLE_AMOUNT_OFFSET + 8),
70
+ "le"
71
+ );
72
+ const borrowedAmountSf = new BN(
73
+ reserveData.slice(RESERVE_BORROWED_AMOUNT_SF_OFFSET, RESERVE_BORROWED_AMOUNT_SF_OFFSET + 16),
74
+ "le"
75
+ );
76
+ const cTokenSupply = new BN(
77
+ reserveData.slice(RESERVE_CTOKENS_TOTAL_SUPPLY_OFFSET, RESERVE_CTOKENS_TOTAL_SUPPLY_OFFSET + 8),
78
+ "le"
79
+ );
80
+
81
+ if (cTokenSupply.isZero()) return 0;
82
+
83
+ // totalLiquidity_sf = available * 2^60 + borrowed_sf
84
+ const totalLiquiditySf = availableAmount.mul(FRACTION_SCALE).add(borrowedAmountSf);
85
+ // underlying_sf = cTokens * totalLiquidity_sf / cTokenSupply
86
+ const cTokensBN = new BN(cTokens);
87
+ const underlyingSf = cTokensBN.mul(totalLiquiditySf).div(cTokenSupply);
88
+ // underlying = underlying_sf / 2^60
89
+ const underlying = underlyingSf.div(FRACTION_SCALE);
90
+
91
+ return underlying.toNumber();
92
+ }
93
+
94
+ /**
95
+ * Convert underlying token amount to cToken amount using reserve exchange rate.
96
+ * cTokens = underlying * cTokenSupply / totalLiquidity
97
+ */
98
+ export function underlyingToCTokens(underlying: number, reserveData: Buffer): number {
99
+ const availableAmount = new BN(
100
+ reserveData.slice(RESERVE_AVAILABLE_AMOUNT_OFFSET, RESERVE_AVAILABLE_AMOUNT_OFFSET + 8),
101
+ "le"
102
+ );
103
+ const borrowedAmountSf = new BN(
104
+ reserveData.slice(RESERVE_BORROWED_AMOUNT_SF_OFFSET, RESERVE_BORROWED_AMOUNT_SF_OFFSET + 16),
105
+ "le"
106
+ );
107
+ const cTokenSupply = new BN(
108
+ reserveData.slice(RESERVE_CTOKENS_TOTAL_SUPPLY_OFFSET, RESERVE_CTOKENS_TOTAL_SUPPLY_OFFSET + 8),
109
+ "le"
110
+ );
111
+
112
+ if (cTokenSupply.isZero()) return 0;
113
+
114
+ // totalLiquidity_sf = available * 2^60 + borrowed_sf
115
+ const totalLiquiditySf = availableAmount.mul(FRACTION_SCALE).add(borrowedAmountSf);
116
+ if (totalLiquiditySf.isZero()) return 0;
117
+
118
+ // cTokens_sf = underlying * cTokenSupply * 2^60 / totalLiquidity_sf
119
+ const underlyingBN = new BN(underlying);
120
+ const cTokensSf = underlyingBN.mul(cTokenSupply).mul(FRACTION_SCALE).div(totalLiquiditySf);
121
+
122
+ return cTokensSf.toNumber();
123
+ }
@@ -0,0 +1,45 @@
1
+ import { PublicKey, TransactionInstruction } from "@solana/web3.js";
2
+ import {
3
+ KAMINO_LENDING_PROGRAM_ID,
4
+ REFRESH_RESERVE_DISCRIMINATOR,
5
+ REFRESH_OBLIGATION_DISCRIMINATOR,
6
+ } from "../constants";
7
+
8
+ export function buildRefreshReserveIx(
9
+ reserve: PublicKey,
10
+ lendingMarket: PublicKey,
11
+ scopePrices: PublicKey
12
+ ): TransactionInstruction {
13
+ return new TransactionInstruction({
14
+ programId: KAMINO_LENDING_PROGRAM_ID,
15
+ keys: [
16
+ { pubkey: reserve, isSigner: false, isWritable: true },
17
+ { pubkey: lendingMarket, isSigner: false, isWritable: false },
18
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
19
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
20
+ { pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
21
+ { pubkey: scopePrices, isSigner: false, isWritable: false },
22
+ ],
23
+ data: REFRESH_RESERVE_DISCRIMINATOR,
24
+ });
25
+ }
26
+
27
+ export function buildRefreshObligationIx(
28
+ lendingMarket: PublicKey,
29
+ obligation: PublicKey,
30
+ reserves: PublicKey[] = []
31
+ ): TransactionInstruction {
32
+ const keys = [
33
+ { pubkey: lendingMarket, isSigner: false, isWritable: false },
34
+ { pubkey: obligation, isSigner: false, isWritable: true },
35
+ ];
36
+ // Reserves must be writable for CPI
37
+ for (const reserve of reserves) {
38
+ keys.push({ pubkey: reserve, isSigner: false, isWritable: true });
39
+ }
40
+ return new TransactionInstruction({
41
+ programId: KAMINO_LENDING_PROGRAM_ID,
42
+ keys,
43
+ data: REFRESH_OBLIGATION_DISCRIMINATOR,
44
+ });
45
+ }