@exponent-labs/generic-sy-sdk 0.9.20 → 0.9.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +16 -3
- package/build/index.js +164 -211
- package/build/index.js.map +1 -1
- package/build/interfaces/exponentTranching.d.ts +12 -0
- package/build/interfaces/exponentTranching.js +75 -0
- package/build/interfaces/exponentTranching.js.map +1 -0
- package/build/interfaces/glowVault.d.ts +7 -0
- package/build/interfaces/glowVault.js +46 -0
- package/build/interfaces/glowVault.js.map +1 -0
- package/build/types.d.ts +0 -1
- package/package.json +9 -8
- package/src/index.ts +287 -165
- package/src/interfaces/exponentTranching.ts +125 -0
- package/src/interfaces/glowVault.ts +55 -0
- package/tsconfig.json +3 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { web3 } from "@coral-xyz/anchor"
|
|
2
|
+
|
|
3
|
+
const GLOW_VAULT_PROGRAM_ID = new web3.PublicKey("gwv1ybUe2JVEpjdWARK1PjZUVY5xdNUCRhu24tgYtxa")
|
|
4
|
+
const GLOW_VAULT_DISCRIMINATOR = Buffer.from([211, 8, 232, 43, 2, 152, 117, 119])
|
|
5
|
+
const GLOW_VAULT_ACCOUNT_SIZE = 8 + 552
|
|
6
|
+
const SHARE_MINT_OFFSET = 8 + 160
|
|
7
|
+
const DEPOSIT_TOKENS_OFFSET = 8 + 320
|
|
8
|
+
const DEPOSIT_SHARES_OFFSET = 8 + 336
|
|
9
|
+
const UNCOLLECTED_MANAGEMENT_FEES_OFFSET = 8 + 344
|
|
10
|
+
const MINIMUM_SHARES_DUST_THRESHOLD_OFFSET = 8 + 408
|
|
11
|
+
const REALISED_PERFORMANCE_FEES_OFFSET = 8 + 488
|
|
12
|
+
const NUMBER_DENOMINATOR = 1_000_000_000_000n
|
|
13
|
+
|
|
14
|
+
/** Reads a Glow vault's net underlying-token value per share. */
|
|
15
|
+
export async function fetchGlowVaultRate(params: {
|
|
16
|
+
connection: web3.Connection
|
|
17
|
+
vaultAddress: web3.PublicKey
|
|
18
|
+
expectedShareMint: web3.PublicKey
|
|
19
|
+
}): Promise<number> {
|
|
20
|
+
const accountInfo = await params.connection.getAccountInfo(params.vaultAddress)
|
|
21
|
+
if (!accountInfo) {
|
|
22
|
+
throw new Error(`Glow vault not found: ${params.vaultAddress.toBase58()}`)
|
|
23
|
+
}
|
|
24
|
+
if (!accountInfo.owner.equals(GLOW_VAULT_PROGRAM_ID)) {
|
|
25
|
+
throw new Error(`Invalid Glow vault owner: ${accountInfo.owner.toBase58()}`)
|
|
26
|
+
}
|
|
27
|
+
if (accountInfo.data.length < GLOW_VAULT_ACCOUNT_SIZE) {
|
|
28
|
+
throw new Error(`Glow vault account is too short: ${accountInfo.data.length}`)
|
|
29
|
+
}
|
|
30
|
+
if (!accountInfo.data.subarray(0, 8).equals(GLOW_VAULT_DISCRIMINATOR)) {
|
|
31
|
+
throw new Error("Invalid Glow vault discriminator")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const shareMint = new web3.PublicKey(accountInfo.data.subarray(SHARE_MINT_OFFSET, SHARE_MINT_OFFSET + 32))
|
|
35
|
+
if (!shareMint.equals(params.expectedShareMint)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Glow vault share mint ${shareMint.toBase58()} does not match ${params.expectedShareMint.toBase58()}`,
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const depositShares = accountInfo.data.readBigUInt64LE(DEPOSIT_SHARES_OFFSET)
|
|
42
|
+
const minimumSharesDustThreshold = accountInfo.data.readBigUInt64LE(MINIMUM_SHARES_DUST_THRESHOLD_OFFSET)
|
|
43
|
+
if (depositShares <= minimumSharesDustThreshold) return 1
|
|
44
|
+
|
|
45
|
+
const depositTokens = accountInfo.data.readBigUInt64LE(DEPOSIT_TOKENS_OFFSET)
|
|
46
|
+
const uncollectedManagementFees = accountInfo.data.readBigUInt64LE(UNCOLLECTED_MANAGEMENT_FEES_OFFSET)
|
|
47
|
+
const realisedPerformanceFees = accountInfo.data.readBigUInt64LE(REALISED_PERFORMANCE_FEES_OFFSET)
|
|
48
|
+
const netAfterManagementFees =
|
|
49
|
+
depositTokens > uncollectedManagementFees ? depositTokens - uncollectedManagementFees : 0n
|
|
50
|
+
const netTokens =
|
|
51
|
+
netAfterManagementFees > realisedPerformanceFees ? netAfterManagementFees - realisedPerformanceFees : 0n
|
|
52
|
+
const rateRaw = (netTokens * NUMBER_DENOMINATOR) / depositShares
|
|
53
|
+
|
|
54
|
+
return Number(rateRaw) / Number(NUMBER_DENOMINATOR)
|
|
55
|
+
}
|