@kamino-finance/klend-sdk 5.10.6 → 5.10.8
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/dist/classes/action.d.ts +7 -3
- package/dist/classes/action.d.ts.map +1 -1
- package/dist/classes/action.js +28 -13
- package/dist/classes/action.js.map +1 -1
- package/dist/classes/manager.d.ts +20 -18
- package/dist/classes/manager.d.ts.map +1 -1
- package/dist/classes/manager.js +27 -20
- package/dist/classes/manager.js.map +1 -1
- package/dist/classes/market.d.ts +11 -0
- package/dist/classes/market.d.ts.map +1 -1
- package/dist/classes/market.js +26 -0
- package/dist/classes/market.js.map +1 -1
- package/dist/classes/obligation.d.ts +14 -0
- package/dist/classes/obligation.d.ts.map +1 -1
- package/dist/classes/obligation.js +25 -0
- package/dist/classes/obligation.js.map +1 -1
- package/dist/classes/types_utils.d.ts +11 -0
- package/dist/classes/types_utils.d.ts.map +1 -0
- package/dist/classes/types_utils.js +21 -0
- package/dist/classes/types_utils.js.map +1 -0
- package/dist/classes/utils.d.ts +7 -0
- package/dist/classes/utils.d.ts.map +1 -1
- package/dist/classes/utils.js +21 -0
- package/dist/classes/utils.js.map +1 -1
- package/dist/classes/vault.d.ts +23 -14
- package/dist/classes/vault.d.ts.map +1 -1
- package/dist/classes/vault.js +65 -33
- package/dist/classes/vault.js.map +1 -1
- package/dist/client_kamino_manager.d.ts.map +1 -1
- package/dist/client_kamino_manager.js +25 -2
- package/dist/client_kamino_manager.js.map +1 -1
- package/dist/lending_operations/index.d.ts +1 -0
- package/dist/lending_operations/index.d.ts.map +1 -1
- package/dist/lending_operations/index.js +1 -0
- package/dist/lending_operations/index.js.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.d.ts +5 -5
- package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.js +3 -2
- package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
- package/dist/lending_operations/swap_collateral_operations.d.ts +102 -0
- package/dist/lending_operations/swap_collateral_operations.d.ts.map +1 -0
- package/dist/lending_operations/swap_collateral_operations.js +306 -0
- package/dist/lending_operations/swap_collateral_operations.js.map +1 -0
- package/dist/leverage/operations.d.ts +2 -2
- package/dist/leverage/operations.d.ts.map +1 -1
- package/dist/leverage/operations.js +4 -0
- package/dist/leverage/operations.js.map +1 -1
- package/dist/leverage/types.d.ts +5 -5
- package/dist/leverage/types.d.ts.map +1 -1
- package/dist/leverage/utils.d.ts +5 -5
- package/dist/leverage/utils.d.ts.map +1 -1
- package/dist/leverage/utils.js.map +1 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +2 -1
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/pubkey.d.ts +3 -0
- package/dist/utils/pubkey.d.ts.map +1 -1
- package/dist/utils/pubkey.js +16 -2
- package/dist/utils/pubkey.js.map +1 -1
- package/dist/utils/seeds.d.ts +1 -1
- package/dist/utils/seeds.js +1 -1
- package/package.json +4 -4
- package/src/classes/action.ts +37 -19
- package/src/classes/manager.ts +40 -23
- package/src/classes/market.ts +35 -1
- package/src/classes/obligation.ts +75 -0
- package/src/classes/types_utils.ts +19 -0
- package/src/classes/utils.ts +22 -1
- package/src/classes/vault.ts +93 -41
- package/src/client_kamino_manager.ts +43 -4
- package/src/lending_operations/index.ts +1 -0
- package/src/lending_operations/repay_with_collateral_operations.ts +10 -9
- package/src/lending_operations/swap_collateral_operations.ts +586 -0
- package/src/leverage/operations.ts +14 -10
- package/src/leverage/types.ts +6 -6
- package/src/leverage/utils.ts +8 -8
- package/src/utils/constants.ts +2 -0
- package/src/utils/pubkey.ts +19 -2
- package/src/utils/seeds.ts +1 -1
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ElevationGroupDescription,
|
|
3
|
+
FeeCalculation,
|
|
4
|
+
KaminoAction,
|
|
5
|
+
KaminoMarket,
|
|
6
|
+
KaminoObligation,
|
|
7
|
+
KaminoReserve,
|
|
8
|
+
} from '../classes';
|
|
9
|
+
import { getFlashLoanInstructions, SwapIxsProvider, SwapQuoteProvider } from '../leverage';
|
|
10
|
+
import {
|
|
11
|
+
createAtasIdempotent,
|
|
12
|
+
DEFAULT_MAX_COMPUTE_UNITS,
|
|
13
|
+
getAssociatedTokenAddress,
|
|
14
|
+
getComputeBudgetAndPriorityFeeIxns,
|
|
15
|
+
PublicKeySet,
|
|
16
|
+
ScopeRefresh,
|
|
17
|
+
U64_MAX,
|
|
18
|
+
uniqueAccounts,
|
|
19
|
+
WRAPPED_SOL_MINT,
|
|
20
|
+
} from '../utils';
|
|
21
|
+
import { AddressLookupTableAccount, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
|
22
|
+
import Decimal from 'decimal.js';
|
|
23
|
+
import { createCloseAccountInstruction, TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Inputs to the `getSwapCollIxns()` operation.
|
|
27
|
+
*/
|
|
28
|
+
export interface SwapCollIxnsInputs<QuoteResponse> {
|
|
29
|
+
/**
|
|
30
|
+
* The amount of source collateral to be swapped-in for the target collateral.
|
|
31
|
+
* This value will be treated exactly (i.e. slippage is not applied here) and thus must not exceed the collateral's
|
|
32
|
+
* total amount.
|
|
33
|
+
*/
|
|
34
|
+
sourceCollSwapAmount: Decimal;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* If true, the source collateral will be closed - whatever amount is left after withdrawing `sourceCollSwapAmount`
|
|
38
|
+
* will be transferred to the user.
|
|
39
|
+
*/
|
|
40
|
+
isClosingSourceColl: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The mint of the source collateral token (i.e. the current one).
|
|
44
|
+
*/
|
|
45
|
+
sourceCollTokenMint: PublicKey;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The mint of the target collateral token (i.e. the new one).
|
|
49
|
+
*/
|
|
50
|
+
targetCollTokenMint: PublicKey;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An elevation group ID that the obligation should end up with after the collateral swap - it will be requested by
|
|
54
|
+
* this operation (if different from the obligation's current elevation group).
|
|
55
|
+
*/
|
|
56
|
+
newElevationGroup: number;
|
|
57
|
+
|
|
58
|
+
// Note: the undocumented inputs below all have their most usual meaning used across the SDK.
|
|
59
|
+
|
|
60
|
+
market: KaminoMarket;
|
|
61
|
+
obligation: KaminoObligation;
|
|
62
|
+
referrer: PublicKey;
|
|
63
|
+
currentSlot: number;
|
|
64
|
+
budgetAndPriorityFeeIxns?: TransactionInstruction[];
|
|
65
|
+
scopeRefresh?: ScopeRefresh;
|
|
66
|
+
quoter: SwapQuoteProvider<QuoteResponse>;
|
|
67
|
+
swapper: SwapIxsProvider<QuoteResponse>;
|
|
68
|
+
logger?: (msg: string, ...extra: any[]) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Outputs from the `getSwapCollIxns()` operation.
|
|
73
|
+
*/
|
|
74
|
+
export interface SwapCollIxnsOutputs<QuoteResponse> {
|
|
75
|
+
/**
|
|
76
|
+
* Instructions for on-chain execution.
|
|
77
|
+
*/
|
|
78
|
+
ixs: TransactionInstruction[];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Required LUTs.
|
|
82
|
+
*/
|
|
83
|
+
lookupTables: AddressLookupTableAccount[];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Informational-only details of the token amounts/fees/rates that were used during construction of `ixs`.
|
|
87
|
+
*/
|
|
88
|
+
simulationDetails: {
|
|
89
|
+
/**
|
|
90
|
+
* Details related to the flash-loan operation needed during collateral swap.
|
|
91
|
+
*/
|
|
92
|
+
flashLoan: {
|
|
93
|
+
/**
|
|
94
|
+
* The amount of flash-borrowed target collateral.
|
|
95
|
+
* It is also *exactly the amount of target collateral that gets added to the obligation*.
|
|
96
|
+
*/
|
|
97
|
+
targetCollFlashBorrowedAmount: Decimal;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The flash-repaid amount.
|
|
101
|
+
* Simply a `targetCollFlashBorrowedAmount` + any flash-loan fees.
|
|
102
|
+
*/
|
|
103
|
+
targetCollFlashRepaidAmount: Decimal;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Details related to the external DEX's swap operation (i.e. `swapper` input) needed during collateral swap.
|
|
108
|
+
*/
|
|
109
|
+
externalSwap: {
|
|
110
|
+
/**
|
|
111
|
+
* The amount swapped-in to an external DEX.
|
|
112
|
+
* It is also *exactly the amount of source collateral that gets removed from the obligation* (i.e. echoed back
|
|
113
|
+
* `sourceCollSwapAmount` input).
|
|
114
|
+
*/
|
|
115
|
+
sourceCollSwapInAmount: Decimal;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The amount swapped-out from an external DEX.
|
|
119
|
+
* Please note that this field will be equal to the `flashBorrow.targetCollFlashRepaidAmount`, but an actual
|
|
120
|
+
* on-chain swap-out is subject to slippage.
|
|
121
|
+
*/
|
|
122
|
+
targetCollSwapOutAmount: Decimal;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The verbatim response coming from the input `quoter`.
|
|
126
|
+
*/
|
|
127
|
+
quoteResponse?: QuoteResponse;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Constructs instructions needed to partially/fully swap the given source collateral for some other collateral type.
|
|
134
|
+
*/
|
|
135
|
+
export async function getSwapCollIxns<QuoteResponse>(
|
|
136
|
+
inputs: SwapCollIxnsInputs<QuoteResponse>
|
|
137
|
+
): Promise<SwapCollIxnsOutputs<QuoteResponse>> {
|
|
138
|
+
const [args, context] = extractArgsAndContext(inputs);
|
|
139
|
+
|
|
140
|
+
// Conceptually, we need to construct the following ixns:
|
|
141
|
+
// 0. any set-up, like budgeting and ATAs
|
|
142
|
+
// 1. `flash-borrowed target coll = targetCollReserve.flashBorrow()`
|
|
143
|
+
// 2. `targetCollReserve.deposit(flash-borrowed target coll)`
|
|
144
|
+
// 3. `sourceCollReserve.withdraw(requested amount to be coll-swapped)`
|
|
145
|
+
// 4. `externally-swapped target coll = externalDex.swap(withdrawn current coll)`
|
|
146
|
+
// 5. `flashRepay(externally-swapped target coll)`
|
|
147
|
+
// However, there is a cyclic dependency:
|
|
148
|
+
// - To construct 4. (specifically, to query the external swap quote), we need to know all accounts used by Kamino's
|
|
149
|
+
// own ixns.
|
|
150
|
+
// - To construct 1. (i.e. flash-borrow), we need to know the target collateral swap-out from 4.
|
|
151
|
+
|
|
152
|
+
// Construct the Klend's own ixns with a fake swap-out (only to learn the klend accounts used):
|
|
153
|
+
const fakeKlendIxns = await getKlendIxns(args, FAKE_TARGET_COLL_SWAP_OUT_AMOUNT, context);
|
|
154
|
+
const klendAccounts = uniqueAccounts(listIxns(fakeKlendIxns));
|
|
155
|
+
|
|
156
|
+
// Construct the external swap ixns (and learn the actual swap-out amount):
|
|
157
|
+
const externalSwapIxns = await getExternalSwapIxns(args, klendAccounts, context);
|
|
158
|
+
|
|
159
|
+
// We now have the full information needed to simulate the end-state, so let's check that the operation is legal:
|
|
160
|
+
context.logger(
|
|
161
|
+
`Expected to swap ${args.sourceCollSwapAmount} ${context.sourceCollReserve.symbol} collateral into ${externalSwapIxns.swapOutAmount} ${context.targetCollReserve.symbol} collateral`
|
|
162
|
+
);
|
|
163
|
+
checkResultingObligationValid(args, externalSwapIxns.swapOutAmount, context);
|
|
164
|
+
|
|
165
|
+
// Construct the Klend's own ixns with an actual swap-out amount:
|
|
166
|
+
const klendIxns = await getKlendIxns(args, externalSwapIxns.swapOutAmount, context);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
ixs: listIxns(klendIxns, externalSwapIxns.ixns),
|
|
170
|
+
lookupTables: externalSwapIxns.luts,
|
|
171
|
+
simulationDetails: {
|
|
172
|
+
flashLoan: {
|
|
173
|
+
targetCollFlashBorrowedAmount: klendIxns.simulationDetails.targetCollFlashBorrowedAmount,
|
|
174
|
+
targetCollFlashRepaidAmount: externalSwapIxns.swapOutAmount,
|
|
175
|
+
},
|
|
176
|
+
externalSwap: {
|
|
177
|
+
sourceCollSwapInAmount: args.sourceCollSwapAmount, // repeated `/inputs.sourceCollSwapAmount`, only for clarity
|
|
178
|
+
targetCollSwapOutAmount: externalSwapIxns.swapOutAmount, // repeated `../flashLoan.targetCollFlashRepaidAmount`, only for clarity
|
|
179
|
+
quoteResponse: externalSwapIxns.simulationDetails.quoteResponse,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
type SwapCollArgs = {
|
|
186
|
+
sourceCollSwapAmount: Decimal;
|
|
187
|
+
isClosingSourceColl: boolean;
|
|
188
|
+
newElevationGroup: ElevationGroupDescription | null;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
type SwapCollContext<QuoteResponse> = {
|
|
192
|
+
budgetAndPriorityFeeIxns: TransactionInstruction[];
|
|
193
|
+
market: KaminoMarket;
|
|
194
|
+
sourceCollReserve: KaminoReserve;
|
|
195
|
+
targetCollReserve: KaminoReserve;
|
|
196
|
+
obligation: KaminoObligation;
|
|
197
|
+
quoter: SwapQuoteProvider<QuoteResponse>;
|
|
198
|
+
swapper: SwapIxsProvider<QuoteResponse>;
|
|
199
|
+
referrer: PublicKey;
|
|
200
|
+
currentSlot: number;
|
|
201
|
+
scopeRefresh: ScopeRefresh | undefined;
|
|
202
|
+
logger: (msg: string, ...extra: any[]) => void;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
function extractArgsAndContext<QuoteResponse>(
|
|
206
|
+
inputs: SwapCollIxnsInputs<QuoteResponse>
|
|
207
|
+
): [SwapCollArgs, SwapCollContext<QuoteResponse>] {
|
|
208
|
+
if (inputs.sourceCollTokenMint.equals(inputs.targetCollTokenMint)) {
|
|
209
|
+
throw new Error(`Cannot swap from/to the same collateral`);
|
|
210
|
+
}
|
|
211
|
+
if (inputs.sourceCollSwapAmount.lte(0)) {
|
|
212
|
+
throw new Error(`Cannot swap a negative amount`);
|
|
213
|
+
}
|
|
214
|
+
return [
|
|
215
|
+
{
|
|
216
|
+
sourceCollSwapAmount: inputs.sourceCollSwapAmount,
|
|
217
|
+
isClosingSourceColl: inputs.isClosingSourceColl,
|
|
218
|
+
newElevationGroup: inputs.market.getExistingElevationGroup(inputs.newElevationGroup, 'Newly-requested'),
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
budgetAndPriorityFeeIxns:
|
|
222
|
+
inputs.budgetAndPriorityFeeIxns || getComputeBudgetAndPriorityFeeIxns(DEFAULT_MAX_COMPUTE_UNITS),
|
|
223
|
+
sourceCollReserve: inputs.market.getExistingReserveByMint(inputs.sourceCollTokenMint, 'Current collateral'),
|
|
224
|
+
targetCollReserve: inputs.market.getExistingReserveByMint(inputs.targetCollTokenMint, 'Target collateral'),
|
|
225
|
+
logger: console.log,
|
|
226
|
+
market: inputs.market,
|
|
227
|
+
obligation: inputs.obligation,
|
|
228
|
+
quoter: inputs.quoter,
|
|
229
|
+
swapper: inputs.swapper,
|
|
230
|
+
referrer: inputs.referrer,
|
|
231
|
+
scopeRefresh: inputs.scopeRefresh,
|
|
232
|
+
currentSlot: inputs.currentSlot,
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const FAKE_TARGET_COLL_SWAP_OUT_AMOUNT = new Decimal(1); // see the lengthy `getSwapCollIxns()` impl comment
|
|
238
|
+
|
|
239
|
+
type SwapCollKlendIxns = {
|
|
240
|
+
setupIxns: TransactionInstruction[];
|
|
241
|
+
targetCollFlashBorrowIxn: TransactionInstruction;
|
|
242
|
+
depositTargetCollIxns: TransactionInstruction[];
|
|
243
|
+
withdrawSourceCollIxns: TransactionInstruction[];
|
|
244
|
+
targetCollFlashRepayIxn: TransactionInstruction;
|
|
245
|
+
cleanupIxns: TransactionInstruction[];
|
|
246
|
+
simulationDetails: {
|
|
247
|
+
targetCollFlashBorrowedAmount: Decimal;
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
async function getKlendIxns(
|
|
252
|
+
args: SwapCollArgs,
|
|
253
|
+
targetCollSwapOutAmount: Decimal,
|
|
254
|
+
context: SwapCollContext<any>
|
|
255
|
+
): Promise<SwapCollKlendIxns> {
|
|
256
|
+
const { ataCreationIxns, targetCollAta } = getAtaCreationIxns(context);
|
|
257
|
+
const setupIxns = [...context.budgetAndPriorityFeeIxns, ...ataCreationIxns];
|
|
258
|
+
|
|
259
|
+
const targetCollFlashBorrowedAmount = calculateTargetCollFlashBorrowedAmount(targetCollSwapOutAmount, context);
|
|
260
|
+
const { targetCollFlashBorrowIxn, targetCollFlashRepayIxn } = getTargetCollFlashLoanIxns(
|
|
261
|
+
targetCollFlashBorrowedAmount,
|
|
262
|
+
setupIxns.length,
|
|
263
|
+
targetCollAta,
|
|
264
|
+
context
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const depositTargetCollIxns = await getDepositTargetCollIxns(targetCollFlashBorrowedAmount, context);
|
|
268
|
+
const withdrawSourceCollIxns = await getWithdrawSourceCollIxns(
|
|
269
|
+
args,
|
|
270
|
+
depositTargetCollIxns.removesElevationGroup,
|
|
271
|
+
context
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const cleanupIxns = getAtaCloseIxns(context);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
setupIxns,
|
|
278
|
+
targetCollFlashBorrowIxn,
|
|
279
|
+
depositTargetCollIxns: depositTargetCollIxns.ixns,
|
|
280
|
+
withdrawSourceCollIxns,
|
|
281
|
+
targetCollFlashRepayIxn,
|
|
282
|
+
cleanupIxns,
|
|
283
|
+
simulationDetails: {
|
|
284
|
+
targetCollFlashBorrowedAmount,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function calculateTargetCollFlashBorrowedAmount(
|
|
290
|
+
targetCollFlashRepaidAmount: Decimal,
|
|
291
|
+
context: SwapCollContext<any>
|
|
292
|
+
): Decimal {
|
|
293
|
+
const { protocolFees, referrerFees } = context.targetCollReserve.calculateFees(
|
|
294
|
+
targetCollFlashRepaidAmount.mul(context.targetCollReserve.getMintFactor()),
|
|
295
|
+
context.targetCollReserve.getFlashLoanFee(),
|
|
296
|
+
FeeCalculation.Inclusive, // denotes that the amount parameter above means "to be repaid" (not "borrowed")
|
|
297
|
+
context.market.state.referralFeeBps,
|
|
298
|
+
!context.referrer.equals(PublicKey.default)
|
|
299
|
+
);
|
|
300
|
+
const targetCollFlashLoanFee = protocolFees.add(referrerFees).div(context.targetCollReserve.getMintFactor());
|
|
301
|
+
return targetCollFlashRepaidAmount.sub(targetCollFlashLoanFee);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getAtaCreationIxns(context: SwapCollContext<any>) {
|
|
305
|
+
const atasAndAtaCreationIxns = createAtasIdempotent(context.obligation.state.owner, [
|
|
306
|
+
{
|
|
307
|
+
mint: context.sourceCollReserve.getLiquidityMint(),
|
|
308
|
+
tokenProgram: context.sourceCollReserve.getLiquidityTokenProgram(),
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
mint: context.targetCollReserve.getLiquidityMint(),
|
|
312
|
+
tokenProgram: context.targetCollReserve.getLiquidityTokenProgram(),
|
|
313
|
+
},
|
|
314
|
+
]);
|
|
315
|
+
return {
|
|
316
|
+
ataCreationIxns: atasAndAtaCreationIxns.map((tuple) => tuple.createAtaIx),
|
|
317
|
+
targetCollAta: atasAndAtaCreationIxns[1].ata,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function getAtaCloseIxns(context: SwapCollContext<any>) {
|
|
322
|
+
const ataCloseIxns = [];
|
|
323
|
+
if (
|
|
324
|
+
context.sourceCollReserve.getLiquidityMint().equals(WRAPPED_SOL_MINT) ||
|
|
325
|
+
context.targetCollReserve.getLiquidityMint().equals(WRAPPED_SOL_MINT)
|
|
326
|
+
) {
|
|
327
|
+
const owner = context.obligation.state.owner;
|
|
328
|
+
const wsolAta = getAssociatedTokenAddress(WRAPPED_SOL_MINT, owner, false);
|
|
329
|
+
ataCloseIxns.push(createCloseAccountInstruction(wsolAta, owner, owner, [], TOKEN_PROGRAM_ID));
|
|
330
|
+
}
|
|
331
|
+
return ataCloseIxns;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function getTargetCollFlashLoanIxns(
|
|
335
|
+
targetCollAmount: Decimal,
|
|
336
|
+
flashBorrowIxnIndex: number,
|
|
337
|
+
destinationAta: PublicKey,
|
|
338
|
+
context: SwapCollContext<any>
|
|
339
|
+
) {
|
|
340
|
+
const { flashBorrowIxn: targetCollFlashBorrowIxn, flashRepayIxn: targetCollFlashRepayIxn } = getFlashLoanInstructions(
|
|
341
|
+
{
|
|
342
|
+
borrowIxnIndex: flashBorrowIxnIndex,
|
|
343
|
+
walletPublicKey: context.obligation.state.owner,
|
|
344
|
+
lendingMarketAuthority: context.market.getLendingMarketAuthority(),
|
|
345
|
+
lendingMarketAddress: context.market.getAddress(),
|
|
346
|
+
reserve: context.targetCollReserve,
|
|
347
|
+
amountLamports: targetCollAmount.mul(context.targetCollReserve.getMintFactor()),
|
|
348
|
+
destinationAta,
|
|
349
|
+
// TODO(referrals): once we support referrals, we will have to replace the placeholder args below:
|
|
350
|
+
referrerAccount: context.market.programId,
|
|
351
|
+
referrerTokenState: context.market.programId,
|
|
352
|
+
programId: context.market.programId,
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
return { targetCollFlashBorrowIxn, targetCollFlashRepayIxn };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
type DepositTargetCollIxns = {
|
|
359
|
+
removesElevationGroup: boolean;
|
|
360
|
+
ixns: TransactionInstruction[];
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
async function getDepositTargetCollIxns(
|
|
364
|
+
targetCollAmount: Decimal,
|
|
365
|
+
context: SwapCollContext<any>
|
|
366
|
+
): Promise<DepositTargetCollIxns> {
|
|
367
|
+
const removesElevationGroup = mustRemoveElevationGroupBeforeDeposit(context);
|
|
368
|
+
const depositCollAction = await KaminoAction.buildDepositTxns(
|
|
369
|
+
context.market,
|
|
370
|
+
targetCollAmount.mul(context.targetCollReserve.getMintFactor()).toString(), // in lamports
|
|
371
|
+
context.targetCollReserve.getLiquidityMint(),
|
|
372
|
+
context.obligation.state.owner,
|
|
373
|
+
context.obligation,
|
|
374
|
+
0, // no extra compute budget
|
|
375
|
+
false, // we do not need ATA ixns here (we construct and close them ourselves)
|
|
376
|
+
removesElevationGroup, // we may need to (temporarily) remove the elevation group; the same or a different one will be set on withdraw, if requested
|
|
377
|
+
false, // we are dealing with an existing obligation, no need to create user metadata
|
|
378
|
+
context.referrer,
|
|
379
|
+
context.currentSlot,
|
|
380
|
+
context.scopeRefresh,
|
|
381
|
+
removesElevationGroup ? 0 : undefined // only applicable when removing the group
|
|
382
|
+
);
|
|
383
|
+
return {
|
|
384
|
+
ixns: KaminoAction.actionToIxs(depositCollAction),
|
|
385
|
+
removesElevationGroup,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function mustRemoveElevationGroupBeforeDeposit(context: SwapCollContext<any>): boolean {
|
|
390
|
+
if (context.obligation.deposits.has(context.targetCollReserve.address)) {
|
|
391
|
+
return false; // the target collateral already was a reserve in the obligation, so we do not affect any potential elevation group
|
|
392
|
+
}
|
|
393
|
+
const currentElevationGroupId = context.obligation.state.elevationGroup;
|
|
394
|
+
if (currentElevationGroupId == 0) {
|
|
395
|
+
return false; // simply nothing to remove
|
|
396
|
+
}
|
|
397
|
+
if (!context.targetCollReserve.state.config.elevationGroups.includes(currentElevationGroupId)) {
|
|
398
|
+
return true; // the target collateral reserve is NOT in the obligation's group - must remove the group
|
|
399
|
+
}
|
|
400
|
+
const currentElevationGroup = context.market.getElevationGroup(currentElevationGroupId);
|
|
401
|
+
if (context.obligation.deposits.size >= currentElevationGroup.maxReservesAsCollateral) {
|
|
402
|
+
return true; // the obligation is already at its elevation group's deposits count limit - must remove the group
|
|
403
|
+
}
|
|
404
|
+
return false; // the obligation has some elevation group and the new collateral can be added to it
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function getWithdrawSourceCollIxns(
|
|
408
|
+
args: SwapCollArgs,
|
|
409
|
+
depositRemovedElevationGroup: boolean,
|
|
410
|
+
context: SwapCollContext<any>
|
|
411
|
+
): Promise<TransactionInstruction[]> {
|
|
412
|
+
const withdrawnSourceCollLamports = args.isClosingSourceColl
|
|
413
|
+
? U64_MAX
|
|
414
|
+
: args.sourceCollSwapAmount.mul(context.sourceCollReserve.getMintFactor()).toString();
|
|
415
|
+
const requestedElevationGroup = elevationGroupIdToRequestAfterWithdraw(args, depositRemovedElevationGroup, context);
|
|
416
|
+
const withdrawCollAction = await KaminoAction.buildWithdrawTxns(
|
|
417
|
+
context.market,
|
|
418
|
+
withdrawnSourceCollLamports,
|
|
419
|
+
context.sourceCollReserve.getLiquidityMint(),
|
|
420
|
+
context.obligation.state.owner,
|
|
421
|
+
context.obligation,
|
|
422
|
+
0, // no extra compute budget
|
|
423
|
+
false, // we do not need ATA ixns here (we construct and close them ourselves)
|
|
424
|
+
requestedElevationGroup !== undefined, // the `elevationGroupIdToRequestAfterWithdraw()` has already decided on this
|
|
425
|
+
false, // we are dealing with an existing obligation, no need to create user metadata
|
|
426
|
+
context.referrer,
|
|
427
|
+
context.currentSlot,
|
|
428
|
+
undefined, // we have refreshed scope already, during depositing
|
|
429
|
+
requestedElevationGroup,
|
|
430
|
+
context.obligation.deposits.has(context.targetCollReserve.address) // if our obligation already had the target coll...
|
|
431
|
+
? undefined // ... then we need no customizations here, but otherwise...
|
|
432
|
+
: {
|
|
433
|
+
addedDepositReserves: [context.targetCollReserve.address], // ... we need to inform our infra that the obligation now has one more reserve that needs refreshing.
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
return KaminoAction.actionToIxs(withdrawCollAction);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function elevationGroupIdToRequestAfterWithdraw(
|
|
440
|
+
args: SwapCollArgs,
|
|
441
|
+
depositRemovedElevationGroup: boolean,
|
|
442
|
+
context: SwapCollContext<any>
|
|
443
|
+
): number | undefined {
|
|
444
|
+
const obligationInitialElevationGroup = context.obligation.state.elevationGroup;
|
|
445
|
+
const requestedElevationGroupId = args.newElevationGroup?.elevationGroup ?? 0;
|
|
446
|
+
if (requestedElevationGroupId === 0) {
|
|
447
|
+
// the user doesn't want any elevation group, and...
|
|
448
|
+
if (obligationInitialElevationGroup === 0) {
|
|
449
|
+
return undefined; // ... he already didn't have it - fine!
|
|
450
|
+
}
|
|
451
|
+
if (depositRemovedElevationGroup) {
|
|
452
|
+
return undefined; // ... our deposit already forced us to remove it - fine!
|
|
453
|
+
}
|
|
454
|
+
return 0; // ... but he *did have one*, and our deposit didn't need to remove it - so we remove it now, just to satisfy him
|
|
455
|
+
} else {
|
|
456
|
+
// the user wants some elevation group, and...
|
|
457
|
+
if (depositRemovedElevationGroup) {
|
|
458
|
+
return requestedElevationGroupId; // ...our deposit forced us to remove it - so we now request the new one, whatever it is
|
|
459
|
+
}
|
|
460
|
+
if (obligationInitialElevationGroup === requestedElevationGroupId) {
|
|
461
|
+
return undefined; // ...and he already had exactly this one - fine!
|
|
462
|
+
}
|
|
463
|
+
return requestedElevationGroupId; // ...and he had some different one - so we request the new one
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
type ExternalSwapIxns<QuoteResponse> = {
|
|
468
|
+
swapOutAmount: Decimal;
|
|
469
|
+
ixns: TransactionInstruction[];
|
|
470
|
+
luts: AddressLookupTableAccount[];
|
|
471
|
+
simulationDetails: {
|
|
472
|
+
quoteResponse?: QuoteResponse;
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
async function getExternalSwapIxns<QuoteResponse>(
|
|
477
|
+
args: SwapCollArgs,
|
|
478
|
+
klendAccounts: PublicKey[],
|
|
479
|
+
context: SwapCollContext<QuoteResponse>
|
|
480
|
+
): Promise<ExternalSwapIxns<QuoteResponse>> {
|
|
481
|
+
const externalSwapInputs = {
|
|
482
|
+
inputAmountLamports: args.sourceCollSwapAmount.mul(context.sourceCollReserve.getMintFactor()),
|
|
483
|
+
inputMint: context.sourceCollReserve.getLiquidityMint(),
|
|
484
|
+
outputMint: context.targetCollReserve.getLiquidityMint(),
|
|
485
|
+
amountDebtAtaBalance: undefined, // only used for kTokens
|
|
486
|
+
};
|
|
487
|
+
const externalSwapQuote = await context.quoter(externalSwapInputs, klendAccounts);
|
|
488
|
+
const swapOutAmount = externalSwapQuote.priceAInB.mul(args.sourceCollSwapAmount);
|
|
489
|
+
const externalSwapIxnsAndLuts = await context.swapper(externalSwapInputs, klendAccounts, externalSwapQuote);
|
|
490
|
+
// Note: we can ignore the returned `preActionIxs` field - we do not request any of them from the swapper.
|
|
491
|
+
return {
|
|
492
|
+
swapOutAmount,
|
|
493
|
+
ixns: externalSwapIxnsAndLuts.swapIxs,
|
|
494
|
+
luts: externalSwapIxnsAndLuts.lookupTables,
|
|
495
|
+
simulationDetails: {
|
|
496
|
+
quoteResponse: externalSwapQuote.quoteResponse,
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function checkResultingObligationValid(
|
|
502
|
+
args: SwapCollArgs,
|
|
503
|
+
targetCollAmount: Decimal,
|
|
504
|
+
context: SwapCollContext<any>
|
|
505
|
+
): void {
|
|
506
|
+
// The newly-requested elevation group must have its conditions satisfied:
|
|
507
|
+
if (args.newElevationGroup !== null) {
|
|
508
|
+
// Note: we cannot use the existing `isLoanEligibleForElevationGroup()`, since it operates on a `KaminoObligation`,
|
|
509
|
+
// and our instance is stale (we want to assert on the state *after* potential changes).
|
|
510
|
+
|
|
511
|
+
// Let's start with the (simpler) debt reserve - it cannot change during a coll-swap:
|
|
512
|
+
const debtReserveAddresses = [...context.obligation.borrows.keys()];
|
|
513
|
+
if (debtReserveAddresses.length > 1) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`The obligation with ${debtReserveAddresses.length} debt reserves cannot request any elevation group`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
if (debtReserveAddresses.length == 1) {
|
|
519
|
+
const debtReserveAddress = debtReserveAddresses[0];
|
|
520
|
+
if (!args.newElevationGroup.debtReserve.equals(debtReserveAddress)) {
|
|
521
|
+
throw new Error(
|
|
522
|
+
`The obligation with debt reserve ${debtReserveAddress.toBase58()} cannot request elevation group ${
|
|
523
|
+
args.newElevationGroup.elevationGroup
|
|
524
|
+
}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Now the coll reserves: this requires first finding out the resulting set of deposits:
|
|
530
|
+
const collReserveAddresses = new PublicKeySet([
|
|
531
|
+
...context.obligation.deposits.keys(),
|
|
532
|
+
context.targetCollReserve.address,
|
|
533
|
+
]);
|
|
534
|
+
if (args.isClosingSourceColl) {
|
|
535
|
+
collReserveAddresses.remove(context.sourceCollReserve.address);
|
|
536
|
+
}
|
|
537
|
+
if (collReserveAddresses.size() > args.newElevationGroup.maxReservesAsCollateral) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`The obligation with ${collReserveAddresses.size()} collateral reserves cannot request elevation group ${
|
|
540
|
+
args.newElevationGroup.elevationGroup
|
|
541
|
+
}`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
for (const collReserveAddress of collReserveAddresses.toArray()) {
|
|
545
|
+
if (!args.newElevationGroup.collateralReserves.contains(collReserveAddress)) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`The obligation with collateral reserve ${collReserveAddress.toBase58()} cannot request elevation group ${
|
|
548
|
+
args.newElevationGroup.elevationGroup
|
|
549
|
+
}`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// The LTV cannot be exceeded:
|
|
556
|
+
const effectiveWithdrawAmount = args.isClosingSourceColl
|
|
557
|
+
? context.obligation.getDepositAmountByReserve(context.sourceCollReserve)
|
|
558
|
+
: args.sourceCollSwapAmount;
|
|
559
|
+
const resultingStats = context.obligation.getPostSwapCollObligationStats({
|
|
560
|
+
withdrawAmountLamports: effectiveWithdrawAmount.mul(context.sourceCollReserve.getMintFactor()),
|
|
561
|
+
withdrawReserveAddress: context.sourceCollReserve.address,
|
|
562
|
+
depositAmountLamports: targetCollAmount.mul(context.targetCollReserve.getMintFactor()),
|
|
563
|
+
depositReserveAddress: context.targetCollReserve.address,
|
|
564
|
+
market: context.market,
|
|
565
|
+
newElevationGroup: args.newElevationGroup?.elevationGroup ?? 0,
|
|
566
|
+
slot: context.currentSlot,
|
|
567
|
+
});
|
|
568
|
+
const maxLtv = resultingStats.borrowLimit.div(resultingStats.userTotalCollateralDeposit);
|
|
569
|
+
if (resultingStats.loanToValue > maxLtv) {
|
|
570
|
+
throw new Error(
|
|
571
|
+
`Swapping collateral ${effectiveWithdrawAmount} ${context.sourceCollReserve.symbol} into ${targetCollAmount} ${context.targetCollReserve.symbol} would result in the obligation's LTV ${resultingStats.loanToValue} exceeding its max LTV ${maxLtv}`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function listIxns(klendIxns: SwapCollKlendIxns, externalSwapIxns?: TransactionInstruction[]): TransactionInstruction[] {
|
|
577
|
+
return [
|
|
578
|
+
...klendIxns.setupIxns,
|
|
579
|
+
klendIxns.targetCollFlashBorrowIxn,
|
|
580
|
+
...klendIxns.depositTargetCollIxns,
|
|
581
|
+
...klendIxns.withdrawSourceCollIxns,
|
|
582
|
+
...(externalSwapIxns || []),
|
|
583
|
+
klendIxns.targetCollFlashRepayIxn,
|
|
584
|
+
...klendIxns.cleanupIxns,
|
|
585
|
+
];
|
|
586
|
+
}
|
|
@@ -52,8 +52,8 @@ import {
|
|
|
52
52
|
DepsoitLeverageIxsResponse,
|
|
53
53
|
PriceAinBProvider,
|
|
54
54
|
SwapInputs,
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
SwapIxs,
|
|
56
|
+
SwapIxsProvider,
|
|
57
57
|
WithdrawLeverageCalcsResult,
|
|
58
58
|
WithdrawLeverageInitialInputs,
|
|
59
59
|
WithdrawLeverageIxsResponse,
|
|
@@ -335,7 +335,7 @@ export async function getDepositWithLeverageIxns<QuoteResponse>({
|
|
|
335
335
|
quoter,
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
-
let depositSwapper:
|
|
338
|
+
let depositSwapper: SwapIxsProvider<QuoteResponse>;
|
|
339
339
|
|
|
340
340
|
if (!initialInputs.collIsKtoken) {
|
|
341
341
|
depositSwapper = swapper;
|
|
@@ -411,7 +411,7 @@ async function buildDepositWithLeverageIxns(
|
|
|
411
411
|
scopeFeed: string | undefined,
|
|
412
412
|
calcs: DepositLeverageCalcsResult,
|
|
413
413
|
budgetAndPriorityFeeIxs: TransactionInstruction[] | undefined,
|
|
414
|
-
swapQuoteIxs:
|
|
414
|
+
swapQuoteIxs: SwapIxs,
|
|
415
415
|
strategy: StrategyWithAddress | undefined,
|
|
416
416
|
collIsKtoken: boolean,
|
|
417
417
|
elevationGroupOverride?: number
|
|
@@ -506,6 +506,7 @@ async function buildDepositWithLeverageIxns(
|
|
|
506
506
|
!collIsKtoken ? collReserve.stats.decimals : debtReserve.stats.decimals
|
|
507
507
|
),
|
|
508
508
|
destinationAta: !collIsKtoken ? collTokenAta : debtTokenAta,
|
|
509
|
+
// TODO(referrals): once we support referrals, we will have to replace the placeholder args below:
|
|
509
510
|
referrerAccount: market.programId,
|
|
510
511
|
referrerTokenState: market.programId,
|
|
511
512
|
programId: market.programId,
|
|
@@ -748,7 +749,7 @@ export async function getWithdrawWithLeverageIxns<QuoteResponse>({
|
|
|
748
749
|
quoter,
|
|
749
750
|
});
|
|
750
751
|
|
|
751
|
-
let withdrawSwapper:
|
|
752
|
+
let withdrawSwapper: SwapIxsProvider<QuoteResponse>;
|
|
752
753
|
|
|
753
754
|
if (initialInputs.collIsKtoken) {
|
|
754
755
|
if (kamino === undefined) {
|
|
@@ -821,7 +822,7 @@ export async function buildWithdrawWithLeverageIxns(
|
|
|
821
822
|
scopeFeed: string | undefined,
|
|
822
823
|
calcs: WithdrawLeverageCalcsResult,
|
|
823
824
|
budgetAndPriorityFeeIxs: TransactionInstruction[] | undefined,
|
|
824
|
-
swapQuoteIxs:
|
|
825
|
+
swapQuoteIxs: SwapIxs,
|
|
825
826
|
strategy: StrategyWithAddress | undefined,
|
|
826
827
|
collIsKtoken: boolean
|
|
827
828
|
): Promise<TransactionInstruction[]> {
|
|
@@ -917,6 +918,7 @@ export async function buildWithdrawWithLeverageIxns(
|
|
|
917
918
|
reserve: debtReserve!,
|
|
918
919
|
amountLamports: toLamports(calcs.repayAmount, debtReserve!.stats.decimals),
|
|
919
920
|
destinationAta: debtTokenAta,
|
|
921
|
+
// TODO(referrals): once we support referrals, we will have to replace the placeholder args below:
|
|
920
922
|
referrerAccount: market.programId,
|
|
921
923
|
referrerTokenState: market.programId,
|
|
922
924
|
programId: market.programId,
|
|
@@ -1258,7 +1260,7 @@ export async function getAdjustLeverageIxns<QuoteResponse>({
|
|
|
1258
1260
|
|
|
1259
1261
|
// leverage increased so we need to deposit and borrow more
|
|
1260
1262
|
if (initialInputs.isDeposit) {
|
|
1261
|
-
let depositSwapper:
|
|
1263
|
+
let depositSwapper: SwapIxsProvider<QuoteResponse>;
|
|
1262
1264
|
|
|
1263
1265
|
if (initialInputs.collIsKtoken) {
|
|
1264
1266
|
if (kamino === undefined) {
|
|
@@ -1312,7 +1314,7 @@ export async function getAdjustLeverageIxns<QuoteResponse>({
|
|
|
1312
1314
|
} else {
|
|
1313
1315
|
console.log('Decreasing leverage');
|
|
1314
1316
|
|
|
1315
|
-
let withdrawSwapper:
|
|
1317
|
+
let withdrawSwapper: SwapIxsProvider<QuoteResponse>;
|
|
1316
1318
|
|
|
1317
1319
|
if (initialInputs.collIsKtoken) {
|
|
1318
1320
|
if (kamino === undefined) {
|
|
@@ -1374,7 +1376,7 @@ async function buildIncreaseLeverageIxns(
|
|
|
1374
1376
|
strategy: StrategyWithAddress | undefined,
|
|
1375
1377
|
scopeFeed: string | undefined,
|
|
1376
1378
|
collIsKtoken: boolean,
|
|
1377
|
-
swapQuoteIxs:
|
|
1379
|
+
swapQuoteIxs: SwapIxs,
|
|
1378
1380
|
budgetAndPriorityFeeIxns: TransactionInstruction[] | undefined
|
|
1379
1381
|
): Promise<TransactionInstruction[]> {
|
|
1380
1382
|
const collReserve = kaminoMarket.getReserveByMint(collTokenMint);
|
|
@@ -1455,6 +1457,7 @@ async function buildIncreaseLeverageIxns(
|
|
|
1455
1457
|
!collIsKtoken ? collReserve!.stats.decimals : debtReserve!.stats.decimals
|
|
1456
1458
|
),
|
|
1457
1459
|
destinationAta: !collIsKtoken ? collTokenAta : debtTokenAta,
|
|
1460
|
+
// TODO(referrals): once we support referrals, we will have to replace the placeholder args below:
|
|
1458
1461
|
referrerAccount: kaminoMarket.programId,
|
|
1459
1462
|
referrerTokenState: kaminoMarket.programId,
|
|
1460
1463
|
programId: kaminoMarket.programId,
|
|
@@ -1539,7 +1542,7 @@ async function buildDecreaseLeverageIxns(
|
|
|
1539
1542
|
strategy: StrategyWithAddress | undefined,
|
|
1540
1543
|
scopeFeed: string | undefined,
|
|
1541
1544
|
collIsKtoken: boolean,
|
|
1542
|
-
swapQuoteIxs:
|
|
1545
|
+
swapQuoteIxs: SwapIxs,
|
|
1543
1546
|
budgetAndPriorityFeeIxns: TransactionInstruction[] | undefined
|
|
1544
1547
|
): Promise<TransactionInstruction[]> {
|
|
1545
1548
|
const collReserve = kaminoMarket.getReserveByMint(collTokenMint);
|
|
@@ -1626,6 +1629,7 @@ async function buildDecreaseLeverageIxns(
|
|
|
1626
1629
|
reserve: debtReserve!,
|
|
1627
1630
|
amountLamports: toLamports(Decimal.abs(calcs.adjustBorrowPosition), debtReserve!.stats.decimals),
|
|
1628
1631
|
destinationAta: debtTokenAta,
|
|
1632
|
+
// TODO(referrals): once we support referrals, we will have to replace the placeholder args below:
|
|
1629
1633
|
referrerAccount: kaminoMarket.programId,
|
|
1630
1634
|
referrerTokenState: kaminoMarket.programId,
|
|
1631
1635
|
programId: kaminoMarket.programId,
|