@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,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
|
+
}
|