@zofai/zo-sdk 0.2.26 → 0.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consts/deployments-slp-mainnet.json +1 -1
- package/dist/consts/deployments-zo-oracle-mainnet.json +9 -0
- package/dist/implementations/SLPAPI.cjs +424 -209
- package/dist/implementations/SLPAPI.cjs.map +1 -1
- package/dist/implementations/SLPAPI.d.cts +19 -2
- package/dist/implementations/SLPAPI.d.cts.map +1 -1
- package/dist/implementations/SLPAPI.d.mts +19 -2
- package/dist/implementations/SLPAPI.d.mts.map +1 -1
- package/dist/implementations/SLPAPI.mjs +425 -210
- package/dist/implementations/SLPAPI.mjs.map +1 -1
- package/dist/implementations/SLPDataAPI.cjs +69 -7
- package/dist/implementations/SLPDataAPI.cjs.map +1 -1
- package/dist/implementations/SLPDataAPI.d.cts +10 -0
- package/dist/implementations/SLPDataAPI.d.cts.map +1 -1
- package/dist/implementations/SLPDataAPI.d.mts +10 -0
- package/dist/implementations/SLPDataAPI.d.mts.map +1 -1
- package/dist/implementations/SLPDataAPI.mjs +70 -8
- package/dist/implementations/SLPDataAPI.mjs.map +1 -1
- package/dist/interfaces/slp.d.cts +16 -0
- package/dist/interfaces/slp.d.cts.map +1 -1
- package/dist/interfaces/slp.d.mts +16 -0
- package/dist/interfaces/slp.d.mts.map +1 -1
- package/dist/oracle.cjs +4 -1
- package/dist/oracle.cjs.map +1 -1
- package/dist/oracle.d.cts.map +1 -1
- package/dist/oracle.d.mts.map +1 -1
- package/dist/oracle.mjs +4 -1
- package/dist/oracle.mjs.map +1 -1
- package/dist/oraclePro.cjs +145 -8
- package/dist/oraclePro.cjs.map +1 -1
- package/dist/oraclePro.d.cts +57 -1
- package/dist/oraclePro.d.cts.map +1 -1
- package/dist/oraclePro.d.mts +57 -1
- package/dist/oraclePro.d.mts.map +1 -1
- package/dist/oraclePro.mjs +138 -8
- package/dist/oraclePro.mjs.map +1 -1
- package/dist/pythProClient.cjs +12 -8
- package/dist/pythProClient.cjs.map +1 -1
- package/dist/pythProClient.d.cts +1 -0
- package/dist/pythProClient.d.cts.map +1 -1
- package/dist/pythProClient.d.mts +1 -0
- package/dist/pythProClient.d.mts.map +1 -1
- package/dist/pythProClient.mjs +11 -7
- package/dist/pythProClient.mjs.map +1 -1
- package/package.json +1 -1
- package/src/consts/deployments-slp-mainnet.json +1 -1
- package/src/consts/deployments-zo-oracle-mainnet.json +9 -0
- package/src/implementations/SLPAPI.ts +723 -239
- package/src/implementations/SLPDataAPI.ts +83 -9
- package/src/interfaces/slp.ts +170 -0
- package/src/oracle.ts +4 -1
- package/src/oraclePro.ts +229 -10
- package/src/pythProClient.ts +12 -7
- package/tests/pythProClient.test.ts +1 -1
|
@@ -12,7 +12,7 @@ import { SUI_CLOCK_OBJECT_ID } from '@mysten/sui/utils'
|
|
|
12
12
|
import { BaseDataAPI } from '../abstract'
|
|
13
13
|
import { Rate, SymbolsValuation, VaultsValuation } from '../bcs'
|
|
14
14
|
import type { Network } from '../consts'
|
|
15
|
-
import { LPToken, SLP_TOKEN_DECIMALS } from '../consts'
|
|
15
|
+
import { getZoOracleConfig, LPToken, SLP_TOKEN_DECIMALS } from '../consts'
|
|
16
16
|
import type {
|
|
17
17
|
IBaseHistoryResponse,
|
|
18
18
|
IBaseOrderType,
|
|
@@ -38,7 +38,14 @@ import type {
|
|
|
38
38
|
ISLPVaultInfo,
|
|
39
39
|
ISwapFeeBreakdown,
|
|
40
40
|
} from '../interfaces'
|
|
41
|
+
import type { OracleInputs } from '../oraclePro'
|
|
41
42
|
import type { DynamicFieldInfo, SuiClient } from '../suiClient'
|
|
43
|
+
import {
|
|
44
|
+
buildPythProPriceFeedMap,
|
|
45
|
+
resolveTokenUsdPriceFromPythPro,
|
|
46
|
+
valuateSymbolsV3,
|
|
47
|
+
valuateVaultsV3,
|
|
48
|
+
} from '../oraclePro'
|
|
42
49
|
import { joinSymbol, parseSymbolKey, parseValue, suiSymbolToSymbol } from '../utils'
|
|
43
50
|
|
|
44
51
|
export interface GetCumulativeAprResponse {
|
|
@@ -210,6 +217,46 @@ export class SLPDataAPI extends BaseDataAPI implements ISLPDataAPI {
|
|
|
210
217
|
return { vaultsValuation, symbolsValuation }
|
|
211
218
|
}
|
|
212
219
|
|
|
220
|
+
/** Creates vaults valuation using valuate_vault_v3 (Pyth Pro + Stork, sudo-core). */
|
|
221
|
+
public valuateVaultsV3(tx: Transaction, oracle: OracleInputs) {
|
|
222
|
+
if (!this.consts.sudoCore) {
|
|
223
|
+
throw new Error('Sudo Core configuration not found. Make sure you are using LPToken.SLP')
|
|
224
|
+
}
|
|
225
|
+
return valuateVaultsV3({
|
|
226
|
+
tx,
|
|
227
|
+
upgradedPackage: this.consts.sudoCore.upgradedPackage,
|
|
228
|
+
lpType: `${this.consts.sudoCore.package}::slp::SLP`,
|
|
229
|
+
market: this.consts.sudoCore.market,
|
|
230
|
+
vaults: this.consts.sudoCore.vaults,
|
|
231
|
+
coinModules: this.consts.coins,
|
|
232
|
+
oracle,
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Creates symbols valuation using valuate_symbol_v3 (Pyth Pro + Stork, sudo-core). */
|
|
237
|
+
public valuateSymbolsV3(tx: Transaction, oracle: OracleInputs) {
|
|
238
|
+
if (!this.consts.sudoCore) {
|
|
239
|
+
throw new Error('Sudo Core configuration not found. Make sure you are using LPToken.SLP')
|
|
240
|
+
}
|
|
241
|
+
return valuateSymbolsV3({
|
|
242
|
+
tx,
|
|
243
|
+
upgradedPackage: this.consts.sudoCore.upgradedPackage,
|
|
244
|
+
marketPackage: this.consts.sudoCore.package,
|
|
245
|
+
lpType: `${this.consts.sudoCore.package}::slp::SLP`,
|
|
246
|
+
market: this.consts.sudoCore.market,
|
|
247
|
+
symbols: this.consts.sudoCore.symbols,
|
|
248
|
+
coinModules: this.consts.coins,
|
|
249
|
+
oracle,
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Creates both vaults and symbols valuation (Pyth Pro + Stork, sudo-core). */
|
|
254
|
+
public valuateV3(tx: Transaction, oracle: OracleInputs) {
|
|
255
|
+
const vaultsValuation = this.valuateVaultsV3(tx, oracle)
|
|
256
|
+
const symbolsValuation = this.valuateSymbolsV3(tx, oracle)
|
|
257
|
+
return { vaultsValuation, symbolsValuation }
|
|
258
|
+
}
|
|
259
|
+
|
|
213
260
|
/**
|
|
214
261
|
* Valuates the SLP market using Sudo SDK's approach
|
|
215
262
|
*/
|
|
@@ -219,11 +266,29 @@ export class SLPDataAPI extends BaseDataAPI implements ISLPDataAPI {
|
|
|
219
266
|
let value = 0
|
|
220
267
|
|
|
221
268
|
const vaultKeys = Object.keys(this.consts.sudoCore.vaults)
|
|
269
|
+
const symbolTokenIds = [
|
|
270
|
+
...new Set(
|
|
271
|
+
Object.keys(this.consts.sudoCore.symbols).map(symbol => parseSymbolKey(symbol)[1]),
|
|
272
|
+
),
|
|
273
|
+
]
|
|
274
|
+
const valuationTokens = [...new Set([...vaultKeys, ...symbolTokenIds])]
|
|
275
|
+
const pythProData = await this.getLatestPythProPricesForTokens(valuationTokens)
|
|
276
|
+
const feedById = buildPythProPriceFeedMap(pythProData)
|
|
277
|
+
const zoOracle = getZoOracleConfig(this.network)
|
|
278
|
+
const resolvePrice = (token: string) => resolveTokenUsdPriceFromPythPro({
|
|
279
|
+
token,
|
|
280
|
+
feedById,
|
|
281
|
+
zoOracle,
|
|
282
|
+
coins: this.consts.coins,
|
|
283
|
+
network: this.network,
|
|
284
|
+
legacyFeeders: this.consts.pythFeeder.feeder,
|
|
285
|
+
})
|
|
286
|
+
|
|
222
287
|
const vaultData = await Promise.all(vaultKeys.map(async (vault) => {
|
|
223
288
|
const vaultInfo = await this.getVaultInfo(vault)
|
|
224
289
|
const reservingFeeDelta = SLPDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
|
|
225
290
|
const totalVaultAmount = reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount
|
|
226
|
-
const oraclePrice = (
|
|
291
|
+
const oraclePrice = resolvePrice(vault)
|
|
227
292
|
const { decimals } = this.consts.coins[vault]
|
|
228
293
|
const vaultValue = totalVaultAmount * oraclePrice / (10 ** decimals)
|
|
229
294
|
return { vault, oraclePrice, vaultValue }
|
|
@@ -234,7 +299,7 @@ export class SLPDataAPI extends BaseDataAPI implements ISLPDataAPI {
|
|
|
234
299
|
const symbolPromises = Object.keys(this.consts.sudoCore.symbols).map(async (symbol) => {
|
|
235
300
|
const [direction, tokenId] = parseSymbolKey(symbol)
|
|
236
301
|
const symbolInfo = await this.getSymbolInfo(tokenId, direction === 'long')
|
|
237
|
-
const price = (
|
|
302
|
+
const price = resolvePrice(tokenId)
|
|
238
303
|
|
|
239
304
|
const oiState = await this.getSymbolOiFundingState(tokenId)
|
|
240
305
|
const pairedInfo = await this.getSymbolInfo(tokenId, direction !== 'long')
|
|
@@ -351,13 +416,22 @@ export class SLPDataAPI extends BaseDataAPI implements ISLPDataAPI {
|
|
|
351
416
|
const slpPrice = value / marketInfo.lpSupplyWithDecimals
|
|
352
417
|
|
|
353
418
|
const vaultKeys = Object.keys(this.consts.sudoCore.vaults)
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
419
|
+
const pythProData = await this.getLatestPythProPricesForTokens(vaultKeys)
|
|
420
|
+
const feedById = buildPythProPriceFeedMap(pythProData)
|
|
421
|
+
const zoOracle = getZoOracleConfig(this.network)
|
|
422
|
+
const vaultPrices = Object.fromEntries(
|
|
423
|
+
vaultKeys.map(vault => [
|
|
424
|
+
vault,
|
|
425
|
+
resolveTokenUsdPriceFromPythPro({
|
|
426
|
+
token: vault,
|
|
427
|
+
feedById,
|
|
428
|
+
zoOracle,
|
|
429
|
+
coins: this.consts.coins,
|
|
430
|
+
network: this.network,
|
|
431
|
+
legacyFeeders: this.consts.pythFeeder.feeder,
|
|
432
|
+
}),
|
|
433
|
+
]),
|
|
359
434
|
)
|
|
360
|
-
const vaultPrices = Object.fromEntries(vaultPricesEntries)
|
|
361
435
|
|
|
362
436
|
return {
|
|
363
437
|
marketCap: value,
|
package/src/interfaces/slp.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { KioskClient, KioskOwnerCap } from '@mysten/kiosk'
|
|
7
7
|
import type { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions'
|
|
8
8
|
|
|
9
|
+
import type { OracleInputs } from '../oraclePro'
|
|
9
10
|
import type {
|
|
10
11
|
IBaseAPI,
|
|
11
12
|
IBaseCredential,
|
|
@@ -217,6 +218,12 @@ export interface ISLPDataAPI extends IBaseDataAPI {
|
|
|
217
218
|
getSymbolConfig: (indexToken: string, long: boolean) => Promise<ISLPSymbolConfig | null>
|
|
218
219
|
getPriceImpactConfig: (indexToken: string) => Promise<ISLPPriceImpactConfig | null>
|
|
219
220
|
calculateSwapFeeBreakdown: (fromToken: string, toToken: string, fromAmount: number) => Promise<ISwapFeeBreakdown>
|
|
221
|
+
valuateVaultsV3: (tx: Transaction, oracle: OracleInputs) => TransactionObjectArgument
|
|
222
|
+
valuateSymbolsV3: (tx: Transaction, oracle: OracleInputs) => TransactionObjectArgument
|
|
223
|
+
valuateV3: (tx: Transaction, oracle: OracleInputs) => {
|
|
224
|
+
vaultsValuation: TransactionObjectArgument
|
|
225
|
+
symbolsValuation: TransactionObjectArgument
|
|
226
|
+
}
|
|
220
227
|
}
|
|
221
228
|
|
|
222
229
|
// SLP-specific API interface
|
|
@@ -286,4 +293,167 @@ export interface ISLPAPI extends IBaseAPI {
|
|
|
286
293
|
minAmountOut?: number,
|
|
287
294
|
tx?: Transaction,
|
|
288
295
|
) => Promise<TransactionObjectArgument>
|
|
296
|
+
|
|
297
|
+
swapV4: (
|
|
298
|
+
fromToken: string,
|
|
299
|
+
toToken: string,
|
|
300
|
+
fromAmount: bigint,
|
|
301
|
+
fromCoinObjects: string[],
|
|
302
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
303
|
+
minAmountOut?: number,
|
|
304
|
+
tx?: Transaction,
|
|
305
|
+
) => Promise<Transaction>
|
|
306
|
+
|
|
307
|
+
swapV4Ptb: (
|
|
308
|
+
fromToken: string,
|
|
309
|
+
toToken: string,
|
|
310
|
+
fromAmount: bigint,
|
|
311
|
+
fromCoinObjects: string[],
|
|
312
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
313
|
+
minAmountOut?: number,
|
|
314
|
+
tx?: Transaction,
|
|
315
|
+
) => Promise<TransactionObjectArgument>
|
|
316
|
+
|
|
317
|
+
openPositionV3: (
|
|
318
|
+
collateralToken: string,
|
|
319
|
+
indexToken: string,
|
|
320
|
+
size: bigint,
|
|
321
|
+
collateralAmount: bigint,
|
|
322
|
+
coinObjects: string[],
|
|
323
|
+
long: boolean,
|
|
324
|
+
reserveAmount: bigint,
|
|
325
|
+
indexPrice: number,
|
|
326
|
+
collateralPrice: number,
|
|
327
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
328
|
+
isLimitOrder?: boolean,
|
|
329
|
+
isIocOrder?: boolean,
|
|
330
|
+
pricesSlippage?: number,
|
|
331
|
+
collateralSlippage?: number,
|
|
332
|
+
relayerFee?: bigint,
|
|
333
|
+
referralAddress?: string,
|
|
334
|
+
sender?: string,
|
|
335
|
+
sponsoredTx?: boolean,
|
|
336
|
+
) => Promise<Transaction>
|
|
337
|
+
|
|
338
|
+
openPositionWithSCardV3: (
|
|
339
|
+
collateralToken: string,
|
|
340
|
+
indexToken: string,
|
|
341
|
+
size: bigint,
|
|
342
|
+
collateralAmount: bigint,
|
|
343
|
+
coinObjects: string[],
|
|
344
|
+
long: boolean,
|
|
345
|
+
reserveAmount: bigint,
|
|
346
|
+
indexPrice: number,
|
|
347
|
+
collateralPrice: number,
|
|
348
|
+
kioskClient: KioskClient,
|
|
349
|
+
kioskCap: KioskOwnerCap,
|
|
350
|
+
scard: string,
|
|
351
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
352
|
+
isLimitOrder?: boolean,
|
|
353
|
+
isIocOrder?: boolean,
|
|
354
|
+
pricesSlippage?: number,
|
|
355
|
+
collateralSlippage?: number,
|
|
356
|
+
relayerFee?: bigint,
|
|
357
|
+
referralAddress?: string,
|
|
358
|
+
sender?: string,
|
|
359
|
+
sponsoredTx?: boolean,
|
|
360
|
+
) => Promise<Transaction>
|
|
361
|
+
|
|
362
|
+
openPositionWithCoinV3: (
|
|
363
|
+
collateralToken: string,
|
|
364
|
+
indexToken: string,
|
|
365
|
+
size: bigint,
|
|
366
|
+
coinObj: TransactionObjectArgument,
|
|
367
|
+
long: boolean,
|
|
368
|
+
reserveAmount: bigint,
|
|
369
|
+
indexPrice: number,
|
|
370
|
+
collateralPrice: number,
|
|
371
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
372
|
+
isLimitOrder?: boolean,
|
|
373
|
+
isIocOrder?: boolean,
|
|
374
|
+
pricesSlippage?: number,
|
|
375
|
+
collateralSlippage?: number,
|
|
376
|
+
relayerFee?: bigint,
|
|
377
|
+
referralAddress?: string,
|
|
378
|
+
sender?: string,
|
|
379
|
+
tx?: Transaction,
|
|
380
|
+
sponsoredTx?: boolean,
|
|
381
|
+
) => Promise<Transaction>
|
|
382
|
+
|
|
383
|
+
openPositionWithCoinAndSCardV3: (
|
|
384
|
+
collateralToken: string,
|
|
385
|
+
indexToken: string,
|
|
386
|
+
size: bigint,
|
|
387
|
+
coinObj: TransactionObjectArgument,
|
|
388
|
+
long: boolean,
|
|
389
|
+
reserveAmount: bigint,
|
|
390
|
+
indexPrice: number,
|
|
391
|
+
collateralPrice: number,
|
|
392
|
+
kioskClient: KioskClient,
|
|
393
|
+
kioskCap: KioskOwnerCap,
|
|
394
|
+
scard: string,
|
|
395
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
396
|
+
isLimitOrder?: boolean,
|
|
397
|
+
isIocOrder?: boolean,
|
|
398
|
+
pricesSlippage?: number,
|
|
399
|
+
collateralSlippage?: number,
|
|
400
|
+
relayerFee?: bigint,
|
|
401
|
+
referralAddress?: string,
|
|
402
|
+
sender?: string,
|
|
403
|
+
tx?: Transaction,
|
|
404
|
+
sponsoredTx?: boolean,
|
|
405
|
+
) => Promise<Transaction>
|
|
406
|
+
|
|
407
|
+
decreasePositionV3: (
|
|
408
|
+
pcpId: string,
|
|
409
|
+
collateralToken: string,
|
|
410
|
+
indexToken: string,
|
|
411
|
+
amount: bigint,
|
|
412
|
+
long: boolean,
|
|
413
|
+
indexPrice: number,
|
|
414
|
+
collateralPrice: number,
|
|
415
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
416
|
+
isTriggerOrder?: boolean,
|
|
417
|
+
isTakeProfitOrder?: boolean,
|
|
418
|
+
isIocOrder?: boolean,
|
|
419
|
+
pricesSlippage?: number,
|
|
420
|
+
collateralSlippage?: number,
|
|
421
|
+
relayerFee?: bigint,
|
|
422
|
+
coinObjects?: string[],
|
|
423
|
+
sponsoredTx?: boolean,
|
|
424
|
+
sender?: string,
|
|
425
|
+
) => Promise<Transaction>
|
|
426
|
+
|
|
427
|
+
decreasePositionWithSCardV3: (
|
|
428
|
+
pcpId: string,
|
|
429
|
+
collateralToken: string,
|
|
430
|
+
indexToken: string,
|
|
431
|
+
amount: bigint,
|
|
432
|
+
long: boolean,
|
|
433
|
+
indexPrice: number,
|
|
434
|
+
collateralPrice: number,
|
|
435
|
+
kioskClient: KioskClient,
|
|
436
|
+
kioskCap: KioskOwnerCap,
|
|
437
|
+
scard: string,
|
|
438
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
439
|
+
isTriggerOrder?: boolean,
|
|
440
|
+
isTakeProfitOrder?: boolean,
|
|
441
|
+
isIocOrder?: boolean,
|
|
442
|
+
pricesSlippage?: number,
|
|
443
|
+
collateralSlippage?: number,
|
|
444
|
+
relayerFee?: bigint,
|
|
445
|
+
coinObjects?: string[],
|
|
446
|
+
sponsoredTx?: boolean,
|
|
447
|
+
sender?: string,
|
|
448
|
+
) => Promise<Transaction>
|
|
449
|
+
|
|
450
|
+
redeemFromPositionV3: (
|
|
451
|
+
pcpId: string,
|
|
452
|
+
collateralToken: string,
|
|
453
|
+
indexToken: string,
|
|
454
|
+
amount: number,
|
|
455
|
+
long: boolean,
|
|
456
|
+
pythProUpdateBytes: Uint8Array | number[],
|
|
457
|
+
tx?: Transaction,
|
|
458
|
+
) => Promise<Transaction>
|
|
289
459
|
}
|
package/src/oracle.ts
CHANGED
|
@@ -345,10 +345,11 @@ export class OracleAPI {
|
|
|
345
345
|
tokens: string[],
|
|
346
346
|
options?: Omit<IGetLatestPythProPricesOptions, 'priceFeedIds'>,
|
|
347
347
|
): Promise<PythProLatestPriceData> {
|
|
348
|
+
const config = this.getOracleProConfig()
|
|
348
349
|
const priceFeedIds = this.resolveLazerFeedIdsForTokens(tokens)
|
|
349
350
|
return getLatestPythProPrices(this.resolveKronosUrl(options?.kronosUrl), {
|
|
350
351
|
priceFeedIds,
|
|
351
|
-
channel: options?.channel,
|
|
352
|
+
channel: options?.channel ?? (config.lazerChannel as PythProChannel),
|
|
352
353
|
includeEmaPrice: options?.includeEmaPrice,
|
|
353
354
|
includeUpdateBytes: options?.includeUpdateBytes,
|
|
354
355
|
})
|
|
@@ -375,6 +376,7 @@ export class OracleAPI {
|
|
|
375
376
|
options?: Omit<IPythProStreamSubscription, 'subscriptionId' | 'priceFeedIds'>,
|
|
376
377
|
subscriptionId = 1,
|
|
377
378
|
): Promise<() => void> {
|
|
379
|
+
const config = this.getOracleProConfig()
|
|
378
380
|
const client = this.createPythProStreamClient()
|
|
379
381
|
const priceFeedIds = this.resolveLazerFeedIdsForTokens(tokens)
|
|
380
382
|
await client.connect()
|
|
@@ -383,6 +385,7 @@ export class OracleAPI {
|
|
|
383
385
|
subscriptionId,
|
|
384
386
|
priceFeedIds,
|
|
385
387
|
...options,
|
|
388
|
+
channel: options?.channel ?? (config.lazerChannel as PythProChannel),
|
|
386
389
|
},
|
|
387
390
|
onUpdate,
|
|
388
391
|
)
|
package/src/oraclePro.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getPythFeederToPriceId,
|
|
14
14
|
getZoOracleConfig,
|
|
15
15
|
} from './consts'
|
|
16
|
-
import type { PythProChannel } from './pythProClient'
|
|
16
|
+
import type { PythProChannel, PythProLatestPriceData, PythProPriceFeed } from './pythProClient'
|
|
17
17
|
import { fetchPythProUpdateBytesFromKronos } from './pythProClient'
|
|
18
18
|
import { hexToBytes } from './storkClient'
|
|
19
19
|
import { parseSymbolKey } from './utils'
|
|
@@ -60,6 +60,44 @@ export function requireOracleProConfig(network: Network): IOracleProConfig {
|
|
|
60
60
|
return buildOracleProConfig(getZoOracleConfig(network))
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* LST SUI vault tokens priced on-chain via Pyth Lazer RR feed × SUI/USD.
|
|
65
|
+
* Numeric ids are Pyth Lazer `priceFeedIds` (not Hermes price ids).
|
|
66
|
+
*/
|
|
67
|
+
export const LST_SUI_PYTH_LAZER_RR_FEED_IDS: Record<string, number> = {
|
|
68
|
+
afSui: 3245,
|
|
69
|
+
stSui: 736,
|
|
70
|
+
vSui: 2349,
|
|
71
|
+
haSui: 3244,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const LST_SUI_TOKENS = new Set(Object.keys(LST_SUI_PYTH_LAZER_RR_FEED_IDS))
|
|
75
|
+
|
|
76
|
+
function lookupLazerFeedIdForToken(
|
|
77
|
+
token: string,
|
|
78
|
+
zoOracle: IZoOracleConfig,
|
|
79
|
+
coins: Record<string, { module: string }>,
|
|
80
|
+
network: Network,
|
|
81
|
+
coinModuleMap: Map<string, number>,
|
|
82
|
+
legacyFeeders?: Record<string, string>,
|
|
83
|
+
): number | undefined {
|
|
84
|
+
const module = coins[token]?.module
|
|
85
|
+
if (!module) {
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let lazerId = coinModuleMap.get(normalizeCoinType(module))
|
|
90
|
+
|
|
91
|
+
if (lazerId === undefined && legacyFeeders?.[token]) {
|
|
92
|
+
const priceId = getPythFeederToPriceId(network)[legacyFeeders[token]]
|
|
93
|
+
if (priceId) {
|
|
94
|
+
lazerId = zoOracle.pythLazer.feedIds[priceId]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return lazerId
|
|
99
|
+
}
|
|
100
|
+
|
|
63
101
|
/**
|
|
64
102
|
* Build coin module -> Lazer feed id using zo_oracle symbol configs + Hermes price id map.
|
|
65
103
|
*/
|
|
@@ -93,9 +131,7 @@ export function resolveLazerFeedIds(
|
|
|
93
131
|
network: Network,
|
|
94
132
|
legacyFeeders?: Record<string, string>,
|
|
95
133
|
): number[] {
|
|
96
|
-
const
|
|
97
|
-
const coinModuleMap = buildCoinModuleToLazerFeedId(pythLazer, zoOracle, network)
|
|
98
|
-
const feederToPriceId = getPythFeederToPriceId(network)
|
|
134
|
+
const coinModuleMap = buildCoinModuleToLazerFeedId(zoOracle.pythLazer, zoOracle, network)
|
|
99
135
|
|
|
100
136
|
const ids = new Set<number>()
|
|
101
137
|
for (const token of tokens) {
|
|
@@ -104,15 +140,32 @@ export function resolveLazerFeedIds(
|
|
|
104
140
|
throw new Error(`Unknown token: ${token}`)
|
|
105
141
|
}
|
|
106
142
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
if (LST_SUI_TOKENS.has(token)) {
|
|
144
|
+
ids.add(LST_SUI_PYTH_LAZER_RR_FEED_IDS[token])
|
|
145
|
+
const suiLazerId = lookupLazerFeedIdForToken(
|
|
146
|
+
'sui',
|
|
147
|
+
zoOracle,
|
|
148
|
+
coins,
|
|
149
|
+
network,
|
|
150
|
+
coinModuleMap,
|
|
151
|
+
legacyFeeders,
|
|
152
|
+
)
|
|
153
|
+
if (suiLazerId === undefined) {
|
|
154
|
+
throw new Error(`No Pyth Lazer feed id for SUI (required for LST token "${token}")`)
|
|
113
155
|
}
|
|
156
|
+
ids.add(suiLazerId)
|
|
157
|
+
continue
|
|
114
158
|
}
|
|
115
159
|
|
|
160
|
+
const lazerId = lookupLazerFeedIdForToken(
|
|
161
|
+
token,
|
|
162
|
+
zoOracle,
|
|
163
|
+
coins,
|
|
164
|
+
network,
|
|
165
|
+
coinModuleMap,
|
|
166
|
+
legacyFeeders,
|
|
167
|
+
)
|
|
168
|
+
|
|
116
169
|
if (lazerId === undefined) {
|
|
117
170
|
throw new Error(`No Pyth Lazer feed id for token "${token}" (${module})`)
|
|
118
171
|
}
|
|
@@ -121,6 +174,87 @@ export function resolveLazerFeedIds(
|
|
|
121
174
|
return [...ids]
|
|
122
175
|
}
|
|
123
176
|
|
|
177
|
+
/** Parse a Pyth Pro feed's raw price + exponent into a decimal number (matches on-chain normalization). */
|
|
178
|
+
export function parsePythProFeedDecimalPrice(feed: PythProPriceFeed): number {
|
|
179
|
+
const raw = Number(feed.price)
|
|
180
|
+
const expo = feed.exponent
|
|
181
|
+
if (expo < 0) {
|
|
182
|
+
return raw / 10 ** -expo
|
|
183
|
+
}
|
|
184
|
+
return raw * 10 ** expo
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function buildPythProPriceFeedMap(
|
|
188
|
+
data: PythProLatestPriceData,
|
|
189
|
+
): Map<number, PythProPriceFeed> {
|
|
190
|
+
const map = new Map<number, PythProPriceFeed>()
|
|
191
|
+
for (const feed of data.priceFeeds) {
|
|
192
|
+
map.set(feed.priceFeedId, feed)
|
|
193
|
+
}
|
|
194
|
+
return map
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface IResolveTokenUsdPriceFromPythProParams {
|
|
198
|
+
token: string
|
|
199
|
+
feedById: Map<number, PythProPriceFeed>
|
|
200
|
+
zoOracle: IZoOracleConfig
|
|
201
|
+
coins: Record<string, { module: string }>
|
|
202
|
+
network: Network
|
|
203
|
+
legacyFeeders?: Record<string, string>
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Resolve a token's USD price from batched Pyth Pro feeds.
|
|
208
|
+
* LST SUI vaults (afSui, vSui, haSui, stSui) use RR feed rate × SUI/USD.
|
|
209
|
+
*/
|
|
210
|
+
export function resolveTokenUsdPriceFromPythPro(
|
|
211
|
+
params: IResolveTokenUsdPriceFromPythProParams,
|
|
212
|
+
): number {
|
|
213
|
+
const { token, feedById, zoOracle, coins, network, legacyFeeders } = params
|
|
214
|
+
const coinModuleMap = buildCoinModuleToLazerFeedId(zoOracle.pythLazer, zoOracle, network)
|
|
215
|
+
|
|
216
|
+
if (LST_SUI_TOKENS.has(token)) {
|
|
217
|
+
const rrFeedId = LST_SUI_PYTH_LAZER_RR_FEED_IDS[token]
|
|
218
|
+
const rrFeed = feedById.get(rrFeedId)
|
|
219
|
+
if (!rrFeed) {
|
|
220
|
+
throw new Error(`Missing Pyth Pro RR feed ${rrFeedId} for LST token "${token}"`)
|
|
221
|
+
}
|
|
222
|
+
const suiLazerId = lookupLazerFeedIdForToken(
|
|
223
|
+
'sui',
|
|
224
|
+
zoOracle,
|
|
225
|
+
coins,
|
|
226
|
+
network,
|
|
227
|
+
coinModuleMap,
|
|
228
|
+
legacyFeeders,
|
|
229
|
+
)
|
|
230
|
+
if (suiLazerId === undefined) {
|
|
231
|
+
throw new Error(`No Pyth Lazer feed id for SUI (required for LST token "${token}")`)
|
|
232
|
+
}
|
|
233
|
+
const suiFeed = feedById.get(suiLazerId)
|
|
234
|
+
if (!suiFeed) {
|
|
235
|
+
throw new Error(`Missing Pyth Pro SUI feed ${suiLazerId} for LST token "${token}"`)
|
|
236
|
+
}
|
|
237
|
+
return parsePythProFeedDecimalPrice(rrFeed) * parsePythProFeedDecimalPrice(suiFeed)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const lazerId = lookupLazerFeedIdForToken(
|
|
241
|
+
token,
|
|
242
|
+
zoOracle,
|
|
243
|
+
coins,
|
|
244
|
+
network,
|
|
245
|
+
coinModuleMap,
|
|
246
|
+
legacyFeeders,
|
|
247
|
+
)
|
|
248
|
+
if (lazerId === undefined) {
|
|
249
|
+
throw new Error(`No Pyth Lazer feed id for token "${token}"`)
|
|
250
|
+
}
|
|
251
|
+
const feed = feedById.get(lazerId)
|
|
252
|
+
if (!feed) {
|
|
253
|
+
throw new Error(`Missing Pyth Pro feed ${lazerId} for token "${token}"`)
|
|
254
|
+
}
|
|
255
|
+
return parsePythProFeedDecimalPrice(feed)
|
|
256
|
+
}
|
|
257
|
+
|
|
124
258
|
function resolveStorkAssetId(symbolKey: string, config: IZoOracleSymbolConfig): string {
|
|
125
259
|
return config.storkAssetId ?? symbolKey.replace('/', '')
|
|
126
260
|
}
|
|
@@ -212,6 +346,11 @@ export function oracleArgsAlt(oracle: OracleInputsAlt): TransactionObjectArgumen
|
|
|
212
346
|
return [oracle.oracleRegistry, oracle.storkState, oracle.pythProUpdate]
|
|
213
347
|
}
|
|
214
348
|
|
|
349
|
+
/** Sudo-core valuation/swap order: `[oracleRegistry, pythProUpdate, storkState, clock]`. */
|
|
350
|
+
export function oracleArgsRegistryFirst(oracle: OracleInputs): TransactionObjectArgument[] {
|
|
351
|
+
return [oracle.oracleRegistry, oracle.pythProUpdate, oracle.storkState, oracle.clock]
|
|
352
|
+
}
|
|
353
|
+
|
|
215
354
|
export function buildOracleInputsAlt(oracle: OracleInputs): OracleInputsAlt {
|
|
216
355
|
return {
|
|
217
356
|
oracleRegistry: oracle.oracleRegistry,
|
|
@@ -312,3 +451,83 @@ export function valuateSymbolsV2(params: IValuateSymbolsV2Params): TransactionOb
|
|
|
312
451
|
|
|
313
452
|
return symbolsValuation
|
|
314
453
|
}
|
|
454
|
+
|
|
455
|
+
export interface IValuateVaultsV3Params {
|
|
456
|
+
tx: Transaction
|
|
457
|
+
upgradedPackage: string
|
|
458
|
+
lpType: string
|
|
459
|
+
market: string
|
|
460
|
+
vaults: Record<string, { reservingFeeModel: string }>
|
|
461
|
+
coinModules: Record<string, { module: string }>
|
|
462
|
+
oracle: OracleInputs
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** create_vaults_valuation + valuate_vault_v3 for each vault (sudo-core). */
|
|
466
|
+
export function valuateVaultsV3(params: IValuateVaultsV3Params): TransactionObjectArgument {
|
|
467
|
+
const { tx, upgradedPackage, lpType, market, vaults, coinModules, oracle } = params
|
|
468
|
+
|
|
469
|
+
const vaultsValuation = tx.moveCall({
|
|
470
|
+
target: `${upgradedPackage}::market::create_vaults_valuation`,
|
|
471
|
+
typeArguments: [lpType],
|
|
472
|
+
arguments: [oracle.clock, tx.object(market)],
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
for (const key of Object.keys(vaults)) {
|
|
476
|
+
const vault = vaults[key]
|
|
477
|
+
tx.moveCall({
|
|
478
|
+
target: `${upgradedPackage}::market::valuate_vault_v3`,
|
|
479
|
+
typeArguments: [lpType, coinModules[key].module],
|
|
480
|
+
arguments: [
|
|
481
|
+
tx.object(market),
|
|
482
|
+
tx.object(vault.reservingFeeModel),
|
|
483
|
+
...oracleArgsRegistryFirst(oracle),
|
|
484
|
+
vaultsValuation,
|
|
485
|
+
],
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return vaultsValuation
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export interface IValuateSymbolsV3Params {
|
|
493
|
+
tx: Transaction
|
|
494
|
+
upgradedPackage: string
|
|
495
|
+
marketPackage: string
|
|
496
|
+
lpType: string
|
|
497
|
+
market: string
|
|
498
|
+
symbols: Record<string, { fundingFeeModel: string }>
|
|
499
|
+
coinModules: Record<string, { module: string }>
|
|
500
|
+
oracle: OracleInputs
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** create_symbols_valuation + valuate_symbol_v3 for each symbol (sudo-core). */
|
|
504
|
+
export function valuateSymbolsV3(params: IValuateSymbolsV3Params): TransactionObjectArgument {
|
|
505
|
+
const { tx, upgradedPackage, marketPackage, lpType, market, symbols, coinModules, oracle } = params
|
|
506
|
+
|
|
507
|
+
const symbolsValuation = tx.moveCall({
|
|
508
|
+
target: `${upgradedPackage}::market::create_symbols_valuation`,
|
|
509
|
+
typeArguments: [lpType],
|
|
510
|
+
arguments: [oracle.clock, tx.object(market)],
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
for (const key of Object.keys(symbols)) {
|
|
514
|
+
const [direction, token] = parseSymbolKey(key)
|
|
515
|
+
const symbol = symbols[key]
|
|
516
|
+
tx.moveCall({
|
|
517
|
+
target: `${upgradedPackage}::market::valuate_symbol_v3`,
|
|
518
|
+
typeArguments: [
|
|
519
|
+
lpType,
|
|
520
|
+
coinModules[token].module,
|
|
521
|
+
`${marketPackage}::market::${direction.toUpperCase()}`,
|
|
522
|
+
],
|
|
523
|
+
arguments: [
|
|
524
|
+
tx.object(market),
|
|
525
|
+
tx.object(symbol.fundingFeeModel),
|
|
526
|
+
...oracleArgsRegistryFirst(oracle),
|
|
527
|
+
symbolsValuation,
|
|
528
|
+
],
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return symbolsValuation
|
|
533
|
+
}
|
package/src/pythProClient.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type PythProChannel
|
|
|
6
6
|
| 'fixed_rate@200ms'
|
|
7
7
|
| 'fixed_rate@1000ms'
|
|
8
8
|
|
|
9
|
+
export const DEFAULT_PYTH_PRO_CHANNEL: PythProChannel = 'fixed_rate@1000ms'
|
|
10
|
+
|
|
9
11
|
export interface PythProPriceFeed {
|
|
10
12
|
priceFeedId: number
|
|
11
13
|
price: string
|
|
@@ -106,7 +108,8 @@ export async function getLatestPythProPrices(
|
|
|
106
108
|
baseUrl: string,
|
|
107
109
|
options: IGetLatestPythProPricesOptions,
|
|
108
110
|
): Promise<PythProLatestPriceData> {
|
|
109
|
-
const { priceFeedIds,
|
|
111
|
+
const { priceFeedIds, includeEmaPrice, includeUpdateBytes } = options
|
|
112
|
+
const channel = options.channel ?? DEFAULT_PYTH_PRO_CHANNEL
|
|
110
113
|
if (priceFeedIds.length === 0) {
|
|
111
114
|
throw new Error('priceFeedIds is required')
|
|
112
115
|
}
|
|
@@ -116,9 +119,7 @@ export async function getLatestPythProPrices(
|
|
|
116
119
|
if (priceFeedIds.length <= 20) {
|
|
117
120
|
const params = new URLSearchParams()
|
|
118
121
|
params.set('priceFeedIds', priceFeedIds.join(','))
|
|
119
|
-
|
|
120
|
-
params.set('channel', channel)
|
|
121
|
-
}
|
|
122
|
+
params.set('channel', channel)
|
|
122
123
|
if (includeEmaPrice) {
|
|
123
124
|
params.set('includeEmaPrice', 'true')
|
|
124
125
|
}
|
|
@@ -280,7 +281,11 @@ export class PythProStreamClient {
|
|
|
280
281
|
options: IPythProStreamSubscription,
|
|
281
282
|
onUpdate: StreamUpdateHandler,
|
|
282
283
|
): () => void {
|
|
283
|
-
|
|
284
|
+
const subscription = {
|
|
285
|
+
...options,
|
|
286
|
+
channel: options.channel ?? DEFAULT_PYTH_PRO_CHANNEL,
|
|
287
|
+
}
|
|
288
|
+
this.subscriptions.set(subscription.subscriptionId, subscription)
|
|
284
289
|
|
|
285
290
|
let handlers = this.updateHandlers.get(options.subscriptionId)
|
|
286
291
|
if (!handlers) {
|
|
@@ -290,11 +295,11 @@ export class PythProStreamClient {
|
|
|
290
295
|
handlers.add(onUpdate)
|
|
291
296
|
|
|
292
297
|
void this.connect().then(() => {
|
|
293
|
-
this.send({ type: 'subscribe', ...
|
|
298
|
+
this.send({ type: 'subscribe', ...subscription })
|
|
294
299
|
})
|
|
295
300
|
|
|
296
301
|
return () => {
|
|
297
|
-
this.unsubscribe(
|
|
302
|
+
this.unsubscribe(subscription.subscriptionId, onUpdate)
|
|
298
303
|
}
|
|
299
304
|
}
|
|
300
305
|
|