@exponent-labs/exponent-sdk 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/build/EventDecoderV2.d.ts +31 -0
  2. package/build/EventDecoderV2.js +76 -0
  3. package/build/EventDecoderV2.js.map +1 -0
  4. package/build/addressLookupTableUtil.d.ts +17 -1
  5. package/build/addressLookupTableUtil.js +35 -1
  6. package/build/addressLookupTableUtil.js.map +1 -1
  7. package/build/clmm/events.d.ts +10 -0
  8. package/build/clmm/events.js +10 -0
  9. package/build/clmm/events.js.map +1 -0
  10. package/build/clmm/index.d.ts +1 -0
  11. package/build/clmm/index.js +18 -0
  12. package/build/clmm/index.js.map +1 -0
  13. package/build/events.d.ts +200 -9
  14. package/build/events.js +73 -24
  15. package/build/events.js.map +1 -1
  16. package/build/eventsV2.d.ts +7 -0
  17. package/build/eventsV2.js +10 -0
  18. package/build/eventsV2.js.map +1 -0
  19. package/build/flavors.d.ts +2 -0
  20. package/build/flavors.js +81 -27
  21. package/build/flavors.js.map +1 -1
  22. package/build/index.d.ts +6 -0
  23. package/build/index.js +14 -4
  24. package/build/index.js.map +1 -1
  25. package/build/lpPosition.js +4 -1
  26. package/build/lpPosition.js.map +1 -1
  27. package/build/market.d.ts +14 -2
  28. package/build/market.js +70 -29
  29. package/build/market.js.map +1 -1
  30. package/build/marketThree.d.ts +664 -0
  31. package/build/marketThree.js +1415 -0
  32. package/build/marketThree.js.map +1 -0
  33. package/build/marketThree.test.d.ts +1 -0
  34. package/build/marketThree.test.js +166 -0
  35. package/build/marketThree.test.js.map +1 -0
  36. package/build/orderbook/events.d.ts +7 -0
  37. package/build/orderbook/events.js +10 -0
  38. package/build/orderbook/events.js.map +1 -0
  39. package/build/orderbook/index.d.ts +4 -0
  40. package/build/orderbook/index.js +41 -0
  41. package/build/orderbook/index.js.map +1 -0
  42. package/build/orderbook/math.d.ts +26 -0
  43. package/build/orderbook/math.js +111 -0
  44. package/build/orderbook/math.js.map +1 -0
  45. package/build/orderbook/orderbook.d.ts +175 -0
  46. package/build/orderbook/orderbook.js +756 -0
  47. package/build/orderbook/orderbook.js.map +1 -0
  48. package/build/orderbook/types.d.ts +49 -0
  49. package/build/orderbook/types.js +27 -0
  50. package/build/orderbook/types.js.map +1 -0
  51. package/build/orderbook/utils.d.ts +18 -0
  52. package/build/orderbook/utils.js +74 -0
  53. package/build/orderbook/utils.js.map +1 -0
  54. package/build/router.d.ts +92 -0
  55. package/build/router.js +214 -0
  56. package/build/router.js.map +1 -0
  57. package/build/syPosition.js +6 -0
  58. package/build/syPosition.js.map +1 -1
  59. package/build/utils/index.d.ts +3 -2
  60. package/build/utils/index.js +22 -1
  61. package/build/utils/index.js.map +1 -1
  62. package/build/vault.d.ts +3 -1
  63. package/build/vault.js +98 -62
  64. package/build/vault.js.map +1 -1
  65. package/build/ytPosition.d.ts +2 -0
  66. package/build/ytPosition.js +18 -5
  67. package/build/ytPosition.js.map +1 -1
  68. package/package.json +28 -23
  69. package/src/EventDecoderV2.ts +96 -0
  70. package/src/addressLookupTableUtil.ts +42 -1
  71. package/src/clmm/events.ts +17 -0
  72. package/src/clmm/index.ts +1 -0
  73. package/src/events.ts +280 -27
  74. package/src/eventsV2.ts +13 -0
  75. package/src/flavors.ts +97 -27
  76. package/src/index.ts +6 -0
  77. package/src/lpPosition.ts +5 -2
  78. package/src/market.ts +100 -31
  79. package/src/marketThree.test.ts +208 -0
  80. package/src/marketThree.ts +2430 -0
  81. package/src/orderbook/events.ts +13 -0
  82. package/src/orderbook/index.ts +12 -0
  83. package/src/orderbook/math.ts +122 -0
  84. package/src/orderbook/orderbook.ts +1153 -0
  85. package/src/orderbook/types.ts +45 -0
  86. package/src/orderbook/utils.ts +74 -0
  87. package/src/router.ts +360 -0
  88. package/src/syPosition.ts +4 -0
  89. package/src/utils/index.ts +27 -2
  90. package/src/vault.ts +100 -62
  91. package/src/ytPosition.ts +28 -7
  92. package/tsconfig.json +4 -1
@@ -0,0 +1,2430 @@
1
+ import { AnchorProvider, BN, Program, web3 } from "@coral-xyz/anchor"
2
+ import {
3
+ TOKEN_PROGRAM_ID,
4
+ createAssociatedTokenAccountIdempotentInstruction,
5
+ getAccount,
6
+ getAssociatedTokenAddressSync,
7
+ } from "@solana/spl-token"
8
+ import Decimal from "decimal.js"
9
+
10
+ import { ExponentClmm, IDL } from "@exponent-labs/exponent-clmm-idl"
11
+ import { ExponentCLMMPDA } from "@exponent-labs/exponent-clmm-pda"
12
+ import {
13
+ ExponentFetcher,
14
+ LiquidityNetBalanceLimits,
15
+ LpFarm,
16
+ LpPositionCLMM,
17
+ MarketConfigurationOptions,
18
+ MarketThreeFinancials,
19
+ Ticks,
20
+ } from "@exponent-labs/exponent-fetcher"
21
+ import { IDL as EXPONENT_CORE_IDL, ExponentCore } from "@exponent-labs/exponent-idl"
22
+ import { ExponentPDA } from "@exponent-labs/exponent-pda"
23
+ import {
24
+ AnchorizedPNum,
25
+ CpiAccountsRaw,
26
+ CpiAccountsRawJson,
27
+ ExponentCoreCpiAccountsRaw,
28
+ Flavor,
29
+ SyPosition,
30
+ } from "@exponent-labs/exponent-types"
31
+ import { LiquidityAdd } from "@exponent-labs/market-math"
32
+ import {
33
+ EffSnap,
34
+ calcDepositSyAndPtFromBaseAmount,
35
+ calcPtPriceInAsset,
36
+ computeLiquidityTargetAndTokenNeeds,
37
+ getPtAndSyOnWithdrawLiquidity,
38
+ normalizedTimeRemaining,
39
+ } from "@exponent-labs/market-three-math"
40
+
41
+ import {
42
+ fetchAddressLookupTable,
43
+ makeCpiAccountMetaLists,
44
+ makeMarketCoreCpiAccountMetaLists,
45
+ } from "./addressLookupTableUtil"
46
+ import { Environment } from "./environment"
47
+ import {
48
+ makeFlavorGenericSync,
49
+ makeFlavorJitoRestakingSync,
50
+ makeFlavorKaminoSync,
51
+ makeFlavorMarginfiSync,
52
+ makeFlavorPerenaSync,
53
+ } from "./flavors"
54
+ import { MyWallet } from "./market"
55
+ import { makeSyPosition } from "./syPosition"
56
+ import { emitEventAuthority, getExponentAdminStatePda, uniqueRemainingAccounts } from "./utils"
57
+ import { Vault } from "./vault"
58
+
59
+ export { LiquidityAdd }
60
+
61
+ const SECONDS_PER_YEAR = 365 * 24 * 60 * 60
62
+
63
+ interface Emission {
64
+ /** Token account that holds emission tokens */
65
+ escrowAccountAddress: web3.PublicKey
66
+
67
+ /** Mint for the emission token */
68
+ mint: web3.PublicKey
69
+
70
+ /** Token program ID for the emission token */
71
+ tokenProgramAddress: web3.PublicKey
72
+
73
+ /** How many emissions have been claimed by SY holders */
74
+ totalClaimed: bigint
75
+
76
+ /** How many emissions have been earned by the SY robot over its lifetime */
77
+ lastSeenTotalAccruedEmissions: bigint
78
+
79
+ /** Global index for sharing out rewards to SY holders */
80
+ index: AnchorizedPNum
81
+ }
82
+
83
+ export type MarketAdminAction =
84
+ | { setStatus: [number] }
85
+ | { setMaxLpSupply: [BN] }
86
+ | { changeTreasuryTradeSyBpsFee: [number] }
87
+ | { changeLnFeeRateRoot: [number] }
88
+ | {
89
+ changeLiquidityNetBalanceLimits: {
90
+ maxNetBalanceChangeNegativePercentage: number
91
+ maxNetBalanceChangePositivePercentage: number
92
+ windowDurationSeconds: number
93
+ }
94
+ }
95
+ | { changeEpsilonClamp: [number] }
96
+ | { changeMinLpTickAmount: [BN] }
97
+ | { changeTickSpace: [number] }
98
+
99
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
100
+ export type SwapDirection = { ptToSy: {} } | { syToPt: {} }
101
+
102
+ interface MarketThreeLoadOptions {
103
+ syConfig?: {
104
+ skipWrap?: boolean
105
+ }
106
+ }
107
+
108
+ type MarketThreeArgs = {
109
+ admin: web3.PublicKey
110
+ addressLookupTable: web3.PublicKey
111
+ mintPt: web3.PublicKey
112
+ mintYt: web3.PublicKey
113
+ mintSy: web3.PublicKey
114
+ vault: Vault
115
+ tokenPtEscrow: web3.PublicKey
116
+ tokenSyEscrow: web3.PublicKey
117
+ tokenYtEscrow: web3.PublicKey
118
+ tokenFeeTreasurySy: web3.PublicKey
119
+ tokenFeeTreasuryPt: web3.PublicKey
120
+ syProgram: web3.PublicKey
121
+ exponentCoreProgram: web3.PublicKey
122
+ selfAddress: web3.PublicKey
123
+ statusFlags: number
124
+ configurationOptions: MarketConfigurationOptions
125
+ financials: MarketThreeFinancials
126
+ cpiSyAccounts: CpiAccountsRaw
127
+ cpiCoreAccounts: ExponentCoreCpiAccountsRaw
128
+ isCurrentFlashSwap: boolean
129
+ lpFarm: LpFarm
130
+ flavor: Flavor
131
+ emissions: {
132
+ trackers: {
133
+ tokenEscrow: web3.PublicKey
134
+ lpShareIndex: number
135
+ lastSeenStaged: number
136
+ }[]
137
+ }
138
+ liquidityNetBalanceLimits: LiquidityNetBalanceLimits
139
+ ticksAccount: web3.PublicKey
140
+ ticks: Ticks
141
+ syPosition: SyPosition
142
+ treasuryFeeOwner: web3.PublicKey
143
+ }
144
+
145
+ export class MarketThree {
146
+ clmmProgram: Program<ExponentClmm>
147
+ coreProgram: Program<ExponentCore>
148
+ xponPda: ExponentPDA
149
+ pda: ExponentCLMMPDA
150
+
151
+ constructor(
152
+ public state: MarketThreeArgs,
153
+ public selfAddress: web3.PublicKey,
154
+ public env: Environment,
155
+ public connection: web3.Connection,
156
+ ) {
157
+ this.xponPda = new ExponentPDA(env.coreProgramId)
158
+ this.pda = new ExponentCLMMPDA()
159
+ const mockWallet = new MyWallet(web3.Keypair.generate())
160
+ this.clmmProgram = new Program<ExponentClmm>(IDL as ExponentClmm, new AnchorProvider(connection, mockWallet))
161
+ this.coreProgram = new Program(
162
+ EXPONENT_CORE_IDL as ExponentCore,
163
+ new AnchorProvider(connection, new MyWallet(web3.Keypair.generate())),
164
+ )
165
+ }
166
+
167
+ static async load(
168
+ env: Environment,
169
+ connection: web3.Connection,
170
+ address: web3.PublicKey,
171
+ vault?: Vault,
172
+ ticks?: Ticks,
173
+ options: MarketThreeLoadOptions = {},
174
+ ): Promise<MarketThree> {
175
+ const fetcher = new ExponentFetcher({ connection })
176
+ const fetchedMarket = await fetcher.fetchMarketThree(address)
177
+ const [alt, loadedVault, loadedTicks] = await Promise.all([
178
+ fetchAddressLookupTable(connection, fetchedMarket.addressLookupTable),
179
+ vault || Vault.load(env, connection, fetchedMarket.vault, options),
180
+ ticks || fetcher.fetchMarketThreeTicks(fetchedMarket.ticks),
181
+ ])
182
+ const cpiSyAccounts = makeCpiAccountMetaLists(alt, fetchedMarket.cpiSyAccounts)
183
+ const cpiCoreAccounts = makeMarketCoreCpiAccountMetaLists(alt, fetchedMarket.cpiCoreAccounts)
184
+
185
+ const flavor = (() => {
186
+ switch (loadedVault.flavor.flavor) {
187
+ case "marginfi":
188
+ return makeFlavorMarginfiSync(loadedVault.flavor)
189
+ case "kamino":
190
+ return makeFlavorKaminoSync(loadedVault.flavor)
191
+ case "jitoRestaking":
192
+ return makeFlavorJitoRestakingSync(loadedVault.flavor)
193
+ case "perena":
194
+ return makeFlavorPerenaSync(loadedVault.flavor)
195
+ case "generic":
196
+ return makeFlavorGenericSync(loadedVault.flavor, options.syConfig)
197
+ default:
198
+ throw new Error(`Unknown flavor: ${loadedVault.flavor}`)
199
+ }
200
+ })()
201
+
202
+ const syPosition = await makeSyPosition(fetcher, flavor, fetchedMarket.syProgram, address)
203
+
204
+ //? Assuming that the owner is the same for both accounts feeAccounts
205
+ const treasuryFeeOwner = await MarketThree.fetchTreasuryFeeOwner(fetchedMarket.tokenFeeTreasuryPt, connection)
206
+
207
+ const state: MarketThreeArgs = {
208
+ ...fetchedMarket,
209
+ vault: loadedVault,
210
+ flavor,
211
+ ticks: loadedTicks,
212
+ syPosition,
213
+ cpiSyAccounts,
214
+ cpiCoreAccounts,
215
+ ticksAccount: fetchedMarket.ticks,
216
+ treasuryFeeOwner,
217
+ }
218
+ return new MarketThree(state, address, env, connection)
219
+ }
220
+
221
+ async reload(connection: web3.Connection = this.connection) {
222
+ const market = await MarketThree.load(this.env, connection, this.selfAddress)
223
+ this.state = market.state
224
+ return market
225
+ }
226
+
227
+ get vault() {
228
+ return this.state.vault
229
+ }
230
+
231
+ get flavor() {
232
+ return this.state.flavor
233
+ }
234
+
235
+ get lpBalance() {
236
+ return this.state.financials.liquidityBalance
237
+ }
238
+
239
+ get syBalance() {
240
+ return this.state.financials.syBalance
241
+ }
242
+
243
+ get ptBalance() {
244
+ return this.state.financials.ptBalance
245
+ }
246
+
247
+ get mintSy() {
248
+ return this.state.mintSy
249
+ }
250
+
251
+ get mintPt() {
252
+ return this.state.mintPt
253
+ }
254
+
255
+ get statusFlags() {
256
+ return this.state.statusFlags
257
+ }
258
+
259
+ get mintYt() {
260
+ return this.state.mintYt
261
+ }
262
+
263
+ get addressLookupTable() {
264
+ return this.state.addressLookupTable
265
+ }
266
+
267
+ get syProgram() {
268
+ return this.state.syProgram
269
+ }
270
+
271
+ get cpiSyAccounts() {
272
+ return this.state.cpiSyAccounts
273
+ }
274
+
275
+ get cpiCoreAccounts() {
276
+ return this.state.cpiCoreAccounts
277
+ }
278
+
279
+ get marketEmissions() {
280
+ return this.state.emissions
281
+ }
282
+
283
+ get ticksKey() {
284
+ return this.state.ticksAccount
285
+ }
286
+
287
+ get corePda() {
288
+ return new ExponentPDA()
289
+ }
290
+
291
+ get coreEventAuthority() {
292
+ return emitEventAuthority(this.state.exponentCoreProgram)
293
+ }
294
+
295
+ get emissions(): Emission[] {
296
+ if (this.flavor.flavor === "marginfi") {
297
+ return this.flavor.mfiSyState.account.emissions.map((e) => ({
298
+ escrowAccountAddress: e.escrowAccount,
299
+ mint: e.mint,
300
+ tokenProgramAddress: e.tokenProgram,
301
+ totalClaimed: BigInt(e.totalClaimedEmissions.toString()),
302
+ lastSeenTotalAccruedEmissions: BigInt(e.lastSeenTotalAccruedEmissions.toString()),
303
+ index: e.index,
304
+ }))
305
+ }
306
+ if (this.flavor.flavor === "kamino") {
307
+ return this.flavor.kaminoSyState.account.emissions.map((e) => ({
308
+ escrowAccountAddress: e.escrowAccount,
309
+ mint: e.mint,
310
+ tokenProgramAddress: e.tokenProgram,
311
+ totalClaimed: BigInt(e.totalClaimedEmissions.toString()),
312
+ lastSeenTotalAccruedEmissions: BigInt(e.lastSeenTotalAccruedEmissions.toString()),
313
+ index: e.index,
314
+ }))
315
+ }
316
+ if (this.flavor.flavor === "jitoRestaking") {
317
+ return this.flavor.jitoSyState.account.emissions.map((e) => ({
318
+ escrowAccountAddress: e.escrowAccount,
319
+ mint: e.mint,
320
+ tokenProgramAddress: e.tokenProgram,
321
+ totalClaimed: BigInt(e.totalClaimedEmissions.toString()),
322
+ lastSeenTotalAccruedEmissions: BigInt(e.lastSeenTotalAccruedEmissions.toString()),
323
+ index: e.index,
324
+ }))
325
+ }
326
+ if (this.flavor.flavor === "perena") {
327
+ return this.flavor.perenaSyState.account.emissions.map((e) => ({
328
+ escrowAccountAddress: e.escrowAccount,
329
+ mint: e.mint,
330
+ tokenProgramAddress: e.tokenProgram,
331
+ totalClaimed: BigInt(e.totalClaimedEmissions.toString()),
332
+ lastSeenTotalAccruedEmissions: BigInt(e.lastSeenTotalAccruedEmissions.toString()),
333
+ index: e.index,
334
+ }))
335
+ }
336
+ if (this.flavor.flavor === "generic") {
337
+ return this.flavor.genericSyState.account.emissions.map((e) => ({
338
+ escrowAccountAddress: e.escrowAccount,
339
+ mint: e.mint,
340
+ tokenProgramAddress: e.tokenProgram,
341
+ totalClaimed: BigInt(e.totalClaimedEmissions.toString()),
342
+ lastSeenTotalAccruedEmissions: BigInt(e.lastSeenTotalAccruedEmissions.toString()),
343
+ index: e.index,
344
+ }))
345
+ }
346
+ throw new Error("Unknown flavor")
347
+ }
348
+
349
+ /** Get the escrow token account addresses for the emissions, in order */
350
+ get emissionTokenAccounts(): web3.PublicKey[] {
351
+ return this.emissions.map((e) => e.escrowAccountAddress)
352
+ }
353
+
354
+ /** Pass-through SY account owned by the market */
355
+ get tokenSyEscrow(): web3.PublicKey {
356
+ return this.state.tokenSyEscrow
357
+ }
358
+
359
+ /** Pass-through YT account owned by the market */
360
+ get tokenYtEscrow(): web3.PublicKey {
361
+ return this.state.tokenYtEscrow
362
+ }
363
+
364
+ /** SY account that holds treasury SY fees from PT trading */
365
+ get tokenFeeTreasurySy(): web3.PublicKey {
366
+ return this.state.tokenFeeTreasurySy
367
+ }
368
+
369
+ /** PT account that holds treasury PT fees from PT trading */
370
+ get tokenFeeTreasuryPt(): web3.PublicKey {
371
+ return this.state.tokenFeeTreasuryPt
372
+ }
373
+
374
+ /** Market liquidity for PT */
375
+ get tokenPtEscrow(): web3.PublicKey {
376
+ return this.state.tokenPtEscrow
377
+ }
378
+
379
+ get currentSyExchangeRate(): number {
380
+ return this.flavor.currentSyExchangeRate
381
+ }
382
+
383
+ /** Special account for event emit self-cpi */
384
+ get eventAuthority(): web3.PublicKey {
385
+ return emitEventAuthority(this.clmmProgram.programId)
386
+ }
387
+
388
+ get secondsRemaining(): number {
389
+ const timeNow = Date.now() / 1000
390
+ return Math.max(0, Math.round(Number(this.state.financials.expirationTs) - timeNow))
391
+ }
392
+
393
+ /** Annualize a rate given the number of seconds remaining until maturity */
394
+ static annualize(rate: number, secondsRemaining: number) {
395
+ return (rate * SECONDS_PER_YEAR) / secondsRemaining
396
+ }
397
+
398
+ static annualizeApy(rate: number, secondsRemaining: number) {
399
+ return (1 + rate) ** (SECONDS_PER_YEAR / secondsRemaining) - 1
400
+ }
401
+
402
+ /** The fee rate taken off of trade fees (typically around 20%) expressed as a BPS number */
403
+ get feeTreasuryBps(): number {
404
+ return this.state.configurationOptions.treasuryFeeBps
405
+ }
406
+
407
+ /** The fee rate taken off of trade fees (typically around 20%) expressed as a rational number */
408
+ get feeTreasuryRate(): number {
409
+ return this.feeTreasuryBps / 10_000
410
+ }
411
+
412
+ /** Get the owner of the treasury fee accounts: tokenFeeTreasuryPt & tokenFeeTreasurySy */
413
+ static async fetchTreasuryFeeOwner(
414
+ tokenFeeTreasury: web3.PublicKey,
415
+ connection: web3.Connection,
416
+ ): Promise<web3.PublicKey> {
417
+ const { owner } = await getAccount(connection, tokenFeeTreasury)
418
+ return owner
419
+ }
420
+
421
+ /** Deposit a pair of tokens as liquidity to the market
422
+ * Adds PT & SY from the `depositor` to the market
423
+ *
424
+ * Due to unforeseeable slippage, the PT & SY amounts intended are effectively the maximum amounts
425
+ * The minimum LP tokens to receive is specified by `minLpOut`
426
+ *
427
+ * The token accounts themselves are optional, and will be derived from the depositor's wallet if not provided
428
+ */
429
+ async ixDepositLiquidity({
430
+ ptInIntent,
431
+ syInIntent,
432
+ depositor,
433
+ lowerTickKey,
434
+ upperTickKey,
435
+ ptSrc: ptSrcParam,
436
+ sySrc: sySrcParam,
437
+ lpPosition: lpPositionParam,
438
+ }: {
439
+ /** Intended (maximum) amount of PT in */
440
+ ptInIntent: bigint
441
+ /** Intended (maximum) amount of SY in */
442
+ syInIntent: bigint
443
+ /** Lower tick key */
444
+ lowerTickKey: number
445
+ /** Upper tick key */
446
+ upperTickKey: number
447
+ depositor: web3.PublicKey
448
+ ptSrc?: web3.PublicKey
449
+ sySrc?: web3.PublicKey
450
+ lpPosition?: web3.Keypair
451
+ }) {
452
+ const tokenProgram = TOKEN_PROGRAM_ID
453
+
454
+ const sySrc = sySrcParam || getAssociatedTokenAddressSync(this.mintSy, depositor, true, TOKEN_PROGRAM_ID)
455
+ const ptSrc = ptSrcParam || getAssociatedTokenAddressSync(this.mintPt, depositor, true, TOKEN_PROGRAM_ID)
456
+
457
+ const lpPosition = lpPositionParam || web3.Keypair.generate()
458
+ const remainingAccounts = uniqueRemainingAccounts([
459
+ ...this.cpiSyAccounts.depositSy,
460
+ ...this.cpiSyAccounts.getPositionState,
461
+ ])
462
+
463
+ const ix = await this.clmmProgram.methods
464
+ .depositLiquidity(
465
+ convertApyToApyBp(lowerTickKey),
466
+ convertApyToApyBp(upperTickKey),
467
+ new BN(ptInIntent.toString()),
468
+ new BN(syInIntent.toString()),
469
+ )
470
+ .accountsStrict({
471
+ depositor,
472
+ market: this.selfAddress,
473
+ tokenPtSrc: ptSrc,
474
+ tokenSySrc: sySrc,
475
+ tokenPtEscrow: this.tokenPtEscrow,
476
+ tokenSyEscrow: this.tokenSyEscrow,
477
+ addressLookupTable: this.addressLookupTable,
478
+ syProgram: this.syProgram,
479
+ tokenProgram,
480
+ eventAuthority: this.eventAuthority,
481
+ program: this.clmmProgram.programId,
482
+ lpPosition: lpPosition.publicKey,
483
+ ticks: this.ticksKey,
484
+ systemProgram: web3.SystemProgram.programId,
485
+ })
486
+ .remainingAccounts(remainingAccounts)
487
+ .instruction()
488
+
489
+ return {
490
+ ix: ix,
491
+ signers: lpPosition,
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Redeem LP tokens for PT & SY (liquidity removal)
497
+ *
498
+ * The lpIn is exactly the amount of LP tokens to burn
499
+ * The minimum PT & SY out are specified by minPtOut & minSyOut
500
+ * The transaction may fail due to unforeseeable slippage on the redemption rate
501
+ *
502
+ * The token accounts themselves are optional, and will be derived from the withdrawer's wallet if not provided
503
+ */
504
+ async ixWithdrawLiquidity({
505
+ lpIn,
506
+ withdrawer,
507
+ lpPosition,
508
+ minPtOut,
509
+ minSyOut,
510
+ ptDst: ptDstParam,
511
+ syDst: syDstParam,
512
+ }: {
513
+ lpIn: bigint
514
+ withdrawer: web3.PublicKey
515
+ lpPosition: web3.PublicKey
516
+ minPtOut: bigint
517
+ minSyOut: bigint
518
+ ptDst?: web3.PublicKey
519
+ syDst?: web3.PublicKey
520
+ lnImpliedApyLimit?: number
521
+ }) {
522
+ const ptDst = ptDstParam || getAssociatedTokenAddressSync(this.mintPt, withdrawer, true, TOKEN_PROGRAM_ID)
523
+ const syDst = syDstParam || getAssociatedTokenAddressSync(this.mintSy, withdrawer, true, TOKEN_PROGRAM_ID)
524
+ const ptDstAtaIx = createAssociatedTokenAccountIdempotentInstruction(withdrawer, ptDst, withdrawer, this.mintPt)
525
+ const syDstAtaIx = createAssociatedTokenAccountIdempotentInstruction(withdrawer, syDst, withdrawer, this.mintSy)
526
+ const remainingAccounts = uniqueRemainingAccounts([
527
+ ...this.cpiSyAccounts.withdrawSy,
528
+ ...this.cpiSyAccounts.getPositionState,
529
+ ])
530
+
531
+ const ixs = await this.clmmProgram.methods
532
+ .withdrawLiquidity(new BN(lpIn.toString()), new BN(minPtOut.toString()), new BN(minSyOut.toString()))
533
+ .accountsStrict({
534
+ owner: withdrawer,
535
+ market: this.selfAddress,
536
+ tokenPtDst: ptDst,
537
+ tokenSyDst: syDst,
538
+ tokenPtEscrow: this.tokenPtEscrow,
539
+ tokenSyEscrow: this.tokenSyEscrow,
540
+ addressLookupTable: this.addressLookupTable,
541
+ syProgram: this.syProgram,
542
+ tokenProgram: TOKEN_PROGRAM_ID,
543
+ eventAuthority: this.eventAuthority,
544
+ program: this.clmmProgram.programId,
545
+ systemProgram: web3.SystemProgram.programId,
546
+ lpPosition,
547
+ ticks: this.ticksKey,
548
+ })
549
+ .remainingAccounts(remainingAccounts)
550
+ .instruction()
551
+
552
+ return { ixs: [ixs], setupIxs: [ptDstAtaIx, syDstAtaIx] }
553
+ }
554
+
555
+ async ixTradePt({
556
+ trader,
557
+ traderAmount,
558
+ outConstraint,
559
+ swapDirection,
560
+ tokenPt: tokenPtParam,
561
+ tokenSy: tokenSyParam,
562
+ lnImpliedApyLimit,
563
+ }: {
564
+ trader: web3.PublicKey
565
+ traderAmount: bigint
566
+ outConstraint: bigint
567
+ swapDirection: SwapDirection
568
+ tokenPt?: web3.PublicKey
569
+ tokenSy?: web3.PublicKey
570
+ lnImpliedApyLimit?: number
571
+ }) {
572
+ const tokenPt = tokenPtParam || getAssociatedTokenAddressSync(this.mintPt, trader, true, TOKEN_PROGRAM_ID)
573
+ const tokenSy = tokenSyParam || getAssociatedTokenAddressSync(this.mintSy, trader, true, TOKEN_PROGRAM_ID)
574
+
575
+ const tokenSyAtaIx = createAssociatedTokenAccountIdempotentInstruction(trader, tokenSy, trader, this.mintSy)
576
+ const tokenPtAtaIx = createAssociatedTokenAccountIdempotentInstruction(trader, tokenPt, trader, this.mintPt)
577
+
578
+ const remainingAccounts = uniqueRemainingAccounts([
579
+ ...this.cpiSyAccounts.getSyState,
580
+ ...this.cpiSyAccounts.getPositionState,
581
+ ...this.cpiSyAccounts.depositSy,
582
+ ...this.cpiSyAccounts.withdrawSy,
583
+ ])
584
+
585
+ let outConstraintOptional = outConstraint ? new BN(outConstraint.toString()) : null
586
+ let lnImpliedApyLimitOptional = lnImpliedApyLimit ? lnImpliedApyLimit : null
587
+
588
+ const ix = await this.clmmProgram.methods
589
+ .tradePt(new BN(traderAmount.toString()), swapDirection, outConstraintOptional, lnImpliedApyLimitOptional)
590
+ .accountsStrict({
591
+ trader,
592
+ market: this.selfAddress,
593
+ tokenPtTrader: tokenPt,
594
+ tokenSyTrader: tokenSy,
595
+ tokenSyEscrow: this.tokenSyEscrow,
596
+ tokenPtEscrow: this.tokenPtEscrow,
597
+ addressLookupTable: this.addressLookupTable,
598
+ tokenProgram: TOKEN_PROGRAM_ID,
599
+ syProgram: this.syProgram,
600
+ tokenFeeTreasurySy: this.tokenFeeTreasurySy,
601
+ eventAuthority: this.eventAuthority,
602
+ program: this.clmmProgram.programId,
603
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
604
+ ticks: this.ticksKey,
605
+ })
606
+ .remainingAccounts(remainingAccounts)
607
+ .instruction()
608
+
609
+ return { ixs: [ix], setupIxs: [tokenPtAtaIx, tokenSyAtaIx] }
610
+ }
611
+
612
+ async ixTradePtExactOut({
613
+ trader,
614
+ amountOut,
615
+ swapDirection,
616
+ amountInConstraint,
617
+ tokenPt: tokenPtParam,
618
+ tokenSy: tokenSyParam,
619
+ lnImpliedApyLimit,
620
+ }: {
621
+ trader: web3.PublicKey
622
+ amountOut: bigint
623
+ swapDirection: SwapDirection
624
+ amountInConstraint?: bigint
625
+ tokenPt?: web3.PublicKey
626
+ tokenSy?: web3.PublicKey
627
+ lnImpliedApyLimit?: number
628
+ }) {
629
+ const tokenPt = tokenPtParam || getAssociatedTokenAddressSync(this.mintPt, trader, true, TOKEN_PROGRAM_ID)
630
+ const tokenSy = tokenSyParam || getAssociatedTokenAddressSync(this.mintSy, trader, true, TOKEN_PROGRAM_ID)
631
+
632
+ const tokenSyAtaIx = createAssociatedTokenAccountIdempotentInstruction(trader, tokenSy, trader, this.mintSy)
633
+ const tokenPtAtaIx = createAssociatedTokenAccountIdempotentInstruction(trader, tokenPt, trader, this.mintPt)
634
+
635
+ const remainingAccounts = uniqueRemainingAccounts([
636
+ ...this.cpiSyAccounts.getSyState,
637
+ ...this.cpiSyAccounts.getPositionState,
638
+ ...this.cpiSyAccounts.depositSy,
639
+ ...this.cpiSyAccounts.withdrawSy,
640
+ ])
641
+
642
+ const amountInConstraintOptional = amountInConstraint !== undefined ? new BN(amountInConstraint.toString()) : null
643
+ const lnImpliedApyLimitOptional = lnImpliedApyLimit ? lnImpliedApyLimit : null
644
+
645
+ const ix = await this.clmmProgram.methods
646
+ .tradePtExactOut(
647
+ new BN(amountOut.toString()),
648
+ swapDirection,
649
+ amountInConstraintOptional,
650
+ lnImpliedApyLimitOptional,
651
+ )
652
+ .accountsStrict({
653
+ trader,
654
+ market: this.selfAddress,
655
+ tokenPtTrader: tokenPt,
656
+ tokenSyTrader: tokenSy,
657
+ tokenSyEscrow: this.tokenSyEscrow,
658
+ tokenPtEscrow: this.tokenPtEscrow,
659
+ addressLookupTable: this.addressLookupTable,
660
+ tokenProgram: TOKEN_PROGRAM_ID,
661
+ syProgram: this.syProgram,
662
+ tokenFeeTreasurySy: this.tokenFeeTreasurySy,
663
+ eventAuthority: this.eventAuthority,
664
+ program: this.clmmProgram.programId,
665
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
666
+ ticks: this.ticksKey,
667
+ })
668
+ .remainingAccounts(remainingAccounts)
669
+ .instruction()
670
+
671
+ return { ixs: [ix], setupIxs: [tokenPtAtaIx, tokenSyAtaIx] }
672
+ }
673
+
674
+ /** Buy PT with SY
675
+ *
676
+ * The trader is the account that sends the SY
677
+ * The amountPt is the exact amount of PT the trader intends to buy
678
+ * The syConstraint is the maximum amount of SY the trader is willing to spend
679
+ *
680
+ * The token accounts themselves are optional, and will be derived from the trader's wallet if not provided
681
+ */
682
+ async ixBuyPt({
683
+ trader,
684
+ amountSy,
685
+ outConstraint,
686
+ tokenPt,
687
+ tokenSy,
688
+ lnImpliedApyLimit,
689
+ }: {
690
+ trader: web3.PublicKey
691
+ amountSy: bigint
692
+ outConstraint: bigint
693
+ tokenPt?: web3.PublicKey
694
+ tokenSy?: web3.PublicKey
695
+ lnImpliedApyLimit?: number
696
+ }) {
697
+ return this.ixTradePt({
698
+ trader,
699
+ traderAmount: amountSy,
700
+ outConstraint: outConstraint,
701
+ swapDirection: { syToPt: {} },
702
+ tokenPt,
703
+ tokenSy,
704
+ lnImpliedApyLimit,
705
+ })
706
+ }
707
+
708
+ /**
709
+ * Sell PT for SY
710
+ * The trader is the account that sends the PT
711
+ * The amountPt is the exact amount of PT the trader intends to sell
712
+ * The minSyReceive is the minimum amount of SY the trader is willing to receive
713
+ *
714
+ * The token accounts themselves are optional, and will be derived from the trader's wallet if not provided
715
+ */
716
+ async ixSellPt({
717
+ trader,
718
+ amountPt,
719
+ outConstraint,
720
+ tokenPt,
721
+ tokenSy,
722
+ lnImpliedApyLimit,
723
+ }: {
724
+ trader: web3.PublicKey
725
+ amountPt: bigint
726
+ outConstraint: bigint
727
+ tokenPt?: web3.PublicKey
728
+ tokenSy?: web3.PublicKey
729
+ lnImpliedApyLimit?: number
730
+ }) {
731
+ return this.ixTradePt({
732
+ trader,
733
+ traderAmount: amountPt,
734
+ outConstraint: outConstraint,
735
+ swapDirection: { ptToSy: {} },
736
+ tokenPt,
737
+ tokenSy,
738
+ lnImpliedApyLimit,
739
+ })
740
+ }
741
+
742
+ /**
743
+ * Buy exact amount of PT with SY
744
+ *
745
+ * The trader specifies the exact amount of PT they want to receive
746
+ * The maxSyIn is the maximum amount of SY the trader is willing to spend
747
+ */
748
+ async ixBuyPtExactOut({
749
+ trader,
750
+ amountPt,
751
+ maxSyIn,
752
+ tokenPt,
753
+ tokenSy,
754
+ lnImpliedApyLimit,
755
+ }: {
756
+ trader: web3.PublicKey
757
+ amountPt: bigint
758
+ maxSyIn?: bigint
759
+ tokenPt?: web3.PublicKey
760
+ tokenSy?: web3.PublicKey
761
+ lnImpliedApyLimit?: number
762
+ }) {
763
+ return this.ixTradePtExactOut({
764
+ trader,
765
+ amountOut: amountPt,
766
+ amountInConstraint: maxSyIn,
767
+ swapDirection: { syToPt: {} },
768
+ tokenPt,
769
+ tokenSy,
770
+ lnImpliedApyLimit,
771
+ })
772
+ }
773
+
774
+ /**
775
+ * Sell PT for exact amount of SY
776
+ *
777
+ * The trader specifies the exact amount of SY they want to receive
778
+ * The maxPtIn is the maximum amount of PT the trader is willing to spend
779
+ */
780
+ async ixSellPtExactOut({
781
+ trader,
782
+ amountSy,
783
+ maxPtIn,
784
+ tokenPt,
785
+ tokenSy,
786
+ lnImpliedApyLimit,
787
+ }: {
788
+ trader: web3.PublicKey
789
+ amountSy: bigint
790
+ maxPtIn?: bigint
791
+ tokenPt?: web3.PublicKey
792
+ tokenSy?: web3.PublicKey
793
+ lnImpliedApyLimit?: number
794
+ }) {
795
+ return this.ixTradePtExactOut({
796
+ trader,
797
+ amountOut: amountSy,
798
+ amountInConstraint: maxPtIn,
799
+ swapDirection: { ptToSy: {} },
800
+ tokenPt,
801
+ tokenSy,
802
+ lnImpliedApyLimit,
803
+ })
804
+ }
805
+
806
+ /** Buy YT with SY
807
+ *
808
+ * The trader is the account that sends the SY
809
+ *
810
+ * The ytOut is the exact amount of YT the trader intends to buy
811
+ *
812
+ * The maxSyIn is the maximum amount of SY the trader is willing to spend
813
+ *
814
+ * The token accounts themselves are optional, and will be derived from the trader's wallet if not provided
815
+ */
816
+ async ixBuyYt({
817
+ trader,
818
+ ytOut,
819
+ maxSyIn,
820
+ ytTrader: ytTraderParam,
821
+ ptTrader: ptTraderParam,
822
+ syTrader: syTraderParam,
823
+ lnImpliedApyLimit,
824
+ }: {
825
+ trader: web3.PublicKey
826
+ ytOut: bigint
827
+ maxSyIn: bigint
828
+ ytTrader?: web3.PublicKey
829
+ ptTrader?: web3.PublicKey
830
+ syTrader?: web3.PublicKey
831
+ lnImpliedApyLimit?: number
832
+ }) {
833
+ const syTrader = syTraderParam || getAssociatedTokenAddressSync(this.mintSy, trader, true, TOKEN_PROGRAM_ID)
834
+ const ptTrader = ptTraderParam || getAssociatedTokenAddressSync(this.mintPt, trader, true, TOKEN_PROGRAM_ID)
835
+ const ytTrader = ytTraderParam || getAssociatedTokenAddressSync(this.mintYt, trader, true, TOKEN_PROGRAM_ID)
836
+
837
+ const syTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(trader, syTrader, trader, this.mintSy)
838
+ const ptTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(trader, ptTrader, trader, this.mintPt)
839
+ const ytTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(trader, ytTrader, trader, this.mintYt)
840
+
841
+ const stripAccounts = this.cpiCoreAccounts.stripSy
842
+
843
+ const remainingAccounts = uniqueRemainingAccounts([
844
+ ...this.cpiSyAccounts.getSyState,
845
+ ...this.cpiSyAccounts.withdrawSy,
846
+ ...this.cpiSyAccounts.depositSy,
847
+ ...stripAccounts,
848
+ ])
849
+
850
+ let lnImpliedApyLimitOptional = lnImpliedApyLimit ? lnImpliedApyLimit : null
851
+ const ix = await this.clmmProgram.methods
852
+ .buyYt(new BN(maxSyIn.toString()), new BN(ytOut.toString()), lnImpliedApyLimitOptional)
853
+ .accountsStrict({
854
+ trader,
855
+ market: this.selfAddress,
856
+ tokenYtTrader: ytTrader,
857
+ tokenPtTrader: ptTrader,
858
+ tokenSyTrader: syTrader,
859
+ tokenSyEscrow: this.tokenSyEscrow,
860
+ tokenPtEscrow: this.tokenPtEscrow,
861
+ addressLookupTable: this.addressLookupTable,
862
+ tokenProgram: TOKEN_PROGRAM_ID,
863
+ syProgram: this.syProgram,
864
+ tokenFeeTreasurySy: this.tokenFeeTreasurySy,
865
+ eventAuthority: this.eventAuthority,
866
+ program: this.clmmProgram.programId,
867
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
868
+ ticks: this.ticksKey,
869
+ tokenYtEscrow: this.tokenYtEscrow,
870
+ exponentCoreProgram: this.state.exponentCoreProgram,
871
+ })
872
+ .remainingAccounts(remainingAccounts)
873
+ .instruction()
874
+
875
+ return { ixs: [ix], setupIxs: [syTraderAtaIx, ptTraderAtaIx, ytTraderAtaIx] }
876
+ }
877
+
878
+ /** Sell YT for SY
879
+ *
880
+ * The trader is the account that sends the YT
881
+ *
882
+ * The amountYt is the exact amount of YT the trader intends to sell
883
+ *
884
+ * The minSyOut is the minimum amount of SY the trader is willing to receive
885
+ *
886
+ * The token accounts themselves are optional, and will be derived from the trader's wallet if not provided
887
+ */
888
+ async ixSellYt({
889
+ trader,
890
+ ytIn,
891
+ minSyOut,
892
+ ytSrc: ytSrcParam,
893
+ ptSrc: ptSrcParam,
894
+ syDst: syDstParam,
895
+ lnImpliedApyLimit,
896
+ }: {
897
+ trader: web3.PublicKey
898
+ ytIn: bigint
899
+ minSyOut: bigint
900
+ ytSrc?: web3.PublicKey
901
+ ptSrc?: web3.PublicKey
902
+ syDst?: web3.PublicKey
903
+ lnImpliedApyLimit?: number
904
+ }) {
905
+ const syDst = syDstParam || getAssociatedTokenAddressSync(this.mintSy, trader, true, TOKEN_PROGRAM_ID)
906
+ const ptSrc = ptSrcParam || getAssociatedTokenAddressSync(this.mintPt, trader, true, TOKEN_PROGRAM_ID)
907
+ const ytSrc = ytSrcParam || getAssociatedTokenAddressSync(this.mintYt, trader, true, TOKEN_PROGRAM_ID)
908
+
909
+ const syDstAtaIxs = createAssociatedTokenAccountIdempotentInstruction(trader, syDst, trader, this.mintSy)
910
+ const ptSrcAtaIxs = createAssociatedTokenAccountIdempotentInstruction(trader, ptSrc, trader, this.mintPt)
911
+ const ytSrcAtaIxs = createAssociatedTokenAccountIdempotentInstruction(trader, ytSrc, trader, this.mintYt)
912
+
913
+ const mergeAccounts = this.cpiCoreAccounts.mergeSy
914
+ const remainingAccounts = uniqueRemainingAccounts([
915
+ ...this.cpiSyAccounts.getSyState,
916
+ ...this.cpiSyAccounts.getPositionState,
917
+ ...this.cpiSyAccounts.depositSy,
918
+ ...mergeAccounts,
919
+ ])
920
+
921
+ let lnImpliedApyLimitOptional = lnImpliedApyLimit ? lnImpliedApyLimit : null
922
+ const ix = await this.clmmProgram.methods
923
+ .sellYt(new BN(ytIn.toString()), new BN(minSyOut.toString()), lnImpliedApyLimitOptional)
924
+ .accountsStrict({
925
+ trader,
926
+ market: this.selfAddress,
927
+ tokenYtTrader: ytSrc,
928
+ tokenPtTrader: ptSrc,
929
+ tokenSyTrader: syDst,
930
+ tokenSyEscrow: this.tokenSyEscrow,
931
+ tokenPtEscrow: this.tokenPtEscrow,
932
+ addressLookupTable: this.addressLookupTable,
933
+ tokenProgram: TOKEN_PROGRAM_ID,
934
+ syProgram: this.syProgram,
935
+ tokenFeeTreasurySy: this.tokenFeeTreasurySy,
936
+ eventAuthority: this.eventAuthority,
937
+ program: this.clmmProgram.programId,
938
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
939
+ ticks: this.ticksKey,
940
+ tokenYtEscrow: this.tokenYtEscrow,
941
+ exponentCoreProgram: this.state.exponentCoreProgram,
942
+ })
943
+ .remainingAccounts(remainingAccounts)
944
+ .instruction()
945
+
946
+ return { ixs: [ix], setupIxs: [syDstAtaIxs, ptSrcAtaIxs, ytSrcAtaIxs] }
947
+ }
948
+
949
+ async ixModifyMarketSetting({ signer, adminAction }: { signer: web3.PublicKey; adminAction: MarketAdminAction }) {
950
+ return this.clmmProgram.methods
951
+ .modifyMarketSetting(adminAction)
952
+ .accountsStrict({
953
+ market: this.selfAddress,
954
+ signer,
955
+ systemProgram: web3.SystemProgram.programId,
956
+ })
957
+ .instruction()
958
+ }
959
+
960
+ /**
961
+ * Add a new farm to the market to distribute rewards to LP holders
962
+ *
963
+ * @param signer - The admin address that signs the transaction
964
+ * @param farmMint - The mint address of the farm reward token
965
+ * @param farmTokenProgram - The token program for the farm reward token
966
+ * @param emissionsRate - The rate of emissions per second (in token smallest units)
967
+ * @param untilTimestamp - Unix timestamp when the farm emissions should end
968
+ * @param farmTokenSrc - Optional source token account for the farm rewards (derived from signer if not provided)
969
+ * @param feePayer - Optional fee payer for account reallocation (defaults to signer)
970
+ */
971
+ async ixAddFarm({
972
+ signer,
973
+ farmMint,
974
+ farmTokenProgram,
975
+ emissionsRate,
976
+ untilTimestamp,
977
+ farmTokenSrc: farmTokenSrcParam,
978
+ feePayer: feePayerParam,
979
+ }: {
980
+ signer: web3.PublicKey
981
+ farmMint: web3.PublicKey
982
+ farmTokenProgram: web3.PublicKey
983
+ emissionsRate: bigint
984
+ untilTimestamp: number
985
+ farmTokenSrc?: web3.PublicKey
986
+ feePayer?: web3.PublicKey
987
+ }) {
988
+ const feePayer = feePayerParam || signer
989
+ const farmTokenEscrow = getAssociatedTokenAddressSync(farmMint, this.selfAddress, true, farmTokenProgram)
990
+ const farmTokenSrc = farmTokenSrcParam || getAssociatedTokenAddressSync(farmMint, signer, true, farmTokenProgram)
991
+
992
+ const farmTokenEscrowAtaIx = createAssociatedTokenAccountIdempotentInstruction(
993
+ feePayer,
994
+ farmTokenEscrow,
995
+ this.selfAddress,
996
+ farmMint,
997
+ farmTokenProgram,
998
+ )
999
+
1000
+ const ix = await this.clmmProgram.methods
1001
+ .addFarm(new BN(emissionsRate.toString()), untilTimestamp)
1002
+ .accountsStrict({
1003
+ market: this.selfAddress,
1004
+ ticks: this.ticksKey,
1005
+ signer,
1006
+ feePayer,
1007
+ mintNew: farmMint,
1008
+ tokenSource: farmTokenSrc,
1009
+ tokenFarm: farmTokenEscrow,
1010
+ tokenProgram: farmTokenProgram,
1011
+ systemProgram: web3.SystemProgram.programId,
1012
+ eventAuthority: this.eventAuthority,
1013
+ program: this.clmmProgram.programId,
1014
+ })
1015
+ .instruction()
1016
+
1017
+ return {
1018
+ ixs: [ix],
1019
+ setupIxs: [farmTokenEscrowAtaIx],
1020
+ }
1021
+ }
1022
+
1023
+ /**
1024
+ * Modify an existing farm's emissions rate and/or expiration timestamp
1025
+ *
1026
+ * If the new parameters require more tokens than previously undistributed,
1027
+ * additional tokens will be transferred from the signer's token account.
1028
+ * If fewer tokens are needed, the surplus will be returned to the signer.
1029
+ *
1030
+ * @param signer - The admin address that signs the transaction
1031
+ * @param farmMint - The mint address of the farm reward token
1032
+ * @param farmTokenProgram - The token program for the farm reward token
1033
+ * @param newRate - The new rate of emissions per second (in token smallest units)
1034
+ * @param untilTimestamp - New unix timestamp when the farm emissions should end
1035
+ * @param farmTokenSrc - Optional source/destination token account (derived from signer if not provided)
1036
+ */
1037
+ async ixModifyFarm({
1038
+ signer,
1039
+ farmMint,
1040
+ farmTokenProgram,
1041
+ newRate,
1042
+ untilTimestamp,
1043
+ farmTokenSrc: farmTokenSrcParam,
1044
+ }: {
1045
+ signer: web3.PublicKey
1046
+ farmMint: web3.PublicKey
1047
+ farmTokenProgram: web3.PublicKey
1048
+ newRate: bigint
1049
+ untilTimestamp: number
1050
+ farmTokenSrc?: web3.PublicKey
1051
+ }) {
1052
+ const farmTokenEscrow = getAssociatedTokenAddressSync(farmMint, this.selfAddress, true, farmTokenProgram)
1053
+ const farmTokenSrc = farmTokenSrcParam || getAssociatedTokenAddressSync(farmMint, signer, true, farmTokenProgram)
1054
+
1055
+ const ix = await this.clmmProgram.methods
1056
+ .modifyFarm(untilTimestamp, new BN(newRate.toString()))
1057
+ .accountsStrict({
1058
+ market: this.selfAddress,
1059
+ ticks: this.ticksKey,
1060
+ signer,
1061
+ mint: farmMint,
1062
+ tokenSource: farmTokenSrc,
1063
+ tokenFarm: farmTokenEscrow,
1064
+ tokenProgram: farmTokenProgram,
1065
+ eventAuthority: this.eventAuthority,
1066
+ program: this.clmmProgram.programId,
1067
+ })
1068
+ .instruction()
1069
+
1070
+ return {
1071
+ ixs: [ix],
1072
+ }
1073
+ }
1074
+
1075
+ async ixWrapperBuyPt({
1076
+ owner,
1077
+ minPtOut,
1078
+ baseIn,
1079
+ tokenSyTrader: tokenSyTraderParam,
1080
+ tokenPtTrader: tokenPtTraderParam,
1081
+ tokenBaseTrader: tokenBaseTraderParam,
1082
+ lnImpliedApyLimit,
1083
+ }: {
1084
+ owner: web3.PublicKey
1085
+ minPtOut: bigint
1086
+ baseIn: bigint
1087
+ tokenSyTrader?: web3.PublicKey
1088
+ tokenPtTrader?: web3.PublicKey
1089
+ tokenBaseTrader?: web3.PublicKey
1090
+ lnImpliedApyLimit?: number
1091
+ }) {
1092
+ const tokenSyTrader =
1093
+ tokenSyTraderParam || getAssociatedTokenAddressSync(this.mintSy, owner, true, TOKEN_PROGRAM_ID)
1094
+ const tokenPtTrader =
1095
+ tokenPtTraderParam || getAssociatedTokenAddressSync(this.mintPt, owner, true, TOKEN_PROGRAM_ID)
1096
+ const tokenBaseTrader =
1097
+ tokenBaseTraderParam ||
1098
+ getAssociatedTokenAddressSync(this.flavor.mintBase, owner, true, this.flavor.baseTokenProgram)
1099
+
1100
+ const tokenSyTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1101
+ owner,
1102
+ tokenSyTrader,
1103
+ owner,
1104
+ this.mintSy,
1105
+ )
1106
+ const tokenPtTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1107
+ owner,
1108
+ tokenPtTrader,
1109
+ owner,
1110
+ this.mintPt,
1111
+ )
1112
+ const tokenBaseTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1113
+ owner,
1114
+ tokenBaseTrader,
1115
+ owner,
1116
+ this.flavor.mintBase,
1117
+ )
1118
+
1119
+ const mintSyIx = await this.flavor.ixMintSy({
1120
+ amountBase: "0",
1121
+ depositor: owner,
1122
+ depositorBaseTokenAccount: tokenBaseTrader,
1123
+ depositorSyTokenAccount: tokenSyTrader,
1124
+ })
1125
+
1126
+ const mintSyRemAccounts = mintSyIx.keys
1127
+
1128
+ const remainingAccounts = uniqueRemainingAccounts([
1129
+ ...this.cpiSyAccounts.getSyState,
1130
+ ...this.cpiSyAccounts.depositSy,
1131
+ ...this.cpiSyAccounts.getPositionState,
1132
+ ])
1133
+
1134
+ let lnImpliedApyLimitOptional = lnImpliedApyLimit ? lnImpliedApyLimit : null
1135
+
1136
+ const ix = await this.clmmProgram.methods
1137
+ .buyPt(
1138
+ new BN(minPtOut.toString()),
1139
+ new BN(baseIn.toString()),
1140
+ lnImpliedApyLimitOptional,
1141
+ mintSyRemAccounts.length,
1142
+ )
1143
+ .accountsStrict({
1144
+ addressLookupTable: this.addressLookupTable,
1145
+ buyer: owner,
1146
+ market: this.selfAddress,
1147
+ tokenSyTrader,
1148
+ tokenPtTrader,
1149
+ tokenSyEscrow: this.tokenSyEscrow,
1150
+ tokenPtEscrow: this.tokenPtEscrow,
1151
+ tokenProgram: TOKEN_PROGRAM_ID,
1152
+ syProgram: this.syProgram,
1153
+ tokenFeeTreasurySy: this.state.tokenFeeTreasurySy,
1154
+ eventAuthority: this.eventAuthority,
1155
+ program: this.clmmProgram.programId,
1156
+ tokenFeeTreasuryPt: this.state.tokenFeeTreasuryPt,
1157
+ ticks: this.ticksKey,
1158
+ })
1159
+ .remainingAccounts(mintSyRemAccounts.concat(remainingAccounts))
1160
+ .instruction()
1161
+
1162
+ return {
1163
+ ixs: [...(await this.flavor.preIxs({ signer: owner })), ix, ...(await this.flavor.postIxs({ signer: owner }))],
1164
+ setupIxs: [tokenSyTraderAtaIx, tokenPtTraderAtaIx, tokenBaseTraderAtaIx],
1165
+ }
1166
+ }
1167
+
1168
+ async ixWrapperSellPt({
1169
+ owner,
1170
+ amount,
1171
+ minBaseOut,
1172
+ tokenSyTrader: tokenSyTraderParam,
1173
+ tokenPtTrader: tokenPtTraderParam,
1174
+ tokenBaseTrader: tokenBaseTraderParam,
1175
+ lnImpliedApyLimit,
1176
+ }: {
1177
+ owner: web3.PublicKey
1178
+ amount: bigint
1179
+ minBaseOut: bigint
1180
+ tokenSyTrader?: web3.PublicKey
1181
+ tokenPtTrader?: web3.PublicKey
1182
+ tokenBaseTrader?: web3.PublicKey
1183
+ lnImpliedApyLimit?: number
1184
+ }) {
1185
+ const tokenSyTrader =
1186
+ tokenSyTraderParam || getAssociatedTokenAddressSync(this.mintSy, owner, true, TOKEN_PROGRAM_ID)
1187
+ const tokenPtTrader =
1188
+ tokenPtTraderParam || getAssociatedTokenAddressSync(this.mintPt, owner, true, TOKEN_PROGRAM_ID)
1189
+ const tokenBaseTrader =
1190
+ tokenBaseTraderParam ||
1191
+ getAssociatedTokenAddressSync(this.flavor.mintBase, owner, true, this.flavor.baseTokenProgram)
1192
+
1193
+ const tokenSyTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1194
+ owner,
1195
+ tokenSyTrader,
1196
+ owner,
1197
+ this.mintSy,
1198
+ )
1199
+ const tokenPtTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1200
+ owner,
1201
+ tokenPtTrader,
1202
+ owner,
1203
+ this.mintPt,
1204
+ )
1205
+ const tokenBaseTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1206
+ owner,
1207
+ tokenBaseTrader,
1208
+ owner,
1209
+ this.flavor.mintBase,
1210
+ )
1211
+
1212
+ const redeemSyIx = await this.flavor.ixRedeemSy({
1213
+ amountSy: "0",
1214
+ redeemer: owner,
1215
+ redeemerBaseTokenAccount: tokenBaseTrader,
1216
+ redeemerSyTokenAccount: tokenSyTrader,
1217
+ })
1218
+
1219
+ const redeemSyRemAccounts = redeemSyIx.keys
1220
+
1221
+ const remainingAccounts = uniqueRemainingAccounts([
1222
+ ...this.cpiSyAccounts.getSyState,
1223
+ ...this.cpiSyAccounts.getPositionState,
1224
+ ...this.cpiSyAccounts.withdrawSy,
1225
+ ])
1226
+
1227
+ let lnImpliedApyLimitOptional = lnImpliedApyLimit ? lnImpliedApyLimit : null
1228
+
1229
+ const ix = await this.clmmProgram.methods
1230
+ .sellPt(
1231
+ new BN(amount.toString()),
1232
+ new BN(minBaseOut.toString()),
1233
+ lnImpliedApyLimitOptional,
1234
+ redeemSyRemAccounts.length,
1235
+ )
1236
+ .accountsStrict({
1237
+ seller: owner,
1238
+ market: this.selfAddress,
1239
+ tokenPtTrader,
1240
+ addressLookupTable: this.addressLookupTable,
1241
+ tokenProgram: TOKEN_PROGRAM_ID,
1242
+ syProgram: this.syProgram,
1243
+ tokenSyTrader,
1244
+ tokenPtEscrow: this.tokenPtEscrow,
1245
+ tokenSyEscrow: this.tokenSyEscrow,
1246
+ tokenFeeTreasurySy: this.state.tokenFeeTreasurySy,
1247
+ eventAuthority: this.eventAuthority,
1248
+ program: this.clmmProgram.programId,
1249
+ ticks: this.ticksKey,
1250
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
1251
+ })
1252
+ .remainingAccounts(redeemSyRemAccounts.concat(remainingAccounts))
1253
+ .instruction()
1254
+
1255
+ return {
1256
+ ixs: [...(await this.flavor.preIxs({ signer: owner })), ix, ...(await this.flavor.postIxs({ signer: owner }))],
1257
+ setupIxs: [tokenSyTraderAtaIx, tokenPtTraderAtaIx, tokenBaseTraderAtaIx],
1258
+ }
1259
+ }
1260
+
1261
+ async ixWrapperBuyYt({
1262
+ owner,
1263
+ ytOut,
1264
+ maxBaseIn,
1265
+ tokenSyTrader: tokenSyTraderParam,
1266
+ tokenPtTrader: tokenPtTraderParam,
1267
+ tokenYtTrader: tokenYtTraderParam,
1268
+ tokenBaseTrader: tokenBaseTraderParam,
1269
+ }: {
1270
+ owner: web3.PublicKey
1271
+ ytOut: bigint
1272
+ maxBaseIn: bigint
1273
+ tokenSyTrader?: web3.PublicKey
1274
+ tokenPtTrader?: web3.PublicKey
1275
+ tokenYtTrader?: web3.PublicKey
1276
+ tokenBaseTrader?: web3.PublicKey
1277
+ }) {
1278
+ const tokenSyTrader =
1279
+ tokenSyTraderParam || getAssociatedTokenAddressSync(this.mintSy, owner, true, TOKEN_PROGRAM_ID)
1280
+ const tokenPtTrader =
1281
+ tokenPtTraderParam || getAssociatedTokenAddressSync(this.mintPt, owner, true, TOKEN_PROGRAM_ID)
1282
+ const tokenYtTrader =
1283
+ tokenYtTraderParam || getAssociatedTokenAddressSync(this.mintYt, owner, true, TOKEN_PROGRAM_ID)
1284
+ const tokenBaseTrader =
1285
+ tokenBaseTraderParam ||
1286
+ getAssociatedTokenAddressSync(this.flavor.mintBase, owner, true, this.flavor.baseTokenProgram)
1287
+
1288
+ const tokenSyTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1289
+ owner,
1290
+ tokenSyTrader,
1291
+ owner,
1292
+ this.mintSy,
1293
+ )
1294
+ const tokenPtTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1295
+ owner,
1296
+ tokenPtTrader,
1297
+ owner,
1298
+ this.mintPt,
1299
+ )
1300
+ const tokenYtTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1301
+ owner,
1302
+ tokenYtTrader,
1303
+ owner,
1304
+ this.mintYt,
1305
+ )
1306
+
1307
+ const mintSyIx = await this.flavor.ixMintSy({
1308
+ amountBase: "0",
1309
+ depositor: owner,
1310
+ depositorBaseTokenAccount: tokenBaseTrader,
1311
+ depositorSyTokenAccount: tokenSyTrader,
1312
+ })
1313
+
1314
+ const depositYtAccounts = await this.depositYtAccounts({
1315
+ owner,
1316
+ ytSrc: tokenYtTrader,
1317
+ })
1318
+
1319
+ const mintSyRemAccounts = mintSyIx.keys
1320
+
1321
+ const remainingAccounts = uniqueRemainingAccounts([
1322
+ ...this.cpiSyAccounts.getSyState,
1323
+ ...this.cpiSyAccounts.getPositionState,
1324
+ ...this.cpiSyAccounts.withdrawSy,
1325
+ ...this.cpiCoreAccounts.stripSy,
1326
+ ...this.cpiSyAccounts.getPositionState,
1327
+ ])
1328
+
1329
+ const depositYtAccounts_until = mintSyRemAccounts.length + depositYtAccounts.keys.length
1330
+ const allRemainingAccounts = mintSyRemAccounts.concat(depositYtAccounts.keys).concat(remainingAccounts)
1331
+
1332
+ const ix1 = await this.clmmProgram.methods
1333
+ .wrapperBuyYt(
1334
+ new BN(ytOut.toString()),
1335
+ new BN(maxBaseIn.toString()),
1336
+ mintSyRemAccounts.length,
1337
+ depositYtAccounts_until,
1338
+ )
1339
+ .accountsStrict({
1340
+ buyer: owner,
1341
+ market: this.selfAddress,
1342
+ tokenSyTrader,
1343
+ tokenYtTrader,
1344
+ tokenPtTrader,
1345
+ tokenSyEscrow: this.tokenSyEscrow,
1346
+ tokenPtEscrow: this.tokenPtEscrow,
1347
+ marketAddressLookupTable: this.addressLookupTable,
1348
+ tokenProgram: TOKEN_PROGRAM_ID,
1349
+ syProgram: this.syProgram,
1350
+ systemProgram: web3.SystemProgram.programId,
1351
+ tokenFeeTreasurySy: this.state.tokenFeeTreasurySy,
1352
+ eventAuthority: this.eventAuthority,
1353
+ program: this.clmmProgram.programId,
1354
+ tokenYtEscrow: this.tokenYtEscrow,
1355
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
1356
+ ticks: this.ticksKey,
1357
+ exponentCoreProgram: this.state.exponentCoreProgram,
1358
+ })
1359
+ .remainingAccounts(allRemainingAccounts)
1360
+ .instruction()
1361
+
1362
+ return {
1363
+ ixs: [...(await this.flavor.preIxs({ signer: owner })), ix1],
1364
+ setupIxs: [tokenSyTraderAtaIx, tokenPtTraderAtaIx, tokenYtTraderAtaIx],
1365
+ }
1366
+ }
1367
+
1368
+ async ixWrapperSellYt({
1369
+ owner,
1370
+ amount,
1371
+ minBaseOut,
1372
+ tokenBaseTrader: tokenBaseTraderParam,
1373
+ tokenSyTrader: tokenSyTraderParam,
1374
+ tokenYtTrader: tokenYtTraderParam,
1375
+ tokenPtTrader: tokenPtTraderParam,
1376
+ }: {
1377
+ owner: web3.PublicKey
1378
+ amount: bigint
1379
+ minBaseOut: bigint
1380
+ tokenBaseTrader?: web3.PublicKey
1381
+ tokenSyTrader?: web3.PublicKey
1382
+ tokenYtTrader?: web3.PublicKey
1383
+ tokenPtTrader?: web3.PublicKey
1384
+ }) {
1385
+ const tokenBaseTrader =
1386
+ tokenBaseTraderParam ||
1387
+ getAssociatedTokenAddressSync(this.flavor.mintBase, owner, true, this.flavor.baseTokenProgram)
1388
+ const tokenSyTrader =
1389
+ tokenSyTraderParam || getAssociatedTokenAddressSync(this.mintSy, owner, true, TOKEN_PROGRAM_ID)
1390
+ const tokenYtTrader =
1391
+ tokenYtTraderParam || getAssociatedTokenAddressSync(this.mintYt, owner, true, TOKEN_PROGRAM_ID)
1392
+ const tokenPtTrader =
1393
+ tokenPtTraderParam || getAssociatedTokenAddressSync(this.mintPt, owner, true, TOKEN_PROGRAM_ID)
1394
+
1395
+ const tokenSyTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1396
+ owner,
1397
+ tokenSyTrader,
1398
+ owner,
1399
+ this.mintSy,
1400
+ )
1401
+ const tokenPtTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1402
+ owner,
1403
+ tokenPtTrader,
1404
+ owner,
1405
+ this.mintPt,
1406
+ )
1407
+ const tokenYtTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1408
+ owner,
1409
+ tokenYtTrader,
1410
+ owner,
1411
+ this.mintYt,
1412
+ )
1413
+ const tokenBaseTraderAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1414
+ owner,
1415
+ tokenBaseTrader,
1416
+ owner,
1417
+ this.flavor.mintBase,
1418
+ )
1419
+
1420
+ const redeemSyIx = await this.flavor.ixRedeemSy({
1421
+ amountSy: "0",
1422
+ redeemer: owner,
1423
+ redeemerBaseTokenAccount: tokenBaseTrader,
1424
+ redeemerSyTokenAccount: tokenSyTrader,
1425
+ })
1426
+
1427
+ const redeemSyRemAccounts = redeemSyIx.keys
1428
+
1429
+ const remainingAccounts = uniqueRemainingAccounts([
1430
+ ...this.cpiSyAccounts.getSyState,
1431
+ ...this.cpiSyAccounts.getPositionState,
1432
+ ...this.cpiCoreAccounts.mergeSy,
1433
+ ...this.cpiSyAccounts.withdrawSy,
1434
+ ...this.cpiSyAccounts.depositSy,
1435
+ ])
1436
+
1437
+ const ix1 = await this.clmmProgram.methods
1438
+ .wrapperSellYt(new BN(amount.toString()), new BN(minBaseOut.toString()), redeemSyRemAccounts.length)
1439
+ .accountsStrict({
1440
+ seller: owner,
1441
+ market: this.selfAddress,
1442
+ tokenYtTrader,
1443
+ marketAddressLookupTable: this.addressLookupTable,
1444
+ tokenPtTrader,
1445
+ tokenPtEscrow: this.tokenPtEscrow,
1446
+ tokenProgram: TOKEN_PROGRAM_ID,
1447
+ syProgram: this.syProgram,
1448
+ tokenSyTrader,
1449
+ tokenSyEscrow: this.tokenSyEscrow,
1450
+ tokenFeeTreasurySy: this.state.tokenFeeTreasurySy,
1451
+ eventAuthority: this.eventAuthority,
1452
+ program: this.clmmProgram.programId,
1453
+ tokenYtEscrow: this.tokenYtEscrow,
1454
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
1455
+ ticks: this.ticksKey,
1456
+ exponentCoreProgram: this.state.exponentCoreProgram,
1457
+ })
1458
+ .remainingAccounts(redeemSyRemAccounts.concat(remainingAccounts))
1459
+ .instruction()
1460
+
1461
+ return {
1462
+ ixs: [ix1, ...(await this.flavor.preIxs({ signer: owner }))],
1463
+ setupIxs: [tokenSyTraderAtaIx, tokenPtTraderAtaIx, tokenYtTraderAtaIx, tokenBaseTraderAtaIx],
1464
+ }
1465
+ }
1466
+
1467
+ /** Provide liquidity from a base asset - and receive YT and LP tokens in return */
1468
+ async ixWrapperProvideLiquidity({
1469
+ depositor,
1470
+ amountBase,
1471
+ minLpOut,
1472
+ lowerTickApy,
1473
+ upperTickApy,
1474
+ tokenSyDepositor: tokenSyDepositorParam,
1475
+ tokenYtDepositor: tokenYtDepositorParam,
1476
+ tokenPtDepositor: tokenPtDepositorParam,
1477
+ tokenBaseDepositor: tokenBaseDepositorParam,
1478
+ lpPosition: lpPositionParam,
1479
+ }: {
1480
+ depositor: web3.PublicKey
1481
+ amountBase: bigint
1482
+ minLpOut: bigint
1483
+ lowerTickApy: number
1484
+ upperTickApy: number
1485
+ tokenSyDepositor?: web3.PublicKey
1486
+ tokenYtDepositor?: web3.PublicKey
1487
+ tokenPtDepositor?: web3.PublicKey
1488
+ tokenBaseDepositor?: web3.PublicKey
1489
+ lpPosition?: web3.Keypair
1490
+ }) {
1491
+ const tokenSyDepositor =
1492
+ tokenSyDepositorParam || getAssociatedTokenAddressSync(this.mintSy, depositor, true, TOKEN_PROGRAM_ID)
1493
+ const tokenYtDepositor =
1494
+ tokenYtDepositorParam || getAssociatedTokenAddressSync(this.mintYt, depositor, true, TOKEN_PROGRAM_ID)
1495
+ const tokenPtDepositor =
1496
+ tokenPtDepositorParam || getAssociatedTokenAddressSync(this.mintPt, depositor, true, TOKEN_PROGRAM_ID)
1497
+ const tokenBaseDepositor =
1498
+ tokenBaseDepositorParam ||
1499
+ getAssociatedTokenAddressSync(this.flavor.mintBase, depositor, true, this.flavor.baseTokenProgram)
1500
+
1501
+ const lpPosition = lpPositionParam || web3.Keypair.generate()
1502
+
1503
+ const tokenSyDepositorAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1504
+ depositor,
1505
+ tokenSyDepositor,
1506
+ depositor,
1507
+ this.mintSy,
1508
+ )
1509
+ const tokenYtDepositorAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1510
+ depositor,
1511
+ tokenYtDepositor,
1512
+ depositor,
1513
+ this.mintYt,
1514
+ )
1515
+ const tokenPtDepositorAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1516
+ depositor,
1517
+ tokenPtDepositor,
1518
+ depositor,
1519
+ this.mintPt,
1520
+ )
1521
+
1522
+ const amountBaseBn = new BN(amountBase.toString())
1523
+ const minLpOutBn = new BN(minLpOut.toString())
1524
+
1525
+ const mintSyIx = await this.flavor.ixMintSy({
1526
+ // argument does not matter - since we are just getting the keys
1527
+ amountBase: "0",
1528
+ depositor,
1529
+ depositorBaseTokenAccount: tokenBaseDepositor,
1530
+ depositorSyTokenAccount: tokenSyDepositor,
1531
+ })
1532
+
1533
+ const mintSyRemAccounts = mintSyIx.keys
1534
+
1535
+ const unorderedRemainingAccounts = uniqueRemainingAccounts([
1536
+ ...this.cpiSyAccounts.getSyState,
1537
+ ...this.cpiSyAccounts.getPositionState,
1538
+ ...this.cpiCoreAccounts.stripSy,
1539
+ ...this.cpiSyAccounts.depositSy,
1540
+ ])
1541
+
1542
+ const depositYtAccounts = await this.depositYtAccounts({
1543
+ owner: depositor,
1544
+ ytSrc: tokenYtDepositor,
1545
+ })
1546
+ const depositAccountsUntil = mintSyRemAccounts.length + depositYtAccounts.keys.length
1547
+ const allRemainingAccounts = [...mintSyRemAccounts, ...depositYtAccounts.keys, ...unorderedRemainingAccounts]
1548
+
1549
+ const ix = await this.clmmProgram.methods
1550
+ .wrapperProvideLiquidity(
1551
+ convertApyToApyBp(lowerTickApy),
1552
+ convertApyToApyBp(upperTickApy),
1553
+ amountBaseBn,
1554
+ minLpOutBn,
1555
+ mintSyRemAccounts.length,
1556
+ depositAccountsUntil,
1557
+ )
1558
+ .accountsStrict({
1559
+ depositor,
1560
+ market: this.selfAddress,
1561
+ tokenPtEscrow: this.tokenPtEscrow,
1562
+ tokenSyEscrow: this.tokenSyEscrow,
1563
+ tokenSyDepositor,
1564
+ tokenYtDepositor,
1565
+ tokenPtDepositor,
1566
+ mintYt: this.vault.mintYt,
1567
+ mintPt: this.vault.mintPt,
1568
+ systemProgram: web3.SystemProgram.programId,
1569
+ tokenProgram: TOKEN_PROGRAM_ID,
1570
+ syProgram: this.syProgram,
1571
+ marketAddressLookupTable: this.addressLookupTable,
1572
+ eventAuthority: this.eventAuthority,
1573
+ program: this.clmmProgram.programId,
1574
+ lpPosition: lpPosition.publicKey,
1575
+ tokenYtEscrow: this.tokenYtEscrow,
1576
+ ticks: this.ticksKey,
1577
+ exponentCoreProgram: this.state.exponentCoreProgram,
1578
+ })
1579
+ .remainingAccounts(allRemainingAccounts)
1580
+ .instruction()
1581
+
1582
+ // console.log("ix accounts length", ix.keys.length)
1583
+
1584
+ return {
1585
+ ixs: [
1586
+ ...(await this.flavor.preIxs({ signer: depositor })),
1587
+ ix,
1588
+ ...(await this.flavor.postIxs({ signer: depositor })),
1589
+ ],
1590
+ signers: [lpPosition],
1591
+ setupIxs: [tokenSyDepositorAtaIx, tokenYtDepositorAtaIx, tokenPtDepositorAtaIx],
1592
+ }
1593
+ }
1594
+
1595
+ async ixProvideLiquidityClassic({
1596
+ depositor,
1597
+ amountBase,
1598
+ amountPt,
1599
+ minLpOut,
1600
+ lowerTickApy,
1601
+ upperTickApy,
1602
+ tokenSyDepositor: tokenSyDepositorParam,
1603
+ tokenYtDepositor: tokenYtDepositorParam,
1604
+ tokenPtDepositor: tokenPtDepositorParam,
1605
+ tokenBaseDepositor: tokenBaseDepositorParam,
1606
+ }: {
1607
+ depositor: web3.PublicKey
1608
+ amountBase: bigint
1609
+ amountPt: bigint
1610
+ minLpOut: bigint
1611
+ lowerTickApy: number
1612
+ upperTickApy: number
1613
+ tokenSyDepositor?: web3.PublicKey
1614
+ tokenYtDepositor?: web3.PublicKey
1615
+ tokenPtDepositor?: web3.PublicKey
1616
+ tokenBaseDepositor?: web3.PublicKey
1617
+ }) {
1618
+ const tokenSyDepositor =
1619
+ tokenSyDepositorParam || getAssociatedTokenAddressSync(this.mintSy, depositor, true, TOKEN_PROGRAM_ID)
1620
+ const tokenYtDepositor =
1621
+ tokenYtDepositorParam || getAssociatedTokenAddressSync(this.mintYt, depositor, true, TOKEN_PROGRAM_ID)
1622
+ const tokenPtDepositor =
1623
+ tokenPtDepositorParam || getAssociatedTokenAddressSync(this.mintPt, depositor, true, TOKEN_PROGRAM_ID)
1624
+ const tokenBaseDepositor =
1625
+ tokenBaseDepositorParam ||
1626
+ getAssociatedTokenAddressSync(this.flavor.mintBase, depositor, true, this.flavor.baseTokenProgram)
1627
+
1628
+ const lpPosition = web3.Keypair.generate()
1629
+
1630
+ const tokenSyAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1631
+ depositor,
1632
+ tokenSyDepositor,
1633
+ depositor,
1634
+ this.mintSy,
1635
+ )
1636
+ const tokenYtAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1637
+ depositor,
1638
+ tokenYtDepositor,
1639
+ depositor,
1640
+ this.mintYt,
1641
+ )
1642
+ const tokenPtAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1643
+ depositor,
1644
+ tokenPtDepositor,
1645
+ depositor,
1646
+ this.mintPt,
1647
+ )
1648
+ const tokenBaseAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1649
+ depositor,
1650
+ tokenBaseDepositor,
1651
+ depositor,
1652
+ this.flavor.mintBase,
1653
+ this.flavor.baseTokenProgram,
1654
+ )
1655
+
1656
+ const remainingAccounts = uniqueRemainingAccounts([
1657
+ ...this.cpiSyAccounts.depositSy,
1658
+ ...this.cpiSyAccounts.withdrawSy,
1659
+ ...this.cpiCoreAccounts.stripSy,
1660
+ ...this.cpiSyAccounts.getPositionState,
1661
+ ])
1662
+
1663
+ const mintSyIx = await this.flavor.ixMintSy({
1664
+ amountBase: "0",
1665
+ depositor,
1666
+ depositorBaseTokenAccount: tokenBaseDepositor,
1667
+ depositorSyTokenAccount: tokenSyDepositor,
1668
+ })
1669
+
1670
+ const mintSyRemAccounts = mintSyIx.keys
1671
+
1672
+ const ix = await this.clmmProgram.methods
1673
+ .wrapperProvideLiquidityClassic(
1674
+ convertApyToApyBp(lowerTickApy),
1675
+ convertApyToApyBp(upperTickApy),
1676
+ new BN(amountBase.toString()),
1677
+ new BN(amountPt.toString()),
1678
+ new BN(minLpOut.toString()),
1679
+ mintSyRemAccounts.length,
1680
+ )
1681
+ .accountsStrict({
1682
+ depositor,
1683
+ lpPosition: lpPosition.publicKey,
1684
+ market: this.selfAddress,
1685
+ tokenPtEscrow: this.tokenPtEscrow,
1686
+ tokenSyEscrow: this.tokenSyEscrow,
1687
+ syProgram: this.syProgram,
1688
+ tokenProgram: TOKEN_PROGRAM_ID,
1689
+ marketAddressLookupTable: this.addressLookupTable,
1690
+ tokenPtDepositor,
1691
+ tokenSyDepositor,
1692
+ systemProgram: web3.SystemProgram.programId,
1693
+ eventAuthority: this.eventAuthority,
1694
+ program: this.clmmProgram.programId,
1695
+ ticks: this.ticksKey,
1696
+ })
1697
+ .remainingAccounts(mintSyRemAccounts.concat(remainingAccounts))
1698
+ .instruction()
1699
+
1700
+ return {
1701
+ ixs: [
1702
+ ...(await this.flavor.preIxs({ signer: depositor })),
1703
+ ix,
1704
+ ...(await this.flavor.postIxs({ signer: depositor })),
1705
+ ],
1706
+ signers: [lpPosition],
1707
+ setupIxs: [tokenSyAtaIx, tokenYtAtaIx, tokenPtAtaIx, tokenBaseAtaIx],
1708
+ }
1709
+ }
1710
+
1711
+ async ixWithdrawLiquidityToBase({
1712
+ owner,
1713
+ amountLp,
1714
+ minBaseOut,
1715
+ lpPosition,
1716
+ tokenSyWithdrawer: tokenSyWithdrawerParam,
1717
+ tokenYtWithdrawer: tokenYtWithdrawerParam,
1718
+ tokenPtWithdrawer: tokenPtWithdrawerParam,
1719
+ tokenBaseWithdrawer: tokenBaseWithdrawerParam,
1720
+ }: {
1721
+ owner: web3.PublicKey
1722
+ amountLp: bigint
1723
+ minBaseOut: bigint
1724
+ lpPosition: web3.PublicKey
1725
+ tokenSyWithdrawer?: web3.PublicKey
1726
+ tokenYtWithdrawer?: web3.PublicKey
1727
+ tokenPtWithdrawer?: web3.PublicKey
1728
+ tokenBaseWithdrawer?: web3.PublicKey
1729
+ }) {
1730
+ const tokenSyWithdrawer =
1731
+ tokenSyWithdrawerParam || getAssociatedTokenAddressSync(this.mintSy, owner, true, TOKEN_PROGRAM_ID)
1732
+ const tokenYtWithdrawer =
1733
+ tokenYtWithdrawerParam || getAssociatedTokenAddressSync(this.mintYt, owner, true, TOKEN_PROGRAM_ID)
1734
+ const tokenPtWithdrawer =
1735
+ tokenPtWithdrawerParam || getAssociatedTokenAddressSync(this.mintPt, owner, true, TOKEN_PROGRAM_ID)
1736
+ const tokenBaseWithdrawer =
1737
+ tokenBaseWithdrawerParam ||
1738
+ getAssociatedTokenAddressSync(this.flavor.mintBase, owner, true, this.flavor.baseTokenProgram)
1739
+
1740
+ const tokenSyAtaIx = createAssociatedTokenAccountIdempotentInstruction(owner, tokenSyWithdrawer, owner, this.mintSy)
1741
+ const tokenYtAtaIx = createAssociatedTokenAccountIdempotentInstruction(owner, tokenYtWithdrawer, owner, this.mintYt)
1742
+ const tokenPtAtaIx = createAssociatedTokenAccountIdempotentInstruction(owner, tokenPtWithdrawer, owner, this.mintPt)
1743
+ const tokenBaseAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1744
+ owner,
1745
+ tokenBaseWithdrawer,
1746
+ owner,
1747
+ this.flavor.mintBase,
1748
+ this.flavor.baseTokenProgram,
1749
+ )
1750
+
1751
+ const remainingAccounts = uniqueRemainingAccounts([
1752
+ ...this.cpiSyAccounts.withdrawSy,
1753
+ ...this.cpiSyAccounts.getPositionState,
1754
+ ])
1755
+
1756
+ const redeemSyIx = await this.flavor.ixRedeemSy({
1757
+ amountSy: "0",
1758
+ redeemer: owner,
1759
+ redeemerBaseTokenAccount: tokenBaseWithdrawer,
1760
+ redeemerSyTokenAccount: tokenSyWithdrawer,
1761
+ })
1762
+
1763
+ const redeemSyRemAccounts = redeemSyIx.keys
1764
+
1765
+ const minSyOut = Number(minBaseOut) / this.currentSyExchangeRate
1766
+
1767
+ const ix = await this.clmmProgram.methods
1768
+ .wrapperWithdrawLiquidity(
1769
+ new BN(amountLp.toString()),
1770
+ new BN(minSyOut.toFixed(0).toString()),
1771
+ redeemSyRemAccounts.length,
1772
+ )
1773
+ .accountsStrict({
1774
+ market: this.selfAddress,
1775
+ tokenPtEscrow: this.tokenPtEscrow,
1776
+ tokenSyEscrow: this.tokenSyEscrow,
1777
+ lpPosition,
1778
+ marketAddressLookupTable: this.addressLookupTable,
1779
+ eventAuthority: this.eventAuthority,
1780
+ syProgram: this.syProgram,
1781
+ systemProgram: web3.SystemProgram.programId,
1782
+ tokenFeeTreasurySy: this.state.tokenFeeTreasurySy,
1783
+ tokenProgram: TOKEN_PROGRAM_ID,
1784
+ tokenPtWithdrawer,
1785
+ tokenSyWithdrawer,
1786
+ withdrawer: owner,
1787
+ ticks: this.ticksKey,
1788
+ tokenFeeTreasuryPt: this.tokenFeeTreasuryPt,
1789
+ program: this.clmmProgram.programId,
1790
+ })
1791
+ .remainingAccounts(redeemSyRemAccounts.concat(remainingAccounts))
1792
+ .instruction()
1793
+
1794
+ return {
1795
+ ixs: [...(await this.flavor.preIxs({ signer: owner })), ix, ...(await this.flavor.postIxs({ signer: owner }))],
1796
+ setupIxs: [tokenSyAtaIx, tokenYtAtaIx, tokenPtAtaIx, tokenBaseAtaIx],
1797
+ }
1798
+ }
1799
+
1800
+ async ixWithdrawLiquidityClassic({
1801
+ owner,
1802
+ amountLp,
1803
+ lpPosition,
1804
+ tokenSyWithdrawer: tokenSyWithdrawerParam,
1805
+ tokenYtWithdrawer: tokenYtWithdrawerParam,
1806
+ tokenPtWithdrawer: tokenPtWithdrawerParam,
1807
+ tokenBaseWithdrawer: tokenBaseWithdrawerParam,
1808
+ }: {
1809
+ owner: web3.PublicKey
1810
+ amountLp: bigint
1811
+ lpPosition: web3.PublicKey
1812
+ tokenSyWithdrawer?: web3.PublicKey
1813
+ tokenYtWithdrawer?: web3.PublicKey
1814
+ tokenPtWithdrawer?: web3.PublicKey
1815
+ tokenBaseWithdrawer?: web3.PublicKey
1816
+ }) {
1817
+ const tokenSyWithdrawer =
1818
+ tokenSyWithdrawerParam || getAssociatedTokenAddressSync(this.mintSy, owner, true, TOKEN_PROGRAM_ID)
1819
+ const tokenYtWithdrawer =
1820
+ tokenYtWithdrawerParam || getAssociatedTokenAddressSync(this.mintYt, owner, true, TOKEN_PROGRAM_ID)
1821
+ const tokenPtWithdrawer =
1822
+ tokenPtWithdrawerParam || getAssociatedTokenAddressSync(this.mintPt, owner, true, TOKEN_PROGRAM_ID)
1823
+ const tokenBaseWithdrawer =
1824
+ tokenBaseWithdrawerParam ||
1825
+ getAssociatedTokenAddressSync(this.flavor.mintBase, owner, true, this.flavor.baseTokenProgram)
1826
+
1827
+ const tokenSyAtaIx = createAssociatedTokenAccountIdempotentInstruction(owner, tokenSyWithdrawer, owner, this.mintSy)
1828
+ const tokenYtAtaIx = createAssociatedTokenAccountIdempotentInstruction(owner, tokenYtWithdrawer, owner, this.mintYt)
1829
+ const tokenPtAtaIx = createAssociatedTokenAccountIdempotentInstruction(owner, tokenPtWithdrawer, owner, this.mintPt)
1830
+ const tokenBaseAtaIx = createAssociatedTokenAccountIdempotentInstruction(
1831
+ owner,
1832
+ tokenBaseWithdrawer,
1833
+ owner,
1834
+ this.flavor.mintBase,
1835
+ this.flavor.baseTokenProgram,
1836
+ )
1837
+
1838
+ const remainingAccounts = uniqueRemainingAccounts([
1839
+ ...this.cpiSyAccounts.withdrawSy,
1840
+ ...this.cpiSyAccounts.getPositionState,
1841
+ ])
1842
+
1843
+ const redeemSyIx = await this.flavor.ixRedeemSy({
1844
+ amountSy: "0",
1845
+ redeemer: owner,
1846
+ redeemerBaseTokenAccount: tokenBaseWithdrawer,
1847
+ redeemerSyTokenAccount: tokenSyWithdrawer,
1848
+ })
1849
+
1850
+ const redeemSyRemAccounts = redeemSyIx.keys
1851
+
1852
+ const ixn = await this.clmmProgram.methods
1853
+ .wrapperWithdrawLiquidityClassic(new BN(amountLp.toString()), redeemSyRemAccounts.length)
1854
+ .accountsStrict({
1855
+ market: this.selfAddress,
1856
+ tokenPtEscrow: this.tokenPtEscrow,
1857
+ tokenSyEscrow: this.tokenSyEscrow,
1858
+ lpPosition,
1859
+ marketAddressLookupTable: this.addressLookupTable,
1860
+ eventAuthority: this.eventAuthority,
1861
+ syProgram: this.syProgram,
1862
+ systemProgram: web3.SystemProgram.programId,
1863
+ tokenProgram: TOKEN_PROGRAM_ID,
1864
+ tokenPtWithdrawer,
1865
+ tokenSyWithdrawer,
1866
+ withdrawer: owner,
1867
+ ticks: this.ticksKey,
1868
+ program: this.clmmProgram.programId,
1869
+ })
1870
+ .remainingAccounts(redeemSyRemAccounts.concat(remainingAccounts))
1871
+ .instruction()
1872
+
1873
+ return {
1874
+ ixs: [...(await this.flavor.preIxs({ signer: owner })), ixn, ...(await this.flavor.postIxs({ signer: owner }))],
1875
+ setupIxs: [tokenSyAtaIx, tokenYtAtaIx, tokenPtAtaIx, tokenBaseAtaIx],
1876
+ }
1877
+ }
1878
+
1879
+ async getUserLpPositions(owner: web3.PublicKey, market: web3.PublicKey) {
1880
+ const lpPositions = await (
1881
+ await this.clmmProgram.account.lpPosition.all()
1882
+ ).filter(
1883
+ (lpPos) =>
1884
+ lpPos.account.owner.toString() === owner.toString() && lpPos.account.market.toString() === market.toString(),
1885
+ )
1886
+ return {
1887
+ lpPositions: [lpPositions],
1888
+ }
1889
+ }
1890
+
1891
+ // async ixCloseMarket({ owner }: { owner: web3.PublicKey }) {
1892
+ // const ixn = await this.clmmProgram.methods
1893
+ // .closeMarket()
1894
+ // .accountsStrict({
1895
+ // market: this.selfAddress,
1896
+ // systemProgram: web3.SystemProgram.programId,
1897
+ // payer: owner,
1898
+ // ticks: this.ticksKey,
1899
+ // program: this.clmmProgram.programId,
1900
+ // eventAuthority: this.eventAuthority,
1901
+ // rent: web3.SYSVAR_RENT_PUBKEY,
1902
+ // })
1903
+ // .instruction()
1904
+
1905
+ // return {
1906
+ // ixs: [ixn],
1907
+ // }
1908
+ // }
1909
+
1910
+ async ixMarketAccureEmissions({ owner, lpPosition }: { owner: web3.PublicKey; lpPosition: web3.PublicKey }) {
1911
+ const remainingAccounts = uniqueRemainingAccounts([...this.cpiSyAccounts.getPositionState])
1912
+ const ixn = await this.clmmProgram.methods
1913
+ .marketAccrueEmission()
1914
+ .accountsStrict({
1915
+ market: this.selfAddress,
1916
+ systemProgram: web3.SystemProgram.programId,
1917
+ ticks: this.ticksKey,
1918
+ program: this.clmmProgram.programId,
1919
+ eventAuthority: this.eventAuthority,
1920
+ lpPosition,
1921
+ owner,
1922
+ addressLookupTable: this.addressLookupTable,
1923
+ syProgram: this.syProgram,
1924
+ })
1925
+ .remainingAccounts(remainingAccounts)
1926
+ .instruction()
1927
+
1928
+ return {
1929
+ ixs: [ixn],
1930
+ }
1931
+ }
1932
+
1933
+ /**
1934
+ * Calculate the current price per unit of liquidity (principal only) for a tick range.
1935
+ * Mirrors the on-chain `liquidity_unit_price_in_asset` helper but performs the
1936
+ * principal aggregation client-side using the fetched ticks account.
1937
+ *
1938
+ * @param lowerTickKey - Inclusive tick key (APY in parts-per-million) for the left boundary
1939
+ * @param upperTickKey - Exclusive tick key for the right boundary
1940
+ * @param ticksOverride - Optional ticks account (defaults to the instance state)
1941
+ * @param syExchangeRateOverride - Optional SY exchange rate (defaults to current flavor rate)
1942
+ * @returns Price per unit of liquidity in underlying asset terms (principal only)
1943
+ */
1944
+ liquidityUnitPriceInAsset({
1945
+ lowerTickKey,
1946
+ upperTickKey,
1947
+ ticksOverride,
1948
+ syExchangeRateOverride,
1949
+ }: {
1950
+ lowerTickKey: number
1951
+ upperTickKey: number
1952
+ ticksOverride?: Ticks
1953
+ syExchangeRateOverride?: number
1954
+ }): number {
1955
+ if (lowerTickKey >= upperTickKey) {
1956
+ throw new Error("lowerTickKey must be less than upperTickKey")
1957
+ }
1958
+
1959
+ const ticksAccount = ticksOverride ?? this.state.ticks
1960
+ if (!ticksAccount) {
1961
+ throw new Error("Ticks account data is unavailable")
1962
+ }
1963
+
1964
+ const relevantIntervals = ticksAccount.ticksTree.filter(
1965
+ (tick) => tick.apyBasePoints >= lowerTickKey && tick.apyBasePoints < upperTickKey,
1966
+ )
1967
+
1968
+ if (relevantIntervals.length === 0) {
1969
+ return 0
1970
+ }
1971
+
1972
+ let totalPrincipalPt = 0n
1973
+ let totalPrincipalSy = 0n
1974
+ let totalShareSupply = 0n
1975
+
1976
+ for (const interval of relevantIntervals) {
1977
+ totalPrincipalPt += interval.principalPt
1978
+ totalPrincipalSy += interval.principalSy
1979
+ totalShareSupply += interval.principalShareSupply
1980
+ }
1981
+
1982
+ if (totalShareSupply === 0n) {
1983
+ return 0
1984
+ }
1985
+
1986
+ const timeFactor = this.secondsRemaining / SECONDS_PER_YEAR
1987
+ const ptAssetValuePerToken = Math.exp(-timeFactor * ticksAccount.currentSpotPrice)
1988
+ const syExchangeRate = syExchangeRateOverride ?? this.currentSyExchangeRate
1989
+
1990
+ const principalPtValue = new Decimal(totalPrincipalPt.toString()).mul(ptAssetValuePerToken)
1991
+ const principalSyValue = new Decimal(totalPrincipalSy.toString()).mul(syExchangeRate)
1992
+ const principalValue = principalPtValue.plus(principalSyValue)
1993
+
1994
+ return principalValue.div(new Decimal(totalShareSupply.toString())).toNumber()
1995
+ }
1996
+
1997
+ /**
1998
+ * Compute fee growth inside a tick range using Uniswap V3 formula
1999
+ *
2000
+ * @param lowerTick - Lower tick boundary
2001
+ * @param upperTick - Upper tick boundary
2002
+ * @param feeGlobalPt - Optional: Override global PT fees (for historical calculations)
2003
+ * @param feeGlobalSy - Optional: Override global SY fees (for historical calculations)
2004
+ * @returns Object with inside_sy and inside_pt fee growth values
2005
+ */
2006
+ static computeFeeInsideForRange({
2007
+ currentTickPrice,
2008
+ lowerTickPrice,
2009
+ upperTickPrice,
2010
+ feeGrowthIndexGlobalPt,
2011
+ feeGrowthIndexGlobalSy,
2012
+ lowerTickOutsidePt,
2013
+ lowerTickOutsideSy,
2014
+ upperTickOutsidePt,
2015
+ upperTickOutsideSy,
2016
+ }: {
2017
+ currentTickPrice: number
2018
+ lowerTickPrice: number
2019
+ upperTickPrice: number
2020
+ feeGrowthIndexGlobalPt: bigint
2021
+ feeGrowthIndexGlobalSy: bigint
2022
+ lowerTickOutsidePt: bigint
2023
+ lowerTickOutsideSy: bigint
2024
+ upperTickOutsidePt: bigint
2025
+ upperTickOutsideSy: bigint
2026
+ }): { insideSy: bigint; insidePt: bigint } {
2027
+ // Uniswap V3 formula for computing fee inside a range
2028
+ let insideSy: bigint
2029
+ let insidePt: bigint
2030
+
2031
+ if (currentTickPrice < lowerTickPrice) {
2032
+ // Price below range: inside = outside(lower) - outside(upper)
2033
+ insideSy = lowerTickOutsideSy - upperTickOutsideSy
2034
+ insidePt = lowerTickOutsidePt - upperTickOutsidePt
2035
+ } else if (currentTickPrice >= upperTickPrice) {
2036
+ // Price above range: inside = outside(upper) - outside(lower)
2037
+ insideSy = upperTickOutsideSy - lowerTickOutsideSy
2038
+ insidePt = upperTickOutsidePt - lowerTickOutsidePt
2039
+ } else {
2040
+ // Price in range: inside = global - outside(lower) - outside(upper)
2041
+ insideSy = feeGrowthIndexGlobalSy - lowerTickOutsideSy - upperTickOutsideSy
2042
+ insidePt = feeGrowthIndexGlobalPt - lowerTickOutsidePt - upperTickOutsidePt
2043
+ }
2044
+
2045
+ return { insideSy, insidePt }
2046
+ }
2047
+
2048
+ /**
2049
+ * Calculate claimable fees for a position
2050
+ * Uses Q64.64 fixed-point math: tokens = floor((L * Δindex) >> 64)
2051
+ *
2052
+ * @param lpBalance - Position's liquidity balance
2053
+ * @param feeInsideLastSy - Last snapshot of inside SY fee growth
2054
+ * @param feeInsideLastPt - Last snapshot of inside PT fee growth
2055
+ * @param currentInsideSy - Current inside SY fee growth
2056
+ * @param currentInsidePt - Current inside PT fee growth
2057
+ * @param tokensOwedSy - Previously owed SY fees
2058
+ * @param tokensOwedPt - Previously owed PT fees
2059
+ * @returns Object with claimable and total fees
2060
+ */
2061
+ static calculateClaimableFees({
2062
+ lpBalance,
2063
+ feeInsideLastSy,
2064
+ feeInsideLastPt,
2065
+ currentInsideSy,
2066
+ currentInsidePt,
2067
+ tokensOwedSy,
2068
+ tokensOwedPt,
2069
+ }: {
2070
+ lpBalance: bigint
2071
+ feeInsideLastSy: bigint
2072
+ feeInsideLastPt: bigint
2073
+ currentInsideSy: bigint
2074
+ currentInsidePt: bigint
2075
+ tokensOwedSy: bigint
2076
+ tokensOwedPt: bigint
2077
+ }): {
2078
+ claimableSy: bigint
2079
+ claimablePt: bigint
2080
+ tokensOwedSy: bigint
2081
+ tokensOwedPt: bigint
2082
+ totalSy: bigint
2083
+ totalPt: bigint
2084
+ } {
2085
+ // Calculate delta (growth since last snapshot)
2086
+ const deltaSy = currentInsideSy > feeInsideLastSy ? currentInsideSy - feeInsideLastSy : 0n
2087
+ const deltaPt = currentInsidePt > feeInsideLastPt ? currentInsidePt - feeInsideLastPt : 0n
2088
+
2089
+ // Calculate claimable fees using Q64.64 math: tokens = floor((L * Δindex) >> 64)
2090
+ const claimableSy = (lpBalance * deltaSy) >> 64n
2091
+ const claimablePt = (lpBalance * deltaPt) >> 64n
2092
+
2093
+ //? TotalPt and TotalSy don't make sense here because tokensOwedSy and tokensOwedPt are always 0
2094
+ // Calculate totals
2095
+ const totalSy = claimableSy + tokensOwedSy
2096
+ const totalPt = claimablePt + tokensOwedPt
2097
+
2098
+ return {
2099
+ claimableSy,
2100
+ claimablePt,
2101
+ tokensOwedSy,
2102
+ tokensOwedPt,
2103
+ totalSy,
2104
+ totalPt,
2105
+ }
2106
+ }
2107
+
2108
+ /**
2109
+ * Calculate historical fee APY for a tick range
2110
+ *
2111
+ * @param feeGrowthDeltaSy - Change in global SY fee growth over period
2112
+ * @param feeGrowthDeltaPt - Change in global PT fee growth over period
2113
+ * @param avgLiquidity - Average liquidity in the range during period
2114
+ * @param hoursElapsed - Number of hours in the period
2115
+ * @returns Object with fee APY metrics
2116
+ */
2117
+ static calculateHistoricalFeeApy({
2118
+ feeGrowthDeltaSy,
2119
+ feeGrowthDeltaPt,
2120
+ avgLiquidity,
2121
+ hoursElapsed,
2122
+ }: {
2123
+ feeGrowthDeltaSy: bigint
2124
+ feeGrowthDeltaPt: bigint
2125
+ avgLiquidity: bigint
2126
+ hoursElapsed: number
2127
+ }): {
2128
+ feeApySy: number
2129
+ feeApyPt: number
2130
+ feeApyTotal: number
2131
+ totalFeesSy: bigint
2132
+ totalFeesPt: bigint
2133
+ } {
2134
+ if (avgLiquidity === 0n || hoursElapsed === 0) {
2135
+ return {
2136
+ feeApySy: 0,
2137
+ feeApyPt: 0,
2138
+ feeApyTotal: 0,
2139
+ totalFeesSy: 0n,
2140
+ totalFeesPt: 0n,
2141
+ }
2142
+ }
2143
+
2144
+ // Calculate total fees earned in the period using Q64.64 math
2145
+ const totalFeesSy = (feeGrowthDeltaSy * avgLiquidity) >> 64n
2146
+ const totalFeesPt = (feeGrowthDeltaPt * avgLiquidity) >> 64n
2147
+
2148
+ // Annualization factor (hours in a year / hours elapsed)
2149
+ const hoursPerYear = 8760
2150
+ const annualizationFactor = hoursPerYear / hoursElapsed
2151
+
2152
+ // Calculate APY as: (fees / avg_liquidity) * annualization_factor * 100
2153
+ const feeApySy = (Number((totalFeesSy * 10000n) / avgLiquidity) / 100) * annualizationFactor
2154
+ const feeApyPt = (Number((totalFeesPt * 10000n) / avgLiquidity) / 100) * annualizationFactor
2155
+ const feeApyTotal = feeApySy + feeApyPt
2156
+
2157
+ return {
2158
+ feeApySy,
2159
+ feeApyPt,
2160
+ feeApyTotal,
2161
+ totalFeesSy,
2162
+ totalFeesPt,
2163
+ }
2164
+ }
2165
+
2166
+ async depositYtAccounts({
2167
+ owner,
2168
+ ytSrc: ytSrcParam,
2169
+ }: {
2170
+ owner: web3.PublicKey
2171
+ ytSrc?: web3.PublicKey
2172
+ }): Promise<web3.TransactionInstruction> {
2173
+ const ytSrc = ytSrcParam || getAssociatedTokenAddressSync(this.mintYt, owner, true, TOKEN_PROGRAM_ID)
2174
+ const userYieldPosition = this.corePda.yieldPosition({ vault: this.state.vault.selfAddress, owner })
2175
+ const yieldPosition = this.corePda.vaultYieldPosition({ vault: this.state.vault.selfAddress })
2176
+ const escrowYt = this.corePda.escrowYt({ vault: this.state.vault.selfAddress })
2177
+ const mainAccounts = {
2178
+ depositor: owner,
2179
+ ytSrc,
2180
+ vault: this.state.vault.selfAddress,
2181
+ userYieldPosition: userYieldPosition,
2182
+ escrowYt: escrowYt,
2183
+ tokenProgram: TOKEN_PROGRAM_ID,
2184
+ syProgram: this.state.syProgram,
2185
+ addressLookupTable: this.vault.addressLookupTable,
2186
+ yieldPosition,
2187
+ systemProgram: web3.SystemProgram.programId,
2188
+ eventAuthority: this.coreEventAuthority,
2189
+ program: this.state.exponentCoreProgram,
2190
+ }
2191
+ const remainingAccounts = this.cpiSyAccounts.getSyState
2192
+
2193
+ const depositIx = await this.coreProgram.methods
2194
+ .depositYt(new BN(0))
2195
+ .accountsStrict(mainAccounts)
2196
+ .remainingAccounts(remainingAccounts)
2197
+ .instruction()
2198
+
2199
+ return depositIx
2200
+ }
2201
+
2202
+ static calcEstimatedYieldForPosition(
2203
+ marketSnapshotDataCurrent: MarketSnapshotData,
2204
+ marketSnapshotDataHistory: MarketSnapshotData,
2205
+ lowerPricePercentage: number,
2206
+ upperPricePercentage: number,
2207
+ baseTokenAmount: number,
2208
+ ) {
2209
+ //? Prices in format of 1 + percentage / 100
2210
+ const lowerPrice = 1 + lowerPricePercentage / 100
2211
+ //? Prices in format of 1 + percentage / 100
2212
+ const upperPrice = 1 + upperPricePercentage / 100
2213
+
2214
+ const { syNeeded: userSyProvided, ptNeeded: userPtProvided } = calcDepositSyAndPtFromBaseAmount({
2215
+ expirationTs:
2216
+ Number(marketSnapshotDataCurrent.financials.expirationTs) - marketSnapshotDataCurrent.snapshotTimeUnix,
2217
+ currentSpotPrice: marketSnapshotDataCurrent.currentSpotPrice,
2218
+ syExchangeRate: marketSnapshotDataCurrent.syExchangeRate,
2219
+ lowerPrice,
2220
+ upperPrice,
2221
+ baseTokenAmount: baseTokenAmount,
2222
+ })
2223
+
2224
+ const [lowerFeeGrowthOutsidePtHistory, lowerFeeGrowthOutsideSyHistory] =
2225
+ lowerPrice <= marketSnapshotDataHistory.currentSpotPrice
2226
+ ? [marketSnapshotDataHistory.feeGrowthIndexGlobalPt, marketSnapshotDataHistory.feeGrowthIndexGlobalSy]
2227
+ : [0n, 0n]
2228
+
2229
+ const [upperFeeGrowthOutsidePtHistory, upperFeeGrowthOutsideSyHistory] =
2230
+ upperPrice <= marketSnapshotDataHistory.currentSpotPrice
2231
+ ? [marketSnapshotDataHistory.feeGrowthIndexGlobalPt, marketSnapshotDataHistory.feeGrowthIndexGlobalSy]
2232
+ : [0n, 0n]
2233
+
2234
+ const { insideSy: insideSyHistory, insidePt: insidePtHistory } = MarketThree.computeFeeInsideForRange({
2235
+ currentTickPrice: marketSnapshotDataHistory.currentSpotPrice,
2236
+ lowerTickPrice: lowerPrice,
2237
+ upperTickPrice: upperPrice,
2238
+ feeGrowthIndexGlobalPt: marketSnapshotDataHistory.feeGrowthIndexGlobalPt,
2239
+ feeGrowthIndexGlobalSy: marketSnapshotDataHistory.feeGrowthIndexGlobalSy,
2240
+ lowerTickOutsidePt: lowerFeeGrowthOutsidePtHistory,
2241
+ lowerTickOutsideSy: lowerFeeGrowthOutsideSyHistory,
2242
+ upperTickOutsidePt: upperFeeGrowthOutsidePtHistory,
2243
+ upperTickOutsideSy: upperFeeGrowthOutsideSyHistory,
2244
+ })
2245
+
2246
+ const {
2247
+ liquidityTarget: liquidityTargetCurrent,
2248
+ syNeeded,
2249
+ ptNeeded,
2250
+ } = computeLiquidityTargetAndTokenNeedsForSnapshot(marketSnapshotDataCurrent, {
2251
+ lowerPrice,
2252
+ upperPrice,
2253
+ maxSy: userSyProvided,
2254
+ maxPt: userPtProvided,
2255
+ })
2256
+
2257
+ const { insideSy: insideSyCurrent, insidePt: insidePtCurrent } = MarketThree.computeFeeInsideForRange({
2258
+ currentTickPrice: marketSnapshotDataCurrent.currentSpotPrice,
2259
+ lowerTickPrice: lowerPrice,
2260
+ upperTickPrice: upperPrice,
2261
+ feeGrowthIndexGlobalPt: BigInt(marketSnapshotDataCurrent.feeGrowthIndexGlobalPt),
2262
+ feeGrowthIndexGlobalSy: BigInt(marketSnapshotDataCurrent.feeGrowthIndexGlobalSy),
2263
+ lowerTickOutsidePt: BigInt(lowerFeeGrowthOutsidePtHistory),
2264
+ lowerTickOutsideSy: BigInt(lowerFeeGrowthOutsideSyHistory),
2265
+ upperTickOutsidePt: BigInt(upperFeeGrowthOutsidePtHistory),
2266
+ upperTickOutsideSy: BigInt(upperFeeGrowthOutsideSyHistory),
2267
+ })
2268
+
2269
+ //? Single invocation
2270
+ const { claimablePt: ptExpectedFees, claimableSy: syExpectedFees } = MarketThree.calculateClaimableFees({
2271
+ lpBalance: BigInt(liquidityTargetCurrent),
2272
+ feeInsideLastSy: insideSyHistory,
2273
+ feeInsideLastPt: insidePtHistory,
2274
+ currentInsideSy: insideSyCurrent,
2275
+ currentInsidePt: insidePtCurrent,
2276
+ tokensOwedSy: 0n,
2277
+ tokensOwedPt: 0n,
2278
+ })
2279
+
2280
+ //? As we don't have autocompounding, use 1 as annualization factor
2281
+ const ANNUALIZATION_FACTOR = 1
2282
+
2283
+ //? Convert SY amounts to baseToken: baseToken = syAmount / syExchangeRate
2284
+ const syNeededBaseToken = syNeeded > 0 ? syNeeded * marketSnapshotDataCurrent.syExchangeRate : 0
2285
+ const syExpectedFeesBaseToken =
2286
+ syExpectedFees > 0n ? Number(syExpectedFees) * marketSnapshotDataCurrent.syExchangeRate : 0
2287
+
2288
+ const ptPriceInBaseToken = calcPtPriceInAsset(
2289
+ marketSnapshotDataCurrent.currentSpotPrice,
2290
+ Number(marketSnapshotDataCurrent.financials.expirationTs) - marketSnapshotDataCurrent.snapshotTimeUnix,
2291
+ )
2292
+
2293
+ const ptNeededBaseToken = ptNeeded * ptPriceInBaseToken
2294
+ const ptExpectedFeesBaseToken = Number(ptExpectedFees) * ptPriceInBaseToken
2295
+
2296
+ //? Calculate total baseToken value of the position
2297
+ const totalPositionValueBaseToken = syNeededBaseToken + ptNeededBaseToken
2298
+
2299
+ //? Calculate total fees earned in baseToken
2300
+ const totalFeesBaseToken = syExpectedFeesBaseToken + ptExpectedFeesBaseToken
2301
+
2302
+ //? Calculate unified fee rate: total fees / total position value
2303
+ const totalFeeRate = totalPositionValueBaseToken > 0 ? totalFeesBaseToken / totalPositionValueBaseToken : 0
2304
+
2305
+ //? Annualize the fee rate
2306
+ const totalFeeRateAnnualized = totalFeeRate * ANNUALIZATION_FACTOR
2307
+
2308
+ return {
2309
+ totalFeeRate: totalFeeRateAnnualized,
2310
+ ptExpectedFees,
2311
+ syExpectedFees,
2312
+ expectedFeesBaseToken: totalFeesBaseToken,
2313
+ }
2314
+ }
2315
+
2316
+ static calcGlobalFeeRate(
2317
+ marketSnapshotDataCurrent: MarketSnapshotData,
2318
+ marketSnapshotDataHistory: MarketSnapshotData,
2319
+ ) {
2320
+ const {
2321
+ financials: financialsCurrent,
2322
+ feeGrowthIndexGlobalPt: feeGrowthIndexGlobalPtCurrent,
2323
+ feeGrowthIndexGlobalSy: feeGrowthIndexGlobalSyCurrent,
2324
+ currentSpotPrice,
2325
+ currentPrefixSum,
2326
+ syExchangeRate,
2327
+ } = marketSnapshotDataCurrent
2328
+
2329
+ const {
2330
+ feeGrowthIndexGlobalPt: feeGrowthIndexGlobalPtHistory,
2331
+ feeGrowthIndexGlobalSy: feeGrowthIndexGlobalSyHistory,
2332
+ } = marketSnapshotDataHistory
2333
+
2334
+ const ptPriceInBaseToken = calcPtPriceInAsset(
2335
+ currentSpotPrice,
2336
+ Number(financialsCurrent.expirationTs) - marketSnapshotDataCurrent.snapshotTimeUnix,
2337
+ )
2338
+
2339
+ const feePt = Number(feeGrowthIndexGlobalPtCurrent - feeGrowthIndexGlobalPtHistory) / Number(currentPrefixSum)
2340
+ const feePtInBaseToken = feePt * ptPriceInBaseToken
2341
+
2342
+ const feeSy = Number(feeGrowthIndexGlobalSyCurrent - feeGrowthIndexGlobalSyHistory) / Number(currentPrefixSum)
2343
+ const feeSyInBaseToken = feeSy * syExchangeRate
2344
+
2345
+ const totalFeesInBaseToken = feePtInBaseToken + feeSyInBaseToken
2346
+
2347
+ const ptLiquidityInBaseToken = Number(financialsCurrent.ptBalance) * ptPriceInBaseToken
2348
+ const syLiquidityInBaseToken = Number(financialsCurrent.syBalance) * syExchangeRate
2349
+ const tvlInBaseToken = ptLiquidityInBaseToken + syLiquidityInBaseToken
2350
+
2351
+ const totalFeeRate = totalFeesInBaseToken / tvlInBaseToken
2352
+
2353
+ return {
2354
+ totalFeeRate,
2355
+ totalFeesInBaseToken,
2356
+ tvlInBaseToken,
2357
+ }
2358
+ }
2359
+
2360
+ /**
2361
+ * Returns PT and SY amounts that will be received on liquidity removal from a position
2362
+ * If liquidityToRemove is not provided, assume that the full position balance will be removed
2363
+ */
2364
+ getPtAndSyOnWithdrawLiquidity(position: LpPositionCLMM, liquidityToRemove?: bigint) {
2365
+ const { ticks, emissions } = this.state
2366
+
2367
+ return getPtAndSyOnWithdrawLiquidity(emissions, ticks, position, liquidityToRemove ?? position.lpBalance)
2368
+ }
2369
+ }
2370
+
2371
+ /**
2372
+ * Convert apy percents to input format
2373
+ */
2374
+ function convertApyToApyBp(price: number): number {
2375
+ return price * 1e4
2376
+ }
2377
+
2378
+ /** Market snapshot data for CLMM market */
2379
+ export type MarketSnapshotData = {
2380
+ syExchangeRate: number
2381
+ /** Current spot price in format of 1 + percentage / 100 */
2382
+ currentSpotPrice: number
2383
+ financials: MarketThreeFinancials
2384
+ feeGrowthIndexGlobalPt: bigint
2385
+ feeGrowthIndexGlobalSy: bigint
2386
+ currentPrefixSum: bigint
2387
+ snapshotTimeUnix: number
2388
+ }
2389
+
2390
+ /** Wrapper for computeLiquidityTargetAndTokenNeeds that takes a MarketSnapshotData as input */
2391
+ function computeLiquidityTargetAndTokenNeedsForSnapshot(
2392
+ marketSnapshotData: MarketSnapshotData,
2393
+ params: {
2394
+ /** lowerPrice in format of 1 + percentage / 100 */
2395
+ lowerPrice: number
2396
+ /** lowerPrice in format of 1 + percentage / 100 */
2397
+ upperPrice: number
2398
+ maxSy: number
2399
+ maxPt: number
2400
+ },
2401
+ ) {
2402
+ const EPSILON_CLAMP = 1e-18
2403
+
2404
+ const { lowerPrice, upperPrice, maxSy, maxPt } = params
2405
+ const { financials, currentSpotPrice, syExchangeRate, snapshotTimeUnix } = marketSnapshotData
2406
+
2407
+ const expirationTsNumber = Number(financials.expirationTs)
2408
+
2409
+ const secondsRemaining = Math.max(0, expirationTsNumber - snapshotTimeUnix)
2410
+
2411
+ const effSnap = new EffSnap(normalizedTimeRemaining(secondsRemaining), syExchangeRate)
2412
+
2413
+ const priceEffLower = effSnap.getEffectivePrice(lowerPrice)
2414
+ const priceEffUpper = effSnap.getEffectivePrice(upperPrice)
2415
+
2416
+ return computeLiquidityTargetAndTokenNeeds(
2417
+ effSnap,
2418
+ currentSpotPrice,
2419
+ priceEffLower,
2420
+ priceEffUpper,
2421
+ lowerPrice,
2422
+ upperPrice,
2423
+ 0,
2424
+ 0,
2425
+ 0,
2426
+ maxSy,
2427
+ maxPt,
2428
+ EPSILON_CLAMP,
2429
+ )
2430
+ }