@exponent-labs/generic-sy-sdk 0.9.19 → 0.9.21

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/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  ASSOCIATED_TOKEN_PROGRAM_ID,
5
5
  TOKEN_2022_PROGRAM_ID,
6
6
  TOKEN_PROGRAM_ID,
7
+ createAssociatedTokenAccountIdempotentInstruction,
7
8
  getAssociatedTokenAddressSync,
8
9
  } from "@solana/spl-token"
9
10
 
@@ -30,12 +31,19 @@ import {
30
31
  GenericSyState,
31
32
  Hook,
32
33
  InterfaceTypeGeneric,
34
+ getGenericSyMetaInitInterfaceType,
35
+ getGenericSyMetaInterfaceType,
33
36
  } from "@exponent-labs/exponent-types"
34
37
  import { FRAGMETRIC_PROGRAM_ID, FragmetricPda } from "@exponent-labs/fragmetric-pda"
35
38
  import { PROGRAM_ID as GENERIC_STANDARD_PROGRAM_ID, GenericStandard, IDL } from "@exponent-labs/generic-sy-idl"
36
39
  import { GenericSyPda } from "@exponent-labs/generic-sy-pda"
37
40
  import { PreciseNumber } from "@exponent-labs/precise-number"
38
41
 
42
+ import {
43
+ buildExponentTranchingUpdateMarketInstruction,
44
+ fetchExponentTranchingRate,
45
+ } from "./interfaces/exponentTranching"
46
+ import { fetchGlowVaultRate } from "./interfaces/glowVault"
39
47
  import { SyInitializeConfig, SyTokenMetadata } from "./types"
40
48
  import { MyWallet } from "./utils"
41
49
 
@@ -86,48 +94,7 @@ async function loadAccountState(
86
94
  genericStandardProgramId: web3.PublicKey,
87
95
  interfaceType: InterfaceTypeGeneric,
88
96
  ) {
89
- let interfaceIndex: number
90
- if ("pyth" in interfaceType) {
91
- interfaceIndex = InterfaceType.Pyth
92
- } else if ("splStakePool" in interfaceType) {
93
- interfaceIndex = InterfaceType.SplStakePool
94
- } else if ("one" in interfaceType) {
95
- interfaceIndex = InterfaceType.One
96
- } else if ("fragmetric" in interfaceType) {
97
- interfaceIndex = InterfaceType.Fragmetric
98
- } else if ("meteora" in interfaceType) {
99
- interfaceIndex = InterfaceType.Meteora
100
- } else if ("sanctum" in interfaceType) {
101
- interfaceIndex = InterfaceType.Sanctum
102
- } else if ("fragmetricSupportedAsset" in interfaceType) {
103
- interfaceIndex = InterfaceType.FragmetricSupportedAsset
104
- } else if ("jupiterPerps" in interfaceType) {
105
- interfaceIndex = InterfaceType.JupiterPerps
106
- } else if ("adrena" in interfaceType) {
107
- interfaceIndex = InterfaceType.Adrena
108
- } else if ("hyloLpToken" in interfaceType) {
109
- interfaceIndex = InterfaceType.HyloLpToken
110
- } else if ("jupiterLend" in interfaceType) {
111
- interfaceIndex = InterfaceType.JupiterLend
112
- } else if ("kaminoVault" in interfaceType) {
113
- interfaceIndex = InterfaceType.KaminoVault
114
- } else if ("solstice" in interfaceType) {
115
- interfaceIndex = InterfaceType.Solstice
116
- } else if ("reflect" in interfaceType) {
117
- interfaceIndex = InterfaceType.Reflect
118
- } else if ("ore" in interfaceType) {
119
- interfaceIndex = InterfaceType.Ore
120
- } else if ("chainlink" in interfaceType) {
121
- interfaceIndex = InterfaceType.Chainlink
122
- } else if ("chainlinkDataStream" in interfaceType) {
123
- interfaceIndex = InterfaceType.ChainlinkDataStream
124
- } else if ("scope" in interfaceType) {
125
- interfaceIndex = InterfaceType.Scope
126
- } else if ("solsticeGlamVault" in interfaceType) {
127
- interfaceIndex = InterfaceType.SolsticeGlamVault
128
- } else {
129
- throw new Error("Unsupported interface type")
130
- }
97
+ const interfaceIndex = getInterfaceTypeIndex(interfaceType)
131
98
  const pda = new GenericSyPda(genericStandardProgramId, interfaceIndex)
132
99
  const fetcher = new ExponentFetcher({ connection: cnx, genericStandardProgramId })
133
100
  const syMetaAddress = pda.syMeta({ yieldBearingMint })
@@ -157,6 +124,34 @@ export enum InterfaceType {
157
124
  ChainlinkDataStream = 16,
158
125
  Scope = 17,
159
126
  SolsticeGlamVault = 18,
127
+ ExponentTranching = 19,
128
+ GlowVault = 20,
129
+ }
130
+
131
+ export function getInterfaceTypeIndex(interfaceType: InterfaceTypeGeneric): InterfaceType {
132
+ if ("pyth" in interfaceType) return InterfaceType.Pyth
133
+ if ("splStakePool" in interfaceType) return InterfaceType.SplStakePool
134
+ if ("one" in interfaceType) return InterfaceType.One
135
+ if ("fragmetricSupportedAsset" in interfaceType) return InterfaceType.FragmetricSupportedAsset
136
+ if ("fragmetric" in interfaceType) return InterfaceType.Fragmetric
137
+ if ("meteora" in interfaceType) return InterfaceType.Meteora
138
+ if ("sanctum" in interfaceType) return InterfaceType.Sanctum
139
+ if ("jupiterPerps" in interfaceType) return InterfaceType.JupiterPerps
140
+ if ("adrena" in interfaceType) return InterfaceType.Adrena
141
+ if ("hyloLpToken" in interfaceType) return InterfaceType.HyloLpToken
142
+ if ("jupiterLend" in interfaceType) return InterfaceType.JupiterLend
143
+ if ("kaminoVault" in interfaceType) return InterfaceType.KaminoVault
144
+ if ("solstice" in interfaceType) return InterfaceType.Solstice
145
+ if ("reflect" in interfaceType) return InterfaceType.Reflect
146
+ if ("ore" in interfaceType) return InterfaceType.Ore
147
+ if ("chainlinkDataStream" in interfaceType) return InterfaceType.ChainlinkDataStream
148
+ if ("chainlink" in interfaceType) return InterfaceType.Chainlink
149
+ if ("scope" in interfaceType) return InterfaceType.Scope
150
+ if ("solsticeGlamVault" in interfaceType) return InterfaceType.SolsticeGlamVault
151
+ if ("exponentTranching" in interfaceType) return InterfaceType.ExponentTranching
152
+ if ("glowVault" in interfaceType) return InterfaceType.GlowVault
153
+
154
+ throw new Error("Unsupported interface type")
160
155
  }
161
156
 
162
157
  export function getInterfaceTypeObj(
@@ -219,6 +214,10 @@ export function getInterfaceTypeObj(
219
214
  return { scope: { priceChain: scopePriceChain, maximumAgeSeconds: new BN(scopeMaximumAgeSeconds) } }
220
215
  case InterfaceType.SolsticeGlamVault:
221
216
  return { solsticeGlamVault: { padding: [] } }
217
+ case InterfaceType.ExponentTranching:
218
+ return { exponentTranching: {} }
219
+ case InterfaceType.GlowVault:
220
+ return { glowVault: { padding: [] } }
222
221
  default:
223
222
  throw new Error(`Unsupported interface type: ${interfaceType}`)
224
223
  }
@@ -278,6 +277,10 @@ export function getInterfaceTypeFromString(interfaceType: string): InterfaceType
278
277
  return { scope: { priceChain: [0, 512, 512, 512], maximumAgeSeconds: new BN(3600) } }
279
278
  case "solstice-glam-vault":
280
279
  return { solsticeGlamVault: { padding: [] } }
280
+ case "exponent-tranching":
281
+ return { exponentTranching: {} }
282
+ case "glow-vault":
283
+ return { glowVault: { padding: [] } }
281
284
  default:
282
285
  throw new Error(`Unsupported interface type: ${interfaceType}`)
283
286
  }
@@ -306,50 +309,7 @@ export class GenericSySdk {
306
309
  new AnchorProvider(new web3.Connection("https://api.devnet.solana.com"), new MyWallet(web3.Keypair.generate())),
307
310
  )
308
311
 
309
- // Determine interface index based on the interface type from state
310
- let interfaceIndex: number
311
- if ("pyth" in this.state.account.interfaceType) {
312
- interfaceIndex = InterfaceType.Pyth
313
- } else if ("splStakePool" in this.state.account.interfaceType) {
314
- interfaceIndex = InterfaceType.SplStakePool
315
- } else if ("one" in this.state.account.interfaceType) {
316
- interfaceIndex = InterfaceType.One
317
- } else if ("fragmetricSupportedAsset" in this.state.account.interfaceType) {
318
- interfaceIndex = InterfaceType.FragmetricSupportedAsset
319
- } else if ("fragmetric" in this.state.account.interfaceType) {
320
- interfaceIndex = InterfaceType.Fragmetric
321
- } else if ("meteora" in this.state.account.interfaceType) {
322
- interfaceIndex = InterfaceType.Meteora
323
- } else if ("sanctum" in this.state.account.interfaceType) {
324
- interfaceIndex = InterfaceType.Sanctum
325
- } else if ("jupiterPerps" in this.state.account.interfaceType) {
326
- interfaceIndex = InterfaceType.JupiterPerps
327
- } else if ("adrena" in this.state.account.interfaceType) {
328
- interfaceIndex = InterfaceType.Adrena
329
- } else if ("hyloLpToken" in this.state.account.interfaceType) {
330
- interfaceIndex = InterfaceType.HyloLpToken
331
- } else if ("jupiterLend" in this.state.account.interfaceType) {
332
- interfaceIndex = InterfaceType.JupiterLend
333
- } else if ("kaminoVault" in this.state.account.interfaceType) {
334
- interfaceIndex = InterfaceType.KaminoVault
335
- } else if ("solstice" in this.state.account.interfaceType) {
336
- interfaceIndex = InterfaceType.Solstice
337
- } else if ("reflect" in this.state.account.interfaceType) {
338
- interfaceIndex = InterfaceType.Reflect
339
- } else if ("ore" in this.state.account.interfaceType) {
340
- interfaceIndex = InterfaceType.Ore
341
- } else if ("chainlink" in this.state.account.interfaceType) {
342
- interfaceIndex = InterfaceType.Chainlink
343
- } else if ("chainlinkDataStream" in this.state.account.interfaceType) {
344
- interfaceIndex = InterfaceType.ChainlinkDataStream
345
- } else if ("scope" in this.state.account.interfaceType) {
346
- interfaceIndex = InterfaceType.Scope
347
- } else if ("solsticeGlamVault" in this.state.account.interfaceType) {
348
- interfaceIndex = InterfaceType.SolsticeGlamVault
349
- } else {
350
- throw new Error("Unsupported interface type")
351
- }
352
-
312
+ const interfaceIndex = getInterfaceTypeIndex(getGenericSyMetaInitInterfaceType(this.state.account))
353
313
  this.syPda = new GenericSyPda(programId, interfaceIndex)
354
314
  }
355
315
 
@@ -414,7 +374,7 @@ export class GenericSySdk {
414
374
  } else if ("meteora" in interfaceType) {
415
375
  const currentTs = Math.floor(Date.now() / 1000)
416
376
 
417
- const accounts = {
377
+ let accounts = {
418
378
  pool: account.interfaceAccounts[0],
419
379
  vaultA: account.interfaceAccounts[4],
420
380
  vaultB: account.interfaceAccounts[5],
@@ -434,11 +394,10 @@ export class GenericSySdk {
434
394
 
435
395
  return { syRate: exchangeRate }
436
396
  } else if ("adrena" in interfaceType) {
437
- // @ts-ignore
438
- const previousTotalFees = account.interfaceType.adrena.previousTotalFees
397
+ const previousTotalFees = interfaceType.adrena.previousTotalFees
439
398
  const currentIndex = account.index
440
399
 
441
- let accounts = {
400
+ const accounts = {
442
401
  pool: account.interfaceAccounts[0],
443
402
  custody1: account.interfaceAccounts[1],
444
403
  custody2: account.interfaceAccounts[2],
@@ -450,8 +409,7 @@ export class GenericSySdk {
450
409
 
451
410
  return { syRate: syRate.index }
452
411
  } else if ("jupiterPerps" in interfaceType) {
453
- // @ts-ignore
454
- const j = account.interfaceType.jupiterPerps
412
+ const j = interfaceType.jupiterPerps
455
413
  const { index } = await fetchJupiterPerpsIndex({
456
414
  connection: cnx,
457
415
  pool: account.interfaceAccounts[0],
@@ -574,6 +532,28 @@ export class GenericSySdk {
574
532
  stslxMint,
575
533
  })
576
534
  return { syRate }
535
+ } else if ("exponentTranching" in interfaceType) {
536
+ const marketAddress = account.interfaceAccounts[0]
537
+ if (account.interfaceAccounts.length !== 1 || !marketAddress) {
538
+ throw new Error("Exponent Tranching interface requires exactly one market account")
539
+ }
540
+ const syRate = await fetchExponentTranchingRate({
541
+ connection: cnx,
542
+ marketAddress,
543
+ yieldBearingMint: account.yieldBearingMint,
544
+ })
545
+ return { syRate }
546
+ } else if ("glowVault" in interfaceType) {
547
+ const vaultAddress = account.interfaceAccounts[0]
548
+ if (account.interfaceAccounts.length !== 1 || !vaultAddress) {
549
+ throw new Error("Glow Vault interface requires exactly one vault account")
550
+ }
551
+ const syRate = await fetchGlowVaultRate({
552
+ connection: cnx,
553
+ vaultAddress,
554
+ expectedShareMint: account.yieldBearingMint,
555
+ })
556
+ return { syRate }
577
557
  } else {
578
558
  return { syRate: 1 }
579
559
  }
@@ -596,53 +576,16 @@ export class GenericSySdk {
596
576
  const account = await loadAccountState(cnx, yieldBearingMint, genericStandardProgramId, interfaceType)
597
577
  const tokenProgramBase = (await cnx.getAccountInfo(yieldBearingMint))?.owner
598
578
 
599
- let interfaceIndex: number
600
- if ("pyth" in interfaceType) {
601
- interfaceIndex = InterfaceType.Pyth
602
- } else if ("splStakePool" in interfaceType) {
603
- interfaceIndex = InterfaceType.SplStakePool
604
- } else if ("one" in interfaceType) {
605
- interfaceIndex = InterfaceType.One
606
- } else if ("fragmetricSupportedAsset" in interfaceType) {
607
- interfaceIndex = InterfaceType.FragmetricSupportedAsset
608
- } else if ("fragmetric" in interfaceType) {
609
- interfaceIndex = InterfaceType.Fragmetric
610
- } else if ("meteora" in interfaceType) {
611
- interfaceIndex = InterfaceType.Meteora
612
- } else if ("sanctum" in interfaceType) {
613
- interfaceIndex = InterfaceType.Sanctum
614
- } else if ("jupiterPerps" in interfaceType) {
615
- interfaceIndex = InterfaceType.JupiterPerps
616
- } else if ("adrena" in interfaceType) {
617
- interfaceIndex = InterfaceType.Adrena
618
- } else if ("hyloLpToken" in interfaceType) {
619
- interfaceIndex = InterfaceType.HyloLpToken
620
- } else if ("jupiterLend" in interfaceType) {
621
- interfaceIndex = InterfaceType.JupiterLend
622
- } else if ("kaminoVault" in interfaceType) {
623
- interfaceIndex = InterfaceType.KaminoVault
624
- } else if ("solstice" in interfaceType) {
625
- interfaceIndex = InterfaceType.Solstice
626
- } else if ("reflect" in interfaceType) {
627
- interfaceIndex = InterfaceType.Reflect
628
- } else if ("ore" in interfaceType) {
629
- interfaceIndex = InterfaceType.Ore
630
- } else if ("chainlink" in interfaceType) {
631
- interfaceIndex = InterfaceType.Chainlink
632
- } else if ("chainlinkDataStream" in interfaceType) {
633
- interfaceIndex = InterfaceType.ChainlinkDataStream
634
- } else if ("scope" in interfaceType) {
635
- interfaceIndex = InterfaceType.Scope
636
- } else if ("solsticeGlamVault" in interfaceType) {
637
- interfaceIndex = InterfaceType.SolsticeGlamVault
638
- } else {
639
- throw new Error("Unsupported interface type")
640
- }
579
+ const interfaceIndex = getInterfaceTypeIndex(getGenericSyMetaInitInterfaceType(account))
641
580
  const selfAddress = new GenericSyPda(genericStandardProgramId, interfaceIndex).syMeta({
642
581
  yieldBearingMint,
643
582
  })
644
583
 
645
- const { syRate, additionalData } = await GenericSySdk.calculateSyRate(account, interfaceType, cnx)
584
+ const { syRate, additionalData } = await GenericSySdk.calculateSyRate(
585
+ account,
586
+ getGenericSyMetaInterfaceType(account),
587
+ cnx,
588
+ )
646
589
 
647
590
  const state: GenericSyState = {
648
591
  selfAddress,
@@ -671,7 +614,11 @@ export class GenericSySdk {
671
614
  const account = await fetcher.fetchGenericSyMeta(syMetaAddress)
672
615
  const tokenProgramBase = (await cnx.getAccountInfo(account.yieldBearingMint))?.owner
673
616
 
674
- const { syRate, additionalData } = await GenericSySdk.calculateSyRate(account, account.interfaceType, cnx)
617
+ const { syRate, additionalData } = await GenericSySdk.calculateSyRate(
618
+ account,
619
+ getGenericSyMetaInterfaceType(account),
620
+ cnx,
621
+ )
675
622
 
676
623
  const state: GenericSyState = {
677
624
  selfAddress: syMetaAddress,
@@ -686,14 +633,14 @@ export class GenericSySdk {
686
633
 
687
634
  /** Reload the state of the account */
688
635
  async reload(cnx: web3.Connection) {
689
- const account = await loadAccountState(
636
+ const fetcher = new ExponentFetcher({ connection: cnx, genericStandardProgramId: this.program.programId })
637
+ const account = await fetcher.fetchGenericSyMeta(this.state.selfAddress)
638
+ this.state.account = account
639
+ const { syRate, additionalData } = await GenericSySdk.calculateSyRate(
640
+ account,
641
+ getGenericSyMetaInterfaceType(account),
690
642
  cnx,
691
- this.state.account.yieldBearingMint,
692
- this.program.programId,
693
- this.state.account.interfaceType,
694
643
  )
695
- this.state.account = account
696
- const { syRate, additionalData } = await GenericSySdk.calculateSyRate(account, account.interfaceType, cnx)
697
644
  this.state.syRate = syRate
698
645
  this.state.additionalData = additionalData
699
646
  }
@@ -702,6 +649,14 @@ export class GenericSySdk {
702
649
  return this.state.account
703
650
  }
704
651
 
652
+ get interfaceType() {
653
+ return getGenericSyMetaInterfaceType(this.account)
654
+ }
655
+
656
+ get initInterfaceType() {
657
+ return getGenericSyMetaInitInterfaceType(this.account)
658
+ }
659
+
705
660
  get mintSy() {
706
661
  return this.account.mintSy
707
662
  }
@@ -751,10 +706,7 @@ export class GenericSySdk {
751
706
  }
752
707
 
753
708
  get fragmetricData() {
754
- if (
755
- !("fragmetric" in this.state.account.interfaceType) &&
756
- !("fragmetricSupportedAsset" in this.state.account.interfaceType)
757
- ) {
709
+ if (!("fragmetric" in this.interfaceType) && !("fragmetricSupportedAsset" in this.interfaceType)) {
758
710
  throw new Error("Not a fragmetric interface")
759
711
  }
760
712
  if (!this.state.additionalData?.fragmetricData) {
@@ -764,7 +716,7 @@ export class GenericSySdk {
764
716
  }
765
717
 
766
718
  get jupiterLendData() {
767
- if (!("jupiterLend" in this.state.account.interfaceType)) {
719
+ if (!("jupiterLend" in this.interfaceType)) {
768
720
  throw new Error("Not a Jupiter Lend interface")
769
721
  }
770
722
  if (!this.state.additionalData?.jupiterLendData) {
@@ -774,7 +726,7 @@ export class GenericSySdk {
774
726
  }
775
727
 
776
728
  get kaminoVaultData() {
777
- if (!("kaminoVault" in this.state.account.interfaceType)) {
729
+ if (!("kaminoVault" in this.interfaceType)) {
778
730
  throw new Error("Not a Kamino Vault interface")
779
731
  }
780
732
  if (!this.state.additionalData?.kaminoVaultData) {
@@ -804,11 +756,7 @@ export class GenericSySdk {
804
756
  }))
805
757
  .concat(this.emissions.map((e) => ({ pubkey: e.escrowAccount, isSigner: false, isWritable: false })))
806
758
 
807
- if (
808
- ("fragmetric" in this.state.account.interfaceType ||
809
- "fragmetricSupportedAsset" in this.state.account.interfaceType) &&
810
- signer
811
- ) {
759
+ if (("fragmetric" in this.interfaceType || "fragmetricSupportedAsset" in this.interfaceType) && signer) {
812
760
  // If skipWrap is true, return minimal accounts (skip additional interface-specific accounts)
813
761
  if (this.config.skipWrap) {
814
762
  return accounts
@@ -881,7 +829,7 @@ export class GenericSySdk {
881
829
  )
882
830
  }
883
831
 
884
- if ("jupiterLend" in this.state.account.interfaceType) {
832
+ if ("jupiterLend" in this.interfaceType) {
885
833
  // If skipWrap is true, return minimal accounts (skip additional interface-specific accounts)
886
834
  if (this.config.skipWrap) {
887
835
  return accounts
@@ -1024,7 +972,7 @@ export class GenericSySdk {
1024
972
  }
1025
973
  }
1026
974
 
1027
- if ("kaminoVault" in this.state.account.interfaceType) {
975
+ if ("kaminoVault" in this.interfaceType) {
1028
976
  // If skipWrap is true, return minimal accounts (skip additional interface-specific accounts)
1029
977
  if (this.config.skipWrap) {
1030
978
  return accounts
@@ -1155,9 +1103,33 @@ export class GenericSySdk {
1155
1103
  .instruction()
1156
1104
  }
1157
1105
 
1158
- preInstructions() {
1159
- let ixs = []
1160
- if ("jupiterLend" in this.state.account.interfaceType) {
1106
+ private getFragmetricReceiptTokenAccountSetupInstruction(signer?: web3.PublicKey) {
1107
+ const isFragmetric = "fragmetric" in this.interfaceType || "fragmetricSupportedAsset" in this.interfaceType
1108
+ if (!signer || !isFragmetric || this.config.skipWrap) {
1109
+ return null
1110
+ }
1111
+
1112
+ const { receiptTokenMint } = this.fragmetricData
1113
+ const userReceiptTokenAccount = getAssociatedTokenAddressSync(receiptTokenMint, signer, true, TOKEN_2022_PROGRAM_ID)
1114
+
1115
+ return createAssociatedTokenAccountIdempotentInstruction(
1116
+ signer,
1117
+ userReceiptTokenAccount,
1118
+ signer,
1119
+ receiptTokenMint,
1120
+ TOKEN_2022_PROGRAM_ID,
1121
+ )
1122
+ }
1123
+
1124
+ /** Returns interface-specific setup instructions for the signer. */
1125
+ preInstructions(signer?: web3.PublicKey) {
1126
+ const ixs: web3.TransactionInstruction[] = []
1127
+ const receiptTokenAccountSetupIx = this.getFragmetricReceiptTokenAccountSetupInstruction(signer)
1128
+ if (receiptTokenAccountSetupIx) {
1129
+ ixs.push(receiptTokenAccountSetupIx)
1130
+ }
1131
+
1132
+ if ("jupiterLend" in this.interfaceType) {
1161
1133
  const jupiterLendData = this.state.additionalData?.jupiterLendData
1162
1134
  if (!jupiterLendData) {
1163
1135
  throw new Error("Jupiter lend data not loaded")
@@ -1197,7 +1169,7 @@ export class GenericSySdk {
1197
1169
  ixs.push(refreshInstruction)
1198
1170
  }
1199
1171
 
1200
- if ("splStakePool" in this.state.account.interfaceType) {
1172
+ if ("splStakePool" in this.interfaceType) {
1201
1173
  const splStakePoolData = this.state.additionalData?.splStakePoolData
1202
1174
  if (!splStakePoolData) {
1203
1175
  throw new Error("SPL stake pool data not loaded")
@@ -1226,6 +1198,23 @@ export class GenericSySdk {
1226
1198
  return ixs
1227
1199
  }
1228
1200
 
1201
+ /** Returns signer setup and refresh instructions, including those that require live interface state. */
1202
+ async getPreInstructions(cnx: web3.Connection, signer?: web3.PublicKey): Promise<web3.TransactionInstruction[]> {
1203
+ const instructions = this.preInstructions(signer)
1204
+ if (!("exponentTranching" in this.interfaceType)) return instructions
1205
+
1206
+ const marketAddress = this.account.interfaceAccounts[0]
1207
+ if (this.account.interfaceAccounts.length !== 1 || !marketAddress) {
1208
+ throw new Error("Exponent Tranching interface requires exactly one market account")
1209
+ }
1210
+
1211
+ const updateMarket = await buildExponentTranchingUpdateMarketInstruction({
1212
+ connection: cnx,
1213
+ marketAddress,
1214
+ })
1215
+ return [updateMarket, ...instructions]
1216
+ }
1217
+
1229
1218
  getSyState() {
1230
1219
  return this.program.methods
1231
1220
  .getState()
@@ -1304,6 +1293,37 @@ export class GenericSySdk {
1304
1293
  })
1305
1294
  .instruction()
1306
1295
  }
1296
+
1297
+ ixChangeInterfaceType({
1298
+ signer,
1299
+ interfaceAccounts,
1300
+ interfaceType,
1301
+ }: {
1302
+ signer: web3.PublicKey
1303
+ interfaceAccounts: web3.PublicKey[]
1304
+ interfaceType: InterfaceTypeGeneric
1305
+ }) {
1306
+ return this.program.methods
1307
+ .changeInterfaceType(interfaceAccounts, interfaceType)
1308
+ .accountsStrict({
1309
+ adminState: this.adminState,
1310
+ feePayer: signer,
1311
+ meta: this.selfAddress,
1312
+ mintSy: this.mintSy,
1313
+ signer,
1314
+ systemProgram: web3.SystemProgram.programId,
1315
+ tokenSyEscrow: this.tokenSyEscrow,
1316
+ yieldBearingMint: this.vrtMint,
1317
+ })
1318
+ .remainingAccounts(
1319
+ interfaceAccounts.map((pubkey) => ({
1320
+ pubkey,
1321
+ isSigner: false,
1322
+ isWritable: true,
1323
+ })),
1324
+ )
1325
+ .instruction()
1326
+ }
1307
1327
  }
1308
1328
 
1309
1329
  export function initSy(
@@ -0,0 +1,125 @@
1
+ import { BN, BorshAccountsCoder, type Idl, web3 } from "@coral-xyz/anchor"
2
+
3
+ import { IDL as EXPONENT_TRANCHING_IDL, PROGRAM_ID } from "@exponent-labs/exponent-tranching-idl"
4
+
5
+ const EXPONENT_TRANCHING_PROGRAM_ID = new web3.PublicKey(PROGRAM_ID)
6
+ const UPDATE_MARKET_DISCRIMINATOR = Buffer.from([153, 39, 2, 197, 179, 50, 199, 217])
7
+ const NUMBER_DENOMINATOR = 1_000_000_000_000n
8
+
9
+ type TranchingNumber = { 0: BN[] }
10
+
11
+ type CpiInterfaceContext = {
12
+ alt_index: number
13
+ is_signer: boolean
14
+ is_writable: boolean
15
+ }
16
+
17
+ type ExponentTranchingMarketAccount = {
18
+ address_lookup_table: web3.PublicKey
19
+ sy_program: web3.PublicKey
20
+ mint_lp_senior: web3.PublicKey
21
+ mint_lp_junior: web3.PublicKey
22
+ return_model_storage: web3.PublicKey
23
+ financials: {
24
+ sr_effective_net_asset: TranchingNumber
25
+ jr_effective_net_asset: TranchingNumber
26
+ }
27
+ tranche_supply_state: {
28
+ total_senior_lp_supply: BN
29
+ total_junior_lp_supply: BN
30
+ }
31
+ sy_cpi_accounts: {
32
+ get_sy_state: CpiInterfaceContext[]
33
+ }
34
+ }
35
+
36
+ const accountCoder = new BorshAccountsCoder(EXPONENT_TRANCHING_IDL as Idl)
37
+
38
+ function tranchingNumberToRaw(value: TranchingNumber): bigint {
39
+ if (value[0].length !== 4) {
40
+ throw new Error("Invalid Exponent Tranching number")
41
+ }
42
+
43
+ let raw = 0n
44
+ for (let index = 3; index >= 0; index--) {
45
+ raw = (raw << 64n) + BigInt(value[0][index]!.toString())
46
+ }
47
+ return raw
48
+ }
49
+
50
+ function calculateLpRate(effectiveNetAsset: TranchingNumber, totalLpSupply: BN): number {
51
+ const priceRaw =
52
+ (tranchingNumberToRaw(effectiveNetAsset) + NUMBER_DENOMINATOR) / (BigInt(totalLpSupply.toString()) + 1n)
53
+ return Number(priceRaw) / Number(NUMBER_DENOMINATOR)
54
+ }
55
+
56
+ async function fetchMarket(
57
+ connection: web3.Connection,
58
+ marketAddress: web3.PublicKey,
59
+ ): Promise<ExponentTranchingMarketAccount> {
60
+ const accountInfo = await connection.getAccountInfo(marketAddress)
61
+ if (!accountInfo) {
62
+ throw new Error(`Exponent Tranching market not found: ${marketAddress.toBase58()}`)
63
+ }
64
+ if (!accountInfo.owner.equals(EXPONENT_TRANCHING_PROGRAM_ID)) {
65
+ throw new Error(`Invalid Exponent Tranching market owner: ${accountInfo.owner.toBase58()}`)
66
+ }
67
+
68
+ return accountCoder.decode("ExponentTranchingMarket", accountInfo.data) as ExponentTranchingMarketAccount
69
+ }
70
+
71
+ /** Reads the selected tranche's virtual-supply NAV from an Exponent Tranching market. */
72
+ export async function fetchExponentTranchingRate(params: {
73
+ connection: web3.Connection
74
+ marketAddress: web3.PublicKey
75
+ yieldBearingMint: web3.PublicKey
76
+ }): Promise<number> {
77
+ const market = await fetchMarket(params.connection, params.marketAddress)
78
+
79
+ if (params.yieldBearingMint.equals(market.mint_lp_senior)) {
80
+ return calculateLpRate(market.financials.sr_effective_net_asset, market.tranche_supply_state.total_senior_lp_supply)
81
+ }
82
+ if (params.yieldBearingMint.equals(market.mint_lp_junior)) {
83
+ return calculateLpRate(market.financials.jr_effective_net_asset, market.tranche_supply_state.total_junior_lp_supply)
84
+ }
85
+
86
+ throw new Error(`Tranching market does not contain yield-bearing mint ${params.yieldBearingMint.toBase58()}`)
87
+ }
88
+
89
+ /** Builds the permissionless refresh required before reading a slot-fresh tranching NAV. */
90
+ export async function buildExponentTranchingUpdateMarketInstruction(params: {
91
+ connection: web3.Connection
92
+ marketAddress: web3.PublicKey
93
+ }): Promise<web3.TransactionInstruction> {
94
+ const market = await fetchMarket(params.connection, params.marketAddress)
95
+ const lookupTable = await params.connection.getAddressLookupTable(market.address_lookup_table)
96
+ if (!lookupTable.value) {
97
+ throw new Error(`Exponent Tranching lookup table not found: ${market.address_lookup_table.toBase58()}`)
98
+ }
99
+
100
+ const getSyStateAccounts = market.sy_cpi_accounts.get_sy_state.map((context) => {
101
+ const pubkey = lookupTable.value!.state.addresses[context.alt_index]
102
+ if (!pubkey) {
103
+ throw new Error(`Exponent Tranching get_sy_state ALT index ${context.alt_index} is out of bounds`)
104
+ }
105
+ return { pubkey, isSigner: context.is_signer, isWritable: context.is_writable }
106
+ })
107
+ const [eventAuthority] = web3.PublicKey.findProgramAddressSync(
108
+ [Buffer.from("__event_authority")],
109
+ EXPONENT_TRANCHING_PROGRAM_ID,
110
+ )
111
+
112
+ return new web3.TransactionInstruction({
113
+ programId: EXPONENT_TRANCHING_PROGRAM_ID,
114
+ data: UPDATE_MARKET_DISCRIMINATOR,
115
+ keys: [
116
+ { pubkey: params.marketAddress, isSigner: false, isWritable: true },
117
+ { pubkey: market.return_model_storage, isSigner: false, isWritable: true },
118
+ { pubkey: market.address_lookup_table, isSigner: false, isWritable: false },
119
+ { pubkey: market.sy_program, isSigner: false, isWritable: false },
120
+ { pubkey: eventAuthority, isSigner: false, isWritable: false },
121
+ { pubkey: EXPONENT_TRANCHING_PROGRAM_ID, isSigner: false, isWritable: false },
122
+ ...getSyStateAccounts,
123
+ ],
124
+ })
125
+ }