@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,606 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { createAssociatedTokenAccountInstruction, createCloseAccountInstruction, getAssociatedTokenAddress, NATIVE_MINT, TOKEN_PROGRAM_ID, } from "@solana/spl-token";
|
|
3
|
+
import BN from "bn.js";
|
|
4
|
+
import { CBBTC_MINT, SOL_MINT, USDC_MINT, JUPLEND_ORACLE, JUPLEND_PROGRAM, LENDING_PROVIDER_JUPLEND, } from "./constants";
|
|
5
|
+
import { deriveUserStatePda, deriveAllowlistEntryPda, deriveObligationPda, deriveFarmPdas, deriveUserTokenAccounts, deriveJuplendPositionMintPda, deriveJuplendTickPda, deriveJuplendTickIdLiquidationPda, } from "./pdas";
|
|
6
|
+
import { getDepositAccounts, getKaminoDepositLenderAccounts, getBorrowAccounts, getKaminoBorrowLenderAccounts, getRepayAccounts, getKaminoRepayLenderAccounts, getWithdrawAccounts, getKaminoWithdrawLenderAccounts, getPrepareWithdrawAccounts, getKaminoPrepareWithdrawLenderAccounts, } from "./kamino/accountResolvers";
|
|
7
|
+
import { buildRefreshReserveIx, buildRefreshObligationIx, } from "./kamino/refreshInstructions";
|
|
8
|
+
import { underlyingToCTokens } from "./kamino/obligationParser";
|
|
9
|
+
import { getJuplendDepositAccounts, getJuplendDepositLenderAccounts, getJuplendBorrowAccounts, getJuplendBorrowLenderAccounts, getJuplendRepayAccounts, getJuplendRepayLenderAccounts, getJuplendPrepareWithdrawAccounts, getJuplendWithdrawAccounts, getJuplendWithdrawLenderAccounts, } from "./juplend/accountResolvers";
|
|
10
|
+
import { buildFallbackOperateRemaining } from "./juplend/operateRemaining";
|
|
11
|
+
import { isTickError, parseTickMismatch, resolveTickAccounts, buildInitTickIx, } from "./juplend/tickUtils";
|
|
12
|
+
import { fetchOperateContext, deserializeRemaining, deserializeOtherIxs, } from "./juplend/sdkApi";
|
|
13
|
+
import { MAIN_MARKET_CONFIG } from "./marketConfig";
|
|
14
|
+
import { getVaultInfo, resolveWithdrawFixed, getKvaultWithdrawRemaining, getPerenaWithdrawAccountsWithMarginfi, } from "./vault/vaultAccounts";
|
|
15
|
+
import { sendLegacyTx, sendVersionedTx } from "./transactionBuilder";
|
|
16
|
+
import { buildHobbaProgram } from "./program";
|
|
17
|
+
import { resolveKaminoMarketConfig } from "./position";
|
|
18
|
+
import { fetchUserState } from "./userState";
|
|
19
|
+
import { buildWrapSolIxs, fetchMarketLut, getSharesAtaForVault, makeSafeGetAccountInfo, resolveVaultLuts, } from "./txHelpers";
|
|
20
|
+
/**
|
|
21
|
+
* Manage an existing Hobba position: deposit / borrow / repay / withdraw.
|
|
22
|
+
*
|
|
23
|
+
* Lender routing happens inside: the (owner, collateral) UserState decides
|
|
24
|
+
* whether the JupLend or Kamino flow runs — integrators never pick a lender.
|
|
25
|
+
* Both flows are lifted verbatim from the app's page.tsx handleJuplendAction /
|
|
26
|
+
* handleKaminoAction so app and headless integrators share one implementation.
|
|
27
|
+
*/
|
|
28
|
+
export async function executeAction(params) {
|
|
29
|
+
const { connection, signer, collateralMint, action, amount } = params;
|
|
30
|
+
if (!signer.publicKey || !signer.signTransaction) {
|
|
31
|
+
throw new Error("Wallet not connected");
|
|
32
|
+
}
|
|
33
|
+
const owner = signer.publicKey;
|
|
34
|
+
const base = (params.apiBaseUrl ?? "").replace(/\/+$/, "");
|
|
35
|
+
const setStep = (s) => params.onStep?.(s);
|
|
36
|
+
const program = params.program ?? buildHobbaProgram(connection, owner);
|
|
37
|
+
const userState = await fetchUserState(connection, owner, collateralMint);
|
|
38
|
+
if (!userState) {
|
|
39
|
+
throw new Error("No Hobba position for this collateral — use executeDepositBorrow first");
|
|
40
|
+
}
|
|
41
|
+
const [usPda] = deriveUserStatePda(owner, collateralMint);
|
|
42
|
+
const [allowlistPda] = deriveAllowlistEntryPda(owner);
|
|
43
|
+
const tokens = await deriveUserTokenAccounts(owner, usPda, collateralMint);
|
|
44
|
+
const safeGetAccountInfo = makeSafeGetAccountInfo(connection);
|
|
45
|
+
setStep(`Processing ${action}...`);
|
|
46
|
+
if (userState.lendingProvider === LENDING_PROVIDER_JUPLEND) {
|
|
47
|
+
const signature = await juplendAction({ connection, signer, program, collateralMint, action, amount, base, userState }, owner, usPda, allowlistPda, tokens, safeGetAccountInfo);
|
|
48
|
+
return { lender: "juplend", signature };
|
|
49
|
+
}
|
|
50
|
+
const mc = resolveKaminoMarketConfig(userState.lendingMarket, collateralMint);
|
|
51
|
+
const signature = await kaminoAction({ connection, signer, program, collateralMint, action, amount, base, userState }, owner, usPda, allowlistPda, tokens, mc, safeGetAccountInfo);
|
|
52
|
+
return { lender: "kamino", signature };
|
|
53
|
+
}
|
|
54
|
+
// --- JupLend action flow (lifted from app page.tsx handleJuplendAction) ---
|
|
55
|
+
async function juplendAction(ctx, owner, usPda, allowlistPda, tokens, safeGetAccountInfo) {
|
|
56
|
+
const { connection, signer, program, collateralMint, action, amount, base, userState } = ctx;
|
|
57
|
+
const isSol = collateralMint.equals(SOL_MINT);
|
|
58
|
+
const positionAddress = userState.obligation;
|
|
59
|
+
const positionInfo = await connection.getAccountInfo(positionAddress);
|
|
60
|
+
if (!positionInfo)
|
|
61
|
+
throw new Error("JupLend position not found");
|
|
62
|
+
const vaultId = positionInfo.data.readUInt16LE(8);
|
|
63
|
+
const positionId = positionInfo.data.readUInt32LE(10);
|
|
64
|
+
const [positionMintPda] = deriveJuplendPositionMintPda(vaultId, positionId);
|
|
65
|
+
const positionTokenAccount = await getAssociatedTokenAddress(positionMintPda, usPda, true);
|
|
66
|
+
const juplendPdas = {
|
|
67
|
+
userStatePda: usPda,
|
|
68
|
+
allowlistEntryPda: allowlistPda,
|
|
69
|
+
obligationPda: positionAddress,
|
|
70
|
+
userMetadataPda: usPda,
|
|
71
|
+
userCollateralAta: tokens.userCollateralAta,
|
|
72
|
+
userUsdcAta: tokens.userUsdcAta,
|
|
73
|
+
pdaCollateralAta: tokens.pdaCollateralAta,
|
|
74
|
+
pdaUsdcAta: tokens.pdaUsdcAta,
|
|
75
|
+
};
|
|
76
|
+
const juplendLut = await fetchMarketLut(base, "juplend");
|
|
77
|
+
if (!juplendLut)
|
|
78
|
+
throw new Error("JupLend LUT not found.");
|
|
79
|
+
const ataIxs = [];
|
|
80
|
+
if (action === "deposit") {
|
|
81
|
+
const pdaColInfo = await safeGetAccountInfo(tokens.pdaCollateralAta);
|
|
82
|
+
if (!pdaColInfo)
|
|
83
|
+
ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.pdaCollateralAta, usPda, collateralMint));
|
|
84
|
+
}
|
|
85
|
+
else if (action === "borrow") {
|
|
86
|
+
const pdaUsdcInfo = await safeGetAccountInfo(tokens.pdaUsdcAta);
|
|
87
|
+
if (!pdaUsdcInfo)
|
|
88
|
+
ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.pdaUsdcAta, usPda, USDC_MINT));
|
|
89
|
+
const userUsdcInfo = await safeGetAccountInfo(tokens.userUsdcAta);
|
|
90
|
+
if (!userUsdcInfo)
|
|
91
|
+
ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userUsdcAta, owner, USDC_MINT));
|
|
92
|
+
const vaultType = userState.currentVaultType;
|
|
93
|
+
if (vaultType !== "none") {
|
|
94
|
+
const sharesAta = getSharesAtaForVault(vaultType, tokens);
|
|
95
|
+
let hasVaultShares = false;
|
|
96
|
+
try {
|
|
97
|
+
const sharesBal = await connection.getTokenAccountBalance(sharesAta);
|
|
98
|
+
hasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* no shares */
|
|
102
|
+
}
|
|
103
|
+
if (hasVaultShares) {
|
|
104
|
+
const vi = getVaultInfo(vaultType);
|
|
105
|
+
let vaultRemaining;
|
|
106
|
+
if (vi.provider === "perena") {
|
|
107
|
+
vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
|
|
111
|
+
vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
|
|
112
|
+
}
|
|
113
|
+
const juplendLender = getJuplendBorrowLenderAccounts(juplendPdas);
|
|
114
|
+
const borrowIx = await program.methods
|
|
115
|
+
.borrow(amount, vaultRemaining.length)
|
|
116
|
+
.accounts(getJuplendBorrowAccounts(owner, juplendPdas, collateralMint, true, {
|
|
117
|
+
vault: vi.vault,
|
|
118
|
+
sharesMint: vi.sharesMint,
|
|
119
|
+
sharesAta,
|
|
120
|
+
}))
|
|
121
|
+
.remainingAccounts([...vaultRemaining, ...juplendLender])
|
|
122
|
+
.instruction();
|
|
123
|
+
const vaultLuts = await resolveVaultLuts(base, vi);
|
|
124
|
+
return await sendVersionedTx(connection, [...ataIxs, borrowIx], signer, vaultLuts, 1400000);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let wrapIxs = [];
|
|
129
|
+
if (isSol && action === "deposit") {
|
|
130
|
+
wrapIxs = await buildWrapSolIxs(owner, tokens.userCollateralAta, amount.addn(1), safeGetAccountInfo);
|
|
131
|
+
}
|
|
132
|
+
const withdrawAtaIxs = [];
|
|
133
|
+
const unwrapIxs = [];
|
|
134
|
+
if (isSol && action === "withdraw") {
|
|
135
|
+
const ataInfo = await safeGetAccountInfo(tokens.userCollateralAta);
|
|
136
|
+
if (!ataInfo)
|
|
137
|
+
withdrawAtaIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userCollateralAta, owner, NATIVE_MINT));
|
|
138
|
+
unwrapIxs.push(createCloseAccountInstruction(tokens.userCollateralAta, owner, owner, [], TOKEN_PROGRAM_ID));
|
|
139
|
+
}
|
|
140
|
+
const colAmount = action === "deposit" ? amount.toString() : action === "withdraw" ? "-" + amount.toString() : "0";
|
|
141
|
+
const debtAmount = action === "borrow" ? amount.toString() : action === "repay" ? "-" + amount.toString() : "0";
|
|
142
|
+
let sdkResult = null;
|
|
143
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
144
|
+
try {
|
|
145
|
+
sdkResult = await fetchOperateContext({ vaultId, positionId, colAmount, debtAmount, signerPda: usPda.toBase58() }, base);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
catch (apiErr) {
|
|
149
|
+
console.warn("[action] SDK API failed (attempt", attempt + 1, "):", apiErr.message);
|
|
150
|
+
if (attempt === 0)
|
|
151
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!sdkResult && !collateralMint.equals(CBBTC_MINT)) {
|
|
155
|
+
throw new Error(`SDK API failed for ${action} on vault ${vaultId}. SOL vault requires SDK for remaining accounts.`);
|
|
156
|
+
}
|
|
157
|
+
const buildIx = async (rem) => {
|
|
158
|
+
if (action === "deposit") {
|
|
159
|
+
const juplendDepositLender = getJuplendDepositLenderAccounts(juplendPdas);
|
|
160
|
+
return program.methods
|
|
161
|
+
.deposit(amount)
|
|
162
|
+
.accounts(getJuplendDepositAccounts(owner, juplendPdas, collateralMint))
|
|
163
|
+
.remainingAccounts([...juplendDepositLender, ...rem])
|
|
164
|
+
.instruction();
|
|
165
|
+
}
|
|
166
|
+
else if (action === "borrow") {
|
|
167
|
+
const juplendLender = getJuplendBorrowLenderAccounts(juplendPdas);
|
|
168
|
+
return program.methods
|
|
169
|
+
.borrow(amount, 0)
|
|
170
|
+
.accounts(getJuplendBorrowAccounts(owner, juplendPdas, collateralMint))
|
|
171
|
+
.remainingAccounts([...juplendLender, ...rem])
|
|
172
|
+
.instruction();
|
|
173
|
+
}
|
|
174
|
+
else if (action === "repay") {
|
|
175
|
+
const juplendRepayLender = getJuplendRepayLenderAccounts(juplendPdas);
|
|
176
|
+
return program.methods
|
|
177
|
+
.repay(amount)
|
|
178
|
+
.accounts(getJuplendRepayAccounts(owner, juplendPdas, collateralMint))
|
|
179
|
+
.remainingAccounts([...juplendRepayLender, ...rem])
|
|
180
|
+
.instruction();
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const juplendWithdrawLender = getJuplendWithdrawLenderAccounts(juplendPdas);
|
|
184
|
+
return program.methods
|
|
185
|
+
.withdraw(amount)
|
|
186
|
+
.accounts(getJuplendWithdrawAccounts(owner, juplendPdas, collateralMint))
|
|
187
|
+
.remainingAccounts([...juplendWithdrawLender, ...rem])
|
|
188
|
+
.instruction();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
// Check vault shares for prepareWithdraw (withdraw only)
|
|
192
|
+
let withdrawHasVaultShares = false;
|
|
193
|
+
let withdrawVaultInfo = null;
|
|
194
|
+
if (action === "withdraw") {
|
|
195
|
+
const vaultType = userState.currentVaultType;
|
|
196
|
+
if (vaultType !== "none") {
|
|
197
|
+
const sharesAta = getSharesAtaForVault(vaultType, tokens);
|
|
198
|
+
try {
|
|
199
|
+
const sharesBal = await connection.getTokenAccountBalance(sharesAta);
|
|
200
|
+
withdrawHasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// ATA doesn't exist = no shares
|
|
204
|
+
}
|
|
205
|
+
if (withdrawHasVaultShares) {
|
|
206
|
+
const vi = getVaultInfo(vaultType);
|
|
207
|
+
const vaAccounts = { vault: vi.vault, sharesMint: vi.sharesMint, sharesAta };
|
|
208
|
+
let vaultRemaining;
|
|
209
|
+
if (vi.provider === "perena") {
|
|
210
|
+
vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
|
|
214
|
+
vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
|
|
215
|
+
}
|
|
216
|
+
withdrawVaultInfo = { vi, vaAccounts, vaultRemaining };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// prepareWithdraw for JupLend withdraw with vault shares. Lender remaining (6):
|
|
221
|
+
// [position, vault_state(mut), vault_config, supply_token_reserves_liquidity,
|
|
222
|
+
// borrow_token_reserves_liquidity, juplend_program] — pulled from the same
|
|
223
|
+
// operate account list the withdraw CPI uses (indices 0, 1, 12, 13).
|
|
224
|
+
const buildPrepareWithdrawIx = async (withdrawAmt, operateRemaining) => {
|
|
225
|
+
if (!withdrawVaultInfo)
|
|
226
|
+
throw new Error("No vault info for prepareWithdraw");
|
|
227
|
+
const { vaAccounts, vaultRemaining } = withdrawVaultInfo;
|
|
228
|
+
const juplendPrepLender = [
|
|
229
|
+
{ pubkey: positionAddress, isSigner: false, isWritable: false },
|
|
230
|
+
{ pubkey: operateRemaining[1].pubkey, isSigner: false, isWritable: true }, // vault_state
|
|
231
|
+
{ pubkey: operateRemaining[0].pubkey, isSigner: false, isWritable: false }, // vault_config
|
|
232
|
+
{ pubkey: operateRemaining[12].pubkey, isSigner: false, isWritable: false }, // supply reserves liquidity
|
|
233
|
+
{ pubkey: operateRemaining[13].pubkey, isSigner: false, isWritable: false }, // borrow reserves liquidity
|
|
234
|
+
{ pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false },
|
|
235
|
+
];
|
|
236
|
+
return program.methods
|
|
237
|
+
.prepareWithdraw(vaultRemaining.length, withdrawAmt)
|
|
238
|
+
.accounts(getJuplendPrepareWithdrawAccounts(owner, juplendPdas, collateralMint, vaAccounts))
|
|
239
|
+
.remainingAccounts([...vaultRemaining, ...juplendPrepLender])
|
|
240
|
+
.instruction();
|
|
241
|
+
};
|
|
242
|
+
if (sdkResult) {
|
|
243
|
+
const sdkRemaining = deserializeRemaining(sdkResult);
|
|
244
|
+
const otherIxs = deserializeOtherIxs(sdkResult, usPda, owner);
|
|
245
|
+
if (withdrawHasVaultShares && withdrawVaultInfo) {
|
|
246
|
+
// Single TX: prepareWithdraw (vault unwind) + withdraw (combined repay +
|
|
247
|
+
// collateral withdrawal). SDK call needs proportional debt for tick prediction.
|
|
248
|
+
const withdrawAmt = amount;
|
|
249
|
+
let proportionalDebt = new BN(0);
|
|
250
|
+
try {
|
|
251
|
+
const posResp = await fetch(`${base}/api/juplend-position?address=${positionAddress.toBase58()}`);
|
|
252
|
+
const posData = await posResp.json();
|
|
253
|
+
const totalCollateral = new BN(posData.collateral);
|
|
254
|
+
const totalDebt = new BN(posData.debt);
|
|
255
|
+
if (totalCollateral.gt(new BN(0))) {
|
|
256
|
+
proportionalDebt = withdrawAmt.gte(totalCollateral)
|
|
257
|
+
? totalDebt
|
|
258
|
+
: totalDebt.mul(withdrawAmt).div(totalCollateral);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
console.warn("[withdraw] Failed to fetch position data for LTV calc:", e.message);
|
|
263
|
+
}
|
|
264
|
+
const combinedSdk = await fetchOperateContext({
|
|
265
|
+
vaultId,
|
|
266
|
+
positionId,
|
|
267
|
+
colAmount: "-" + withdrawAmt.toString(),
|
|
268
|
+
debtAmount: "-" + proportionalDebt.toString(),
|
|
269
|
+
signerPda: usPda.toBase58(),
|
|
270
|
+
}, base);
|
|
271
|
+
const wdRemaining = deserializeRemaining(combinedSdk);
|
|
272
|
+
const combinedOtherIxs = deserializeOtherIxs(combinedSdk, usPda, owner);
|
|
273
|
+
// Override SDK's currentTick with the actual on-chain tick if they differ
|
|
274
|
+
const onChainTick = positionInfo.data.readInt32LE(47);
|
|
275
|
+
const sdkCurrentTick = combinedSdk.currentTickValue;
|
|
276
|
+
if (sdkCurrentTick != null && sdkCurrentTick !== onChainTick) {
|
|
277
|
+
wdRemaining[7] = {
|
|
278
|
+
pubkey: deriveJuplendTickPda(vaultId, onChainTick),
|
|
279
|
+
isSigner: false,
|
|
280
|
+
isWritable: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const prepIx = await buildPrepareWithdrawIx(withdrawAmt, wdRemaining);
|
|
284
|
+
const juplendWithdrawLender = getJuplendWithdrawLenderAccounts(juplendPdas);
|
|
285
|
+
const buildWdIx = () => program.methods
|
|
286
|
+
.withdraw(withdrawAmt)
|
|
287
|
+
.accounts(getJuplendWithdrawAccounts(owner, juplendPdas, collateralMint))
|
|
288
|
+
.remainingAccounts([...juplendWithdrawLender, ...wdRemaining])
|
|
289
|
+
.instruction();
|
|
290
|
+
const luts = await resolveVaultLuts(base, withdrawVaultInfo.vi, [juplendLut]);
|
|
291
|
+
// Pre-sign rebuilder: refetch combinedSdk, recompute remaining + other ixs.
|
|
292
|
+
const rebuildOnTickError = async () => {
|
|
293
|
+
const fresh = await fetchOperateContext({
|
|
294
|
+
vaultId,
|
|
295
|
+
positionId,
|
|
296
|
+
colAmount: "-" + withdrawAmt.toString(),
|
|
297
|
+
debtAmount: "-" + proportionalDebt.toString(),
|
|
298
|
+
signerPda: usPda.toBase58(),
|
|
299
|
+
}, base).catch(() => null);
|
|
300
|
+
if (!fresh)
|
|
301
|
+
return null;
|
|
302
|
+
const freshWdRemaining = deserializeRemaining(fresh);
|
|
303
|
+
const freshOnChainTick = positionInfo.data.readInt32LE(47);
|
|
304
|
+
if (fresh.currentTickValue != null && fresh.currentTickValue !== freshOnChainTick) {
|
|
305
|
+
freshWdRemaining[7] = {
|
|
306
|
+
pubkey: deriveJuplendTickPda(vaultId, freshOnChainTick),
|
|
307
|
+
isSigner: false,
|
|
308
|
+
isWritable: true,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const freshOtherIxs = deserializeOtherIxs(fresh, usPda, owner);
|
|
312
|
+
const freshWdIx = await program.methods
|
|
313
|
+
.withdraw(withdrawAmt)
|
|
314
|
+
.accounts(getJuplendWithdrawAccounts(owner, juplendPdas, collateralMint))
|
|
315
|
+
.remainingAccounts([...juplendWithdrawLender, ...freshWdRemaining])
|
|
316
|
+
.instruction();
|
|
317
|
+
return [...ataIxs, ...withdrawAtaIxs, ...freshOtherIxs, prepIx, freshWdIx, ...unwrapIxs];
|
|
318
|
+
};
|
|
319
|
+
// Single TX with tick correction retry (post-sign fallback for parseable mismatch)
|
|
320
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
321
|
+
try {
|
|
322
|
+
const wdIx = await buildWdIx();
|
|
323
|
+
return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...combinedOtherIxs, prepIx, wdIx, ...unwrapIxs], signer, luts, 1400000, false, rebuildOnTickError);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
if (!isTickError(err.message))
|
|
327
|
+
throw err;
|
|
328
|
+
const correctTick = parseTickMismatch(err.message);
|
|
329
|
+
if (correctTick !== null && attempt < 2) {
|
|
330
|
+
const correctedTickPda = deriveJuplendTickPda(vaultId, correctTick);
|
|
331
|
+
const tickInfo = await connection.getAccountInfo(correctedTickPda).catch(() => null);
|
|
332
|
+
if (!tickInfo) {
|
|
333
|
+
combinedOtherIxs.push(buildInitTickIx(vaultId, correctTick, owner));
|
|
334
|
+
}
|
|
335
|
+
wdRemaining[8] = { pubkey: correctedTickPda, isSigner: false, isWritable: true };
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
throw err;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
throw new Error("Withdraw failed after tick correction attempts");
|
|
342
|
+
}
|
|
343
|
+
// Simple withdraw (no vault shares) — and the deposit/borrow/repay actions
|
|
344
|
+
const ix = await buildIx(sdkRemaining);
|
|
345
|
+
const luts = [juplendLut];
|
|
346
|
+
const rebuildOnTickError = async () => {
|
|
347
|
+
const fresh = await fetchOperateContext({ vaultId, positionId, colAmount, debtAmount, signerPda: usPda.toBase58() }, base).catch(() => null);
|
|
348
|
+
if (!fresh)
|
|
349
|
+
return null;
|
|
350
|
+
const freshRemaining = deserializeRemaining(fresh);
|
|
351
|
+
const freshOtherIxs = deserializeOtherIxs(fresh, usPda, owner);
|
|
352
|
+
const freshIx = await buildIx(freshRemaining);
|
|
353
|
+
return [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...freshOtherIxs, freshIx, ...unwrapIxs];
|
|
354
|
+
};
|
|
355
|
+
try {
|
|
356
|
+
return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...otherIxs, ix, ...unwrapIxs], signer, luts, 800000, false, rebuildOnTickError);
|
|
357
|
+
}
|
|
358
|
+
catch (txErr) {
|
|
359
|
+
// Belt-and-suspenders: post-sign retry for the parseable tick mismatch case
|
|
360
|
+
if (isTickError(txErr.message)) {
|
|
361
|
+
const correctTick = parseTickMismatch(txErr.message);
|
|
362
|
+
if (correctTick !== null) {
|
|
363
|
+
sdkRemaining[8] = {
|
|
364
|
+
pubkey: deriveJuplendTickPda(vaultId, correctTick),
|
|
365
|
+
isSigner: false,
|
|
366
|
+
isWritable: true,
|
|
367
|
+
};
|
|
368
|
+
sdkRemaining[10] = {
|
|
369
|
+
pubkey: deriveJuplendTickIdLiquidationPda(vaultId, correctTick),
|
|
370
|
+
isSigner: false,
|
|
371
|
+
isWritable: true,
|
|
372
|
+
};
|
|
373
|
+
const retryIx = await buildIx(sdkRemaining);
|
|
374
|
+
return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...otherIxs, retryIx, ...unwrapIxs], signer, luts, 800000);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
throw txErr;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// SDK unavailable — cbBTC fallback with hardcoded remaining accounts
|
|
381
|
+
const positionTick = positionInfo.data.length >= 83 ? positionInfo.data.readInt32LE(47) : -16383;
|
|
382
|
+
if (action === "borrow" || action === "repay") {
|
|
383
|
+
const oracleInfo = await connection.getAccountInfo(JUPLEND_ORACLE);
|
|
384
|
+
if (!oracleInfo)
|
|
385
|
+
throw new Error("JupLend oracle account not found on-chain.");
|
|
386
|
+
}
|
|
387
|
+
const currentTickPda = deriveJuplendTickPda(vaultId, positionTick);
|
|
388
|
+
const currentTickIdPda = deriveJuplendTickIdLiquidationPda(vaultId, positionTick);
|
|
389
|
+
let finalTickPda = currentTickPda;
|
|
390
|
+
let finalTickIdPda = currentTickIdPda;
|
|
391
|
+
const tickAccounts = await resolveTickAccounts(connection, owner, positionTick, positionTick, vaultId);
|
|
392
|
+
const buildRemaining = () => buildFallbackOperateRemaining(positionAddress, positionTokenAccount, currentTickPda, finalTickPda, currentTickIdPda, finalTickIdPda).remaining;
|
|
393
|
+
const fallbackRemaining = buildRemaining();
|
|
394
|
+
const ix = await buildIx(fallbackRemaining);
|
|
395
|
+
const fallbackPrepIxs = [];
|
|
396
|
+
if (withdrawHasVaultShares && withdrawVaultInfo) {
|
|
397
|
+
fallbackPrepIxs.push(await buildPrepareWithdrawIx(amount, fallbackRemaining));
|
|
398
|
+
}
|
|
399
|
+
const fallbackLuts = withdrawVaultInfo
|
|
400
|
+
? await resolveVaultLuts(base, withdrawVaultInfo.vi, [juplendLut])
|
|
401
|
+
: [juplendLut];
|
|
402
|
+
const fallbackCU = withdrawHasVaultShares ? 1400000 : 600000;
|
|
403
|
+
try {
|
|
404
|
+
return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...tickAccounts.initIxs, ...fallbackPrepIxs, ix, ...unwrapIxs], signer, fallbackLuts, fallbackCU);
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
if (isTickError(err.message)) {
|
|
408
|
+
const correctFinalTick = parseTickMismatch(err.message);
|
|
409
|
+
if (correctFinalTick !== null) {
|
|
410
|
+
finalTickPda = deriveJuplendTickPda(vaultId, correctFinalTick);
|
|
411
|
+
finalTickIdPda = deriveJuplendTickIdLiquidationPda(vaultId, correctFinalTick);
|
|
412
|
+
const retryTickAccounts = await resolveTickAccounts(connection, owner, positionTick, correctFinalTick, vaultId);
|
|
413
|
+
const retryRemaining = buildRemaining();
|
|
414
|
+
const retryIx = await buildIx(retryRemaining);
|
|
415
|
+
const retryPrepIxs = [];
|
|
416
|
+
if (withdrawHasVaultShares && withdrawVaultInfo) {
|
|
417
|
+
retryPrepIxs.push(await buildPrepareWithdrawIx(amount, retryRemaining));
|
|
418
|
+
}
|
|
419
|
+
return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...retryTickAccounts.initIxs, ...retryPrepIxs, retryIx, ...unwrapIxs], signer, fallbackLuts, fallbackCU);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
throw err;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// --- Kamino action flow (lifted from app page.tsx handleKaminoAction) ---
|
|
426
|
+
async function kaminoAction(ctx, owner, usPda, allowlistPda, tokens, mc, safeGetAccountInfo) {
|
|
427
|
+
const { connection, signer, program, collateralMint, action, amount, base, userState } = ctx;
|
|
428
|
+
const isSol = collateralMint.equals(SOL_MINT);
|
|
429
|
+
// Use the obligation stored in userState (set during init) rather than re-deriving
|
|
430
|
+
const obligPda = userState.obligation;
|
|
431
|
+
const [derivedObligPda] = deriveObligationPda(usPda, mc.market);
|
|
432
|
+
if (!obligPda.equals(derivedObligPda)) {
|
|
433
|
+
console.warn("[action] Stored obligation", obligPda.toBase58(), "differs from derived", derivedObligPda.toBase58(), "— using stored");
|
|
434
|
+
}
|
|
435
|
+
const { obligationFarmUserStatePda, obligationFarmDebtUserStatePda } = deriveFarmPdas(obligPda, mc.collateralFarmState, mc.usdcReserveFarmDebt);
|
|
436
|
+
const pdas = {
|
|
437
|
+
userStatePda: usPda,
|
|
438
|
+
allowlistEntryPda: allowlistPda,
|
|
439
|
+
obligationPda: obligPda,
|
|
440
|
+
userMetadataPda: usPda,
|
|
441
|
+
obligationFarmUserStatePda,
|
|
442
|
+
obligationFarmDebtUserStatePda,
|
|
443
|
+
...tokens,
|
|
444
|
+
};
|
|
445
|
+
const refreshCollateralIx = buildRefreshReserveIx(mc.collateralReserve, mc.market, mc.scopePrices);
|
|
446
|
+
const refreshUsdcIx = buildRefreshReserveIx(mc.usdcReserve, mc.market, mc.scopePrices);
|
|
447
|
+
const refreshReserves = [];
|
|
448
|
+
const obligInfo = await connection.getAccountInfo(obligPda);
|
|
449
|
+
const obligationExists = obligInfo !== null && obligInfo.data.length > 0;
|
|
450
|
+
if (obligInfo && obligInfo.data.length > 1208) {
|
|
451
|
+
const depositReserveKey = new PublicKey(obligInfo.data.slice(96, 128));
|
|
452
|
+
if (!depositReserveKey.equals(PublicKey.default))
|
|
453
|
+
refreshReserves.push(mc.collateralReserve);
|
|
454
|
+
const borrowReserveKey = new PublicKey(obligInfo.data.slice(1208, 1240));
|
|
455
|
+
if (!borrowReserveKey.equals(PublicKey.default))
|
|
456
|
+
refreshReserves.push(mc.usdcReserve);
|
|
457
|
+
}
|
|
458
|
+
// Only build refreshObligation if the obligation exists on-chain
|
|
459
|
+
const refreshObligIxs = obligationExists
|
|
460
|
+
? [buildRefreshObligationIx(mc.market, obligPda, refreshReserves)]
|
|
461
|
+
: [];
|
|
462
|
+
const lutParam = mc.market.equals(MAIN_MARKET_CONFIG.market) ? "main" : "xstocks";
|
|
463
|
+
const marketLut = await fetchMarketLut(base, lutParam);
|
|
464
|
+
if (action === "deposit") {
|
|
465
|
+
let wrapIxs = [];
|
|
466
|
+
if (isSol) {
|
|
467
|
+
wrapIxs = await buildWrapSolIxs(owner, tokens.userCollateralAta, amount, safeGetAccountInfo);
|
|
468
|
+
}
|
|
469
|
+
const kaminoDepositLender = getKaminoDepositLenderAccounts(pdas, mc);
|
|
470
|
+
const ix = await program.methods
|
|
471
|
+
.deposit(amount)
|
|
472
|
+
.accounts(getDepositAccounts(owner, pdas, mc))
|
|
473
|
+
.remainingAccounts(kaminoDepositLender)
|
|
474
|
+
.instruction();
|
|
475
|
+
return await sendLegacyTx(connection, [...wrapIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer);
|
|
476
|
+
}
|
|
477
|
+
else if (action === "borrow") {
|
|
478
|
+
const ataIxs = [];
|
|
479
|
+
const pdaUsdcInfo = await safeGetAccountInfo(tokens.pdaUsdcAta);
|
|
480
|
+
if (!pdaUsdcInfo)
|
|
481
|
+
ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.pdaUsdcAta, usPda, USDC_MINT));
|
|
482
|
+
const userUsdcInfo = await safeGetAccountInfo(tokens.userUsdcAta);
|
|
483
|
+
if (!userUsdcInfo)
|
|
484
|
+
ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userUsdcAta, owner, USDC_MINT));
|
|
485
|
+
let hasVaultShares = false;
|
|
486
|
+
const vaultType = userState.currentVaultType;
|
|
487
|
+
if (vaultType !== "none") {
|
|
488
|
+
const sharesAtaToCheck = getSharesAtaForVault(vaultType, tokens);
|
|
489
|
+
try {
|
|
490
|
+
const sharesBal = await connection.getTokenAccountBalance(sharesAtaToCheck);
|
|
491
|
+
hasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
/* no shares */
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (hasVaultShares) {
|
|
498
|
+
const vi = getVaultInfo(vaultType);
|
|
499
|
+
const sharesAta = getSharesAtaForVault(vaultType, tokens);
|
|
500
|
+
let vaultRemaining;
|
|
501
|
+
if (vi.provider === "perena") {
|
|
502
|
+
vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
|
|
506
|
+
vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
|
|
507
|
+
}
|
|
508
|
+
const kaminoLender = getKaminoBorrowLenderAccounts(pdas, mc, true);
|
|
509
|
+
const ix = await program.methods
|
|
510
|
+
.borrow(amount, vaultRemaining.length)
|
|
511
|
+
.accounts(getBorrowAccounts(owner, pdas, mc, true, {
|
|
512
|
+
vault: vi.vault,
|
|
513
|
+
sharesMint: vi.sharesMint,
|
|
514
|
+
sharesAta,
|
|
515
|
+
}))
|
|
516
|
+
.remainingAccounts([...vaultRemaining, ...kaminoLender])
|
|
517
|
+
.instruction();
|
|
518
|
+
const vaultLuts = await resolveVaultLuts(base, vi, marketLut ? [marketLut] : []);
|
|
519
|
+
return await sendVersionedTx(connection, [...ataIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer, vaultLuts, 1400000);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
const kaminoLender = getKaminoBorrowLenderAccounts(pdas, mc, false);
|
|
523
|
+
const ix = await program.methods
|
|
524
|
+
.borrow(amount, 0)
|
|
525
|
+
.accounts(getBorrowAccounts(owner, pdas, mc, false))
|
|
526
|
+
.remainingAccounts([...kaminoLender])
|
|
527
|
+
.instruction();
|
|
528
|
+
return await sendLegacyTx(connection, [...ataIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
else if (action === "repay") {
|
|
532
|
+
const kaminoRepayLender = getKaminoRepayLenderAccounts(pdas, mc);
|
|
533
|
+
const ix = await program.methods
|
|
534
|
+
.repay(amount)
|
|
535
|
+
.accounts(getRepayAccounts(owner, pdas, mc))
|
|
536
|
+
.remainingAccounts([...kaminoRepayLender])
|
|
537
|
+
.instruction();
|
|
538
|
+
return await sendLegacyTx(connection, [refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
const underlyingAmount = amount;
|
|
542
|
+
const reserveInfo = await connection.getAccountInfo(mc.collateralReserve);
|
|
543
|
+
const withdrawAmount = reserveInfo
|
|
544
|
+
? new BN(underlyingToCTokens(underlyingAmount.toNumber(), reserveInfo.data))
|
|
545
|
+
: underlyingAmount;
|
|
546
|
+
const withdrawAtaIxs = [];
|
|
547
|
+
if (isSol) {
|
|
548
|
+
const ataInfo = await safeGetAccountInfo(tokens.userCollateralAta);
|
|
549
|
+
if (!ataInfo)
|
|
550
|
+
withdrawAtaIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userCollateralAta, owner, NATIVE_MINT));
|
|
551
|
+
}
|
|
552
|
+
let hasVaultShares = false;
|
|
553
|
+
const vaultType = userState.currentVaultType;
|
|
554
|
+
if (vaultType !== "none") {
|
|
555
|
+
const sharesAtaToCheck = getSharesAtaForVault(vaultType, tokens);
|
|
556
|
+
try {
|
|
557
|
+
const sharesBal = await connection.getTokenAccountBalance(sharesAtaToCheck);
|
|
558
|
+
hasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
/* no shares */
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// For SOL: close wSOL ATA to unwrap back to native SOL
|
|
565
|
+
const unwrapIxs = isSol
|
|
566
|
+
? [createCloseAccountInstruction(tokens.userCollateralAta, owner, owner, [], TOKEN_PROGRAM_ID)]
|
|
567
|
+
: [];
|
|
568
|
+
if (hasVaultShares) {
|
|
569
|
+
const vi = getVaultInfo(vaultType);
|
|
570
|
+
const sharesAta = getSharesAtaForVault(vaultType, tokens);
|
|
571
|
+
const vaAccounts = { vault: vi.vault, sharesMint: vi.sharesMint, sharesAta };
|
|
572
|
+
let vaultRemaining;
|
|
573
|
+
if (vi.provider === "perena") {
|
|
574
|
+
vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
|
|
578
|
+
vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
|
|
579
|
+
}
|
|
580
|
+
const kaminoPrepLender = getKaminoPrepareWithdrawLenderAccounts(pdas, mc);
|
|
581
|
+
const prepareIx = await program.methods
|
|
582
|
+
.prepareWithdraw(vaultRemaining.length, underlyingAmount)
|
|
583
|
+
.accounts(getPrepareWithdrawAccounts(owner, pdas, mc, vaAccounts))
|
|
584
|
+
.remainingAccounts([...vaultRemaining, ...kaminoPrepLender])
|
|
585
|
+
.instruction();
|
|
586
|
+
const kaminoWithdrawLender = getKaminoWithdrawLenderAccounts(pdas, mc);
|
|
587
|
+
const withdrawIx = await program.methods
|
|
588
|
+
.withdraw(withdrawAmount)
|
|
589
|
+
.accounts(getWithdrawAccounts(owner, pdas, mc))
|
|
590
|
+
.remainingAccounts(kaminoWithdrawLender)
|
|
591
|
+
.instruction();
|
|
592
|
+
const vaultLutsW = await resolveVaultLuts(base, vi, marketLut ? [marketLut] : []);
|
|
593
|
+
return await sendVersionedTx(connection, [...withdrawAtaIxs, prepareIx, withdrawIx, ...unwrapIxs], signer, vaultLutsW, 1400000);
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
const kaminoWithdrawLender = getKaminoWithdrawLenderAccounts(pdas, mc);
|
|
597
|
+
const ix = await program.methods
|
|
598
|
+
.withdraw(withdrawAmount)
|
|
599
|
+
.accounts(getWithdrawAccounts(owner, pdas, mc))
|
|
600
|
+
.remainingAccounts(kaminoWithdrawLender)
|
|
601
|
+
.instruction();
|
|
602
|
+
return await sendLegacyTx(connection, [...withdrawAtaIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix, ...unwrapIxs], signer);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
//# sourceMappingURL=executeAction.js.map
|