@exponent-labs/exponent-sdk 0.9.0 → 0.9.1
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/build/client/vaults/index.d.ts +2 -0
- package/build/client/vaults/index.js +2 -0
- package/build/client/vaults/index.js.map +1 -1
- package/build/client/vaults/types/index.d.ts +2 -0
- package/build/client/vaults/types/index.js +2 -0
- package/build/client/vaults/types/index.js.map +1 -1
- package/build/client/vaults/types/kaminoFarmEntry.d.ts +15 -0
- package/build/client/vaults/types/kaminoFarmEntry.js +17 -0
- package/build/client/vaults/types/kaminoFarmEntry.js.map +1 -0
- package/build/client/vaults/types/kaminoObligationEntry.d.ts +24 -7
- package/build/client/vaults/types/kaminoObligationEntry.js +2 -1
- package/build/client/vaults/types/kaminoObligationEntry.js.map +1 -1
- package/build/client/vaults/types/obligationType.d.ts +1 -1
- package/build/client/vaults/types/positionUpdate.d.ts +9 -0
- package/build/client/vaults/types/positionUpdate.js +23 -0
- package/build/client/vaults/types/positionUpdate.js.map +1 -1
- package/build/client/vaults/types/proposalAction.d.ts +54 -54
- package/build/client/vaults/types/proposalAction.js +0 -3
- package/build/client/vaults/types/proposalAction.js.map +1 -1
- package/build/client/vaults/types/reserveFarmMapping.d.ts +19 -0
- package/build/client/vaults/types/reserveFarmMapping.js +18 -0
- package/build/client/vaults/types/reserveFarmMapping.js.map +1 -0
- package/build/client/vaults/types/strategyPosition.d.ts +6 -1
- package/build/client/vaults/types/strategyPosition.js +5 -0
- package/build/client/vaults/types/strategyPosition.js.map +1 -1
- package/build/exponentVaults/aumCalculator.d.ts +25 -4
- package/build/exponentVaults/aumCalculator.js +236 -15
- package/build/exponentVaults/aumCalculator.js.map +1 -1
- package/build/exponentVaults/fetcher.d.ts +52 -0
- package/build/exponentVaults/fetcher.js +199 -0
- package/build/exponentVaults/fetcher.js.map +1 -0
- package/build/exponentVaults/index.d.ts +10 -9
- package/build/exponentVaults/index.js +25 -8
- package/build/exponentVaults/index.js.map +1 -1
- package/build/exponentVaults/kamino-farms.d.ts +144 -0
- package/build/exponentVaults/kamino-farms.js +396 -0
- package/build/exponentVaults/kamino-farms.js.map +1 -0
- package/build/exponentVaults/loopscale/client.d.ts +240 -0
- package/build/exponentVaults/loopscale/client.js +590 -0
- package/build/exponentVaults/loopscale/client.js.map +1 -0
- package/build/exponentVaults/loopscale/client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/client.test.js +183 -0
- package/build/exponentVaults/loopscale/client.test.js.map +1 -0
- package/build/exponentVaults/loopscale/helpers.d.ts +29 -0
- package/build/exponentVaults/loopscale/helpers.js +119 -0
- package/build/exponentVaults/loopscale/helpers.js.map +1 -0
- package/build/exponentVaults/loopscale/index.d.ts +3 -0
- package/build/exponentVaults/loopscale/index.js +12 -0
- package/build/exponentVaults/loopscale/index.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.d.ts +13 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js +271 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js +400 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-types.d.ts +62 -0
- package/build/exponentVaults/loopscale/prepared-types.js +3 -0
- package/build/exponentVaults/loopscale/prepared-types.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.d.ts +69 -0
- package/build/exponentVaults/loopscale/response-plan.js +141 -0
- package/build/exponentVaults/loopscale/response-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.js +139 -0
- package/build/exponentVaults/loopscale/response-plan.test.js.map +1 -0
- package/build/exponentVaults/loopscale/send-plan.d.ts +75 -0
- package/build/exponentVaults/loopscale/send-plan.js +235 -0
- package/build/exponentVaults/loopscale/send-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/types.d.ts +443 -0
- package/build/exponentVaults/loopscale/types.js +3 -0
- package/build/exponentVaults/loopscale/types.js.map +1 -0
- package/build/exponentVaults/loopscale-client.d.ts +113 -524
- package/build/exponentVaults/loopscale-client.js +296 -539
- package/build/exponentVaults/loopscale-client.js.map +1 -1
- package/build/exponentVaults/loopscale-client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-client.test.js +162 -0
- package/build/exponentVaults/loopscale-client.test.js.map +1 -0
- package/build/exponentVaults/loopscale-client.types.d.ts +425 -0
- package/build/exponentVaults/loopscale-client.types.js +3 -0
- package/build/exponentVaults/loopscale-client.types.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.d.ts +125 -0
- package/build/exponentVaults/loopscale-execution.js +341 -0
- package/build/exponentVaults/loopscale-execution.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-execution.test.js +139 -0
- package/build/exponentVaults/loopscale-execution.test.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.d.ts +115 -0
- package/build/exponentVaults/loopscale-vault.js +275 -0
- package/build/exponentVaults/loopscale-vault.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-vault.test.js +102 -0
- package/build/exponentVaults/loopscale-vault.test.js.map +1 -0
- package/build/exponentVaults/policyBuilders.d.ts +62 -0
- package/build/exponentVaults/policyBuilders.js +119 -2
- package/build/exponentVaults/policyBuilders.js.map +1 -1
- package/build/exponentVaults/pricePathResolver.d.ts +45 -0
- package/build/exponentVaults/pricePathResolver.js +198 -0
- package/build/exponentVaults/pricePathResolver.js.map +1 -0
- package/build/exponentVaults/pricePathResolver.test.d.ts +1 -0
- package/build/exponentVaults/pricePathResolver.test.js +369 -0
- package/build/exponentVaults/pricePathResolver.test.js.map +1 -0
- package/build/exponentVaults/syncTransaction.js +4 -1
- package/build/exponentVaults/syncTransaction.js.map +1 -1
- package/build/exponentVaults/titan-quote.js +170 -36
- package/build/exponentVaults/titan-quote.js.map +1 -1
- package/build/exponentVaults/vault-instruction-types.d.ts +363 -0
- package/build/exponentVaults/vault-instruction-types.js +128 -0
- package/build/exponentVaults/vault-instruction-types.js.map +1 -0
- package/build/exponentVaults/vault-interaction.d.ts +156 -313
- package/build/exponentVaults/vault-interaction.js +1581 -353
- package/build/exponentVaults/vault-interaction.js.map +1 -1
- package/build/exponentVaults/vault.d.ts +51 -2
- package/build/exponentVaults/vault.js +324 -48
- package/build/exponentVaults/vault.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.d.ts +100 -134
- package/build/exponentVaults/vaultTransactionBuilder.js +359 -266
- package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.test.d.ts +1 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js +214 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -0
- package/build/marketThree.d.ts +6 -2
- package/build/marketThree.js +10 -8
- package/build/marketThree.js.map +1 -1
- package/package.json +32 -32
- package/src/client/vaults/index.ts +2 -0
- package/src/client/vaults/types/index.ts +2 -0
- package/src/client/vaults/types/kaminoFarmEntry.ts +32 -0
- package/src/client/vaults/types/kaminoObligationEntry.ts +6 -3
- package/src/client/vaults/types/positionUpdate.ts +62 -0
- package/src/client/vaults/types/proposalAction.ts +0 -3
- package/src/client/vaults/types/reserveFarmMapping.ts +35 -0
- package/src/client/vaults/types/strategyPosition.ts +18 -1
- package/src/exponentVaults/aumCalculator.ts +353 -16
- package/src/exponentVaults/fetcher.ts +257 -0
- package/src/exponentVaults/index.ts +64 -40
- package/src/exponentVaults/kamino-farms.ts +538 -0
- package/src/exponentVaults/loopscale/client.ts +808 -0
- package/src/exponentVaults/loopscale/helpers.ts +172 -0
- package/src/exponentVaults/loopscale/index.ts +57 -0
- package/src/exponentVaults/loopscale/prepared-transactions.ts +435 -0
- package/src/exponentVaults/loopscale/prepared-types.ts +73 -0
- package/src/exponentVaults/loopscale/types.ts +466 -0
- package/src/exponentVaults/policyBuilders.ts +170 -0
- package/src/exponentVaults/pricePathResolver.test.ts +466 -0
- package/src/exponentVaults/pricePathResolver.ts +273 -0
- package/src/exponentVaults/syncTransaction.ts +6 -1
- package/src/exponentVaults/titan-quote.ts +231 -45
- package/src/exponentVaults/vault-instruction-types.ts +493 -0
- package/src/exponentVaults/vault-interaction.ts +2227 -636
- package/src/exponentVaults/vault.ts +474 -63
- package/src/exponentVaults/vaultTransactionBuilder.test.ts +256 -0
- package/src/exponentVaults/vaultTransactionBuilder.ts +555 -413
- package/src/marketThree.ts +14 -6
- package/src/exponentVaults/loopscale-client.ts +0 -1373
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
import { Reserve } from "@exponent-labs/kamino-reserve-deserializer"
|
|
2
|
+
import { fetchKaminoVaultIndex } from "@exponent-labs/exponent-fetcher"
|
|
2
3
|
import { KAMINO_MARKETS, KAMINO_RESERVES, KaminoMarket } from "./kamino-markets"
|
|
3
4
|
import Decimal from "decimal.js"
|
|
4
|
-
import {
|
|
5
|
-
|
|
5
|
+
import {
|
|
6
|
+
getKaminoFarmsObligationFarm,
|
|
7
|
+
getKaminoFarmsRewardsTreasuryVault,
|
|
8
|
+
getKaminoLendObligation,
|
|
9
|
+
getKaminoUserMetadata,
|
|
10
|
+
} from "./../../../kamino-lend-standard/src/constants"
|
|
11
|
+
import {
|
|
12
|
+
KAMINO_FARM_DISCRIMINATORS,
|
|
13
|
+
KAMINO_LENDING_PROGRAM_ID,
|
|
14
|
+
KAMINO_VAULT_DISCRIMINATORS,
|
|
15
|
+
KAMINO_VAULT_PROGRAM_ID,
|
|
16
|
+
} from "./policyBuilders"
|
|
6
17
|
import { Obligation } from "@exponent-labs/klend-idl/accounts"
|
|
7
|
-
import { bigintU256ToString } from "@exponent-labs/precise-number"
|
|
8
18
|
import { Orderbook } from "../orderbook/orderbook"
|
|
9
19
|
import { MarketThree } from "../marketThree"
|
|
10
|
-
import { uniqueRemainingAccounts } from "../utils"
|
|
20
|
+
import { emitEventAuthority, uniqueRemainingAccounts } from "../utils"
|
|
11
21
|
import { SwapDirection } from "../client/clmm"
|
|
12
22
|
import * as exponentClmm from "../client/clmm"
|
|
13
23
|
import * as exponentVaults from "../client/vaults"
|
|
14
24
|
import { OfferType, offerOptions, amount as createAmount } from "../client/orderbook"
|
|
15
25
|
import { LOCAL_ENV, Environment } from "../environment"
|
|
16
26
|
import { Vault } from "../vault"
|
|
27
|
+
import { collectTrackedStrategyVaultPriceIds } from "./vault"
|
|
17
28
|
import { YtPosition } from "../ytPosition"
|
|
18
29
|
import { ExponentVault as StrategyVault } from "./vault"
|
|
19
30
|
import type { ExponentPrice, ExponentPrices } from "@exponent-labs/exponent-vaults-fetcher"
|
|
31
|
+
import { decodeKaminoFarmState, getKaminoFarmScopePricesAddress } from "./kamino-farms"
|
|
32
|
+
import type { PriceId as ClientPriceId } from "../client/vaults/types/priceId"
|
|
33
|
+
import {
|
|
34
|
+
extractPriceIds,
|
|
35
|
+
getPriceInputMintFromPriceId,
|
|
36
|
+
resolveBestKaminoQuotePath,
|
|
37
|
+
resolveKaminoReservePriceIdOrThrow,
|
|
38
|
+
resolvePriceIdFromMintToUnderlyingOrThrow,
|
|
39
|
+
} from "./pricePathResolver"
|
|
20
40
|
|
|
21
41
|
import {
|
|
22
42
|
depositReserveLiquidityAndObligationCollateralV2,
|
|
@@ -47,399 +67,130 @@ import { AccountMeta, Connection, PublicKey, SystemProgram, SYSVAR_INSTRUCTIONS_
|
|
|
47
67
|
import BN from "bn.js"
|
|
48
68
|
|
|
49
69
|
const KAMINO_FARMS_PROGRAM_ID = new PublicKey("FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr")
|
|
70
|
+
const KAMINO_VAULT_PRICE_TYPE_WIRE = 14
|
|
71
|
+
const KAMINO_VAULT_ACCOUNT_DISCRIMINATOR_LEN = 8
|
|
72
|
+
const KAMINO_VAULT_ALLOCATION_STRATEGY_OFFSET = 304
|
|
73
|
+
const KAMINO_VAULT_ALLOCATION_SIZE = 2160
|
|
74
|
+
const KAMINO_VAULT_ALLOCATION_CTOKEN_VAULT_OFFSET = 32
|
|
75
|
+
const KAMINO_VAULT_GLOBAL_CONFIG_SEED = Buffer.from("global_config")
|
|
50
76
|
|
|
51
77
|
// ============================================================================
|
|
52
|
-
// Vault Instruction Types
|
|
53
|
-
// ============================================================================
|
|
54
|
-
|
|
55
|
-
type KaminoReserves = typeof KAMINO_RESERVES
|
|
56
|
-
|
|
57
|
-
/** Actions that can be performed through the vault instruction builder. */
|
|
58
|
-
export enum VaultAction {
|
|
59
|
-
INIT_USER_METADATA = "INIT_USER_METADATA",
|
|
60
|
-
INIT_OBLIGATION = "INIT_OBLIGATION",
|
|
61
|
-
DEPOSIT = "DEPOSIT",
|
|
62
|
-
WITHDRAW = "WITHDRAW",
|
|
63
|
-
BORROW = "BORROW",
|
|
64
|
-
REPAY = "REPAY",
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** A market-level instruction (no specific reserve needed). */
|
|
68
|
-
export type MarketInstruction = {
|
|
69
|
-
action: VaultAction.INIT_USER_METADATA | VaultAction.INIT_OBLIGATION
|
|
70
|
-
market: KaminoMarket
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** A reserve-level instruction with an amount. */
|
|
74
|
-
export type ReserveInstruction = {
|
|
75
|
-
action: VaultAction.DEPOSIT | VaultAction.WITHDRAW | VaultAction.BORROW | VaultAction.REPAY
|
|
76
|
-
market: KaminoMarket
|
|
77
|
-
asset: string
|
|
78
|
-
amount: BN
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ============================================================================
|
|
82
|
-
// Orderbook Instruction Types
|
|
83
|
-
// ============================================================================
|
|
84
|
-
|
|
85
|
-
/** Orderbook trade direction */
|
|
86
|
-
export enum OrderbookTradeDirection {
|
|
87
|
-
BUY_PT = "BUY_PT",
|
|
88
|
-
SELL_PT = "SELL_PT",
|
|
89
|
-
BUY_YT = "BUY_YT",
|
|
90
|
-
SELL_YT = "SELL_YT",
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Offer options for limit orders (currently only FillOrKill supported) */
|
|
94
|
-
export type OrderbookOfferOption = "FillOrKill"
|
|
95
|
-
|
|
96
|
-
/** Actions that can be performed on the Exponent Orderbook */
|
|
97
|
-
export enum OrderbookAction {
|
|
98
|
-
POST_OFFER = "POST_OFFER",
|
|
99
|
-
MARKET_OFFER = "MARKET_OFFER",
|
|
100
|
-
REMOVE_OFFER = "REMOVE_OFFER",
|
|
101
|
-
WITHDRAW_FUNDS = "WITHDRAW_FUNDS",
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export type OrderbookInstructionMode = "wrapper" | "raw"
|
|
105
|
-
|
|
106
|
-
/** Base instruction type for all orderbook operations */
|
|
107
|
-
interface OrderbookInstructionBase {
|
|
108
|
-
action: OrderbookAction
|
|
109
|
-
orderbook: PublicKey
|
|
110
|
-
mode?: OrderbookInstructionMode
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Post a limit order on the orderbook */
|
|
114
|
-
export interface OrderbookPostOfferInstruction extends OrderbookInstructionBase {
|
|
115
|
-
action: OrderbookAction.POST_OFFER
|
|
116
|
-
direction: OrderbookTradeDirection
|
|
117
|
-
priceApy: number
|
|
118
|
-
amount: bigint
|
|
119
|
-
offerIdx: number
|
|
120
|
-
offerOption?: OrderbookOfferOption
|
|
121
|
-
virtualOffer?: boolean
|
|
122
|
-
expirySeconds?: number
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Execute a market order on the orderbook */
|
|
126
|
-
export interface OrderbookMarketOfferInstruction extends OrderbookInstructionBase {
|
|
127
|
-
action: OrderbookAction.MARKET_OFFER
|
|
128
|
-
direction: OrderbookTradeDirection
|
|
129
|
-
maxPriceApy: number
|
|
130
|
-
amount: bigint
|
|
131
|
-
minAmountOut: bigint
|
|
132
|
-
virtualOffer?: boolean
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Cancel an existing limit order */
|
|
136
|
-
export interface OrderbookRemoveOfferInstruction extends OrderbookInstructionBase {
|
|
137
|
-
action: OrderbookAction.REMOVE_OFFER
|
|
138
|
-
offerIdx: number
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Withdraw funds from user escrow */
|
|
142
|
-
export interface OrderbookWithdrawFundsInstruction extends OrderbookInstructionBase {
|
|
143
|
-
action: OrderbookAction.WITHDRAW_FUNDS
|
|
144
|
-
ptAmount?: bigint | null
|
|
145
|
-
ytAmount?: bigint | null
|
|
146
|
-
syAmount?: bigint | null
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** A single orderbook instruction */
|
|
150
|
-
export type OrderbookInstruction =
|
|
151
|
-
| OrderbookPostOfferInstruction
|
|
152
|
-
| OrderbookMarketOfferInstruction
|
|
153
|
-
| OrderbookRemoveOfferInstruction
|
|
154
|
-
| OrderbookWithdrawFundsInstruction
|
|
155
|
-
|
|
156
|
-
// ============================================================================
|
|
157
|
-
// Core Instruction Types (Strip/Merge)
|
|
158
|
-
// ============================================================================
|
|
159
|
-
|
|
160
|
-
/** Actions that can be performed on Exponent Core */
|
|
161
|
-
export enum CoreAction {
|
|
162
|
-
STRIP = "STRIP",
|
|
163
|
-
MERGE = "MERGE",
|
|
164
|
-
WITHDRAW_YT = "WITHDRAW_YT",
|
|
165
|
-
DEPOSIT_YT = "DEPOSIT_YT",
|
|
166
|
-
INITIALIZE_YIELD_POSITION = "INITIALIZE_YIELD_POSITION",
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Base instruction type for all core operations */
|
|
170
|
-
interface CoreInstructionBase {
|
|
171
|
-
action: CoreAction
|
|
172
|
-
vault: PublicKey
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Strip LST into PT + YT */
|
|
176
|
-
export interface CoreStripInstruction extends CoreInstructionBase {
|
|
177
|
-
action: CoreAction.STRIP
|
|
178
|
-
/** Amount of base token (LST) to strip */
|
|
179
|
-
amountBase: bigint
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Merge PT + YT into LST */
|
|
183
|
-
export interface CoreMergeInstruction extends CoreInstructionBase {
|
|
184
|
-
action: CoreAction.MERGE
|
|
185
|
-
/** Amount of PT/YT to merge (must have equal amounts of both) */
|
|
186
|
-
amountPy: bigint
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Withdraw YT from the tracked yield position back into the YT token account */
|
|
190
|
-
export interface CoreWithdrawYtInstruction extends CoreInstructionBase {
|
|
191
|
-
action: CoreAction.WITHDRAW_YT
|
|
192
|
-
amountYt: bigint
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Deposit YT from the YT token account back into the tracked yield position */
|
|
196
|
-
export interface CoreDepositYtInstruction extends CoreInstructionBase {
|
|
197
|
-
action: CoreAction.DEPOSIT_YT
|
|
198
|
-
amountYt: bigint
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Initialize yield position for a vault (owner = Squads vault) */
|
|
202
|
-
export interface CoreInitializeYieldPositionInstruction extends CoreInstructionBase {
|
|
203
|
-
action: CoreAction.INITIALIZE_YIELD_POSITION
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/** A single core instruction */
|
|
207
|
-
export type CoreInstruction =
|
|
208
|
-
| CoreStripInstruction
|
|
209
|
-
| CoreMergeInstruction
|
|
210
|
-
| CoreWithdrawYtInstruction
|
|
211
|
-
| CoreDepositYtInstruction
|
|
212
|
-
| CoreInitializeYieldPositionInstruction
|
|
213
|
-
|
|
214
|
-
// ============================================================================
|
|
215
|
-
// Standard Program Instruction Types (mint_sy / redeem_sy)
|
|
216
|
-
// ============================================================================
|
|
217
|
-
|
|
218
|
-
export enum SyAction {
|
|
219
|
-
MINT = "MINT_SY",
|
|
220
|
-
REDEEM = "REDEEM_SY",
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
interface SyInstructionBase {
|
|
224
|
-
action: SyAction
|
|
225
|
-
vault: PublicKey
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export interface SyMintInstruction extends SyInstructionBase {
|
|
229
|
-
action: SyAction.MINT
|
|
230
|
-
amountBase: bigint
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export interface SyRedeemInstruction extends SyInstructionBase {
|
|
234
|
-
action: SyAction.REDEEM
|
|
235
|
-
amountSy: bigint
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export type SyInstruction = SyMintInstruction | SyRedeemInstruction
|
|
239
|
-
|
|
240
|
-
// ============================================================================
|
|
241
|
-
// Titan Instruction Types
|
|
242
|
-
// ============================================================================
|
|
243
|
-
|
|
244
|
-
export enum TitanAction {
|
|
245
|
-
SWAP = "SWAP",
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** A pre-built Titan swap instruction to wrap in a sync transaction. */
|
|
249
|
-
export interface TitanSwapInstruction {
|
|
250
|
-
action: TitanAction.SWAP
|
|
251
|
-
/** The raw Titan SwapRouteV2 TransactionInstruction (from Titan's router API) */
|
|
252
|
-
instruction: TransactionInstruction
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// ============================================================================
|
|
256
|
-
// Loopscale Instruction Types
|
|
257
|
-
// ============================================================================
|
|
258
|
-
|
|
259
|
-
/** Actions for Loopscale interactions (loans = borrower side, strategies = lender side). */
|
|
260
|
-
export enum LoopscaleAction {
|
|
261
|
-
CREATE_LOAN = "LOOPSCALE_CREATE_LOAN",
|
|
262
|
-
DEPOSIT_COLLATERAL = "LOOPSCALE_DEPOSIT_COLLATERAL",
|
|
263
|
-
BORROW_PRINCIPAL = "LOOPSCALE_BORROW_PRINCIPAL",
|
|
264
|
-
REPAY_PRINCIPAL = "LOOPSCALE_REPAY_PRINCIPAL",
|
|
265
|
-
WITHDRAW_COLLATERAL = "LOOPSCALE_WITHDRAW_COLLATERAL",
|
|
266
|
-
CLOSE_LOAN = "LOOPSCALE_CLOSE_LOAN",
|
|
267
|
-
UPDATE_WEIGHT_MATRIX = "LOOPSCALE_UPDATE_WEIGHT_MATRIX",
|
|
268
|
-
CREATE_STRATEGY = "LOOPSCALE_CREATE_STRATEGY",
|
|
269
|
-
DEPOSIT_STRATEGY = "LOOPSCALE_DEPOSIT_STRATEGY",
|
|
270
|
-
WITHDRAW_STRATEGY = "LOOPSCALE_WITHDRAW_STRATEGY",
|
|
271
|
-
CLOSE_STRATEGY = "LOOPSCALE_CLOSE_STRATEGY",
|
|
272
|
-
UPDATE_STRATEGY = "LOOPSCALE_UPDATE_STRATEGY",
|
|
273
|
-
LOCK_LOAN = "LOOPSCALE_LOCK_LOAN",
|
|
274
|
-
UNLOCK_LOAN = "LOOPSCALE_UNLOCK_LOAN",
|
|
275
|
-
REFINANCE_LEDGER = "LOOPSCALE_REFINANCE_LEDGER",
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** A pre-built Loopscale instruction (loan or strategy) to wrap in a sync transaction. */
|
|
279
|
-
export interface LoopscaleInstruction {
|
|
280
|
-
action: LoopscaleAction
|
|
281
|
-
/** The raw Loopscale TransactionInstruction (from Loopscale API or local builder) */
|
|
282
|
-
instruction: TransactionInstruction
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ============================================================================
|
|
286
|
-
// CLMM Instruction Types
|
|
78
|
+
// Vault Instruction Types (re-exported from vault-instruction-types.ts)
|
|
287
79
|
// ============================================================================
|
|
288
80
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
81
|
+
export {
|
|
82
|
+
VaultAction,
|
|
83
|
+
KaminoVaultAction,
|
|
84
|
+
KaminoFarmAction,
|
|
85
|
+
OrderbookTradeDirection,
|
|
86
|
+
OrderbookAction,
|
|
87
|
+
CoreAction,
|
|
88
|
+
SyAction,
|
|
89
|
+
TitanAction,
|
|
90
|
+
LoopscaleAction,
|
|
91
|
+
ClmmAction,
|
|
92
|
+
} from "./vault-instruction-types"
|
|
93
|
+
|
|
94
|
+
export type {
|
|
95
|
+
MarketInstruction,
|
|
96
|
+
ReserveInstruction,
|
|
97
|
+
KaminoVaultInstruction,
|
|
98
|
+
KaminoVaultDepositInstruction,
|
|
99
|
+
KaminoVaultWithdrawInstruction,
|
|
100
|
+
KaminoFarmInstruction,
|
|
101
|
+
KaminoFarmInitializeUserInstruction,
|
|
102
|
+
KaminoFarmStakeInstruction,
|
|
103
|
+
KaminoFarmUnstakeInstruction,
|
|
104
|
+
KaminoFarmWithdrawUnstakedDepositsInstruction,
|
|
105
|
+
KaminoFarmHarvestRewardInstruction,
|
|
106
|
+
OrderbookOfferOption,
|
|
107
|
+
OrderbookInstructionMode,
|
|
108
|
+
OrderbookPostOfferInstruction,
|
|
109
|
+
OrderbookMarketOfferInstruction,
|
|
110
|
+
OrderbookRemoveOfferInstruction,
|
|
111
|
+
OrderbookWithdrawFundsInstruction,
|
|
112
|
+
OrderbookInstruction,
|
|
113
|
+
CoreStripInstruction,
|
|
114
|
+
CoreMergeInstruction,
|
|
115
|
+
CoreWithdrawYtInstruction,
|
|
116
|
+
CoreDepositYtInstruction,
|
|
117
|
+
CoreInitializeYieldPositionInstruction,
|
|
118
|
+
CoreInstruction,
|
|
119
|
+
SyMintInstruction,
|
|
120
|
+
SyRedeemInstruction,
|
|
121
|
+
SyInstruction,
|
|
122
|
+
TitanSwapInstruction,
|
|
123
|
+
LoopscaleInstruction,
|
|
124
|
+
ClmmDepositLiquidityInstruction,
|
|
125
|
+
ClmmAddLiquidityInstruction,
|
|
126
|
+
ClmmWithdrawLiquidityInstruction,
|
|
127
|
+
ClmmTradePtInstruction,
|
|
128
|
+
ClmmBuyPtInstruction,
|
|
129
|
+
ClmmSellPtInstruction,
|
|
130
|
+
ClmmBuyYtInstruction,
|
|
131
|
+
ClmmSellYtInstruction,
|
|
132
|
+
ClmmClaimFarmEmissionInstruction,
|
|
133
|
+
ClmmInstruction,
|
|
134
|
+
VaultInstruction,
|
|
135
|
+
KaminoReserves,
|
|
136
|
+
} from "./vault-instruction-types"
|
|
137
|
+
|
|
138
|
+
import type {
|
|
139
|
+
MarketInstruction,
|
|
140
|
+
ReserveInstruction,
|
|
141
|
+
KaminoVaultInstruction,
|
|
142
|
+
KaminoVaultDepositInstruction,
|
|
143
|
+
KaminoVaultWithdrawInstruction,
|
|
144
|
+
KaminoFarmInstruction,
|
|
145
|
+
KaminoFarmInitializeUserInstruction,
|
|
146
|
+
KaminoFarmStakeInstruction,
|
|
147
|
+
KaminoFarmUnstakeInstruction,
|
|
148
|
+
KaminoFarmWithdrawUnstakedDepositsInstruction,
|
|
149
|
+
KaminoFarmHarvestRewardInstruction,
|
|
150
|
+
OrderbookOfferOption,
|
|
151
|
+
OrderbookInstructionMode,
|
|
152
|
+
OrderbookInstruction,
|
|
153
|
+
OrderbookPostOfferInstruction,
|
|
154
|
+
OrderbookMarketOfferInstruction,
|
|
155
|
+
OrderbookRemoveOfferInstruction,
|
|
156
|
+
OrderbookWithdrawFundsInstruction,
|
|
157
|
+
CoreInstruction,
|
|
158
|
+
CoreStripInstruction,
|
|
159
|
+
CoreMergeInstruction,
|
|
160
|
+
CoreWithdrawYtInstruction,
|
|
161
|
+
CoreDepositYtInstruction,
|
|
162
|
+
CoreInitializeYieldPositionInstruction,
|
|
163
|
+
SyInstruction,
|
|
164
|
+
TitanSwapInstruction,
|
|
165
|
+
LoopscaleInstruction,
|
|
166
|
+
ClmmInstruction,
|
|
167
|
+
ClmmDepositLiquidityInstruction,
|
|
168
|
+
ClmmAddLiquidityInstruction,
|
|
169
|
+
ClmmWithdrawLiquidityInstruction,
|
|
170
|
+
ClmmTradePtInstruction,
|
|
171
|
+
ClmmBuyPtInstruction,
|
|
172
|
+
ClmmSellPtInstruction,
|
|
173
|
+
ClmmBuyYtInstruction,
|
|
174
|
+
ClmmSellYtInstruction,
|
|
175
|
+
ClmmClaimFarmEmissionInstruction,
|
|
176
|
+
VaultInstruction,
|
|
177
|
+
KaminoReserves,
|
|
178
|
+
SyMintInstruction,
|
|
179
|
+
SyRedeemInstruction,
|
|
180
|
+
} from "./vault-instruction-types"
|
|
341
181
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/** Low-level PT/SY swap on the CLMM. Prefer buyPt/sellPt for directional trades. */
|
|
356
|
-
export interface ClmmTradePtInstruction extends ClmmInstructionBase {
|
|
357
|
-
action: ClmmAction.TRADE_PT
|
|
358
|
-
/** Amount of the input token. */
|
|
359
|
-
traderAmount: bigint
|
|
360
|
-
/** Minimum output amount (slippage protection). */
|
|
361
|
-
outConstraint: bigint
|
|
362
|
-
/** Swap direction: SyToPt or PtToSy. */
|
|
363
|
-
swapDirection: SwapDirection
|
|
364
|
-
/** Optional price limit (ln implied APY). */
|
|
365
|
-
lnImpliedApyLimit?: number
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/** Buy PT with SY on the CLMM. */
|
|
369
|
-
export interface ClmmBuyPtInstruction extends ClmmInstructionBase {
|
|
370
|
-
action: ClmmAction.BUY_PT
|
|
371
|
-
/** Amount of SY to spend. */
|
|
372
|
-
amountSy: bigint
|
|
373
|
-
/** Minimum PT to receive (slippage protection). */
|
|
374
|
-
outConstraint: bigint
|
|
375
|
-
/** Optional price limit (ln implied APY). */
|
|
376
|
-
lnImpliedApyLimit?: number
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/** Sell PT for SY on the CLMM. */
|
|
380
|
-
export interface ClmmSellPtInstruction extends ClmmInstructionBase {
|
|
381
|
-
action: ClmmAction.SELL_PT
|
|
382
|
-
/** Amount of PT to sell. */
|
|
383
|
-
amountPt: bigint
|
|
384
|
-
/** Minimum SY to receive (slippage protection). */
|
|
385
|
-
outConstraint: bigint
|
|
386
|
-
/** Optional price limit (ln implied APY). */
|
|
387
|
-
lnImpliedApyLimit?: number
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** Buy YT with SY on the CLMM. */
|
|
391
|
-
export interface ClmmBuyYtInstruction extends ClmmInstructionBase {
|
|
392
|
-
action: ClmmAction.BUY_YT
|
|
393
|
-
/** Minimum amount of YT to receive. */
|
|
394
|
-
ytOut: bigint
|
|
395
|
-
/** Maximum amount of SY to spend. */
|
|
396
|
-
maxSyIn: bigint
|
|
397
|
-
/** Optional price limit (ln implied APY). */
|
|
398
|
-
lnImpliedApyLimit?: number
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/** Sell YT for SY on the CLMM. */
|
|
402
|
-
export interface ClmmSellYtInstruction extends ClmmInstructionBase {
|
|
403
|
-
action: ClmmAction.SELL_YT
|
|
404
|
-
/** Amount of YT to sell. */
|
|
405
|
-
ytIn: bigint
|
|
406
|
-
/** Minimum SY to receive (slippage protection). */
|
|
407
|
-
minSyOut: bigint
|
|
408
|
-
/** Optional price limit (ln implied APY). */
|
|
409
|
-
lnImpliedApyLimit?: number
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/** Claim farm emissions from an LP position. */
|
|
413
|
-
export interface ClmmClaimFarmEmissionInstruction extends ClmmInstructionBase {
|
|
414
|
-
action: ClmmAction.CLAIM_FARM_EMISSION
|
|
415
|
-
/** The LpPosition account to claim from. */
|
|
416
|
-
lpPosition: PublicKey
|
|
417
|
-
/** Index of the farm to claim from. */
|
|
418
|
-
farmIndex: number
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/** A single CLMM instruction. */
|
|
422
|
-
export type ClmmInstruction =
|
|
423
|
-
| ClmmDepositLiquidityInstruction
|
|
424
|
-
| ClmmAddLiquidityInstruction
|
|
425
|
-
| ClmmWithdrawLiquidityInstruction
|
|
426
|
-
| ClmmTradePtInstruction
|
|
427
|
-
| ClmmBuyPtInstruction
|
|
428
|
-
| ClmmSellPtInstruction
|
|
429
|
-
| ClmmBuyYtInstruction
|
|
430
|
-
| ClmmSellYtInstruction
|
|
431
|
-
| ClmmClaimFarmEmissionInstruction
|
|
432
|
-
|
|
433
|
-
/** A single vault instruction — pass an array of these to `createVaultSyncTransaction`. */
|
|
434
|
-
export type VaultInstruction =
|
|
435
|
-
| MarketInstruction
|
|
436
|
-
| ReserveInstruction
|
|
437
|
-
| OrderbookInstruction
|
|
438
|
-
| CoreInstruction
|
|
439
|
-
| SyInstruction
|
|
440
|
-
| TitanSwapInstruction
|
|
441
|
-
| ClmmInstruction
|
|
442
|
-
| LoopscaleInstruction
|
|
182
|
+
import {
|
|
183
|
+
VaultAction,
|
|
184
|
+
KaminoVaultAction,
|
|
185
|
+
KaminoFarmAction,
|
|
186
|
+
OrderbookAction,
|
|
187
|
+
CoreAction,
|
|
188
|
+
SyAction,
|
|
189
|
+
TitanAction,
|
|
190
|
+
LoopscaleAction,
|
|
191
|
+
ClmmAction,
|
|
192
|
+
OrderbookTradeDirection,
|
|
193
|
+
} from "./vault-instruction-types"
|
|
443
194
|
|
|
444
195
|
// ============================================================================
|
|
445
196
|
// Kamino Action Builders
|
|
@@ -483,7 +234,8 @@ export const kaminoAction = {
|
|
|
483
234
|
|
|
484
235
|
/**
|
|
485
236
|
* Initialize a Kamino obligation for a market.
|
|
486
|
-
*
|
|
237
|
+
* When `autoManagePositions` is enabled, the SDK also registers the new
|
|
238
|
+
* obligation as a tracked strategy position after the init succeeds.
|
|
487
239
|
* @param market - The Kamino lending market
|
|
488
240
|
*/
|
|
489
241
|
initObligation(market: KaminoMarket): MarketInstruction {
|
|
@@ -532,6 +284,92 @@ export const kaminoAction = {
|
|
|
532
284
|
},
|
|
533
285
|
}
|
|
534
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Builder for direct Kamino Vault action descriptors.
|
|
289
|
+
*
|
|
290
|
+
* These actions move vault-owned tokens into a Kamino Vault and, when
|
|
291
|
+
* `autoManagePositions` is enabled, automatically track the resulting share
|
|
292
|
+
* token account as a managed strategy position.
|
|
293
|
+
*/
|
|
294
|
+
export const kaminoVaultAction = {
|
|
295
|
+
/**
|
|
296
|
+
* Deposit the vault-owned token account into a Kamino Vault.
|
|
297
|
+
* @param params.vault - Kamino Vault address
|
|
298
|
+
* @param params.amount - Amount of deposit tokens to move into the vault
|
|
299
|
+
*/
|
|
300
|
+
deposit(params: {
|
|
301
|
+
vault: PublicKey
|
|
302
|
+
amount: BN
|
|
303
|
+
}): KaminoVaultDepositInstruction {
|
|
304
|
+
return { action: KaminoVaultAction.DEPOSIT, ...params }
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Withdraw Kamino Vault shares back into the vault-owned token account.
|
|
309
|
+
* When the Kamino Vault currently allocates across multiple reserves,
|
|
310
|
+
* specify the reserve to disinvest from.
|
|
311
|
+
*/
|
|
312
|
+
withdraw(params: {
|
|
313
|
+
vault: PublicKey
|
|
314
|
+
sharesAmount: BN
|
|
315
|
+
reserve?: PublicKey
|
|
316
|
+
}): KaminoVaultWithdrawInstruction {
|
|
317
|
+
return { action: KaminoVaultAction.WITHDRAW, ...params }
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Builder for direct Kamino Farm action descriptors.
|
|
323
|
+
*
|
|
324
|
+
* These actions operate on a farm `user_state` derived from the managed vault
|
|
325
|
+
* owner by default. Pass `delegatee` when targeting a delegated farm user
|
|
326
|
+
* state, such as a Kamino obligation-owned farm entry.
|
|
327
|
+
*/
|
|
328
|
+
export const kaminoFarmAction = {
|
|
329
|
+
/** Initialize the farm `user_state` PDA. */
|
|
330
|
+
initializeUser(params: {
|
|
331
|
+
farmState: PublicKey
|
|
332
|
+
delegatee?: PublicKey
|
|
333
|
+
}): KaminoFarmInitializeUserInstruction {
|
|
334
|
+
return { action: KaminoFarmAction.INITIALIZE_USER, ...params }
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/** Stake the managed vault's token ATA into the farm. */
|
|
338
|
+
stake(params: {
|
|
339
|
+
farmState: PublicKey
|
|
340
|
+
amount: BN | "ALL"
|
|
341
|
+
delegatee?: PublicKey
|
|
342
|
+
}): KaminoFarmStakeInstruction {
|
|
343
|
+
return { action: KaminoFarmAction.STAKE, ...params }
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
/** Unstake a scaled share amount from the farm. */
|
|
347
|
+
unstake(params: {
|
|
348
|
+
farmState: PublicKey
|
|
349
|
+
stakeSharesScaled: BN
|
|
350
|
+
delegatee?: PublicKey
|
|
351
|
+
}): KaminoFarmUnstakeInstruction {
|
|
352
|
+
return { action: KaminoFarmAction.UNSTAKE, ...params }
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
/** Withdraw matured unstaked deposits back into the managed vault ATA. */
|
|
356
|
+
withdrawUnstakedDeposits(params: {
|
|
357
|
+
farmState: PublicKey
|
|
358
|
+
delegatee?: PublicKey
|
|
359
|
+
}): KaminoFarmWithdrawUnstakedDepositsInstruction {
|
|
360
|
+
return { action: KaminoFarmAction.WITHDRAW_UNSTAKED_DEPOSITS, ...params }
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
/** Harvest a specific reward index into a managed vault reward ATA. */
|
|
364
|
+
harvestReward(params: {
|
|
365
|
+
farmState: PublicKey
|
|
366
|
+
rewardIndex: number
|
|
367
|
+
delegatee?: PublicKey
|
|
368
|
+
}): KaminoFarmHarvestRewardInstruction {
|
|
369
|
+
return { action: KaminoFarmAction.HARVEST_REWARD, ...params }
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
|
|
535
373
|
// ============================================================================
|
|
536
374
|
// Sync Transaction Builder
|
|
537
375
|
// ============================================================================
|
|
@@ -610,12 +448,14 @@ export async function createVaultSyncTransaction({
|
|
|
610
448
|
preHookAccounts,
|
|
611
449
|
postHookAccounts,
|
|
612
450
|
squadsProgram = SQUADS_PROGRAM_ID,
|
|
451
|
+
autoManagePositions = false,
|
|
452
|
+
setupContext,
|
|
613
453
|
}: {
|
|
614
454
|
instructions: VaultInstruction[]
|
|
615
455
|
owner: PublicKey
|
|
616
456
|
connection: Connection
|
|
617
457
|
policyPda?: PublicKey
|
|
618
|
-
vaultPda
|
|
458
|
+
vaultPda?: PublicKey
|
|
619
459
|
signer: PublicKey
|
|
620
460
|
accountIndex?: number
|
|
621
461
|
constraintIndices?: number[]
|
|
@@ -625,7 +465,27 @@ export async function createVaultSyncTransaction({
|
|
|
625
465
|
preHookAccounts?: PublicKey[] | AccountMeta[]
|
|
626
466
|
postHookAccounts?: PublicKey[] | AccountMeta[]
|
|
627
467
|
squadsProgram?: PublicKey
|
|
468
|
+
/** Automatically manage new Kamino/CLMM/yield position tracking for manager flows. Defaults to `false` for this low-level helper. */
|
|
469
|
+
autoManagePositions?: boolean
|
|
470
|
+
/** Optional shared setup context — when provided, setup state (tracked accounts, positions) is shared across calls, preventing duplicate setup instructions. */
|
|
471
|
+
setupContext?: StrategySetupContext
|
|
628
472
|
}): Promise<VaultSyncTransactionResult> {
|
|
473
|
+
vaultPda ??= owner
|
|
474
|
+
const resolvedSetupContext = setupContext ?? createStrategySetupContext({
|
|
475
|
+
connection,
|
|
476
|
+
env: LOCAL_ENV,
|
|
477
|
+
owner,
|
|
478
|
+
signer,
|
|
479
|
+
vaultAddress,
|
|
480
|
+
policyPda,
|
|
481
|
+
vaultPda,
|
|
482
|
+
accountIndex,
|
|
483
|
+
squadsProgram,
|
|
484
|
+
leadingAccounts,
|
|
485
|
+
preHookAccounts,
|
|
486
|
+
postHookAccounts,
|
|
487
|
+
autoManagePositions,
|
|
488
|
+
})
|
|
629
489
|
const { setupInstructions, syncInstructions, preInstructions, postInstructions, signers, addressLookupTableAddresses } = await buildVaultInstructions(
|
|
630
490
|
instructions,
|
|
631
491
|
owner,
|
|
@@ -639,7 +499,12 @@ export async function createVaultSyncTransaction({
|
|
|
639
499
|
preHookAccounts,
|
|
640
500
|
postHookAccounts,
|
|
641
501
|
squadsProgram,
|
|
502
|
+
autoManagePositions,
|
|
503
|
+
resolvedSetupContext,
|
|
642
504
|
)
|
|
505
|
+
const setupStatePriceRefreshInstructions = setupContext
|
|
506
|
+
? []
|
|
507
|
+
: await buildSetupStatePriceRefreshInstructions(resolvedSetupContext)
|
|
643
508
|
|
|
644
509
|
let resolvedPolicyPda = policyPda
|
|
645
510
|
let resolvedConstraintIndices = constraintIndices
|
|
@@ -683,7 +548,14 @@ export async function createVaultSyncTransaction({
|
|
|
683
548
|
postHookAccounts: resolvedPostHookAccounts,
|
|
684
549
|
})
|
|
685
550
|
|
|
686
|
-
return {
|
|
551
|
+
return {
|
|
552
|
+
setupInstructions,
|
|
553
|
+
preInstructions: [...setupStatePriceRefreshInstructions, ...preInstructions],
|
|
554
|
+
instruction,
|
|
555
|
+
postInstructions,
|
|
556
|
+
signers,
|
|
557
|
+
addressLookupTableAddresses,
|
|
558
|
+
}
|
|
687
559
|
}
|
|
688
560
|
|
|
689
561
|
// ============================================================================
|
|
@@ -704,6 +576,14 @@ interface InstructionBuckets {
|
|
|
704
576
|
addressLookupTableAddresses: PublicKey[]
|
|
705
577
|
}
|
|
706
578
|
|
|
579
|
+
function isKaminoVaultInstruction(ix: VaultInstruction): ix is KaminoVaultInstruction {
|
|
580
|
+
return Object.values(KaminoVaultAction).includes(ix.action as KaminoVaultAction)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function isKaminoFarmInstruction(ix: VaultInstruction): ix is KaminoFarmInstruction {
|
|
584
|
+
return Object.values(KaminoFarmAction).includes(ix.action as KaminoFarmAction)
|
|
585
|
+
}
|
|
586
|
+
|
|
707
587
|
function isOrderbookInstruction(ix: VaultInstruction): ix is OrderbookInstruction {
|
|
708
588
|
return Object.values(OrderbookAction).includes(ix.action as OrderbookAction)
|
|
709
589
|
}
|
|
@@ -740,24 +620,37 @@ const TITAN_OUTPUT_TOKEN_PROGRAM_ACCOUNT_INDEX = 7
|
|
|
740
620
|
const YIELD_POSITION_BASE_SIZE = 124
|
|
741
621
|
const YIELD_POSITION_TRACKER_SIZE = 40
|
|
742
622
|
|
|
743
|
-
type
|
|
744
|
-
|
|
745
|
-
|
|
623
|
+
type TrackedKaminoObligationState = {
|
|
624
|
+
quotePriceId: ClientPriceId
|
|
625
|
+
quoteInputMint: PublicKey
|
|
626
|
+
mappedReserves: Set<string>
|
|
746
627
|
}
|
|
747
628
|
|
|
748
629
|
type StrategySetupState = {
|
|
749
630
|
strategyVault: StrategyVault
|
|
750
631
|
prices: ExponentPrices
|
|
632
|
+
requiredPriceIds: Set<number>
|
|
751
633
|
nextStrategyPositionIndex: number
|
|
752
634
|
trackedOrderbooks: Set<string>
|
|
753
635
|
trackedYieldVaults: Set<string>
|
|
636
|
+
trackedKaminoObligations: Map<string, TrackedKaminoObligationState>
|
|
637
|
+
trackedKaminoFarms: Set<string>
|
|
638
|
+
trackedClmmPositions: Set<string>
|
|
754
639
|
tokenEntryAccountByMint: Map<string, string>
|
|
755
640
|
tokenPositionIndexByMint: Map<string, number>
|
|
756
641
|
trackedTokenAccounts: Set<string>
|
|
642
|
+
baseAumAccounts: AccountMeta[]
|
|
643
|
+
plannedAumAccounts: AccountMeta[]
|
|
757
644
|
existingAccounts: Map<string, boolean>
|
|
758
645
|
}
|
|
759
646
|
|
|
760
|
-
type
|
|
647
|
+
type ResolvedHookAccounts = {
|
|
648
|
+
leadingAccounts: PublicKey[] | AccountMeta[]
|
|
649
|
+
preHookAccounts: PublicKey[] | AccountMeta[]
|
|
650
|
+
postHookAccounts: PublicKey[] | AccountMeta[]
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export type StrategySetupContext = {
|
|
761
654
|
connection: Connection
|
|
762
655
|
env: Environment
|
|
763
656
|
owner: PublicKey
|
|
@@ -770,10 +663,14 @@ type StrategySetupContext = {
|
|
|
770
663
|
leadingAccounts?: PublicKey[] | AccountMeta[]
|
|
771
664
|
preHookAccounts?: PublicKey[] | AccountMeta[]
|
|
772
665
|
postHookAccounts?: PublicKey[] | AccountMeta[]
|
|
666
|
+
autoManagePositions: boolean
|
|
667
|
+
pricesAccount?: ExponentPrices
|
|
773
668
|
statePromise?: Promise<StrategySetupState | null>
|
|
669
|
+
/** Cached hook resolution promise — avoids redundant RPC calls when wrapping multiple setup instructions. */
|
|
670
|
+
resolvedHooksPromise?: Promise<ResolvedHookAccounts>
|
|
774
671
|
}
|
|
775
672
|
|
|
776
|
-
function createStrategySetupContext({
|
|
673
|
+
export function createStrategySetupContext({
|
|
777
674
|
connection,
|
|
778
675
|
env,
|
|
779
676
|
owner,
|
|
@@ -786,6 +683,8 @@ function createStrategySetupContext({
|
|
|
786
683
|
leadingAccounts,
|
|
787
684
|
preHookAccounts,
|
|
788
685
|
postHookAccounts,
|
|
686
|
+
autoManagePositions = true,
|
|
687
|
+
pricesAccount,
|
|
789
688
|
}: {
|
|
790
689
|
connection: Connection
|
|
791
690
|
env: Environment
|
|
@@ -799,6 +698,8 @@ function createStrategySetupContext({
|
|
|
799
698
|
leadingAccounts?: PublicKey[] | AccountMeta[]
|
|
800
699
|
preHookAccounts?: PublicKey[] | AccountMeta[]
|
|
801
700
|
postHookAccounts?: PublicKey[] | AccountMeta[]
|
|
701
|
+
autoManagePositions?: boolean
|
|
702
|
+
pricesAccount?: ExponentPrices
|
|
802
703
|
}): StrategySetupContext {
|
|
803
704
|
return {
|
|
804
705
|
connection,
|
|
@@ -813,6 +714,17 @@ function createStrategySetupContext({
|
|
|
813
714
|
leadingAccounts,
|
|
814
715
|
preHookAccounts,
|
|
815
716
|
postHookAccounts,
|
|
717
|
+
autoManagePositions,
|
|
718
|
+
pricesAccount,
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function trackRequiredPriceIds(requiredPriceIds: Set<number>, priceIdValue: unknown) {
|
|
723
|
+
for (const id of extractPriceIds(priceIdValue)) {
|
|
724
|
+
const numericId = Number(id)
|
|
725
|
+
if (numericId !== 0) {
|
|
726
|
+
requiredPriceIds.add(numericId)
|
|
727
|
+
}
|
|
816
728
|
}
|
|
817
729
|
}
|
|
818
730
|
|
|
@@ -828,10 +740,13 @@ async function loadStrategySetupState(context: StrategySetupContext): Promise<St
|
|
|
828
740
|
connection: context.connection,
|
|
829
741
|
address: context.vaultAddress!,
|
|
830
742
|
})
|
|
831
|
-
const prices = await strategyVault.fetcher.fetchExponentPrices()
|
|
743
|
+
const prices = context.pricesAccount ?? await strategyVault.fetcher.fetchExponentPrices()
|
|
832
744
|
|
|
833
745
|
const trackedOrderbooks = new Set<string>()
|
|
834
746
|
const trackedYieldVaults = new Set<string>()
|
|
747
|
+
const trackedKaminoObligations = new Map<string, TrackedKaminoObligationState>()
|
|
748
|
+
const trackedKaminoFarms = new Set<string>()
|
|
749
|
+
const trackedClmmPositions = new Set<string>()
|
|
835
750
|
const tokenEntryAccountByMint = new Map<string, string>()
|
|
836
751
|
const tokenPositionIndexByMint = new Map<string, number>()
|
|
837
752
|
const trackedTokenAccounts = new Set<string>()
|
|
@@ -850,6 +765,27 @@ async function loadStrategySetupState(context: StrategySetupContext): Promise<St
|
|
|
850
765
|
trackedYieldVaults.add(position.yieldPosition[0].vault.toBase58())
|
|
851
766
|
continue
|
|
852
767
|
}
|
|
768
|
+
const kaminoEntry = getTrackedKaminoObligationFromPosition(position)
|
|
769
|
+
if (kaminoEntry) {
|
|
770
|
+
trackedKaminoObligations.set(kaminoEntry.obligation.toBase58(), {
|
|
771
|
+
quotePriceId: kaminoEntry.quotePriceId,
|
|
772
|
+
quoteInputMint: getPriceInputMintFromPriceId(prices, kaminoEntry.quotePriceId),
|
|
773
|
+
mappedReserves: new Set(
|
|
774
|
+
(kaminoEntry.reservePriceMappings ?? []).map((mapping) => mapping.reserve.toBase58()),
|
|
775
|
+
),
|
|
776
|
+
})
|
|
777
|
+
continue
|
|
778
|
+
}
|
|
779
|
+
const kaminoFarmEntry = getTrackedKaminoFarmFromPosition(position)
|
|
780
|
+
if (kaminoFarmEntry) {
|
|
781
|
+
trackedKaminoFarms.add(kaminoFarmKey(kaminoFarmEntry.farmState, kaminoFarmEntry.userState))
|
|
782
|
+
continue
|
|
783
|
+
}
|
|
784
|
+
const clmmEntry = getTrackedClmmPositionFromPosition(position)
|
|
785
|
+
if (clmmEntry) {
|
|
786
|
+
trackedClmmPositions.add(clmmEntry.lpPosition.toBase58())
|
|
787
|
+
continue
|
|
788
|
+
}
|
|
853
789
|
if ("tokenAccount" in position && position.tokenAccount?.[0]) {
|
|
854
790
|
const entry = position.tokenAccount[0]
|
|
855
791
|
tokenPositionIndexByMint.set(entry.tokenMint.toBase58(), index)
|
|
@@ -862,12 +798,21 @@ async function loadStrategySetupState(context: StrategySetupContext): Promise<St
|
|
|
862
798
|
return {
|
|
863
799
|
strategyVault,
|
|
864
800
|
prices,
|
|
801
|
+
requiredPriceIds: collectTrackedStrategyVaultPriceIds({
|
|
802
|
+
tokenEntries: strategyVault.state.tokenEntries,
|
|
803
|
+
strategyPositions: strategyVault.state.strategyPositions as Array<Record<string, unknown>>,
|
|
804
|
+
}),
|
|
865
805
|
nextStrategyPositionIndex: strategyVault.state.strategyPositions.length,
|
|
866
806
|
trackedOrderbooks,
|
|
867
807
|
trackedYieldVaults,
|
|
808
|
+
trackedKaminoObligations,
|
|
809
|
+
trackedKaminoFarms,
|
|
810
|
+
trackedClmmPositions,
|
|
868
811
|
tokenEntryAccountByMint,
|
|
869
812
|
tokenPositionIndexByMint,
|
|
870
813
|
trackedTokenAccounts,
|
|
814
|
+
baseAumAccounts: mutableStrategyVault(strategyVault).aumRemainingAccounts(),
|
|
815
|
+
plannedAumAccounts: [],
|
|
871
816
|
existingAccounts: new Map<string, boolean>(),
|
|
872
817
|
}
|
|
873
818
|
})()
|
|
@@ -876,32 +821,176 @@ async function loadStrategySetupState(context: StrategySetupContext): Promise<St
|
|
|
876
821
|
return context.statePromise
|
|
877
822
|
}
|
|
878
823
|
|
|
879
|
-
function
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
824
|
+
export async function buildSetupStatePriceRefreshInstructions(
|
|
825
|
+
setupContext: StrategySetupContext,
|
|
826
|
+
): Promise<TransactionInstruction[]> {
|
|
827
|
+
const state = await loadStrategySetupState(setupContext)
|
|
828
|
+
if (!state) {
|
|
829
|
+
return []
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const refreshInstructions: TransactionInstruction[] = []
|
|
833
|
+
const reserveAccounts = new Map<string, { account: Reserve }>()
|
|
834
|
+
for (const priceId of state.requiredPriceIds) {
|
|
835
|
+
const priceEntry = state.prices.prices[priceId]
|
|
836
|
+
if (!priceEntry || !isKaminoVaultPriceType(priceEntry.priceType)) {
|
|
887
837
|
continue
|
|
888
838
|
}
|
|
889
839
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
840
|
+
for (const reserveAddress of priceEntry.interfaceAccounts.slice(1)) {
|
|
841
|
+
const reserveKey = reserveAddress.toBase58()
|
|
842
|
+
if (reserveAccounts.has(reserveKey)) {
|
|
843
|
+
continue
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const reserveAccount = await Reserve.fetch(setupContext.connection, reserveAddress)
|
|
847
|
+
if (!reserveAccount) {
|
|
848
|
+
throw new Error(`Missing Kamino reserve account ${reserveKey} required to refresh Kamino vault prices`)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
reserveAccounts.set(reserveKey, { account: reserveAccount })
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (reserveAccounts.size > 0) {
|
|
856
|
+
const reserves = [...reserveAccounts.values()]
|
|
857
|
+
const defaultKey = PublicKey.default
|
|
858
|
+
const oracleOrSentinel = (key: PublicKey) =>
|
|
859
|
+
key.equals(defaultKey) ? KAMINO_LENDING_PROGRAM_ID : key
|
|
860
|
+
|
|
861
|
+
refreshInstructions.push(...await buildScopeRefreshInstructions(setupContext.connection, reserves))
|
|
862
|
+
|
|
863
|
+
for (const [reserveKey, { account }] of reserveAccounts.entries()) {
|
|
864
|
+
const tokenInfo = account.config.tokenInfo
|
|
865
|
+
refreshInstructions.push(
|
|
866
|
+
refreshReserve({
|
|
867
|
+
reserve: new PublicKey(reserveKey),
|
|
868
|
+
lendingMarket: account.lendingMarket,
|
|
869
|
+
pythOracle: oracleOrSentinel(tokenInfo.pythConfiguration.price),
|
|
870
|
+
switchboardPriceOracle: oracleOrSentinel(tokenInfo.switchboardConfiguration.priceAggregator),
|
|
871
|
+
switchboardTwapOracle: oracleOrSentinel(tokenInfo.switchboardConfiguration.twapAggregator),
|
|
872
|
+
scopePrices: oracleOrSentinel(tokenInfo.scopeConfiguration.priceFeed),
|
|
873
|
+
}),
|
|
874
|
+
)
|
|
893
875
|
}
|
|
894
876
|
}
|
|
895
877
|
|
|
896
|
-
|
|
878
|
+
const updatePriceInstructions = await state.strategyVault.ixsUpdateStrategyVaultPrices(state.prices, {
|
|
879
|
+
manager: setupContext.signer,
|
|
880
|
+
priceIds: [...state.requiredPriceIds].sort((a, b) => a - b),
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
return [...refreshInstructions, ...updatePriceInstructions]
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function isAutoManagePositionsEnabled(setupContext?: StrategySetupContext): boolean {
|
|
887
|
+
return setupContext?.autoManagePositions ?? true
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function unwrapTupleLikeValue(value: unknown): unknown {
|
|
891
|
+
if (Array.isArray(value)) {
|
|
892
|
+
return value[0]
|
|
893
|
+
}
|
|
894
|
+
if (value && typeof value === "object" && "0" in value) {
|
|
895
|
+
return (value as { 0?: unknown })[0]
|
|
896
|
+
}
|
|
897
|
+
return value ?? undefined
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function getTrackedKaminoObligationFromPosition(position: unknown): {
|
|
901
|
+
obligation: PublicKey
|
|
902
|
+
quotePriceId: ClientPriceId
|
|
903
|
+
reservePriceMappings: Array<{ reserve: PublicKey; reservePriceId: ClientPriceId }>
|
|
904
|
+
} | null {
|
|
905
|
+
if (!position || typeof position !== "object" || !("obligation" in position)) {
|
|
906
|
+
return null
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const obligationContainer = (position as { obligation?: unknown }).obligation
|
|
910
|
+
const obligationValue = unwrapTupleLikeValue(obligationContainer)
|
|
911
|
+
const kaminoContainer = (
|
|
912
|
+
obligationValue
|
|
913
|
+
&& typeof obligationValue === "object"
|
|
914
|
+
&& "kaminoObligation" in (obligationValue as Record<string, unknown>)
|
|
915
|
+
)
|
|
916
|
+
? (obligationValue as { kaminoObligation?: unknown }).kaminoObligation
|
|
917
|
+
: obligationValue
|
|
918
|
+
const kaminoEntry = unwrapTupleLikeValue(kaminoContainer)
|
|
919
|
+
|
|
920
|
+
if (
|
|
921
|
+
!kaminoEntry
|
|
922
|
+
|| typeof kaminoEntry !== "object"
|
|
923
|
+
|| !("obligation" in kaminoEntry)
|
|
924
|
+
|| !((kaminoEntry as { obligation?: unknown }).obligation instanceof PublicKey)
|
|
925
|
+
) {
|
|
926
|
+
return null
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const typedKaminoEntry = kaminoEntry as {
|
|
930
|
+
obligation: PublicKey
|
|
931
|
+
quotePriceId: ClientPriceId
|
|
932
|
+
reservePriceMappings?: Array<{ reserve: PublicKey; reservePriceId: ClientPriceId }>
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
return {
|
|
936
|
+
obligation: typedKaminoEntry.obligation,
|
|
937
|
+
quotePriceId: typedKaminoEntry.quotePriceId,
|
|
938
|
+
reservePriceMappings: typedKaminoEntry.reservePriceMappings ?? [],
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function getTrackedKaminoFarmFromPosition(position: unknown): {
|
|
943
|
+
farmState: PublicKey
|
|
944
|
+
userState: PublicKey
|
|
945
|
+
} | null {
|
|
946
|
+
if (!position || typeof position !== "object" || !("kaminoFarm" in position)) {
|
|
947
|
+
return null
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const kaminoFarmContainer = (position as { kaminoFarm?: unknown }).kaminoFarm
|
|
951
|
+
const kaminoFarmEntry = unwrapTupleLikeValue(kaminoFarmContainer)
|
|
952
|
+
if (
|
|
953
|
+
!kaminoFarmEntry
|
|
954
|
+
|| typeof kaminoFarmEntry !== "object"
|
|
955
|
+
|| !((kaminoFarmEntry as { farmState?: unknown }).farmState instanceof PublicKey)
|
|
956
|
+
|| !((kaminoFarmEntry as { userState?: unknown }).userState instanceof PublicKey)
|
|
957
|
+
) {
|
|
958
|
+
return null
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return {
|
|
962
|
+
farmState: (kaminoFarmEntry as { farmState: PublicKey }).farmState,
|
|
963
|
+
userState: (kaminoFarmEntry as { userState: PublicKey }).userState,
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function kaminoFarmKey(farmState: PublicKey, userState: PublicKey): string {
|
|
968
|
+
return `${farmState.toBase58()}:${userState.toBase58()}`
|
|
897
969
|
}
|
|
898
970
|
|
|
899
|
-
function
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
971
|
+
function getTrackedClmmPositionFromPosition(position: unknown): {
|
|
972
|
+
lpPosition: PublicKey
|
|
973
|
+
market: PublicKey
|
|
974
|
+
} | null {
|
|
975
|
+
if (!position || typeof position !== "object" || !("clmmPosition" in position)) {
|
|
976
|
+
return null
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const clmmContainer = (position as { clmmPosition?: unknown }).clmmPosition
|
|
980
|
+
const clmmEntry = unwrapTupleLikeValue(clmmContainer)
|
|
981
|
+
if (
|
|
982
|
+
!clmmEntry
|
|
983
|
+
|| typeof clmmEntry !== "object"
|
|
984
|
+
|| !((clmmEntry as { lpPosition?: unknown }).lpPosition instanceof PublicKey)
|
|
985
|
+
|| !((clmmEntry as { market?: unknown }).market instanceof PublicKey)
|
|
986
|
+
) {
|
|
987
|
+
return null
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
return {
|
|
991
|
+
lpPosition: (clmmEntry as { lpPosition: PublicKey }).lpPosition,
|
|
992
|
+
market: (clmmEntry as { market: PublicKey }).market,
|
|
903
993
|
}
|
|
904
|
-
return new Decimal(bigintU256ToString(raw.map((value) => BigInt(value.toString()))))
|
|
905
994
|
}
|
|
906
995
|
|
|
907
996
|
async function accountExists(
|
|
@@ -920,42 +1009,279 @@ async function accountExists(
|
|
|
920
1009
|
return exists
|
|
921
1010
|
}
|
|
922
1011
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
owner,
|
|
928
|
-
mint,
|
|
929
|
-
tokenProgram,
|
|
930
|
-
tokenAccount,
|
|
931
|
-
}: {
|
|
932
|
-
state: StrategySetupState
|
|
933
|
-
connection: Connection
|
|
934
|
-
payer: PublicKey
|
|
935
|
-
owner: PublicKey
|
|
936
|
-
mint: PublicKey
|
|
937
|
-
tokenProgram: PublicKey
|
|
938
|
-
tokenAccount: PublicKey
|
|
939
|
-
}): Promise<TransactionInstruction | null> {
|
|
940
|
-
if (await accountExists(state, connection, tokenAccount)) {
|
|
941
|
-
return null
|
|
942
|
-
}
|
|
1012
|
+
type MutableStrategyVault = {
|
|
1013
|
+
aumRemainingAccounts(): AccountMeta[]
|
|
1014
|
+
clmmTicksMap: Map<string, PublicKey>
|
|
1015
|
+
}
|
|
943
1016
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
}
|
|
1017
|
+
function mutableStrategyVault(value: StrategySetupState | StrategyVault): MutableStrategyVault {
|
|
1018
|
+
return ("strategyVault" in value ? value.strategyVault : value) as unknown as MutableStrategyVault
|
|
1019
|
+
}
|
|
948
1020
|
|
|
949
|
-
|
|
950
|
-
|
|
1021
|
+
function buildTrackedAumRemainingAccounts(
|
|
1022
|
+
state: StrategySetupState,
|
|
1023
|
+
extraAccounts: AccountMeta[] = [],
|
|
1024
|
+
): AccountMeta[] {
|
|
1025
|
+
return uniqueRemainingAccounts([
|
|
1026
|
+
...state.baseAumAccounts,
|
|
1027
|
+
...state.plannedAumAccounts,
|
|
1028
|
+
...extraAccounts,
|
|
1029
|
+
])
|
|
951
1030
|
}
|
|
952
1031
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
setupContext: StrategySetupContext,
|
|
1032
|
+
function recordPlannedOrderbookEntry(
|
|
1033
|
+
state: StrategySetupState,
|
|
1034
|
+
params: { orderbook: PublicKey; mint: PublicKey; priceIdPt: ClientPriceId; baseMint: PublicKey },
|
|
957
1035
|
) {
|
|
958
|
-
|
|
1036
|
+
state.trackedOrderbooks.add(params.orderbook.toBase58())
|
|
1037
|
+
state.plannedAumAccounts.push({ pubkey: params.orderbook, isSigner: false, isWritable: false })
|
|
1038
|
+
;(state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>).push({
|
|
1039
|
+
orderbook: [{
|
|
1040
|
+
orderbook: params.orderbook,
|
|
1041
|
+
mint: params.mint,
|
|
1042
|
+
offerIdxVec: [],
|
|
1043
|
+
priceIdPt: params.priceIdPt,
|
|
1044
|
+
baseMint: params.baseMint,
|
|
1045
|
+
}],
|
|
1046
|
+
})
|
|
1047
|
+
state.nextStrategyPositionIndex += 1
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function recordPlannedYieldPosition(
|
|
1051
|
+
state: StrategySetupState,
|
|
1052
|
+
params: { yieldPosition: PublicKey; vault: PublicKey; priceIdPt: ClientPriceId },
|
|
1053
|
+
) {
|
|
1054
|
+
state.trackedYieldVaults.add(params.vault.toBase58())
|
|
1055
|
+
state.existingAccounts.set(params.yieldPosition.toBase58(), true)
|
|
1056
|
+
;(state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>).push({
|
|
1057
|
+
yieldPosition: [{
|
|
1058
|
+
yieldPosition: params.yieldPosition,
|
|
1059
|
+
vault: params.vault,
|
|
1060
|
+
priceIdPt: params.priceIdPt,
|
|
1061
|
+
}],
|
|
1062
|
+
})
|
|
1063
|
+
state.nextStrategyPositionIndex += 1
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function recordPlannedTokenAccountEntry(
|
|
1067
|
+
state: StrategySetupState,
|
|
1068
|
+
params: { tokenMint: PublicKey; tokenAccount: PublicKey; priceId: ClientPriceId },
|
|
1069
|
+
) {
|
|
1070
|
+
const tokenMintKey = params.tokenMint.toBase58()
|
|
1071
|
+
const tokenAccountKey = params.tokenAccount.toBase58()
|
|
1072
|
+
state.tokenPositionIndexByMint.set(tokenMintKey, state.nextStrategyPositionIndex)
|
|
1073
|
+
state.trackedTokenAccounts.add(tokenAccountKey)
|
|
1074
|
+
state.plannedAumAccounts.push({ pubkey: params.tokenAccount, isSigner: false, isWritable: false })
|
|
1075
|
+
;(state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>).push({
|
|
1076
|
+
tokenAccount: [{
|
|
1077
|
+
tokenMint: params.tokenMint,
|
|
1078
|
+
balances: [{
|
|
1079
|
+
tokenAccount: params.tokenAccount,
|
|
1080
|
+
mint: params.tokenMint,
|
|
1081
|
+
priceId: params.priceId,
|
|
1082
|
+
}],
|
|
1083
|
+
}],
|
|
1084
|
+
})
|
|
1085
|
+
state.nextStrategyPositionIndex += 1
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function recordPlannedTokenAccountBalance(
|
|
1089
|
+
state: StrategySetupState,
|
|
1090
|
+
params: { tokenMint: PublicKey; tokenAccount: PublicKey; priceId: ClientPriceId },
|
|
1091
|
+
) {
|
|
1092
|
+
const tokenMintKey = params.tokenMint.toBase58()
|
|
1093
|
+
const tokenAccountKey = params.tokenAccount.toBase58()
|
|
1094
|
+
state.trackedTokenAccounts.add(tokenAccountKey)
|
|
1095
|
+
state.plannedAumAccounts.push({ pubkey: params.tokenAccount, isSigner: false, isWritable: false })
|
|
1096
|
+
|
|
1097
|
+
const positions = state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>
|
|
1098
|
+
const target = positions.find((position) => {
|
|
1099
|
+
const tokenAccountEntry = Array.isArray(position.tokenAccount) ? position.tokenAccount[0] : undefined
|
|
1100
|
+
return tokenAccountEntry && tokenAccountEntry.tokenMint instanceof PublicKey && tokenAccountEntry.tokenMint.equals(params.tokenMint)
|
|
1101
|
+
})
|
|
1102
|
+
const tokenAccountEntry = target?.tokenAccount?.[0] as
|
|
1103
|
+
| { balances?: Array<{ tokenAccount: PublicKey; mint: PublicKey; priceId: ClientPriceId }> }
|
|
1104
|
+
| undefined
|
|
1105
|
+
tokenAccountEntry?.balances?.push({
|
|
1106
|
+
tokenAccount: params.tokenAccount,
|
|
1107
|
+
mint: params.tokenMint,
|
|
1108
|
+
priceId: params.priceId,
|
|
1109
|
+
})
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function recordPlannedKaminoObligation(
|
|
1113
|
+
state: StrategySetupState,
|
|
1114
|
+
entry: {
|
|
1115
|
+
obligation: PublicKey
|
|
1116
|
+
quotePriceId: ClientPriceId
|
|
1117
|
+
quoteInputMint: PublicKey
|
|
1118
|
+
reservePriceMappings: Array<{ reserve: PublicKey; reservePriceId: ClientPriceId }>
|
|
1119
|
+
remainingAccountsAmount: bigint
|
|
1120
|
+
minPriceStatusFlags: number
|
|
1121
|
+
},
|
|
1122
|
+
) {
|
|
1123
|
+
state.existingAccounts.set(entry.obligation.toBase58(), true)
|
|
1124
|
+
state.trackedKaminoObligations.set(entry.obligation.toBase58(), {
|
|
1125
|
+
quotePriceId: entry.quotePriceId,
|
|
1126
|
+
quoteInputMint: entry.quoteInputMint,
|
|
1127
|
+
mappedReserves: new Set(entry.reservePriceMappings.map((mapping) => mapping.reserve.toBase58())),
|
|
1128
|
+
})
|
|
1129
|
+
state.plannedAumAccounts.push({ pubkey: entry.obligation, isSigner: false, isWritable: false })
|
|
1130
|
+
for (const mapping of entry.reservePriceMappings) {
|
|
1131
|
+
state.plannedAumAccounts.push({ pubkey: mapping.reserve, isSigner: false, isWritable: false })
|
|
1132
|
+
}
|
|
1133
|
+
;(state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>).push({
|
|
1134
|
+
obligation: [{
|
|
1135
|
+
kaminoObligation: [{
|
|
1136
|
+
obligation: entry.obligation,
|
|
1137
|
+
lendingProgramId: KAMINO_LENDING_PROGRAM_ID,
|
|
1138
|
+
quotePriceId: entry.quotePriceId,
|
|
1139
|
+
reservePriceMappings: entry.reservePriceMappings,
|
|
1140
|
+
remainingAccountsAmount: entry.remainingAccountsAmount,
|
|
1141
|
+
minPriceStatusFlags: entry.minPriceStatusFlags,
|
|
1142
|
+
}],
|
|
1143
|
+
}],
|
|
1144
|
+
})
|
|
1145
|
+
state.nextStrategyPositionIndex += 1
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function recordPlannedKaminoReserveMappings(
|
|
1149
|
+
state: StrategySetupState,
|
|
1150
|
+
params: {
|
|
1151
|
+
obligation: PublicKey
|
|
1152
|
+
quoteInputMint: PublicKey
|
|
1153
|
+
reservePriceMappings: Array<{ reserve: PublicKey; reservePriceId: ClientPriceId }>
|
|
1154
|
+
},
|
|
1155
|
+
) {
|
|
1156
|
+
const tracked = state.trackedKaminoObligations.get(params.obligation.toBase58())
|
|
1157
|
+
if (!tracked) {
|
|
1158
|
+
return
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
tracked.quoteInputMint = params.quoteInputMint
|
|
1162
|
+
for (const mapping of params.reservePriceMappings) {
|
|
1163
|
+
tracked.mappedReserves.add(mapping.reserve.toBase58())
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const positions = state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>
|
|
1167
|
+
const target = positions.find((position) => {
|
|
1168
|
+
const entry = getTrackedKaminoObligationFromPosition(position)
|
|
1169
|
+
return entry?.obligation.equals(params.obligation)
|
|
1170
|
+
})
|
|
1171
|
+
const kaminoEntry = (
|
|
1172
|
+
Array.isArray(target?.obligation)
|
|
1173
|
+
? target?.obligation?.[0]?.kaminoObligation?.[0]
|
|
1174
|
+
: undefined
|
|
1175
|
+
) as { reservePriceMappings?: Array<{ reserve: PublicKey; reservePriceId: ClientPriceId }>; remainingAccountsAmount?: bigint } | undefined
|
|
1176
|
+
if (!kaminoEntry) {
|
|
1177
|
+
return
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
for (const mapping of params.reservePriceMappings) {
|
|
1181
|
+
const existing = kaminoEntry.reservePriceMappings?.find((item) => item.reserve.equals(mapping.reserve))
|
|
1182
|
+
if (existing) {
|
|
1183
|
+
existing.reservePriceId = mapping.reservePriceId
|
|
1184
|
+
} else {
|
|
1185
|
+
kaminoEntry.reservePriceMappings ??= []
|
|
1186
|
+
kaminoEntry.reservePriceMappings.push(mapping)
|
|
1187
|
+
}
|
|
1188
|
+
state.plannedAumAccounts.push({ pubkey: mapping.reserve, isSigner: false, isWritable: false })
|
|
1189
|
+
}
|
|
1190
|
+
kaminoEntry.remainingAccountsAmount = BigInt(1 + (kaminoEntry.reservePriceMappings?.length ?? 0))
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function recordPlannedKaminoFarmPosition(
|
|
1194
|
+
state: StrategySetupState,
|
|
1195
|
+
params: {
|
|
1196
|
+
farmState: PublicKey
|
|
1197
|
+
userState: PublicKey
|
|
1198
|
+
globalConfig: PublicKey
|
|
1199
|
+
scopePrices: PublicKey | null
|
|
1200
|
+
},
|
|
1201
|
+
) {
|
|
1202
|
+
const trackingKey = kaminoFarmKey(params.farmState, params.userState)
|
|
1203
|
+
if (state.trackedKaminoFarms.has(trackingKey)) {
|
|
1204
|
+
return
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
state.trackedKaminoFarms.add(trackingKey)
|
|
1208
|
+
state.existingAccounts.set(params.userState.toBase58(), true)
|
|
1209
|
+
state.plannedAumAccounts.push({ pubkey: params.farmState, isSigner: false, isWritable: false })
|
|
1210
|
+
state.plannedAumAccounts.push({ pubkey: params.userState, isSigner: false, isWritable: false })
|
|
1211
|
+
state.plannedAumAccounts.push({ pubkey: params.globalConfig, isSigner: false, isWritable: false })
|
|
1212
|
+
if (params.scopePrices) {
|
|
1213
|
+
state.plannedAumAccounts.push({ pubkey: params.scopePrices, isSigner: false, isWritable: false })
|
|
1214
|
+
}
|
|
1215
|
+
;(state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>).push({
|
|
1216
|
+
kaminoFarm: [{
|
|
1217
|
+
farmState: params.farmState,
|
|
1218
|
+
userState: params.userState,
|
|
1219
|
+
}],
|
|
1220
|
+
})
|
|
1221
|
+
state.nextStrategyPositionIndex += 1
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function recordPlannedClmmPosition(
|
|
1225
|
+
state: StrategySetupState,
|
|
1226
|
+
params: {
|
|
1227
|
+
lpPosition: PublicKey
|
|
1228
|
+
market: PublicKey
|
|
1229
|
+
priceIdPt: ClientPriceId
|
|
1230
|
+
priceIdSy: ClientPriceId
|
|
1231
|
+
ticksKey: PublicKey
|
|
1232
|
+
},
|
|
1233
|
+
) {
|
|
1234
|
+
state.existingAccounts.set(params.lpPosition.toBase58(), true)
|
|
1235
|
+
state.trackedClmmPositions.add(params.lpPosition.toBase58())
|
|
1236
|
+
state.plannedAumAccounts.push({ pubkey: params.lpPosition, isSigner: false, isWritable: false })
|
|
1237
|
+
mutableStrategyVault(state).clmmTicksMap.set(params.market.toBase58(), params.ticksKey)
|
|
1238
|
+
;(state.strategyVault.state.strategyPositions as unknown as Array<Record<string, unknown>>).push({
|
|
1239
|
+
clmmPosition: [{
|
|
1240
|
+
lpPosition: params.lpPosition,
|
|
1241
|
+
market: params.market,
|
|
1242
|
+
priceIdPt: params.priceIdPt,
|
|
1243
|
+
priceIdSy: params.priceIdSy,
|
|
1244
|
+
}],
|
|
1245
|
+
})
|
|
1246
|
+
state.nextStrategyPositionIndex += 1
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
async function maybeCreateOwnedAtaSetupInstruction({
|
|
1250
|
+
state,
|
|
1251
|
+
connection,
|
|
1252
|
+
payer,
|
|
1253
|
+
owner,
|
|
1254
|
+
mint,
|
|
1255
|
+
tokenProgram,
|
|
1256
|
+
tokenAccount,
|
|
1257
|
+
}: {
|
|
1258
|
+
state: StrategySetupState
|
|
1259
|
+
connection: Connection
|
|
1260
|
+
payer: PublicKey
|
|
1261
|
+
owner: PublicKey
|
|
1262
|
+
mint: PublicKey
|
|
1263
|
+
tokenProgram: PublicKey
|
|
1264
|
+
tokenAccount: PublicKey
|
|
1265
|
+
}): Promise<TransactionInstruction | null> {
|
|
1266
|
+
if (await accountExists(state, connection, tokenAccount)) {
|
|
1267
|
+
return null
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const expectedAta = getAssociatedTokenAddressSync(mint, owner, true, tokenProgram)
|
|
1271
|
+
if (!expectedAta.equals(tokenAccount)) {
|
|
1272
|
+
return null
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
state.existingAccounts.set(tokenAccount.toBase58(), true)
|
|
1276
|
+
return createAssociatedTokenAccountIdempotentInstruction(payer, tokenAccount, owner, mint, tokenProgram)
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
async function ensureOrderbookPositionSetup(
|
|
1280
|
+
orderbook: Orderbook,
|
|
1281
|
+
buckets: InstructionBuckets,
|
|
1282
|
+
setupContext: StrategySetupContext,
|
|
1283
|
+
) {
|
|
1284
|
+
const state = await loadStrategySetupState(setupContext)
|
|
959
1285
|
if (!state) {
|
|
960
1286
|
return
|
|
961
1287
|
}
|
|
@@ -965,16 +1291,13 @@ async function ensureOrderbookPositionSetup(
|
|
|
965
1291
|
return
|
|
966
1292
|
}
|
|
967
1293
|
|
|
968
|
-
const
|
|
969
|
-
state.prices,
|
|
970
|
-
orderbook.mintPt,
|
|
971
|
-
state.strategyVault.state.underlyingMint,
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
`Missing Exponent price for orderbook setup (${orderbook.selfAddress.toBase58()})`,
|
|
976
|
-
)
|
|
977
|
-
}
|
|
1294
|
+
const priceIdPt = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
1295
|
+
prices: state.prices,
|
|
1296
|
+
sourceMint: orderbook.mintPt,
|
|
1297
|
+
targetMint: state.strategyVault.state.underlyingMint,
|
|
1298
|
+
label: `orderbook setup (${orderbook.selfAddress.toBase58()})`,
|
|
1299
|
+
})
|
|
1300
|
+
trackRequiredPriceIds(state.requiredPriceIds, priceIdPt)
|
|
978
1301
|
|
|
979
1302
|
buckets.setupInstructions.push(
|
|
980
1303
|
state.strategyVault.ixWrapperManageVaultSettings({
|
|
@@ -987,7 +1310,7 @@ async function ensureOrderbookPositionSetup(
|
|
|
987
1310
|
userEscrowIdx: 0,
|
|
988
1311
|
mint: orderbook.vault.mintSy,
|
|
989
1312
|
offerIdxVec: [],
|
|
990
|
-
priceIdPt
|
|
1313
|
+
priceIdPt,
|
|
991
1314
|
baseMint: state.strategyVault.state.underlyingMint,
|
|
992
1315
|
}]),
|
|
993
1316
|
],
|
|
@@ -998,8 +1321,12 @@ async function ensureOrderbookPositionSetup(
|
|
|
998
1321
|
}),
|
|
999
1322
|
)
|
|
1000
1323
|
|
|
1001
|
-
state
|
|
1002
|
-
|
|
1324
|
+
recordPlannedOrderbookEntry(state, {
|
|
1325
|
+
orderbook: orderbook.selfAddress,
|
|
1326
|
+
mint: orderbook.vault.mintSy,
|
|
1327
|
+
priceIdPt,
|
|
1328
|
+
baseMint: state.strategyVault.state.underlyingMint,
|
|
1329
|
+
})
|
|
1003
1330
|
}
|
|
1004
1331
|
|
|
1005
1332
|
async function ensureYieldPositionSetup(
|
|
@@ -1017,14 +1344,13 @@ async function ensureYieldPositionSetup(
|
|
|
1017
1344
|
return
|
|
1018
1345
|
}
|
|
1019
1346
|
|
|
1020
|
-
const
|
|
1021
|
-
state.prices,
|
|
1022
|
-
coreVault.mintPt,
|
|
1023
|
-
state.strategyVault.state.underlyingMint,
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1347
|
+
const priceIdPt = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
1348
|
+
prices: state.prices,
|
|
1349
|
+
sourceMint: coreVault.mintPt,
|
|
1350
|
+
targetMint: state.strategyVault.state.underlyingMint,
|
|
1351
|
+
label: `core vault setup (${coreVault.selfAddress.toBase58()})`,
|
|
1352
|
+
})
|
|
1353
|
+
trackRequiredPriceIds(state.requiredPriceIds, priceIdPt)
|
|
1028
1354
|
|
|
1029
1355
|
const yieldPosition = coreVault.pda.yieldPosition({ owner: setupContext.owner, vault: coreVault.selfAddress })
|
|
1030
1356
|
if (!(await accountExists(state, setupContext.connection, yieldPosition))) {
|
|
@@ -1066,7 +1392,7 @@ async function ensureYieldPositionSetup(
|
|
|
1066
1392
|
exponentVaults.vaultSettingsAction("AddYieldPositionEntry", {
|
|
1067
1393
|
yieldPosition,
|
|
1068
1394
|
vault: coreVault.selfAddress,
|
|
1069
|
-
priceIdPt
|
|
1395
|
+
priceIdPt,
|
|
1070
1396
|
}),
|
|
1071
1397
|
],
|
|
1072
1398
|
remainingAccounts: [
|
|
@@ -1076,8 +1402,11 @@ async function ensureYieldPositionSetup(
|
|
|
1076
1402
|
}),
|
|
1077
1403
|
)
|
|
1078
1404
|
|
|
1079
|
-
state
|
|
1080
|
-
|
|
1405
|
+
recordPlannedYieldPosition(state, {
|
|
1406
|
+
yieldPosition,
|
|
1407
|
+
vault: coreVault.selfAddress,
|
|
1408
|
+
priceIdPt,
|
|
1409
|
+
})
|
|
1081
1410
|
}
|
|
1082
1411
|
|
|
1083
1412
|
async function wrapVaultSignedSetupInstruction({
|
|
@@ -1120,12 +1449,17 @@ async function wrapVaultSignedSetupInstruction({
|
|
|
1120
1449
|
setupContext.vaultAddress
|
|
1121
1450
|
&& (!resolvedLeadingAccounts || !resolvedPreHookAccounts || !resolvedPostHookAccounts)
|
|
1122
1451
|
) {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
setupContext.
|
|
1127
|
-
|
|
1128
|
-
|
|
1452
|
+
// Cache hook resolution on the context to avoid redundant RPC calls
|
|
1453
|
+
// when wrapping multiple setup instructions in the same build.
|
|
1454
|
+
if (!setupContext.resolvedHooksPromise) {
|
|
1455
|
+
setupContext.resolvedHooksPromise = resolveHookAccounts(
|
|
1456
|
+
setupContext.connection,
|
|
1457
|
+
resolvedPolicyPda,
|
|
1458
|
+
setupContext.vaultAddress,
|
|
1459
|
+
setupContext.signer,
|
|
1460
|
+
)
|
|
1461
|
+
}
|
|
1462
|
+
const hooks = await setupContext.resolvedHooksPromise
|
|
1129
1463
|
resolvedLeadingAccounts ??= hooks.leadingAccounts
|
|
1130
1464
|
resolvedPreHookAccounts ??= hooks.preHookAccounts
|
|
1131
1465
|
resolvedPostHookAccounts ??= hooks.postHookAccounts
|
|
@@ -1180,14 +1514,13 @@ async function ensureTrackedTokenAccountSetup({
|
|
|
1180
1514
|
return
|
|
1181
1515
|
}
|
|
1182
1516
|
|
|
1183
|
-
const
|
|
1184
|
-
state.prices,
|
|
1185
|
-
tokenMint,
|
|
1186
|
-
state.strategyVault.state.underlyingMint,
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
}
|
|
1517
|
+
const priceId = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
1518
|
+
prices: state.prices,
|
|
1519
|
+
sourceMint: tokenMint,
|
|
1520
|
+
targetMint: state.strategyVault.state.underlyingMint,
|
|
1521
|
+
label: `token position setup (${tokenMint.toBase58()})`,
|
|
1522
|
+
})
|
|
1523
|
+
trackRequiredPriceIds(state.requiredPriceIds, priceId)
|
|
1191
1524
|
|
|
1192
1525
|
const maybeAtaIx = await maybeCreateOwnedAtaSetupInstruction({
|
|
1193
1526
|
state,
|
|
@@ -1213,7 +1546,7 @@ async function ensureTrackedTokenAccountSetup({
|
|
|
1213
1546
|
balances: [{
|
|
1214
1547
|
tokenAccount,
|
|
1215
1548
|
mint: tokenMint,
|
|
1216
|
-
priceId
|
|
1549
|
+
priceId,
|
|
1217
1550
|
}],
|
|
1218
1551
|
}]),
|
|
1219
1552
|
],
|
|
@@ -1222,8 +1555,11 @@ async function ensureTrackedTokenAccountSetup({
|
|
|
1222
1555
|
],
|
|
1223
1556
|
}),
|
|
1224
1557
|
)
|
|
1225
|
-
state
|
|
1226
|
-
|
|
1558
|
+
recordPlannedTokenAccountEntry(state, {
|
|
1559
|
+
tokenMint,
|
|
1560
|
+
tokenAccount,
|
|
1561
|
+
priceId,
|
|
1562
|
+
})
|
|
1227
1563
|
} else {
|
|
1228
1564
|
buckets.setupInstructions.push(
|
|
1229
1565
|
state.strategyVault.ixWrapperManagerUpdatePosition({
|
|
@@ -1234,7 +1570,7 @@ async function ensureTrackedTokenAccountSetup({
|
|
|
1234
1570
|
balance: {
|
|
1235
1571
|
tokenAccount,
|
|
1236
1572
|
mint: tokenMint,
|
|
1237
|
-
priceId
|
|
1573
|
+
priceId,
|
|
1238
1574
|
},
|
|
1239
1575
|
},
|
|
1240
1576
|
remainingAccounts: [
|
|
@@ -1242,6 +1578,11 @@ async function ensureTrackedTokenAccountSetup({
|
|
|
1242
1578
|
],
|
|
1243
1579
|
}),
|
|
1244
1580
|
)
|
|
1581
|
+
recordPlannedTokenAccountBalance(state, {
|
|
1582
|
+
tokenMint,
|
|
1583
|
+
tokenAccount,
|
|
1584
|
+
priceId,
|
|
1585
|
+
})
|
|
1245
1586
|
}
|
|
1246
1587
|
|
|
1247
1588
|
state.trackedTokenAccounts.add(tokenAccountKey)
|
|
@@ -1260,6 +1601,8 @@ async function buildVaultInstructions(
|
|
|
1260
1601
|
preHookAccounts?: PublicKey[] | AccountMeta[],
|
|
1261
1602
|
postHookAccounts?: PublicKey[] | AccountMeta[],
|
|
1262
1603
|
squadsProgram: PublicKey = SQUADS_PROGRAM_ID,
|
|
1604
|
+
autoManagePositions: boolean = true,
|
|
1605
|
+
sharedSetupContext?: StrategySetupContext,
|
|
1263
1606
|
): Promise<InstructionBuckets> {
|
|
1264
1607
|
const buckets: InstructionBuckets = {
|
|
1265
1608
|
setupInstructions: [],
|
|
@@ -1269,7 +1612,7 @@ async function buildVaultInstructions(
|
|
|
1269
1612
|
signers: [],
|
|
1270
1613
|
addressLookupTableAddresses: [],
|
|
1271
1614
|
}
|
|
1272
|
-
const setupContext = createStrategySetupContext({
|
|
1615
|
+
const setupContext = sharedSetupContext ?? createStrategySetupContext({
|
|
1273
1616
|
connection,
|
|
1274
1617
|
env: LOCAL_ENV,
|
|
1275
1618
|
owner,
|
|
@@ -1282,9 +1625,20 @@ async function buildVaultInstructions(
|
|
|
1282
1625
|
leadingAccounts,
|
|
1283
1626
|
preHookAccounts,
|
|
1284
1627
|
postHookAccounts,
|
|
1628
|
+
autoManagePositions,
|
|
1285
1629
|
})
|
|
1286
1630
|
|
|
1287
1631
|
for (const ix of instructions) {
|
|
1632
|
+
if (isKaminoVaultInstruction(ix)) {
|
|
1633
|
+
await buildKaminoVaultInstruction(ix, buckets, setupContext)
|
|
1634
|
+
continue
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
if (isKaminoFarmInstruction(ix)) {
|
|
1638
|
+
await buildKaminoFarmInstruction(ix, buckets, setupContext)
|
|
1639
|
+
continue
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1288
1642
|
if (isOrderbookInstruction(ix)) {
|
|
1289
1643
|
await buildOrderbookInstruction(ix, owner, connection, buckets, setupContext)
|
|
1290
1644
|
continue
|
|
@@ -1320,19 +1674,19 @@ async function buildVaultInstructions(
|
|
|
1320
1674
|
await buildInitUserMetadata(owner, connection, buckets)
|
|
1321
1675
|
break
|
|
1322
1676
|
case VaultAction.INIT_OBLIGATION:
|
|
1323
|
-
await buildInitObligation(ix, owner, connection, buckets)
|
|
1677
|
+
await buildInitObligation(ix, owner, connection, buckets, setupContext)
|
|
1324
1678
|
break
|
|
1325
1679
|
case VaultAction.DEPOSIT:
|
|
1326
|
-
await buildDeposit(ix, owner, connection, signer, buckets)
|
|
1680
|
+
await buildDeposit(ix, owner, connection, signer, buckets, setupContext)
|
|
1327
1681
|
break
|
|
1328
1682
|
case VaultAction.WITHDRAW:
|
|
1329
|
-
await buildWithdraw(ix, owner, connection, signer, buckets)
|
|
1683
|
+
await buildWithdraw(ix, owner, connection, signer, buckets, setupContext)
|
|
1330
1684
|
break
|
|
1331
1685
|
case VaultAction.BORROW:
|
|
1332
|
-
await buildBorrow(ix, owner, connection, signer, buckets)
|
|
1686
|
+
await buildBorrow(ix, owner, connection, signer, buckets, setupContext)
|
|
1333
1687
|
break
|
|
1334
1688
|
case VaultAction.REPAY:
|
|
1335
|
-
await buildRepay(ix, owner, connection, signer, buckets)
|
|
1689
|
+
await buildRepay(ix, owner, connection, signer, buckets, setupContext)
|
|
1336
1690
|
break
|
|
1337
1691
|
}
|
|
1338
1692
|
}
|
|
@@ -1340,6 +1694,832 @@ async function buildVaultInstructions(
|
|
|
1340
1694
|
return buckets
|
|
1341
1695
|
}
|
|
1342
1696
|
|
|
1697
|
+
const KAMINO_VAULT_EVENT_AUTHORITY = emitEventAuthority(KAMINO_VAULT_PROGRAM_ID)
|
|
1698
|
+
const KAMINO_FARM_USER_STATE_SIZE = 920
|
|
1699
|
+
const KAMINO_STAKE_ALL_AMOUNT = new BN("18446744073709551615")
|
|
1700
|
+
|
|
1701
|
+
type KaminoVaultContext = {
|
|
1702
|
+
index: Awaited<ReturnType<typeof fetchKaminoVaultIndex>>
|
|
1703
|
+
tokenAta: PublicKey
|
|
1704
|
+
sharesAta: PublicKey
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
type KaminoFarmContext = {
|
|
1708
|
+
farm: ReturnType<typeof decodeKaminoFarmState>
|
|
1709
|
+
farmState: PublicKey
|
|
1710
|
+
userState: PublicKey
|
|
1711
|
+
delegatee: PublicKey
|
|
1712
|
+
sourceAta: PublicKey
|
|
1713
|
+
scopePrices: PublicKey | null
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
function toBn(value: BN | bigint | number): BN {
|
|
1717
|
+
if (BN.isBN(value)) {
|
|
1718
|
+
return value
|
|
1719
|
+
}
|
|
1720
|
+
return new BN(value.toString())
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
function encodeU64InstructionData(discriminator: Buffer, value: BN | bigint | number): Buffer {
|
|
1724
|
+
return Buffer.concat([discriminator, toBn(value).toArrayLike(Buffer, "le", 8)])
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
function encodeU128InstructionData(discriminator: Buffer, value: BN | bigint | number): Buffer {
|
|
1728
|
+
return Buffer.concat([discriminator, toBn(value).toArrayLike(Buffer, "le", 16)])
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function getKaminoFarmUserStateAddress(delegatee: PublicKey, farmState: PublicKey): PublicKey {
|
|
1732
|
+
return getKaminoFarmsObligationFarm(delegatee, farmState, KAMINO_FARMS_PROGRAM_ID)
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
async function accountExistsMaybeTracked(
|
|
1736
|
+
setupContext: StrategySetupContext | undefined,
|
|
1737
|
+
address: PublicKey,
|
|
1738
|
+
): Promise<boolean> {
|
|
1739
|
+
if (!setupContext) {
|
|
1740
|
+
return false
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
const state = await loadStrategySetupState(setupContext)
|
|
1744
|
+
if (state) {
|
|
1745
|
+
return accountExists(state, setupContext.connection, address)
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
return (await setupContext.connection.getAccountInfo(address)) !== null
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function matchesKaminoVaultInterfaceAccounts(
|
|
1752
|
+
entry: ExponentPrice,
|
|
1753
|
+
interfaceAccounts: PublicKey[],
|
|
1754
|
+
): boolean {
|
|
1755
|
+
return (
|
|
1756
|
+
entry.interfaceAccounts.length === interfaceAccounts.length
|
|
1757
|
+
&& entry.interfaceAccounts.every((account, index) => account.equals(interfaceAccounts[index]!))
|
|
1758
|
+
)
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// ExponentPrices stores explicit wire discriminators, which can drift from the
|
|
1762
|
+
// generated TypeScript enum ordinals. Accept both so Kamino vault share
|
|
1763
|
+
// tracking works across current program/IDL combinations.
|
|
1764
|
+
function isKaminoVaultPriceType(priceType: number): boolean {
|
|
1765
|
+
return priceType === KAMINO_VAULT_PRICE_TYPE_WIRE || priceType === exponentVaults.PriceType.KaminoVault
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
function resolveKaminoVaultPriceEntry(params: {
|
|
1769
|
+
prices: ExponentPrices
|
|
1770
|
+
sharesMint: PublicKey
|
|
1771
|
+
depositTokenMint: PublicKey
|
|
1772
|
+
interfaceAccounts: PublicKey[]
|
|
1773
|
+
}): ExponentPrice {
|
|
1774
|
+
const candidates = params.prices.prices.filter((entry): entry is ExponentPrice => entry !== null).filter((entry) =>
|
|
1775
|
+
isKaminoVaultPriceType(entry.priceType)
|
|
1776
|
+
&& entry.priceMint.equals(params.sharesMint)
|
|
1777
|
+
&& entry.underlyingMint.equals(params.depositTokenMint)
|
|
1778
|
+
&& matchesKaminoVaultInterfaceAccounts(entry, params.interfaceAccounts),
|
|
1779
|
+
)
|
|
1780
|
+
|
|
1781
|
+
if (candidates.length === 0) {
|
|
1782
|
+
const interfaceAccountsLabel = params.interfaceAccounts.map((account) => account.toBase58()).join(", ")
|
|
1783
|
+
throw new Error(
|
|
1784
|
+
`Missing Exponent KaminoVault price for shares mint ${params.sharesMint.toBase58()} and deposit mint ${params.depositTokenMint.toBase58()} (interface accounts: ${interfaceAccountsLabel}). Register a PriceType.KaminoVault price for this vault before using auto-managed Kamino vault share tracking.`,
|
|
1785
|
+
)
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if (candidates.length > 1) {
|
|
1789
|
+
throw new Error(
|
|
1790
|
+
`Multiple Exponent KaminoVault prices matched shares mint ${params.sharesMint.toBase58()} and deposit mint ${params.depositTokenMint.toBase58()}`,
|
|
1791
|
+
)
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
return candidates[0]!
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function resolveKaminoVaultTrackedPriceId(params: {
|
|
1798
|
+
state: StrategySetupState
|
|
1799
|
+
depositTokenMint: PublicKey
|
|
1800
|
+
sharePriceId: bigint
|
|
1801
|
+
label: string
|
|
1802
|
+
}): ClientPriceId {
|
|
1803
|
+
if (params.depositTokenMint.equals(params.state.strategyVault.state.underlyingMint)) {
|
|
1804
|
+
return { __kind: "Simple", priceId: params.sharePriceId }
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
const reservePriceId = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
1808
|
+
prices: params.state.prices,
|
|
1809
|
+
sourceMint: params.depositTokenMint,
|
|
1810
|
+
targetMint: params.state.strategyVault.state.underlyingMint,
|
|
1811
|
+
label: params.label,
|
|
1812
|
+
})
|
|
1813
|
+
const reservePriceIds = extractPriceIds(reservePriceId).map((id) => BigInt(id))
|
|
1814
|
+
return { __kind: "Multiply", priceIds: [...reservePriceIds, params.sharePriceId] }
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
async function resolveKaminoVaultContext(
|
|
1818
|
+
vaultAddress: PublicKey,
|
|
1819
|
+
setupContext: StrategySetupContext,
|
|
1820
|
+
): Promise<KaminoVaultContext> {
|
|
1821
|
+
const rawIndex = await fetchKaminoVaultIndex({
|
|
1822
|
+
connection: setupContext.connection,
|
|
1823
|
+
kaminoVaultAccount: vaultAddress,
|
|
1824
|
+
})
|
|
1825
|
+
const reserveAddresses = rawIndex.reserves.map((reserve) => reserve.reserveAddress)
|
|
1826
|
+
const [vaultInfo, tokenMintInfo, sharesMintInfo, reserveInfos] = await Promise.all([
|
|
1827
|
+
setupContext.connection.getAccountInfo(vaultAddress),
|
|
1828
|
+
setupContext.connection.getAccountInfo(rawIndex.tokenMint),
|
|
1829
|
+
setupContext.connection.getAccountInfo(rawIndex.sharesMint),
|
|
1830
|
+
setupContext.connection.getMultipleAccountsInfo(reserveAddresses),
|
|
1831
|
+
])
|
|
1832
|
+
if (!vaultInfo?.data) {
|
|
1833
|
+
throw new Error(`Kamino vault account not found: ${vaultAddress.toBase58()}`)
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
const decodedReserves = reserveInfos.map((reserveInfo, index) => {
|
|
1837
|
+
if (!reserveInfo?.data) {
|
|
1838
|
+
throw new Error(`Missing Kamino reserve account ${reserveAddresses[index]!.toBase58()}`)
|
|
1839
|
+
}
|
|
1840
|
+
return Reserve.decode(reserveInfo.data)
|
|
1841
|
+
})
|
|
1842
|
+
const collateralMintInfos = await setupContext.connection.getMultipleAccountsInfo(
|
|
1843
|
+
decodedReserves.map((reserve) => reserve.collateral.mintPubkey),
|
|
1844
|
+
)
|
|
1845
|
+
const normalizedReserves = reserveAddresses.map((reserveAddress, index) => {
|
|
1846
|
+
const reserveAccount = decodedReserves[index]!
|
|
1847
|
+
const rawReserve = rawIndex.reserves[index] as typeof rawIndex.reserves[number] & {
|
|
1848
|
+
reserve?: PublicKey
|
|
1849
|
+
marketAddress?: PublicKey
|
|
1850
|
+
ctokenVault?: PublicKey
|
|
1851
|
+
lendingMarketAuthority?: PublicKey
|
|
1852
|
+
pythOracle?: PublicKey
|
|
1853
|
+
switchboardPriceOracle?: PublicKey
|
|
1854
|
+
switchboardTwapOracle?: PublicKey
|
|
1855
|
+
scopePrices?: PublicKey
|
|
1856
|
+
reserveLiquiditySupply?: PublicKey
|
|
1857
|
+
reserveCollateralMint?: PublicKey
|
|
1858
|
+
reserveCollateralTokenProgram?: PublicKey
|
|
1859
|
+
}
|
|
1860
|
+
const allocationOffset =
|
|
1861
|
+
KAMINO_VAULT_ACCOUNT_DISCRIMINATOR_LEN
|
|
1862
|
+
+ KAMINO_VAULT_ALLOCATION_STRATEGY_OFFSET
|
|
1863
|
+
+ (index * KAMINO_VAULT_ALLOCATION_SIZE)
|
|
1864
|
+
const ctokenVaultOffset = allocationOffset + KAMINO_VAULT_ALLOCATION_CTOKEN_VAULT_OFFSET
|
|
1865
|
+
const [lendingMarketAuthority] = PublicKey.findProgramAddressSync(
|
|
1866
|
+
[Buffer.from("lma"), reserveAccount.lendingMarket.toBuffer()],
|
|
1867
|
+
KAMINO_LENDING_PROGRAM_ID,
|
|
1868
|
+
)
|
|
1869
|
+
|
|
1870
|
+
return {
|
|
1871
|
+
reserveAddress,
|
|
1872
|
+
marketAddress: rawReserve.marketAddress ?? rawReserve.reserve ?? reserveAccount.lendingMarket,
|
|
1873
|
+
ctokenVault:
|
|
1874
|
+
rawReserve.ctokenVault
|
|
1875
|
+
?? new PublicKey(vaultInfo.data.subarray(ctokenVaultOffset, ctokenVaultOffset + 32)),
|
|
1876
|
+
lendingMarketAuthority: rawReserve.lendingMarketAuthority ?? lendingMarketAuthority,
|
|
1877
|
+
pythOracle: rawReserve.pythOracle ?? reserveAccount.config.tokenInfo.pythConfiguration.price,
|
|
1878
|
+
switchboardPriceOracle:
|
|
1879
|
+
rawReserve.switchboardPriceOracle
|
|
1880
|
+
?? reserveAccount.config.tokenInfo.switchboardConfiguration.priceAggregator,
|
|
1881
|
+
switchboardTwapOracle:
|
|
1882
|
+
rawReserve.switchboardTwapOracle
|
|
1883
|
+
?? reserveAccount.config.tokenInfo.switchboardConfiguration.twapAggregator,
|
|
1884
|
+
scopePrices: rawReserve.scopePrices ?? reserveAccount.config.tokenInfo.scopeConfiguration.priceFeed,
|
|
1885
|
+
reserveLiquiditySupply: rawReserve.reserveLiquiditySupply ?? reserveAccount.liquidity.supplyVault,
|
|
1886
|
+
reserveCollateralMint: rawReserve.reserveCollateralMint ?? reserveAccount.collateral.mintPubkey,
|
|
1887
|
+
reserveCollateralTokenProgram:
|
|
1888
|
+
rawReserve.reserveCollateralTokenProgram ?? collateralMintInfos[index]?.owner ?? PublicKey.default,
|
|
1889
|
+
}
|
|
1890
|
+
})
|
|
1891
|
+
const index = {
|
|
1892
|
+
...rawIndex,
|
|
1893
|
+
reserves: normalizedReserves,
|
|
1894
|
+
tokenProgram: (rawIndex as typeof rawIndex & { tokenProgram?: PublicKey }).tokenProgram ?? tokenMintInfo?.owner ?? TOKEN_PROGRAM_ID,
|
|
1895
|
+
sharesTokenProgram:
|
|
1896
|
+
(rawIndex as typeof rawIndex & { sharesTokenProgram?: PublicKey }).sharesTokenProgram
|
|
1897
|
+
?? sharesMintInfo?.owner
|
|
1898
|
+
?? TOKEN_PROGRAM_ID,
|
|
1899
|
+
vaultLookupTable:
|
|
1900
|
+
(rawIndex as typeof rawIndex & { vaultLookupTable?: PublicKey }).vaultLookupTable ?? PublicKey.default,
|
|
1901
|
+
}
|
|
1902
|
+
const tokenAta = getAssociatedTokenAddressSync(index.tokenMint, setupContext.owner, true, index.tokenProgram)
|
|
1903
|
+
const sharesAta = getAssociatedTokenAddressSync(index.sharesMint, setupContext.owner, true, index.sharesTokenProgram)
|
|
1904
|
+
return { index, tokenAta, sharesAta }
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
async function queueKaminoVaultSharesTracking(params: {
|
|
1908
|
+
setupContext: StrategySetupContext
|
|
1909
|
+
buckets: InstructionBuckets
|
|
1910
|
+
kaminoVaultAddress: PublicKey
|
|
1911
|
+
vaultContext: KaminoVaultContext
|
|
1912
|
+
}) {
|
|
1913
|
+
const state = await loadStrategySetupState(params.setupContext)
|
|
1914
|
+
if (!state) {
|
|
1915
|
+
return
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
const sharesMintKey = params.vaultContext.index.sharesMint.toBase58()
|
|
1919
|
+
const sharesAtaKey = params.vaultContext.sharesAta.toBase58()
|
|
1920
|
+
|
|
1921
|
+
if (state.trackedTokenAccounts.has(sharesAtaKey)) {
|
|
1922
|
+
return
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
const tokenEntryAccount = state.tokenEntryAccountByMint.get(sharesMintKey)
|
|
1926
|
+
if (tokenEntryAccount) {
|
|
1927
|
+
throw new Error(
|
|
1928
|
+
`Kamino Vault shares mint ${sharesMintKey} is already configured as a token entry on ${tokenEntryAccount}`,
|
|
1929
|
+
)
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
const interfaceAccounts = [
|
|
1933
|
+
params.kaminoVaultAddress,
|
|
1934
|
+
...params.vaultContext.index.reserves.map((reserve) => reserve.reserveAddress),
|
|
1935
|
+
]
|
|
1936
|
+
const priceEntry = resolveKaminoVaultPriceEntry({
|
|
1937
|
+
prices: state.prices,
|
|
1938
|
+
sharesMint: params.vaultContext.index.sharesMint,
|
|
1939
|
+
depositTokenMint: params.vaultContext.index.tokenMint,
|
|
1940
|
+
interfaceAccounts,
|
|
1941
|
+
})
|
|
1942
|
+
const resolvedPriceId = resolveKaminoVaultTrackedPriceId({
|
|
1943
|
+
state,
|
|
1944
|
+
depositTokenMint: params.vaultContext.index.tokenMint,
|
|
1945
|
+
sharePriceId: priceEntry.priceId,
|
|
1946
|
+
label: `Kamino Vault shares tracking (${params.kaminoVaultAddress.toBase58()})`,
|
|
1947
|
+
})
|
|
1948
|
+
const remainingAccounts = uniqueRemainingAccounts([
|
|
1949
|
+
{ pubkey: params.vaultContext.sharesAta, isSigner: false, isWritable: false },
|
|
1950
|
+
{ pubkey: priceEntry.priceInterfaceAccounts, isSigner: false, isWritable: false },
|
|
1951
|
+
...priceEntry.interfaceAccounts.map((account) => ({
|
|
1952
|
+
pubkey: account,
|
|
1953
|
+
isSigner: false,
|
|
1954
|
+
isWritable: false,
|
|
1955
|
+
})),
|
|
1956
|
+
...buildTrackedAumRemainingAccounts(state),
|
|
1957
|
+
])
|
|
1958
|
+
|
|
1959
|
+
// The hook validates Kamino vault deposits against the currently tracked
|
|
1960
|
+
// shares ATA, so this registration must happen before the Squads sync step.
|
|
1961
|
+
params.buckets.preInstructions.push(
|
|
1962
|
+
state.strategyVault.ixWrapperManagerUpdatePosition({
|
|
1963
|
+
manager: params.setupContext.signer,
|
|
1964
|
+
update: exponentVaults.positionUpdate("TrackKaminoVaultShares", {
|
|
1965
|
+
sharesMint: params.vaultContext.index.sharesMint,
|
|
1966
|
+
depositTokenMint: params.vaultContext.index.tokenMint,
|
|
1967
|
+
sharesTokenAccount: params.vaultContext.sharesAta,
|
|
1968
|
+
priceInterfaceAccounts: priceEntry.priceInterfaceAccounts,
|
|
1969
|
+
}),
|
|
1970
|
+
remainingAccounts,
|
|
1971
|
+
}),
|
|
1972
|
+
)
|
|
1973
|
+
|
|
1974
|
+
trackRequiredPriceIds(state.requiredPriceIds, resolvedPriceId)
|
|
1975
|
+
if (state.tokenPositionIndexByMint.get(sharesMintKey) === undefined) {
|
|
1976
|
+
recordPlannedTokenAccountEntry(state, {
|
|
1977
|
+
tokenMint: params.vaultContext.index.sharesMint,
|
|
1978
|
+
tokenAccount: params.vaultContext.sharesAta,
|
|
1979
|
+
priceId: resolvedPriceId,
|
|
1980
|
+
})
|
|
1981
|
+
} else {
|
|
1982
|
+
recordPlannedTokenAccountBalance(state, {
|
|
1983
|
+
tokenMint: params.vaultContext.index.sharesMint,
|
|
1984
|
+
tokenAccount: params.vaultContext.sharesAta,
|
|
1985
|
+
priceId: resolvedPriceId,
|
|
1986
|
+
})
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
async function resolveKaminoVaultValidationAccounts(params: {
|
|
1991
|
+
setupContext: StrategySetupContext
|
|
1992
|
+
kaminoVaultAddress: PublicKey
|
|
1993
|
+
vaultContext: KaminoVaultContext
|
|
1994
|
+
}): Promise<AccountMeta[]> {
|
|
1995
|
+
const state = await loadStrategySetupState(params.setupContext)
|
|
1996
|
+
if (!state) {
|
|
1997
|
+
return []
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
const priceEntry = resolveKaminoVaultPriceEntry({
|
|
2001
|
+
prices: state.prices,
|
|
2002
|
+
sharesMint: params.vaultContext.index.sharesMint,
|
|
2003
|
+
depositTokenMint: params.vaultContext.index.tokenMint,
|
|
2004
|
+
interfaceAccounts: [
|
|
2005
|
+
params.kaminoVaultAddress,
|
|
2006
|
+
...params.vaultContext.index.reserves.map((reserve) => reserve.reserveAddress),
|
|
2007
|
+
],
|
|
2008
|
+
})
|
|
2009
|
+
|
|
2010
|
+
return [
|
|
2011
|
+
{ pubkey: priceEntry.priceInterfaceAccounts, isSigner: false, isWritable: false },
|
|
2012
|
+
]
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function buildKaminoVaultDepositInstruction(params: {
|
|
2016
|
+
owner: PublicKey
|
|
2017
|
+
kaminoVaultAddress: PublicKey
|
|
2018
|
+
vaultContext: KaminoVaultContext
|
|
2019
|
+
amount: BN
|
|
2020
|
+
validationAccounts?: AccountMeta[]
|
|
2021
|
+
}): TransactionInstruction {
|
|
2022
|
+
const reserveAccounts = params.vaultContext.index.reserves.flatMap((reserve) => [
|
|
2023
|
+
{ pubkey: reserve.reserveAddress, isSigner: false, isWritable: true },
|
|
2024
|
+
{ pubkey: reserve.ctokenVault, isSigner: false, isWritable: true },
|
|
2025
|
+
{ pubkey: reserve.marketAddress, isSigner: false, isWritable: false },
|
|
2026
|
+
{ pubkey: reserve.lendingMarketAuthority, isSigner: false, isWritable: false },
|
|
2027
|
+
{ pubkey: reserve.reserveLiquiditySupply, isSigner: false, isWritable: true },
|
|
2028
|
+
{ pubkey: reserve.reserveCollateralMint, isSigner: false, isWritable: true },
|
|
2029
|
+
{ pubkey: reserve.reserveCollateralTokenProgram, isSigner: false, isWritable: false },
|
|
2030
|
+
{ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
|
|
2031
|
+
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
2032
|
+
{ pubkey: KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
2033
|
+
])
|
|
2034
|
+
|
|
2035
|
+
return new TransactionInstruction({
|
|
2036
|
+
programId: KAMINO_VAULT_PROGRAM_ID,
|
|
2037
|
+
keys: [
|
|
2038
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2039
|
+
{ pubkey: params.kaminoVaultAddress, isSigner: false, isWritable: true },
|
|
2040
|
+
{ pubkey: params.vaultContext.index.tokenVault, isSigner: false, isWritable: true },
|
|
2041
|
+
{ pubkey: params.vaultContext.index.tokenMint, isSigner: false, isWritable: false },
|
|
2042
|
+
{ pubkey: params.vaultContext.index.baseVaultAuthority, isSigner: false, isWritable: false },
|
|
2043
|
+
{ pubkey: params.vaultContext.index.sharesMint, isSigner: false, isWritable: true },
|
|
2044
|
+
{ pubkey: params.vaultContext.tokenAta, isSigner: false, isWritable: true },
|
|
2045
|
+
{ pubkey: params.vaultContext.sharesAta, isSigner: false, isWritable: true },
|
|
2046
|
+
{ pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
2047
|
+
{ pubkey: params.vaultContext.index.tokenProgram, isSigner: false, isWritable: false },
|
|
2048
|
+
{ pubkey: params.vaultContext.index.sharesTokenProgram, isSigner: false, isWritable: false },
|
|
2049
|
+
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
2050
|
+
{ pubkey: KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
2051
|
+
...reserveAccounts,
|
|
2052
|
+
...params.vaultContext.index.reserves.map((reserve) => ({
|
|
2053
|
+
pubkey: reserve.reserveAddress,
|
|
2054
|
+
isSigner: false,
|
|
2055
|
+
isWritable: true,
|
|
2056
|
+
})),
|
|
2057
|
+
...(params.validationAccounts ?? []),
|
|
2058
|
+
],
|
|
2059
|
+
data: encodeU64InstructionData(KAMINO_VAULT_DISCRIMINATORS.deposit, params.amount),
|
|
2060
|
+
})
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function resolveKaminoVaultWithdrawReserve(
|
|
2064
|
+
ix: KaminoVaultWithdrawInstruction,
|
|
2065
|
+
vaultContext: KaminoVaultContext,
|
|
2066
|
+
): KaminoVaultContext["index"]["reserves"][number] {
|
|
2067
|
+
if (ix.reserve) {
|
|
2068
|
+
const reserve = vaultContext.index.reserves.find((entry) => entry.reserveAddress.equals(ix.reserve!))
|
|
2069
|
+
if (!reserve) {
|
|
2070
|
+
throw new Error(
|
|
2071
|
+
`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${ix.reserve.toBase58()}`,
|
|
2072
|
+
)
|
|
2073
|
+
}
|
|
2074
|
+
return reserve
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
if (vaultContext.index.reserves.length !== 1) {
|
|
2078
|
+
throw new Error(
|
|
2079
|
+
`Kamino Vault ${ix.vault.toBase58()} uses ${vaultContext.index.reserves.length} reserves; specify withdraw.reserve`,
|
|
2080
|
+
)
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
return vaultContext.index.reserves[0]!
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function buildKaminoVaultWithdrawInstruction(params: {
|
|
2087
|
+
owner: PublicKey
|
|
2088
|
+
ix: KaminoVaultWithdrawInstruction
|
|
2089
|
+
vaultContext: KaminoVaultContext
|
|
2090
|
+
validationAccounts?: AccountMeta[]
|
|
2091
|
+
}): TransactionInstruction {
|
|
2092
|
+
const reserve = resolveKaminoVaultWithdrawReserve(params.ix, params.vaultContext)
|
|
2093
|
+
const [globalConfig] = PublicKey.findProgramAddressSync(
|
|
2094
|
+
[KAMINO_VAULT_GLOBAL_CONFIG_SEED],
|
|
2095
|
+
KAMINO_VAULT_PROGRAM_ID,
|
|
2096
|
+
)
|
|
2097
|
+
return new TransactionInstruction({
|
|
2098
|
+
programId: KAMINO_VAULT_PROGRAM_ID,
|
|
2099
|
+
keys: [
|
|
2100
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2101
|
+
{ pubkey: params.ix.vault, isSigner: false, isWritable: true },
|
|
2102
|
+
{ pubkey: globalConfig, isSigner: false, isWritable: false },
|
|
2103
|
+
{ pubkey: params.vaultContext.index.tokenVault, isSigner: false, isWritable: true },
|
|
2104
|
+
{ pubkey: params.vaultContext.index.baseVaultAuthority, isSigner: false, isWritable: false },
|
|
2105
|
+
{ pubkey: params.vaultContext.tokenAta, isSigner: false, isWritable: true },
|
|
2106
|
+
{ pubkey: params.vaultContext.index.tokenMint, isSigner: false, isWritable: true },
|
|
2107
|
+
{ pubkey: params.vaultContext.sharesAta, isSigner: false, isWritable: true },
|
|
2108
|
+
{ pubkey: params.vaultContext.index.sharesMint, isSigner: false, isWritable: true },
|
|
2109
|
+
{ pubkey: params.vaultContext.index.tokenProgram, isSigner: false, isWritable: false },
|
|
2110
|
+
{ pubkey: params.vaultContext.index.sharesTokenProgram, isSigner: false, isWritable: false },
|
|
2111
|
+
{ pubkey: KAMINO_LENDING_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
2112
|
+
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
2113
|
+
{ pubkey: KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
2114
|
+
{ pubkey: params.ix.vault, isSigner: false, isWritable: true },
|
|
2115
|
+
{ pubkey: reserve.reserveAddress, isSigner: false, isWritable: true },
|
|
2116
|
+
{ pubkey: reserve.ctokenVault, isSigner: false, isWritable: true },
|
|
2117
|
+
{ pubkey: reserve.marketAddress, isSigner: false, isWritable: false },
|
|
2118
|
+
{ pubkey: reserve.lendingMarketAuthority, isSigner: false, isWritable: false },
|
|
2119
|
+
{ pubkey: reserve.reserveLiquiditySupply, isSigner: false, isWritable: true },
|
|
2120
|
+
{ pubkey: reserve.reserveCollateralMint, isSigner: false, isWritable: true },
|
|
2121
|
+
{ pubkey: reserve.reserveCollateralTokenProgram, isSigner: false, isWritable: false },
|
|
2122
|
+
{ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
|
|
2123
|
+
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
2124
|
+
{ pubkey: KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
2125
|
+
...params.vaultContext.index.reserves.map((entry) => ({
|
|
2126
|
+
pubkey: entry.reserveAddress,
|
|
2127
|
+
isSigner: false,
|
|
2128
|
+
isWritable: true,
|
|
2129
|
+
})),
|
|
2130
|
+
...(params.validationAccounts ?? []),
|
|
2131
|
+
],
|
|
2132
|
+
data: encodeU64InstructionData(KAMINO_VAULT_DISCRIMINATORS.withdraw, params.ix.sharesAmount),
|
|
2133
|
+
})
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
async function buildKaminoVaultInstruction(
|
|
2137
|
+
ix: KaminoVaultInstruction,
|
|
2138
|
+
buckets: InstructionBuckets,
|
|
2139
|
+
setupContext: StrategySetupContext,
|
|
2140
|
+
): Promise<void> {
|
|
2141
|
+
const vaultContext = await resolveKaminoVaultContext(ix.vault, setupContext)
|
|
2142
|
+
const validationAccounts = await resolveKaminoVaultValidationAccounts({
|
|
2143
|
+
setupContext,
|
|
2144
|
+
kaminoVaultAddress: ix.vault,
|
|
2145
|
+
vaultContext,
|
|
2146
|
+
})
|
|
2147
|
+
|
|
2148
|
+
buckets.setupInstructions.push(
|
|
2149
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
2150
|
+
setupContext.signer,
|
|
2151
|
+
vaultContext.sharesAta,
|
|
2152
|
+
setupContext.owner,
|
|
2153
|
+
vaultContext.index.sharesMint,
|
|
2154
|
+
vaultContext.index.sharesTokenProgram,
|
|
2155
|
+
),
|
|
2156
|
+
)
|
|
2157
|
+
|
|
2158
|
+
if (setupContext.autoManagePositions) {
|
|
2159
|
+
await ensureTrackedTokenAccountSetup({
|
|
2160
|
+
tokenMint: vaultContext.index.tokenMint,
|
|
2161
|
+
tokenAccount: vaultContext.tokenAta,
|
|
2162
|
+
tokenProgram: vaultContext.index.tokenProgram,
|
|
2163
|
+
buckets,
|
|
2164
|
+
setupContext,
|
|
2165
|
+
})
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
const vaultLookupTable = (vaultContext.index as typeof vaultContext.index & {
|
|
2169
|
+
vaultLookupTable?: PublicKey
|
|
2170
|
+
}).vaultLookupTable ?? PublicKey.default
|
|
2171
|
+
|
|
2172
|
+
if (!vaultLookupTable.equals(PublicKey.default)) {
|
|
2173
|
+
buckets.addressLookupTableAddresses.push(vaultLookupTable)
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
if (ix.action === KaminoVaultAction.DEPOSIT) {
|
|
2177
|
+
buckets.syncInstructions.push(buildKaminoVaultDepositInstruction({
|
|
2178
|
+
owner: setupContext.owner,
|
|
2179
|
+
kaminoVaultAddress: ix.vault,
|
|
2180
|
+
vaultContext,
|
|
2181
|
+
amount: ix.amount,
|
|
2182
|
+
validationAccounts,
|
|
2183
|
+
}))
|
|
2184
|
+
|
|
2185
|
+
if (isAutoManagePositionsEnabled(setupContext)) {
|
|
2186
|
+
await queueKaminoVaultSharesTracking({
|
|
2187
|
+
setupContext,
|
|
2188
|
+
buckets,
|
|
2189
|
+
kaminoVaultAddress: ix.vault,
|
|
2190
|
+
vaultContext,
|
|
2191
|
+
})
|
|
2192
|
+
}
|
|
2193
|
+
return
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
buckets.syncInstructions.push(buildKaminoVaultWithdrawInstruction({
|
|
2197
|
+
owner: setupContext.owner,
|
|
2198
|
+
ix,
|
|
2199
|
+
vaultContext,
|
|
2200
|
+
validationAccounts,
|
|
2201
|
+
}))
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
function getOptionalReadonlyAccountMeta(account: PublicKey | null, placeholderProgram: PublicKey): AccountMeta {
|
|
2205
|
+
return {
|
|
2206
|
+
pubkey: account ?? placeholderProgram,
|
|
2207
|
+
isSigner: false,
|
|
2208
|
+
isWritable: false,
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
async function resolveKaminoFarmContext(
|
|
2213
|
+
ix: KaminoFarmInstruction,
|
|
2214
|
+
setupContext: StrategySetupContext,
|
|
2215
|
+
): Promise<KaminoFarmContext> {
|
|
2216
|
+
const farmInfo = await setupContext.connection.getAccountInfo(ix.farmState)
|
|
2217
|
+
if (!farmInfo?.data) {
|
|
2218
|
+
throw new Error(`Kamino farm not found: ${ix.farmState.toBase58()}`)
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
const farm = decodeKaminoFarmState(Buffer.from(farmInfo.data))
|
|
2222
|
+
const delegatee = ix.delegatee ?? setupContext.owner
|
|
2223
|
+
const userState = getKaminoFarmUserStateAddress(delegatee, ix.farmState)
|
|
2224
|
+
const sourceAta = getAssociatedTokenAddressSync(
|
|
2225
|
+
farm.underlyingMint,
|
|
2226
|
+
setupContext.owner,
|
|
2227
|
+
true,
|
|
2228
|
+
farm.tokenProgram,
|
|
2229
|
+
)
|
|
2230
|
+
|
|
2231
|
+
return {
|
|
2232
|
+
farm,
|
|
2233
|
+
farmState: ix.farmState,
|
|
2234
|
+
userState,
|
|
2235
|
+
delegatee,
|
|
2236
|
+
sourceAta,
|
|
2237
|
+
scopePrices: getKaminoFarmScopePricesAddress(farm),
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
function buildKaminoFarmInitializeUserRawInstruction(params: {
|
|
2242
|
+
owner: PublicKey
|
|
2243
|
+
delegatee: PublicKey
|
|
2244
|
+
userState: PublicKey
|
|
2245
|
+
farmState: PublicKey
|
|
2246
|
+
}): TransactionInstruction {
|
|
2247
|
+
return new TransactionInstruction({
|
|
2248
|
+
programId: KAMINO_FARMS_PROGRAM_ID,
|
|
2249
|
+
keys: [
|
|
2250
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2251
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2252
|
+
{ pubkey: params.owner, isSigner: false, isWritable: false },
|
|
2253
|
+
{ pubkey: params.delegatee, isSigner: false, isWritable: false },
|
|
2254
|
+
{ pubkey: params.userState, isSigner: false, isWritable: true },
|
|
2255
|
+
{ pubkey: params.farmState, isSigner: false, isWritable: true },
|
|
2256
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
2257
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
2258
|
+
],
|
|
2259
|
+
data: Buffer.from(KAMINO_FARM_DISCRIMINATORS.initializeUser),
|
|
2260
|
+
})
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
async function ensureKaminoFarmUserSetup(params: {
|
|
2264
|
+
farmContext: KaminoFarmContext
|
|
2265
|
+
buckets: InstructionBuckets
|
|
2266
|
+
setupContext: StrategySetupContext
|
|
2267
|
+
}) {
|
|
2268
|
+
if (await accountExistsMaybeTracked(params.setupContext, params.farmContext.userState)) {
|
|
2269
|
+
return
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
const requiredLamports = await params.setupContext.connection.getMinimumBalanceForRentExemption(
|
|
2273
|
+
KAMINO_FARM_USER_STATE_SIZE,
|
|
2274
|
+
)
|
|
2275
|
+
const ownerLamports = await params.setupContext.connection.getBalance(params.setupContext.owner)
|
|
2276
|
+
if (ownerLamports < requiredLamports) {
|
|
2277
|
+
params.buckets.setupInstructions.push(
|
|
2278
|
+
SystemProgram.transfer({
|
|
2279
|
+
fromPubkey: params.setupContext.signer,
|
|
2280
|
+
toPubkey: params.setupContext.owner,
|
|
2281
|
+
lamports: requiredLamports - ownerLamports,
|
|
2282
|
+
}),
|
|
2283
|
+
)
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
params.buckets.syncInstructions.push(
|
|
2287
|
+
buildKaminoFarmInitializeUserRawInstruction({
|
|
2288
|
+
owner: params.setupContext.owner,
|
|
2289
|
+
delegatee: params.farmContext.delegatee,
|
|
2290
|
+
userState: params.farmContext.userState,
|
|
2291
|
+
farmState: params.farmContext.farmState,
|
|
2292
|
+
}),
|
|
2293
|
+
)
|
|
2294
|
+
|
|
2295
|
+
const state = await loadStrategySetupState(params.setupContext)
|
|
2296
|
+
state?.existingAccounts.set(params.farmContext.userState.toBase58(), true)
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function buildKaminoFarmStakeInstructionRaw(params: {
|
|
2300
|
+
owner: PublicKey
|
|
2301
|
+
farmContext: KaminoFarmContext
|
|
2302
|
+
amount: BN | "ALL"
|
|
2303
|
+
}): TransactionInstruction {
|
|
2304
|
+
const amount = params.amount === "ALL" ? KAMINO_STAKE_ALL_AMOUNT : params.amount
|
|
2305
|
+
return new TransactionInstruction({
|
|
2306
|
+
programId: KAMINO_FARMS_PROGRAM_ID,
|
|
2307
|
+
keys: [
|
|
2308
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2309
|
+
{ pubkey: params.farmContext.userState, isSigner: false, isWritable: true },
|
|
2310
|
+
{ pubkey: params.farmContext.farmState, isSigner: false, isWritable: true },
|
|
2311
|
+
{ pubkey: params.farmContext.farm.farmVault, isSigner: false, isWritable: true },
|
|
2312
|
+
{ pubkey: params.farmContext.sourceAta, isSigner: false, isWritable: true },
|
|
2313
|
+
{ pubkey: params.farmContext.farm.underlyingMint, isSigner: false, isWritable: false },
|
|
2314
|
+
getOptionalReadonlyAccountMeta(params.farmContext.scopePrices, KAMINO_FARMS_PROGRAM_ID),
|
|
2315
|
+
{ pubkey: params.farmContext.farm.tokenProgram, isSigner: false, isWritable: false },
|
|
2316
|
+
],
|
|
2317
|
+
data: encodeU64InstructionData(KAMINO_FARM_DISCRIMINATORS.stake, amount),
|
|
2318
|
+
})
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
function buildKaminoFarmUnstakeInstructionRaw(params: {
|
|
2322
|
+
owner: PublicKey
|
|
2323
|
+
farmContext: KaminoFarmContext
|
|
2324
|
+
stakeSharesScaled: BN
|
|
2325
|
+
}): TransactionInstruction {
|
|
2326
|
+
return new TransactionInstruction({
|
|
2327
|
+
programId: KAMINO_FARMS_PROGRAM_ID,
|
|
2328
|
+
keys: [
|
|
2329
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2330
|
+
{ pubkey: params.farmContext.userState, isSigner: false, isWritable: true },
|
|
2331
|
+
{ pubkey: params.farmContext.farmState, isSigner: false, isWritable: true },
|
|
2332
|
+
getOptionalReadonlyAccountMeta(params.farmContext.scopePrices, KAMINO_FARMS_PROGRAM_ID),
|
|
2333
|
+
],
|
|
2334
|
+
data: encodeU128InstructionData(KAMINO_FARM_DISCRIMINATORS.unstake, params.stakeSharesScaled),
|
|
2335
|
+
})
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
function buildKaminoFarmWithdrawUnstakedDepositsRawInstruction(params: {
|
|
2339
|
+
owner: PublicKey
|
|
2340
|
+
farmContext: KaminoFarmContext
|
|
2341
|
+
}): TransactionInstruction {
|
|
2342
|
+
return new TransactionInstruction({
|
|
2343
|
+
programId: KAMINO_FARMS_PROGRAM_ID,
|
|
2344
|
+
keys: [
|
|
2345
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2346
|
+
{ pubkey: params.farmContext.userState, isSigner: false, isWritable: true },
|
|
2347
|
+
{ pubkey: params.farmContext.farmState, isSigner: false, isWritable: true },
|
|
2348
|
+
{ pubkey: params.farmContext.sourceAta, isSigner: false, isWritable: true },
|
|
2349
|
+
{ pubkey: params.farmContext.farm.farmVault, isSigner: false, isWritable: true },
|
|
2350
|
+
{ pubkey: params.farmContext.farm.farmVaultsAuthority, isSigner: false, isWritable: false },
|
|
2351
|
+
{ pubkey: params.farmContext.farm.tokenProgram, isSigner: false, isWritable: false },
|
|
2352
|
+
],
|
|
2353
|
+
data: Buffer.from(KAMINO_FARM_DISCRIMINATORS.withdrawUnstakedDeposits),
|
|
2354
|
+
})
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
function buildKaminoFarmHarvestRewardRawInstruction(params: {
|
|
2358
|
+
owner: PublicKey
|
|
2359
|
+
farmContext: KaminoFarmContext
|
|
2360
|
+
rewardIndex: number
|
|
2361
|
+
rewardAta: PublicKey
|
|
2362
|
+
}): TransactionInstruction {
|
|
2363
|
+
const rewardInfo = params.farmContext.farm.rewardInfos[params.rewardIndex]
|
|
2364
|
+
if (!rewardInfo) {
|
|
2365
|
+
throw new Error(
|
|
2366
|
+
`Reward index ${params.rewardIndex} is out of range for Kamino farm ${params.farmContext.farmState.toBase58()}`,
|
|
2367
|
+
)
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
return new TransactionInstruction({
|
|
2371
|
+
programId: KAMINO_FARMS_PROGRAM_ID,
|
|
2372
|
+
keys: [
|
|
2373
|
+
{ pubkey: params.owner, isSigner: true, isWritable: true },
|
|
2374
|
+
{ pubkey: params.farmContext.userState, isSigner: false, isWritable: true },
|
|
2375
|
+
{ pubkey: params.farmContext.farmState, isSigner: false, isWritable: true },
|
|
2376
|
+
{ pubkey: params.farmContext.farm.globalConfig, isSigner: false, isWritable: false },
|
|
2377
|
+
{ pubkey: rewardInfo.rewardMint, isSigner: false, isWritable: false },
|
|
2378
|
+
{ pubkey: params.rewardAta, isSigner: false, isWritable: true },
|
|
2379
|
+
{ pubkey: rewardInfo.rewardsVault, isSigner: false, isWritable: true },
|
|
2380
|
+
{
|
|
2381
|
+
pubkey: getKaminoFarmsRewardsTreasuryVault(
|
|
2382
|
+
rewardInfo.rewardMint,
|
|
2383
|
+
params.farmContext.farm.globalConfig,
|
|
2384
|
+
KAMINO_FARMS_PROGRAM_ID,
|
|
2385
|
+
),
|
|
2386
|
+
isSigner: false,
|
|
2387
|
+
isWritable: true,
|
|
2388
|
+
},
|
|
2389
|
+
{ pubkey: params.farmContext.farm.farmVaultsAuthority, isSigner: false, isWritable: false },
|
|
2390
|
+
getOptionalReadonlyAccountMeta(params.farmContext.scopePrices, KAMINO_FARMS_PROGRAM_ID),
|
|
2391
|
+
{ pubkey: rewardInfo.tokenProgram, isSigner: false, isWritable: false },
|
|
2392
|
+
],
|
|
2393
|
+
data: encodeU64InstructionData(KAMINO_FARM_DISCRIMINATORS.harvestReward, params.rewardIndex),
|
|
2394
|
+
})
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
async function buildKaminoFarmInstruction(
|
|
2398
|
+
ix: KaminoFarmInstruction,
|
|
2399
|
+
buckets: InstructionBuckets,
|
|
2400
|
+
setupContext: StrategySetupContext,
|
|
2401
|
+
): Promise<void> {
|
|
2402
|
+
const farmContext = await resolveKaminoFarmContext(ix, setupContext)
|
|
2403
|
+
|
|
2404
|
+
switch (ix.action) {
|
|
2405
|
+
case KaminoFarmAction.INITIALIZE_USER: {
|
|
2406
|
+
if (farmContext.farm.isDelegated && !farmContext.delegatee.equals(setupContext.owner)) {
|
|
2407
|
+
throw new Error(`Delegated Kamino farm initialization is not supported for ${ix.farmState.toBase58()}`)
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
await ensureKaminoFarmUserSetup({ farmContext, buckets, setupContext })
|
|
2411
|
+
return
|
|
2412
|
+
}
|
|
2413
|
+
case KaminoFarmAction.STAKE: {
|
|
2414
|
+
if (farmContext.farm.isDelegated) {
|
|
2415
|
+
throw new Error(`Kamino farm ${ix.farmState.toBase58()} is delegated and cannot be staked directly`)
|
|
2416
|
+
}
|
|
2417
|
+
if (isAutoManagePositionsEnabled(setupContext)) {
|
|
2418
|
+
await ensureKaminoFarmUserSetup({ farmContext, buckets, setupContext })
|
|
2419
|
+
await ensureTrackedTokenAccountSetup({
|
|
2420
|
+
tokenMint: farmContext.farm.underlyingMint,
|
|
2421
|
+
tokenAccount: farmContext.sourceAta,
|
|
2422
|
+
tokenProgram: farmContext.farm.tokenProgram,
|
|
2423
|
+
buckets,
|
|
2424
|
+
setupContext,
|
|
2425
|
+
})
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
buckets.syncInstructions.push(
|
|
2429
|
+
buildKaminoFarmStakeInstructionRaw({
|
|
2430
|
+
owner: setupContext.owner,
|
|
2431
|
+
farmContext,
|
|
2432
|
+
amount: ix.amount,
|
|
2433
|
+
}),
|
|
2434
|
+
)
|
|
2435
|
+
|
|
2436
|
+
if (isAutoManagePositionsEnabled(setupContext)) {
|
|
2437
|
+
const state = await loadStrategySetupState(setupContext)
|
|
2438
|
+
if (state) {
|
|
2439
|
+
recordPlannedKaminoFarmPosition(state, {
|
|
2440
|
+
farmState: ix.farmState,
|
|
2441
|
+
userState: farmContext.userState,
|
|
2442
|
+
globalConfig: farmContext.farm.globalConfig,
|
|
2443
|
+
scopePrices: farmContext.scopePrices,
|
|
2444
|
+
})
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
return
|
|
2448
|
+
}
|
|
2449
|
+
case KaminoFarmAction.UNSTAKE: {
|
|
2450
|
+
if (farmContext.farm.isDelegated) {
|
|
2451
|
+
throw new Error(`Kamino farm ${ix.farmState.toBase58()} is delegated and cannot be unstaked directly`)
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
buckets.syncInstructions.push(
|
|
2455
|
+
buildKaminoFarmUnstakeInstructionRaw({
|
|
2456
|
+
owner: setupContext.owner,
|
|
2457
|
+
farmContext,
|
|
2458
|
+
stakeSharesScaled: ix.stakeSharesScaled,
|
|
2459
|
+
}),
|
|
2460
|
+
)
|
|
2461
|
+
return
|
|
2462
|
+
}
|
|
2463
|
+
case KaminoFarmAction.WITHDRAW_UNSTAKED_DEPOSITS: {
|
|
2464
|
+
if (farmContext.farm.isDelegated) {
|
|
2465
|
+
throw new Error(
|
|
2466
|
+
`Kamino farm ${ix.farmState.toBase58()} is delegated and cannot withdraw unstaked deposits directly`,
|
|
2467
|
+
)
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
buckets.syncInstructions.push(
|
|
2471
|
+
buildKaminoFarmWithdrawUnstakedDepositsRawInstruction({
|
|
2472
|
+
owner: setupContext.owner,
|
|
2473
|
+
farmContext,
|
|
2474
|
+
}),
|
|
2475
|
+
)
|
|
2476
|
+
return
|
|
2477
|
+
}
|
|
2478
|
+
case KaminoFarmAction.HARVEST_REWARD: {
|
|
2479
|
+
const rewardInfo = farmContext.farm.rewardInfos[ix.rewardIndex]
|
|
2480
|
+
if (!rewardInfo) {
|
|
2481
|
+
throw new Error(`Reward index ${ix.rewardIndex} is out of range for Kamino farm ${ix.farmState.toBase58()}`)
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
const rewardAta = getAssociatedTokenAddressSync(
|
|
2485
|
+
rewardInfo.rewardMint,
|
|
2486
|
+
setupContext.owner,
|
|
2487
|
+
true,
|
|
2488
|
+
rewardInfo.tokenProgram,
|
|
2489
|
+
)
|
|
2490
|
+
if (isAutoManagePositionsEnabled(setupContext)) {
|
|
2491
|
+
await ensureTrackedTokenAccountSetup({
|
|
2492
|
+
tokenMint: rewardInfo.rewardMint,
|
|
2493
|
+
tokenAccount: rewardAta,
|
|
2494
|
+
tokenProgram: rewardInfo.tokenProgram,
|
|
2495
|
+
buckets,
|
|
2496
|
+
setupContext,
|
|
2497
|
+
})
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
buckets.setupInstructions.push(
|
|
2501
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
2502
|
+
setupContext.signer,
|
|
2503
|
+
rewardAta,
|
|
2504
|
+
setupContext.owner,
|
|
2505
|
+
rewardInfo.rewardMint,
|
|
2506
|
+
rewardInfo.tokenProgram,
|
|
2507
|
+
),
|
|
2508
|
+
)
|
|
2509
|
+
buckets.syncInstructions.push(
|
|
2510
|
+
buildKaminoFarmHarvestRewardRawInstruction({
|
|
2511
|
+
owner: setupContext.owner,
|
|
2512
|
+
farmContext,
|
|
2513
|
+
rewardIndex: ix.rewardIndex,
|
|
2514
|
+
rewardAta,
|
|
2515
|
+
}),
|
|
2516
|
+
)
|
|
2517
|
+
return
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
|
|
1343
2523
|
async function buildTitanInstruction(
|
|
1344
2524
|
ix: TitanSwapInstruction,
|
|
1345
2525
|
buckets: InstructionBuckets,
|
|
@@ -1378,100 +2558,43 @@ async function buildTitanInstruction(
|
|
|
1378
2558
|
setupContext,
|
|
1379
2559
|
})
|
|
1380
2560
|
|
|
2561
|
+
if (ix.addressLookupTableAddresses?.length) {
|
|
2562
|
+
buckets.addressLookupTableAddresses.push(...ix.addressLookupTableAddresses)
|
|
2563
|
+
}
|
|
1381
2564
|
buckets.syncInstructions.push(ix.instruction)
|
|
1382
2565
|
}
|
|
1383
2566
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
const LOOPSCALE_DEPOSIT_STRATEGY_TOKEN_PROGRAM_INDEX = 8
|
|
1400
|
-
const LOOPSCALE_WITHDRAW_STRATEGY_MINT_INDEX = 4
|
|
1401
|
-
const LOOPSCALE_WITHDRAW_STRATEGY_LENDER_TA_INDEX = 6
|
|
1402
|
-
const LOOPSCALE_WITHDRAW_STRATEGY_TOKEN_PROGRAM_INDEX = 9
|
|
2567
|
+
/**
|
|
2568
|
+
* Loopscale account index mapping per instruction (from the Loopscale IDL).
|
|
2569
|
+
* Actions not listed here (create_loan, close_loan, update_weight_matrix,
|
|
2570
|
+
* create_strategy, close_strategy, lock_loan, unlock_loan, refinance_ledger,
|
|
2571
|
+
* update_strategy) have no token accounts to track — the on-chain hook handles
|
|
2572
|
+
* TrackLoopscaleLoan/UntrackLoopscaleLoan mutations.
|
|
2573
|
+
*/
|
|
2574
|
+
const LOOPSCALE_TOKEN_INDICES: Partial<Record<LoopscaleAction, { mint: number; account: number; program: number }>> = {
|
|
2575
|
+
[LoopscaleAction.DEPOSIT_COLLATERAL]: { mint: 6, account: 4, program: 9 },
|
|
2576
|
+
[LoopscaleAction.BORROW_PRINCIPAL]: { mint: 6, account: 7, program: 10 },
|
|
2577
|
+
[LoopscaleAction.REPAY_PRINCIPAL]: { mint: 6, account: 7, program: 10 },
|
|
2578
|
+
[LoopscaleAction.WITHDRAW_COLLATERAL]: { mint: 7, account: 4, program: 8 },
|
|
2579
|
+
[LoopscaleAction.DEPOSIT_STRATEGY]: { mint: 4, account: 6, program: 8 },
|
|
2580
|
+
[LoopscaleAction.WITHDRAW_STRATEGY]: { mint: 4, account: 6, program: 9 },
|
|
2581
|
+
}
|
|
1403
2582
|
|
|
1404
|
-
/** Build a single Loopscale instruction (loan or strategy). Extracts token accounts for tracking. */
|
|
1405
|
-
async function buildLoopscaleInstruction(
|
|
1406
|
-
ix: LoopscaleInstruction,
|
|
1407
|
-
buckets: InstructionBuckets,
|
|
1408
|
-
setupContext: StrategySetupContext,
|
|
1409
|
-
): Promise<void> {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
await ensureTrackedTokenAccountSetup({ tokenMint, tokenAccount, tokenProgram, buckets, setupContext })
|
|
1419
|
-
break
|
|
1420
|
-
}
|
|
1421
|
-
case LoopscaleAction.BORROW_PRINCIPAL: {
|
|
1422
|
-
const tokenMint = ix.instruction.keys[LOOPSCALE_BORROW_PRINCIPAL_MINT_INDEX]?.pubkey
|
|
1423
|
-
const tokenAccount = ix.instruction.keys[LOOPSCALE_BORROW_PRINCIPAL_BORROWER_TA_INDEX]?.pubkey
|
|
1424
|
-
const tokenProgram = ix.instruction.keys[LOOPSCALE_BORROW_PRINCIPAL_TOKEN_PROGRAM_INDEX]?.pubkey
|
|
1425
|
-
if (!tokenMint || !tokenAccount || !tokenProgram) {
|
|
1426
|
-
throw new Error("Loopscale borrow_principal instruction is missing expected token accounts")
|
|
1427
|
-
}
|
|
1428
|
-
await ensureTrackedTokenAccountSetup({ tokenMint, tokenAccount, tokenProgram, buckets, setupContext })
|
|
1429
|
-
break
|
|
1430
|
-
}
|
|
1431
|
-
case LoopscaleAction.REPAY_PRINCIPAL: {
|
|
1432
|
-
const tokenMint = ix.instruction.keys[LOOPSCALE_REPAY_PRINCIPAL_MINT_INDEX]?.pubkey
|
|
1433
|
-
const tokenAccount = ix.instruction.keys[LOOPSCALE_REPAY_PRINCIPAL_BORROWER_TA_INDEX]?.pubkey
|
|
1434
|
-
const tokenProgram = ix.instruction.keys[LOOPSCALE_REPAY_PRINCIPAL_TOKEN_PROGRAM_INDEX]?.pubkey
|
|
1435
|
-
if (!tokenMint || !tokenAccount || !tokenProgram) {
|
|
1436
|
-
throw new Error("Loopscale repay_principal instruction is missing expected token accounts")
|
|
1437
|
-
}
|
|
1438
|
-
await ensureTrackedTokenAccountSetup({ tokenMint, tokenAccount, tokenProgram, buckets, setupContext })
|
|
1439
|
-
break
|
|
1440
|
-
}
|
|
1441
|
-
case LoopscaleAction.WITHDRAW_COLLATERAL: {
|
|
1442
|
-
const tokenMint = ix.instruction.keys[LOOPSCALE_WITHDRAW_COLLATERAL_MINT_INDEX]?.pubkey
|
|
1443
|
-
const tokenAccount = ix.instruction.keys[LOOPSCALE_WITHDRAW_COLLATERAL_BORROWER_TA_INDEX]?.pubkey
|
|
1444
|
-
const tokenProgram = ix.instruction.keys[LOOPSCALE_WITHDRAW_COLLATERAL_TOKEN_PROGRAM_INDEX]?.pubkey
|
|
1445
|
-
if (!tokenMint || !tokenAccount || !tokenProgram) {
|
|
1446
|
-
throw new Error("Loopscale withdraw_collateral instruction is missing expected token accounts")
|
|
1447
|
-
}
|
|
1448
|
-
await ensureTrackedTokenAccountSetup({ tokenMint, tokenAccount, tokenProgram, buckets, setupContext })
|
|
1449
|
-
break
|
|
1450
|
-
}
|
|
1451
|
-
// create_loan, close_loan, and update_weight_matrix have no token accounts to track —
|
|
1452
|
-
// the on-chain hook handles TrackLoopscaleLoan/UntrackLoopscaleLoan mutations.
|
|
1453
|
-
// update_weight_matrix only has 3 accounts (bs_auth, borrower, loan).
|
|
1454
|
-
case LoopscaleAction.DEPOSIT_STRATEGY: {
|
|
1455
|
-
const tokenMint = ix.instruction.keys[LOOPSCALE_DEPOSIT_STRATEGY_MINT_INDEX]?.pubkey
|
|
1456
|
-
const tokenAccount = ix.instruction.keys[LOOPSCALE_DEPOSIT_STRATEGY_LENDER_TA_INDEX]?.pubkey
|
|
1457
|
-
const tokenProgram = ix.instruction.keys[LOOPSCALE_DEPOSIT_STRATEGY_TOKEN_PROGRAM_INDEX]?.pubkey
|
|
1458
|
-
if (!tokenMint || !tokenAccount || !tokenProgram) {
|
|
1459
|
-
throw new Error("Loopscale deposit_strategy instruction is missing expected token accounts")
|
|
1460
|
-
}
|
|
1461
|
-
await ensureTrackedTokenAccountSetup({ tokenMint, tokenAccount, tokenProgram, buckets, setupContext })
|
|
1462
|
-
break
|
|
1463
|
-
}
|
|
1464
|
-
case LoopscaleAction.WITHDRAW_STRATEGY: {
|
|
1465
|
-
const tokenMint = ix.instruction.keys[LOOPSCALE_WITHDRAW_STRATEGY_MINT_INDEX]?.pubkey
|
|
1466
|
-
const tokenAccount = ix.instruction.keys[LOOPSCALE_WITHDRAW_STRATEGY_LENDER_TA_INDEX]?.pubkey
|
|
1467
|
-
const tokenProgram = ix.instruction.keys[LOOPSCALE_WITHDRAW_STRATEGY_TOKEN_PROGRAM_INDEX]?.pubkey
|
|
1468
|
-
if (!tokenMint || !tokenAccount || !tokenProgram) {
|
|
1469
|
-
throw new Error("Loopscale withdraw_strategy instruction is missing expected token accounts")
|
|
1470
|
-
}
|
|
1471
|
-
await ensureTrackedTokenAccountSetup({ tokenMint, tokenAccount, tokenProgram, buckets, setupContext })
|
|
1472
|
-
break
|
|
2583
|
+
/** Build a single Loopscale instruction (loan or strategy). Extracts token accounts for tracking. */
|
|
2584
|
+
async function buildLoopscaleInstruction(
|
|
2585
|
+
ix: LoopscaleInstruction,
|
|
2586
|
+
buckets: InstructionBuckets,
|
|
2587
|
+
setupContext: StrategySetupContext,
|
|
2588
|
+
): Promise<void> {
|
|
2589
|
+
const indices = LOOPSCALE_TOKEN_INDICES[ix.action]
|
|
2590
|
+
if (indices) {
|
|
2591
|
+
const tokenMint = ix.instruction.keys[indices.mint]?.pubkey
|
|
2592
|
+
const tokenAccount = ix.instruction.keys[indices.account]?.pubkey
|
|
2593
|
+
const tokenProgram = ix.instruction.keys[indices.program]?.pubkey
|
|
2594
|
+
if (!tokenMint || !tokenAccount || !tokenProgram) {
|
|
2595
|
+
throw new Error(`Loopscale ${ix.action} instruction is missing expected token accounts`)
|
|
1473
2596
|
}
|
|
1474
|
-
|
|
2597
|
+
await ensureTrackedTokenAccountSetup({ tokenMint, tokenAccount, tokenProgram, buckets, setupContext })
|
|
1475
2598
|
}
|
|
1476
2599
|
|
|
1477
2600
|
buckets.syncInstructions.push(ix.instruction)
|
|
@@ -1513,6 +2636,45 @@ async function buildOrderbookInstruction(
|
|
|
1513
2636
|
// Action Builders (one per VaultAction)
|
|
1514
2637
|
// ============================================================================
|
|
1515
2638
|
|
|
2639
|
+
function createKaminoInitUserMetadataInstruction(owner: PublicKey): TransactionInstruction {
|
|
2640
|
+
const userMetadata = getKaminoUserMetadata(owner, KAMINO_LENDING_PROGRAM_ID)
|
|
2641
|
+
return initUserMetadata(
|
|
2642
|
+
{ userLookupTable: PublicKey.default },
|
|
2643
|
+
{
|
|
2644
|
+
owner,
|
|
2645
|
+
feePayer: owner,
|
|
2646
|
+
userMetadata,
|
|
2647
|
+
referrerUserMetadata: KAMINO_LENDING_PROGRAM_ID,
|
|
2648
|
+
rent: SYSVAR_RENT_PUBKEY,
|
|
2649
|
+
systemProgram: SystemProgram.programId,
|
|
2650
|
+
},
|
|
2651
|
+
)
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
function createKaminoInitObligationInstruction(params: {
|
|
2655
|
+
market: KaminoMarket
|
|
2656
|
+
owner: PublicKey
|
|
2657
|
+
}): TransactionInstruction {
|
|
2658
|
+
const lendingMarket = KAMINO_MARKETS[params.market]
|
|
2659
|
+
const obligation = getKaminoLendObligation(params.owner, lendingMarket, KAMINO_LENDING_PROGRAM_ID)
|
|
2660
|
+
const userMetadata = getKaminoUserMetadata(params.owner, KAMINO_LENDING_PROGRAM_ID)
|
|
2661
|
+
|
|
2662
|
+
return initObligation(
|
|
2663
|
+
{ args: { tag: 0, id: 0 } },
|
|
2664
|
+
{
|
|
2665
|
+
obligationOwner: params.owner,
|
|
2666
|
+
feePayer: params.owner,
|
|
2667
|
+
obligation,
|
|
2668
|
+
lendingMarket,
|
|
2669
|
+
seed1Account: SystemProgram.programId,
|
|
2670
|
+
seed2Account: SystemProgram.programId,
|
|
2671
|
+
ownerUserMetadata: userMetadata,
|
|
2672
|
+
rent: SYSVAR_RENT_PUBKEY,
|
|
2673
|
+
systemProgram: SystemProgram.programId,
|
|
2674
|
+
},
|
|
2675
|
+
)
|
|
2676
|
+
}
|
|
2677
|
+
|
|
1516
2678
|
async function buildInitUserMetadata(
|
|
1517
2679
|
owner: PublicKey,
|
|
1518
2680
|
connection: Connection,
|
|
@@ -1522,49 +2684,317 @@ async function buildInitUserMetadata(
|
|
|
1522
2684
|
const userMetadataAccount = await connection.getAccountInfo(userMetadata)
|
|
1523
2685
|
if (userMetadataAccount) return
|
|
1524
2686
|
|
|
1525
|
-
syncInstructions.push(
|
|
1526
|
-
initUserMetadata(
|
|
1527
|
-
{ userLookupTable: PublicKey.default },
|
|
1528
|
-
{
|
|
1529
|
-
owner,
|
|
1530
|
-
feePayer: owner,
|
|
1531
|
-
userMetadata,
|
|
1532
|
-
referrerUserMetadata: KAMINO_LENDING_PROGRAM_ID,
|
|
1533
|
-
rent: SYSVAR_RENT_PUBKEY,
|
|
1534
|
-
systemProgram: SystemProgram.programId,
|
|
1535
|
-
},
|
|
1536
|
-
),
|
|
1537
|
-
)
|
|
2687
|
+
syncInstructions.push(createKaminoInitUserMetadataInstruction(owner))
|
|
1538
2688
|
}
|
|
1539
2689
|
|
|
1540
2690
|
async function buildInitObligation(
|
|
1541
2691
|
ix: MarketInstruction,
|
|
1542
2692
|
owner: PublicKey,
|
|
1543
2693
|
connection: Connection,
|
|
1544
|
-
{ syncInstructions }: InstructionBuckets,
|
|
2694
|
+
{ syncInstructions, postInstructions }: InstructionBuckets,
|
|
2695
|
+
setupContext?: StrategySetupContext,
|
|
1545
2696
|
) {
|
|
1546
2697
|
const lendingMarket = KAMINO_MARKETS[ix.market]
|
|
1547
2698
|
const obligation = getKaminoLendObligation(owner, lendingMarket, KAMINO_LENDING_PROGRAM_ID)
|
|
1548
|
-
const userMetadata = getKaminoUserMetadata(owner, KAMINO_LENDING_PROGRAM_ID)
|
|
1549
2699
|
const obligationAccount = await connection.getAccountInfo(obligation)
|
|
1550
2700
|
if (obligationAccount) return
|
|
1551
2701
|
|
|
1552
|
-
syncInstructions.push(
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
2702
|
+
syncInstructions.push(createKaminoInitObligationInstruction({ market: ix.market, owner }))
|
|
2703
|
+
if (setupContext && isAutoManagePositionsEnabled(setupContext)) {
|
|
2704
|
+
await queueKaminoObligationTrackingAfterInit({
|
|
2705
|
+
market: ix.market,
|
|
2706
|
+
obligation,
|
|
2707
|
+
postInstructions,
|
|
2708
|
+
setupContext,
|
|
2709
|
+
})
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
async function queueKaminoObligationTrackingAfterInit(params: {
|
|
2714
|
+
market: KaminoMarket
|
|
2715
|
+
obligation: PublicKey
|
|
2716
|
+
postInstructions: TransactionInstruction[]
|
|
2717
|
+
setupContext: StrategySetupContext
|
|
2718
|
+
}) {
|
|
2719
|
+
const state = await loadStrategySetupState(params.setupContext)
|
|
2720
|
+
if (!state || state.trackedKaminoObligations.has(params.obligation.toBase58())) {
|
|
2721
|
+
return
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
const quotePath = resolveBestKaminoQuotePath({
|
|
2725
|
+
prices: state.prices,
|
|
2726
|
+
vaultUnderlyingMint: state.strategyVault.state.underlyingMint,
|
|
2727
|
+
})
|
|
2728
|
+
trackRequiredPriceIds(state.requiredPriceIds, quotePath.quotePriceId)
|
|
2729
|
+
const remainingAccountsAmount = 1n
|
|
2730
|
+
const obligationEntry: exponentVaults.KaminoObligationEntry = {
|
|
2731
|
+
obligation: params.obligation,
|
|
2732
|
+
lendingProgramId: KAMINO_LENDING_PROGRAM_ID,
|
|
2733
|
+
quotePriceId: quotePath.quotePriceId,
|
|
2734
|
+
reservePriceMappings: [],
|
|
2735
|
+
reserveFarmMappings: [],
|
|
2736
|
+
minPriceStatusFlags: 0,
|
|
2737
|
+
}
|
|
2738
|
+
params.postInstructions.push(
|
|
2739
|
+
state.strategyVault.ixWrapperManagerUpdatePosition({
|
|
2740
|
+
manager: params.setupContext.signer,
|
|
2741
|
+
update: exponentVaults.positionUpdate("AddKaminoObligationEntry", [obligationEntry] as [exponentVaults.KaminoObligationEntry]),
|
|
2742
|
+
remainingAccounts: buildTrackedAumRemainingAccounts(state, [
|
|
2743
|
+
{ pubkey: params.obligation, isSigner: false, isWritable: false },
|
|
2744
|
+
]),
|
|
2745
|
+
}),
|
|
2746
|
+
)
|
|
2747
|
+
|
|
2748
|
+
recordPlannedKaminoObligation(state, {
|
|
2749
|
+
obligation: params.obligation,
|
|
2750
|
+
quotePriceId: quotePath.quotePriceId,
|
|
2751
|
+
quoteInputMint: quotePath.quoteInputMint,
|
|
2752
|
+
reservePriceMappings: [],
|
|
2753
|
+
remainingAccountsAmount,
|
|
2754
|
+
minPriceStatusFlags: 0,
|
|
2755
|
+
})
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
async function ensureKaminoObligationSetup(params: {
|
|
2759
|
+
ix: ReserveInstruction
|
|
2760
|
+
reserveContext: ReserveContext
|
|
2761
|
+
buckets: InstructionBuckets
|
|
2762
|
+
setupContext?: StrategySetupContext
|
|
2763
|
+
}) {
|
|
2764
|
+
const { ix, reserveContext, buckets, setupContext } = params
|
|
2765
|
+
if (!setupContext || !isAutoManagePositionsEnabled(setupContext)) {
|
|
2766
|
+
return
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
const state = await loadStrategySetupState(setupContext)
|
|
2770
|
+
if (!state) {
|
|
2771
|
+
return
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// Ensure user metadata exists
|
|
2775
|
+
const userMetadata = getKaminoUserMetadata(setupContext.owner, KAMINO_LENDING_PROGRAM_ID)
|
|
2776
|
+
if (!(await accountExists(state, setupContext.connection, userMetadata))) {
|
|
2777
|
+
buckets.setupInstructions.push(
|
|
2778
|
+
await wrapVaultSignedSetupInstruction({
|
|
2779
|
+
instruction: createKaminoInitUserMetadataInstruction(setupContext.owner),
|
|
2780
|
+
setupContext,
|
|
2781
|
+
}),
|
|
2782
|
+
)
|
|
2783
|
+
state.existingAccounts.set(userMetadata.toBase58(), true)
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
// Ensure obligation exists and is tracked
|
|
2787
|
+
const obligationKey = reserveContext.obligation.toBase58()
|
|
2788
|
+
const obligationExistsOnChain = await accountExists(state, setupContext.connection, reserveContext.obligation)
|
|
2789
|
+
const trackedObligation = state.trackedKaminoObligations.get(obligationKey)
|
|
2790
|
+
|
|
2791
|
+
if (!trackedObligation) {
|
|
2792
|
+
if (obligationExistsOnChain) {
|
|
2793
|
+
throw new Error(
|
|
2794
|
+
`Kamino obligation ${obligationKey} already exists on-chain but is not tracked on vault ${state.strategyVault.selfAddress.toBase58()}. Automatic repair is disabled; manually add the Kamino obligation entry.`,
|
|
2795
|
+
)
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
buckets.setupInstructions.push(
|
|
2799
|
+
await wrapVaultSignedSetupInstruction({
|
|
2800
|
+
instruction: createKaminoInitObligationInstruction({ market: ix.market, owner: setupContext.owner }),
|
|
2801
|
+
setupContext,
|
|
2802
|
+
}),
|
|
2803
|
+
)
|
|
2804
|
+
state.existingAccounts.set(obligationKey, true)
|
|
2805
|
+
|
|
2806
|
+
const quotePath = resolveBestKaminoQuotePath({
|
|
2807
|
+
prices: state.prices,
|
|
2808
|
+
vaultUnderlyingMint: state.strategyVault.state.underlyingMint,
|
|
2809
|
+
})
|
|
2810
|
+
const reservePriceMapping = {
|
|
2811
|
+
reserve: reserveContext.reservePubkey,
|
|
2812
|
+
reservePriceId: resolveKaminoReservePriceIdOrThrow({
|
|
2813
|
+
prices: state.prices,
|
|
2814
|
+
reserveMint: reserveContext.reserveAccount.liquidity.mintPubkey,
|
|
2815
|
+
quoteInputMint: quotePath.quoteInputMint,
|
|
2816
|
+
}),
|
|
2817
|
+
}
|
|
2818
|
+
trackRequiredPriceIds(state.requiredPriceIds, quotePath.quotePriceId)
|
|
2819
|
+
trackRequiredPriceIds(state.requiredPriceIds, reservePriceMapping.reservePriceId)
|
|
2820
|
+
const remainingAccountsAmount = BigInt(1 + 1)
|
|
2821
|
+
const obligationEntry: exponentVaults.KaminoObligationEntry = {
|
|
2822
|
+
obligation: reserveContext.obligation,
|
|
2823
|
+
lendingProgramId: KAMINO_LENDING_PROGRAM_ID,
|
|
2824
|
+
quotePriceId: quotePath.quotePriceId,
|
|
2825
|
+
reservePriceMappings: [reservePriceMapping],
|
|
2826
|
+
reserveFarmMappings: [],
|
|
2827
|
+
minPriceStatusFlags: 0,
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
buckets.setupInstructions.push(
|
|
2831
|
+
state.strategyVault.ixWrapperManagerUpdatePosition({
|
|
2832
|
+
manager: setupContext.signer,
|
|
2833
|
+
update: exponentVaults.positionUpdate("AddKaminoObligationEntry", [obligationEntry] as [exponentVaults.KaminoObligationEntry]),
|
|
2834
|
+
remainingAccounts: buildTrackedAumRemainingAccounts(state, [
|
|
2835
|
+
{ pubkey: reserveContext.obligation, isSigner: false, isWritable: false },
|
|
2836
|
+
{ pubkey: reserveContext.reservePubkey, isSigner: false, isWritable: false },
|
|
2837
|
+
]),
|
|
2838
|
+
}),
|
|
2839
|
+
)
|
|
2840
|
+
|
|
2841
|
+
recordPlannedKaminoObligation(state, {
|
|
2842
|
+
obligation: reserveContext.obligation,
|
|
2843
|
+
quotePriceId: quotePath.quotePriceId,
|
|
2844
|
+
quoteInputMint: quotePath.quoteInputMint,
|
|
2845
|
+
reservePriceMappings: [reservePriceMapping],
|
|
2846
|
+
remainingAccountsAmount,
|
|
2847
|
+
minPriceStatusFlags: 0,
|
|
2848
|
+
})
|
|
2849
|
+
return
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
// Obligation exists and is tracked — ensure the reserve is mapped
|
|
2853
|
+
await ensureKaminoReserveMapped({
|
|
2854
|
+
state,
|
|
2855
|
+
reserveContext,
|
|
2856
|
+
setupContext,
|
|
2857
|
+
buckets,
|
|
2858
|
+
trackedObligation,
|
|
2859
|
+
})
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
/** Ensure a Kamino reserve is mapped on an already-tracked obligation. */
|
|
2863
|
+
async function ensureKaminoReserveMapped(params: {
|
|
2864
|
+
state: StrategySetupState
|
|
2865
|
+
reserveContext: ReserveContext
|
|
2866
|
+
setupContext: StrategySetupContext
|
|
2867
|
+
buckets: InstructionBuckets
|
|
2868
|
+
trackedObligation: TrackedKaminoObligationState
|
|
2869
|
+
}) {
|
|
2870
|
+
const { state, reserveContext, setupContext, buckets, trackedObligation } = params
|
|
2871
|
+
|
|
2872
|
+
if (trackedObligation.mappedReserves.has(reserveContext.reservePubkey.toBase58())) {
|
|
2873
|
+
return
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
const reservePriceMapping = {
|
|
2877
|
+
reserve: reserveContext.reservePubkey,
|
|
2878
|
+
reservePriceId: resolveKaminoReservePriceIdOrThrow({
|
|
2879
|
+
prices: state.prices,
|
|
2880
|
+
reserveMint: reserveContext.reserveAccount.liquidity.mintPubkey,
|
|
2881
|
+
quoteInputMint: trackedObligation.quoteInputMint,
|
|
2882
|
+
}),
|
|
2883
|
+
}
|
|
2884
|
+
trackRequiredPriceIds(state.requiredPriceIds, reservePriceMapping.reservePriceId)
|
|
2885
|
+
|
|
2886
|
+
buckets.setupInstructions.push(...await buildKaminoPositionFreshnessInstructions({
|
|
2887
|
+
reservePubkey: reserveContext.reservePubkey,
|
|
2888
|
+
reserveAccount: reserveContext.reserveAccount,
|
|
2889
|
+
lendingMarket: reserveContext.lendingMarket,
|
|
2890
|
+
obligation: reserveContext.obligation,
|
|
2891
|
+
connection: setupContext.connection,
|
|
2892
|
+
}))
|
|
2893
|
+
|
|
2894
|
+
buckets.setupInstructions.push(
|
|
2895
|
+
state.strategyVault.ixWrapperManagerUpdatePosition({
|
|
2896
|
+
manager: setupContext.signer,
|
|
2897
|
+
update: exponentVaults.positionUpdate("UpsertKaminoObligationReservePriceMappings", {
|
|
2898
|
+
obligation: reserveContext.obligation,
|
|
2899
|
+
reservePriceMappings: [reservePriceMapping],
|
|
2900
|
+
reserveFarmMappings: [],
|
|
2901
|
+
}),
|
|
2902
|
+
remainingAccounts: buildTrackedAumRemainingAccounts(state, [
|
|
2903
|
+
{ pubkey: reserveContext.reservePubkey, isSigner: false, isWritable: false },
|
|
2904
|
+
]),
|
|
2905
|
+
}),
|
|
2906
|
+
)
|
|
2907
|
+
|
|
2908
|
+
recordPlannedKaminoReserveMappings(state, {
|
|
2909
|
+
obligation: reserveContext.obligation,
|
|
2910
|
+
quoteInputMint: trackedObligation.quoteInputMint,
|
|
2911
|
+
reservePriceMappings: [reservePriceMapping],
|
|
2912
|
+
})
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
async function buildKaminoPositionFreshnessInstructions({
|
|
2916
|
+
reservePubkey,
|
|
2917
|
+
reserveAccount,
|
|
2918
|
+
lendingMarket,
|
|
2919
|
+
obligation,
|
|
2920
|
+
connection,
|
|
2921
|
+
}: {
|
|
2922
|
+
reservePubkey: PublicKey
|
|
2923
|
+
reserveAccount: Reserve
|
|
2924
|
+
lendingMarket: PublicKey
|
|
2925
|
+
obligation: PublicKey
|
|
2926
|
+
connection: Connection
|
|
2927
|
+
}): Promise<TransactionInstruction[]> {
|
|
2928
|
+
const instructions: TransactionInstruction[] = []
|
|
2929
|
+
const defaultKey = PublicKey.default
|
|
2930
|
+
const oracleOrSentinel = (key: PublicKey) =>
|
|
2931
|
+
key.equals(defaultKey) ? KAMINO_LENDING_PROGRAM_ID : key
|
|
2932
|
+
|
|
2933
|
+
const obligationState = await Obligation.fetch(connection, obligation)
|
|
2934
|
+
const otherReservePubkeys: PublicKey[] = []
|
|
2935
|
+
if (obligationState) {
|
|
2936
|
+
for (const deposit of obligationState.deposits) {
|
|
2937
|
+
if (!deposit.depositReserve.equals(defaultKey) && !deposit.depositReserve.equals(reservePubkey)) {
|
|
2938
|
+
otherReservePubkeys.push(deposit.depositReserve)
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
for (const borrow of obligationState.borrows) {
|
|
2942
|
+
if (!borrow.borrowReserve.equals(defaultKey) && !borrow.borrowReserve.equals(reservePubkey)) {
|
|
2943
|
+
otherReservePubkeys.push(borrow.borrowReserve)
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
const otherReserves: Array<{ pubkey: PublicKey; account: Reserve }> = []
|
|
2949
|
+
for (const otherReservePubkey of otherReservePubkeys) {
|
|
2950
|
+
const otherReserveAccount = await Reserve.fetch(connection, otherReservePubkey)
|
|
2951
|
+
if (!otherReserveAccount) {
|
|
2952
|
+
continue
|
|
2953
|
+
}
|
|
2954
|
+
otherReserves.push({ pubkey: otherReservePubkey, account: otherReserveAccount })
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
for (const { pubkey, account } of otherReserves) {
|
|
2958
|
+
const tokenInfo = account.config.tokenInfo
|
|
2959
|
+
instructions.push(
|
|
2960
|
+
refreshReserve({
|
|
2961
|
+
reserve: pubkey,
|
|
1559
2962
|
lendingMarket,
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
2963
|
+
pythOracle: oracleOrSentinel(tokenInfo.pythConfiguration.price),
|
|
2964
|
+
switchboardPriceOracle: oracleOrSentinel(tokenInfo.switchboardConfiguration.priceAggregator),
|
|
2965
|
+
switchboardTwapOracle: oracleOrSentinel(tokenInfo.switchboardConfiguration.twapAggregator),
|
|
2966
|
+
scopePrices: oracleOrSentinel(tokenInfo.scopeConfiguration.priceFeed),
|
|
2967
|
+
}),
|
|
2968
|
+
)
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
const tokenInfo = reserveAccount.config.tokenInfo
|
|
2972
|
+
instructions.push(
|
|
2973
|
+
refreshReserve({
|
|
2974
|
+
reserve: reservePubkey,
|
|
2975
|
+
lendingMarket,
|
|
2976
|
+
pythOracle: oracleOrSentinel(tokenInfo.pythConfiguration.price),
|
|
2977
|
+
switchboardPriceOracle: oracleOrSentinel(tokenInfo.switchboardConfiguration.priceAggregator),
|
|
2978
|
+
switchboardTwapOracle: oracleOrSentinel(tokenInfo.switchboardConfiguration.twapAggregator),
|
|
2979
|
+
scopePrices: oracleOrSentinel(tokenInfo.scopeConfiguration.priceFeed),
|
|
2980
|
+
}),
|
|
1567
2981
|
)
|
|
2982
|
+
|
|
2983
|
+
const refreshObligationIx = refreshObligation({ lendingMarket, obligation })
|
|
2984
|
+
if (obligationState) {
|
|
2985
|
+
const depositReserves = obligationState.deposits
|
|
2986
|
+
.map((deposit) => deposit.depositReserve)
|
|
2987
|
+
.filter((reserve) => !reserve.equals(defaultKey))
|
|
2988
|
+
const borrowReserves = obligationState.borrows
|
|
2989
|
+
.map((borrow) => borrow.borrowReserve)
|
|
2990
|
+
.filter((reserve) => !reserve.equals(defaultKey))
|
|
2991
|
+
for (const reserve of [...depositReserves, ...borrowReserves]) {
|
|
2992
|
+
refreshObligationIx.keys.push({ pubkey: reserve, isSigner: false, isWritable: false })
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
instructions.push(refreshObligationIx)
|
|
2996
|
+
|
|
2997
|
+
return instructions
|
|
1568
2998
|
}
|
|
1569
2999
|
|
|
1570
3000
|
async function buildDeposit(
|
|
@@ -1573,8 +3003,10 @@ async function buildDeposit(
|
|
|
1573
3003
|
connection: Connection,
|
|
1574
3004
|
signer: PublicKey,
|
|
1575
3005
|
{ setupInstructions, syncInstructions, preInstructions, postInstructions }: InstructionBuckets,
|
|
3006
|
+
setupContext?: StrategySetupContext,
|
|
1576
3007
|
) {
|
|
1577
3008
|
const ctx = await resolveReserveContext(ix, owner, connection)
|
|
3009
|
+
await ensureKaminoObligationSetup({ ix, reserveContext: ctx, buckets: { setupInstructions, syncInstructions, preInstructions, postInstructions, signers: [], addressLookupTableAddresses: [] }, setupContext })
|
|
1578
3010
|
const refreshes = await buildRefreshInstructions({
|
|
1579
3011
|
...ctx, owner, farmMode: FARM_COLLATERAL, signer, connection, needsScopeRefresh: false,
|
|
1580
3012
|
})
|
|
@@ -1628,8 +3060,10 @@ async function buildWithdraw(
|
|
|
1628
3060
|
connection: Connection,
|
|
1629
3061
|
signer: PublicKey,
|
|
1630
3062
|
{ setupInstructions, syncInstructions, preInstructions, postInstructions }: InstructionBuckets,
|
|
3063
|
+
setupContext?: StrategySetupContext,
|
|
1631
3064
|
) {
|
|
1632
3065
|
const ctx = await resolveReserveContext(ix, owner, connection)
|
|
3066
|
+
await ensureKaminoObligationSetup({ ix, reserveContext: ctx, buckets: { setupInstructions, syncInstructions, preInstructions, postInstructions, signers: [], addressLookupTableAddresses: [] }, setupContext })
|
|
1633
3067
|
const refreshes = await buildRefreshInstructions({
|
|
1634
3068
|
...ctx, owner, farmMode: FARM_COLLATERAL, signer, connection, needsScopeRefresh: true,
|
|
1635
3069
|
})
|
|
@@ -1690,8 +3124,10 @@ async function buildBorrow(
|
|
|
1690
3124
|
connection: Connection,
|
|
1691
3125
|
signer: PublicKey,
|
|
1692
3126
|
{ setupInstructions, syncInstructions, preInstructions, postInstructions }: InstructionBuckets,
|
|
3127
|
+
setupContext?: StrategySetupContext,
|
|
1693
3128
|
) {
|
|
1694
3129
|
const ctx = await resolveReserveContext(ix, owner, connection)
|
|
3130
|
+
await ensureKaminoObligationSetup({ ix, reserveContext: ctx, buckets: { setupInstructions, syncInstructions, preInstructions, postInstructions, signers: [], addressLookupTableAddresses: [] }, setupContext })
|
|
1695
3131
|
const refreshes = await buildRefreshInstructions({
|
|
1696
3132
|
...ctx, owner, farmMode: FARM_DEBT, signer, connection, needsScopeRefresh: true,
|
|
1697
3133
|
})
|
|
@@ -1743,8 +3179,10 @@ async function buildRepay(
|
|
|
1743
3179
|
connection: Connection,
|
|
1744
3180
|
signer: PublicKey,
|
|
1745
3181
|
{ setupInstructions, syncInstructions, preInstructions, postInstructions }: InstructionBuckets,
|
|
3182
|
+
setupContext?: StrategySetupContext,
|
|
1746
3183
|
) {
|
|
1747
3184
|
const ctx = await resolveReserveContext(ix, owner, connection)
|
|
3185
|
+
await ensureKaminoObligationSetup({ ix, reserveContext: ctx, buckets: { setupInstructions, syncInstructions, preInstructions, postInstructions, signers: [], addressLookupTableAddresses: [] }, setupContext })
|
|
1748
3186
|
const refreshes = await buildRefreshInstructions({
|
|
1749
3187
|
...ctx, owner, farmMode: FARM_DEBT, signer, connection, needsScopeRefresh: false,
|
|
1750
3188
|
})
|
|
@@ -2023,17 +3461,13 @@ export const orderbookAction = {
|
|
|
2023
3461
|
/**
|
|
2024
3462
|
* Post a limit order on the orderbook.
|
|
2025
3463
|
* @param params - Order parameters
|
|
2026
|
-
* @param params.offerIdx - Required offer index for position tracking. Must be unique per trader.
|
|
2027
3464
|
*/
|
|
2028
3465
|
postOffer(params: {
|
|
2029
3466
|
orderbook: PublicKey
|
|
2030
3467
|
direction: OrderbookTradeDirection
|
|
2031
3468
|
priceApy: number
|
|
2032
3469
|
amount: bigint
|
|
2033
|
-
/** Required offer index for position tracking. Must be unique per trader. */
|
|
2034
|
-
offerIdx: number
|
|
2035
3470
|
offerOption?: OrderbookOfferOption
|
|
2036
|
-
virtualOffer?: boolean
|
|
2037
3471
|
expirySeconds?: number
|
|
2038
3472
|
mode?: OrderbookInstructionMode
|
|
2039
3473
|
}): OrderbookPostOfferInstruction {
|
|
@@ -2044,9 +3478,7 @@ export const orderbookAction = {
|
|
|
2044
3478
|
direction: params.direction,
|
|
2045
3479
|
priceApy: params.priceApy,
|
|
2046
3480
|
amount: params.amount,
|
|
2047
|
-
offerIdx: params.offerIdx,
|
|
2048
3481
|
offerOption: params.offerOption,
|
|
2049
|
-
virtualOffer: params.virtualOffer,
|
|
2050
3482
|
expirySeconds: params.expirySeconds,
|
|
2051
3483
|
}
|
|
2052
3484
|
},
|
|
@@ -2237,7 +3669,8 @@ export const coreAction = {
|
|
|
2237
3669
|
|
|
2238
3670
|
/**
|
|
2239
3671
|
* Initialize yield position for the Squads vault (owner).
|
|
2240
|
-
* Required before buying YT or depositing YT.
|
|
3672
|
+
* Required before buying YT or depositing YT. When `autoManagePositions`
|
|
3673
|
+
* is enabled, the SDK also tracks the new yield position automatically.
|
|
2241
3674
|
*/
|
|
2242
3675
|
initializeYieldPosition(params: { vault: PublicKey }): CoreInitializeYieldPositionInstruction {
|
|
2243
3676
|
return {
|
|
@@ -2311,7 +3744,7 @@ export async function createOrderbookSyncTransaction({
|
|
|
2311
3744
|
owner: PublicKey
|
|
2312
3745
|
connection: Connection
|
|
2313
3746
|
policyPda?: PublicKey
|
|
2314
|
-
vaultPda
|
|
3747
|
+
vaultPda?: PublicKey
|
|
2315
3748
|
signer: PublicKey
|
|
2316
3749
|
accountIndex?: number
|
|
2317
3750
|
constraintIndices?: number[]
|
|
@@ -2323,6 +3756,7 @@ export async function createOrderbookSyncTransaction({
|
|
|
2323
3756
|
squadsProgram?: PublicKey
|
|
2324
3757
|
env?: Environment
|
|
2325
3758
|
}): Promise<VaultSyncTransactionResult> {
|
|
3759
|
+
vaultPda ??= owner
|
|
2326
3760
|
const { setupInstructions, syncInstructions, preInstructions, postInstructions, signers, addressLookupTableAddresses } = await buildOrderbookInstructions(
|
|
2327
3761
|
instructions,
|
|
2328
3762
|
owner,
|
|
@@ -2445,6 +3879,7 @@ async function buildPostOffer(
|
|
|
2445
3879
|
) {
|
|
2446
3880
|
const offerType = directionToOfferType(ix.direction)
|
|
2447
3881
|
const option = ix.offerOption ?? "FillOrKill"
|
|
3882
|
+
const offerIdx = orderbook.getNextOfferIndex()
|
|
2448
3883
|
|
|
2449
3884
|
if (setupContext) {
|
|
2450
3885
|
await ensureOrderbookPositionSetup(orderbook, buckets, setupContext)
|
|
@@ -2480,10 +3915,10 @@ async function buildPostOffer(
|
|
|
2480
3915
|
amount: ix.amount,
|
|
2481
3916
|
offerType,
|
|
2482
3917
|
offerOption: offerOptions(option, [false]),
|
|
2483
|
-
virtualOffer:
|
|
3918
|
+
virtualOffer: true,
|
|
2484
3919
|
expirySeconds: ix.expirySeconds ?? 3600,
|
|
2485
3920
|
mintSy: orderbook.vault.mintSy,
|
|
2486
|
-
offerIdx
|
|
3921
|
+
offerIdx,
|
|
2487
3922
|
}),
|
|
2488
3923
|
)
|
|
2489
3924
|
return
|
|
@@ -2496,10 +3931,10 @@ async function buildPostOffer(
|
|
|
2496
3931
|
amount: ix.amount,
|
|
2497
3932
|
offerType,
|
|
2498
3933
|
offerOption: offerOptions(option, [false]),
|
|
2499
|
-
virtualOffer:
|
|
3934
|
+
virtualOffer: true,
|
|
2500
3935
|
expirySeconds: ix.expirySeconds ?? 3600,
|
|
2501
3936
|
mintSy: orderbook.vault.mintSy,
|
|
2502
|
-
offerIdx
|
|
3937
|
+
offerIdx,
|
|
2503
3938
|
})
|
|
2504
3939
|
|
|
2505
3940
|
buckets.preInstructions.push(...setupIxs)
|
|
@@ -2616,6 +4051,52 @@ async function buildWithdrawFunds(
|
|
|
2616
4051
|
// Core Instruction Builders (Strip/Merge)
|
|
2617
4052
|
// ============================================================================
|
|
2618
4053
|
|
|
4054
|
+
async function queueYieldPositionTrackingAfterInit(params: {
|
|
4055
|
+
vault: Vault
|
|
4056
|
+
setupContext: StrategySetupContext
|
|
4057
|
+
postInstructions: TransactionInstruction[]
|
|
4058
|
+
}) {
|
|
4059
|
+
const state = await loadStrategySetupState(params.setupContext)
|
|
4060
|
+
if (!state || state.trackedYieldVaults.has(params.vault.selfAddress.toBase58())) {
|
|
4061
|
+
return
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
const yieldPosition = params.vault.pda.yieldPosition({
|
|
4065
|
+
owner: params.setupContext.owner,
|
|
4066
|
+
vault: params.vault.selfAddress,
|
|
4067
|
+
})
|
|
4068
|
+
const priceIdPt = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
4069
|
+
prices: state.prices,
|
|
4070
|
+
sourceMint: params.vault.mintPt,
|
|
4071
|
+
targetMint: state.strategyVault.state.underlyingMint,
|
|
4072
|
+
label: `yield position setup (${params.vault.selfAddress.toBase58()})`,
|
|
4073
|
+
})
|
|
4074
|
+
trackRequiredPriceIds(state.requiredPriceIds, priceIdPt)
|
|
4075
|
+
|
|
4076
|
+
params.postInstructions.push(
|
|
4077
|
+
state.strategyVault.ixWrapperManageVaultSettings({
|
|
4078
|
+
manager: params.setupContext.signer,
|
|
4079
|
+
actions: [
|
|
4080
|
+
exponentVaults.vaultSettingsAction("AddYieldPositionEntry", {
|
|
4081
|
+
yieldPosition,
|
|
4082
|
+
vault: params.vault.selfAddress,
|
|
4083
|
+
priceIdPt,
|
|
4084
|
+
}),
|
|
4085
|
+
],
|
|
4086
|
+
remainingAccounts: [
|
|
4087
|
+
{ pubkey: params.vault.selfAddress, isSigner: false, isWritable: false },
|
|
4088
|
+
{ pubkey: yieldPosition, isSigner: false, isWritable: false },
|
|
4089
|
+
],
|
|
4090
|
+
}),
|
|
4091
|
+
)
|
|
4092
|
+
|
|
4093
|
+
recordPlannedYieldPosition(state, {
|
|
4094
|
+
yieldPosition,
|
|
4095
|
+
vault: params.vault.selfAddress,
|
|
4096
|
+
priceIdPt,
|
|
4097
|
+
})
|
|
4098
|
+
}
|
|
4099
|
+
|
|
2619
4100
|
/** Build a single core instruction (strip/merge) */
|
|
2620
4101
|
async function buildCoreInstruction(
|
|
2621
4102
|
ix: CoreInstruction,
|
|
@@ -2667,7 +4148,7 @@ async function buildCoreInstruction(
|
|
|
2667
4148
|
await buildDepositYt(ix, vault, owner, buckets)
|
|
2668
4149
|
break
|
|
2669
4150
|
case CoreAction.INITIALIZE_YIELD_POSITION:
|
|
2670
|
-
await buildInitializeYieldPosition(ix, vault, owner, buckets)
|
|
4151
|
+
await buildInitializeYieldPosition(ix, vault, owner, buckets, setupContext)
|
|
2671
4152
|
break
|
|
2672
4153
|
}
|
|
2673
4154
|
}
|
|
@@ -2676,10 +4157,18 @@ async function buildInitializeYieldPosition(
|
|
|
2676
4157
|
ix: CoreInitializeYieldPositionInstruction,
|
|
2677
4158
|
vault: Vault,
|
|
2678
4159
|
owner: PublicKey,
|
|
2679
|
-
{ syncInstructions }: InstructionBuckets,
|
|
4160
|
+
{ syncInstructions, postInstructions }: InstructionBuckets,
|
|
4161
|
+
setupContext?: StrategySetupContext,
|
|
2680
4162
|
) {
|
|
2681
4163
|
const initIx = vault.ixInitializeYieldPosition({ owner })
|
|
2682
4164
|
syncInstructions.push(initIx)
|
|
4165
|
+
if (setupContext && isAutoManagePositionsEnabled(setupContext)) {
|
|
4166
|
+
await queueYieldPositionTrackingAfterInit({
|
|
4167
|
+
vault,
|
|
4168
|
+
setupContext,
|
|
4169
|
+
postInstructions,
|
|
4170
|
+
})
|
|
4171
|
+
}
|
|
2683
4172
|
}
|
|
2684
4173
|
|
|
2685
4174
|
async function buildStrip(
|
|
@@ -2859,9 +4348,17 @@ export const titanAction = {
|
|
|
2859
4348
|
/**
|
|
2860
4349
|
* Wrap a Titan SwapRouteV2 instruction for vault execution.
|
|
2861
4350
|
* @param params.instruction - Pre-built TransactionInstruction from Titan's router API
|
|
4351
|
+
* @param params.addressLookupTableAddresses - Optional ALT addresses returned by Titan for this route
|
|
2862
4352
|
*/
|
|
2863
|
-
swap(params: {
|
|
2864
|
-
|
|
4353
|
+
swap(params: {
|
|
4354
|
+
instruction: TransactionInstruction
|
|
4355
|
+
addressLookupTableAddresses?: PublicKey[]
|
|
4356
|
+
}): TitanSwapInstruction {
|
|
4357
|
+
return {
|
|
4358
|
+
action: TitanAction.SWAP,
|
|
4359
|
+
instruction: params.instruction,
|
|
4360
|
+
addressLookupTableAddresses: params.addressLookupTableAddresses,
|
|
4361
|
+
}
|
|
2865
4362
|
},
|
|
2866
4363
|
}
|
|
2867
4364
|
|
|
@@ -2870,7 +4367,7 @@ export const titanAction = {
|
|
|
2870
4367
|
// ============================================================================
|
|
2871
4368
|
|
|
2872
4369
|
/**
|
|
2873
|
-
* Builder for Loopscale
|
|
4370
|
+
* Builder for Loopscale action descriptors used in Exponent sync transactions.
|
|
2874
4371
|
*
|
|
2875
4372
|
* Loans (BORROWER side): create/close loan, deposit/withdraw collateral, borrow/repay principal.
|
|
2876
4373
|
* Strategies (LENDER side): create/close strategy, deposit/withdraw into strategy.
|
|
@@ -2990,7 +4487,9 @@ export const clmmAction = {
|
|
|
2990
4487
|
/**
|
|
2991
4488
|
* Create a new LP position on the CLMM with a specified tick range.
|
|
2992
4489
|
* The LP position keypair is generated internally — retrieve it from
|
|
2993
|
-
* `result.signers[0]` after calling `createVaultSyncTransaction`.
|
|
4490
|
+
* `result.signers[0]` after calling `createVaultSyncTransaction`. When
|
|
4491
|
+
* `autoManagePositions` is enabled, the SDK also tracks the new LP
|
|
4492
|
+
* position automatically after the deposit sync instruction succeeds.
|
|
2994
4493
|
*
|
|
2995
4494
|
* @param params.market - CLMM MarketThree account address
|
|
2996
4495
|
* @param params.ptInIntent - Maximum PT to deposit
|
|
@@ -3158,6 +4657,86 @@ export { SwapDirection }
|
|
|
3158
4657
|
/** Cache for loaded MarketThree instances to avoid redundant fetches. */
|
|
3159
4658
|
const marketThreeCache = new Map<string, MarketThree>()
|
|
3160
4659
|
|
|
4660
|
+
async function ensureTrackedClmmPosition(params: {
|
|
4661
|
+
lpPosition: PublicKey
|
|
4662
|
+
setupContext?: StrategySetupContext
|
|
4663
|
+
}) {
|
|
4664
|
+
if (!params.setupContext || !isAutoManagePositionsEnabled(params.setupContext)) {
|
|
4665
|
+
return
|
|
4666
|
+
}
|
|
4667
|
+
|
|
4668
|
+
const state = await loadStrategySetupState(params.setupContext)
|
|
4669
|
+
if (!state || state.trackedClmmPositions.has(params.lpPosition.toBase58())) {
|
|
4670
|
+
return
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4673
|
+
const existsOnChain = await accountExists(state, params.setupContext.connection, params.lpPosition)
|
|
4674
|
+
if (existsOnChain) {
|
|
4675
|
+
throw new Error(
|
|
4676
|
+
`CLMM lp position ${params.lpPosition.toBase58()} already exists on-chain but is not tracked on vault ${state.strategyVault.selfAddress.toBase58()}. Automatic repair is disabled; manually add the CLMM position entry.`,
|
|
4677
|
+
)
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
throw new Error(
|
|
4681
|
+
`CLMM lp position ${params.lpPosition.toBase58()} is not tracked on vault ${state.strategyVault.selfAddress.toBase58()}. Create it with clmmAction.depositLiquidity() through this SDK flow or manually add the position entry.`,
|
|
4682
|
+
)
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
async function queueClmmPositionTrackingAfterDeposit(params: {
|
|
4686
|
+
market: MarketThree
|
|
4687
|
+
lpPosition: PublicKey
|
|
4688
|
+
postInstructions: TransactionInstruction[]
|
|
4689
|
+
setupContext?: StrategySetupContext
|
|
4690
|
+
}) {
|
|
4691
|
+
if (!params.setupContext || !isAutoManagePositionsEnabled(params.setupContext)) {
|
|
4692
|
+
return
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
const state = await loadStrategySetupState(params.setupContext)
|
|
4696
|
+
if (!state || state.trackedClmmPositions.has(params.lpPosition.toBase58())) {
|
|
4697
|
+
return
|
|
4698
|
+
}
|
|
4699
|
+
|
|
4700
|
+
const priceIdPt = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
4701
|
+
prices: state.prices,
|
|
4702
|
+
sourceMint: params.market.mintPt,
|
|
4703
|
+
targetMint: state.strategyVault.state.underlyingMint,
|
|
4704
|
+
label: `CLMM PT position setup (${params.market.selfAddress.toBase58()})`,
|
|
4705
|
+
})
|
|
4706
|
+
trackRequiredPriceIds(state.requiredPriceIds, priceIdPt)
|
|
4707
|
+
const priceIdSy = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
4708
|
+
prices: state.prices,
|
|
4709
|
+
sourceMint: params.market.mintSy,
|
|
4710
|
+
targetMint: state.strategyVault.state.underlyingMint,
|
|
4711
|
+
label: `CLMM SY position setup (${params.market.selfAddress.toBase58()})`,
|
|
4712
|
+
})
|
|
4713
|
+
trackRequiredPriceIds(state.requiredPriceIds, priceIdSy)
|
|
4714
|
+
|
|
4715
|
+
params.postInstructions.push(
|
|
4716
|
+
state.strategyVault.ixWrapperManageVaultSettings({
|
|
4717
|
+
manager: params.setupContext.signer,
|
|
4718
|
+
actions: [exponentVaults.vaultSettingsAction("AddClmmPositionEntry", [{
|
|
4719
|
+
lpPosition: params.lpPosition,
|
|
4720
|
+
market: params.market.selfAddress,
|
|
4721
|
+
priceIdPt,
|
|
4722
|
+
priceIdSy,
|
|
4723
|
+
}])],
|
|
4724
|
+
remainingAccounts: [
|
|
4725
|
+
{ pubkey: params.lpPosition, isSigner: false, isWritable: false },
|
|
4726
|
+
{ pubkey: params.market.selfAddress, isSigner: false, isWritable: false },
|
|
4727
|
+
],
|
|
4728
|
+
}),
|
|
4729
|
+
)
|
|
4730
|
+
|
|
4731
|
+
recordPlannedClmmPosition(state, {
|
|
4732
|
+
lpPosition: params.lpPosition,
|
|
4733
|
+
market: params.market.selfAddress,
|
|
4734
|
+
priceIdPt,
|
|
4735
|
+
priceIdSy,
|
|
4736
|
+
ticksKey: params.market.ticksKey,
|
|
4737
|
+
})
|
|
4738
|
+
}
|
|
4739
|
+
|
|
3161
4740
|
/**
|
|
3162
4741
|
* Resolve a high-level CLMM action descriptor into raw Solana instructions.
|
|
3163
4742
|
* Loads the MarketThree from cache, derives token accounts from the vault
|
|
@@ -3201,12 +4780,14 @@ async function buildClmmInstruction(
|
|
|
3201
4780
|
|
|
3202
4781
|
switch (ix.action) {
|
|
3203
4782
|
case ClmmAction.DEPOSIT_LIQUIDITY:
|
|
3204
|
-
buildClmmDepositLiquidity(ix, market, owner, buckets)
|
|
4783
|
+
await buildClmmDepositLiquidity(ix, market, owner, buckets, setupContext)
|
|
3205
4784
|
break
|
|
3206
4785
|
case ClmmAction.ADD_LIQUIDITY:
|
|
4786
|
+
await ensureTrackedClmmPosition({ lpPosition: ix.lpPosition, setupContext })
|
|
3207
4787
|
buildClmmAddLiquidity(ix, market, owner, buckets)
|
|
3208
4788
|
break
|
|
3209
4789
|
case ClmmAction.WITHDRAW_LIQUIDITY:
|
|
4790
|
+
await ensureTrackedClmmPosition({ lpPosition: ix.lpPosition, setupContext })
|
|
3210
4791
|
buildClmmWithdrawLiquidity(ix, market, owner, buckets)
|
|
3211
4792
|
break
|
|
3212
4793
|
case ClmmAction.TRADE_PT:
|
|
@@ -3225,6 +4806,7 @@ async function buildClmmInstruction(
|
|
|
3225
4806
|
await buildClmmSellYt(ix, market, owner, buckets, setupContext)
|
|
3226
4807
|
break
|
|
3227
4808
|
case ClmmAction.CLAIM_FARM_EMISSION:
|
|
4809
|
+
await ensureTrackedClmmPosition({ lpPosition: ix.lpPosition, setupContext })
|
|
3228
4810
|
buildClmmClaimFarmEmission(ix, market, owner, buckets)
|
|
3229
4811
|
break
|
|
3230
4812
|
}
|
|
@@ -3234,12 +4816,13 @@ async function buildClmmInstruction(
|
|
|
3234
4816
|
* Create a new LP position. Generates the keypair internally and adds it
|
|
3235
4817
|
* to `buckets.signers` so consumers can include it in the transaction.
|
|
3236
4818
|
*/
|
|
3237
|
-
function buildClmmDepositLiquidity(
|
|
4819
|
+
async function buildClmmDepositLiquidity(
|
|
3238
4820
|
ix: ClmmDepositLiquidityInstruction,
|
|
3239
4821
|
market: MarketThree,
|
|
3240
4822
|
owner: PublicKey,
|
|
3241
4823
|
buckets: InstructionBuckets,
|
|
3242
|
-
|
|
4824
|
+
setupContext?: StrategySetupContext,
|
|
4825
|
+
): Promise<void> {
|
|
3243
4826
|
const ptSrc = getAssociatedTokenAddressSync(market.mintPt, owner, true, TOKEN_PROGRAM_ID)
|
|
3244
4827
|
const sySrc = getAssociatedTokenAddressSync(market.mintSy, owner, true, TOKEN_PROGRAM_ID)
|
|
3245
4828
|
|
|
@@ -3255,6 +4838,12 @@ function buildClmmDepositLiquidity(
|
|
|
3255
4838
|
|
|
3256
4839
|
buckets.syncInstructions.push(depositIx)
|
|
3257
4840
|
buckets.signers.push(lpPositionKeypair)
|
|
4841
|
+
await queueClmmPositionTrackingAfterDeposit({
|
|
4842
|
+
market,
|
|
4843
|
+
lpPosition: lpPositionKeypair.publicKey,
|
|
4844
|
+
postInstructions: buckets.postInstructions,
|
|
4845
|
+
setupContext,
|
|
4846
|
+
})
|
|
3258
4847
|
}
|
|
3259
4848
|
|
|
3260
4849
|
/** Add liquidity to an existing LP position. */
|
|
@@ -3396,6 +4985,7 @@ async function buildClmmBuyYt(
|
|
|
3396
4985
|
|
|
3397
4986
|
const { ixs, setupIxs } = market.ixBuyYt({
|
|
3398
4987
|
trader: owner,
|
|
4988
|
+
payer: setupContext?.signer,
|
|
3399
4989
|
ytOut: ix.ytOut,
|
|
3400
4990
|
maxSyIn: ix.maxSyIn,
|
|
3401
4991
|
lnImpliedApyLimit: ix.lnImpliedApyLimit,
|
|
@@ -3426,6 +5016,7 @@ async function buildClmmSellYt(
|
|
|
3426
5016
|
|
|
3427
5017
|
const { ixs, setupIxs } = market.ixSellYt({
|
|
3428
5018
|
trader: owner,
|
|
5019
|
+
payer: setupContext?.signer,
|
|
3429
5020
|
ytIn: ix.ytIn,
|
|
3430
5021
|
minSyOut: ix.minSyOut,
|
|
3431
5022
|
lnImpliedApyLimit: ix.lnImpliedApyLimit,
|