UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

176 lines (156 loc) 5.48 kB
import { credentialToRewardAddress, Data, fromHex, LucidEvolution, OutRef, scriptHashToCredential, slotToUnixTime, toHex, TxBuilder, UTxO, } from '@lucid-evolution/lucid'; import { IAssetPriceInfo } from './types'; import { matchSingle } from '../../utils/utils'; import { parsePriceOracleDatum } from '../price-oracle/types-new'; import { getInlineDatumOrThrow } from '../../utils/lucid-utils'; import { oracleExpirationAwareValidity } from '../price-oracle/helpers'; import { fromSysParamsDerivedPythPrice, fromSystemParamsScriptRef, getPythFeedConfig, PythConfig, } from '../../types/system-params'; import { parsePythStateDatum, serialisePythFeedRedeemer, serialisePythUpdatesRedeemer, } from '../pyth-feed/types'; import { match, P } from 'ts-pattern'; import { derivePythPrice } from '../pyth-feed/helpers'; import { AssetClass } from '@3rd-eye-labs/cardano-offchain-common'; import * as Core from '@evolution-sdk/evolution'; import { decodePriceUpdate, decodePythMessage } from '../../utils/pyth'; export const MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET = 9; type Interval = { validFrom: number; validTo: number; }; export function attachOracle( iasset: Uint8Array<ArrayBufferLike>, collateralAsset: AssetClass, priceInfo: IAssetPriceInfo, priceOracleOref: OutRef | undefined, pythStateOref: OutRef | undefined, pythMessage: string | undefined, pythConfig: PythConfig, biasTime: bigint, currentSlot: number, lucid: LucidEvolution, tx: TxBuilder, ): Promise<{ interval: Interval; referenceInputs: UTxO[] }> { return match(priceInfo) .returnType<Promise<{ interval: Interval; referenceInputs: UTxO[] }>>() .with({ Delisted: P.any }, () => { const currentTime = BigInt( slotToUnixTime(lucid.config().network!, currentSlot), ); // We want to make sure the transaction is valid within the bias time for interest tracking AND Pyth // For validFrom, we take the higher of the two bias times const validFrom = Number(currentTime - BigInt(biasTime / 2n)); // For validTo, we take the lower of the two bias times const validTo = Number(currentTime + BigInt(biasTime / 2n)); return Promise.resolve({ interval: { validFrom, validTo, }, referenceInputs: [], }); }) .with({ OracleNft: P.any }, async () => { if (!priceOracleOref) throw new Error('Missing price oracle'); const priceOracleUtxo = matchSingle<UTxO>( await lucid.utxosByOutRef([priceOracleOref]), (_) => new Error('Expected a single price oracle UTXO'), ); const priceOracleDatum = parsePriceOracleDatum( getInlineDatumOrThrow(priceOracleUtxo), ); return { interval: oracleExpirationAwareValidity( currentSlot, Number(biasTime), Number(priceOracleDatum.expirationTime), lucid.config().network!, ), referenceInputs: [priceOracleUtxo], }; }) .with( { DeferredValidation: { feedValHash: P.select() } }, async (feedValHash) => { if (priceOracleOref) throw new Error('Cannot pass price oracle oref'); if (!pythMessage) throw new Error('Missing Pyth message'); if (!pythStateOref) throw new Error('Missing pyth state out ref'); const pythStateUtxo = matchSingle( await lucid.utxosByOutRef([pythStateOref]), (_) => new Error('Expected a single pyth state UTXO'), ); const pythStateDatum = parsePythStateDatum( getInlineDatumOrThrow(pythStateUtxo), ); const pythFeedCfg = getPythFeedConfig( pythConfig, iasset, collateralAsset, ); // Lookup PFV Validator const pfvScriptRefUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(pythFeedCfg.pythFeedValScriptRef), ]), (_) => new Error('Expected a single pyth feed validator Ref Script UTXO'), ); const derivedPythPrice = fromSysParamsDerivedPythPrice( pythFeedCfg.params.config, ); const price = derivePythPrice(derivedPythPrice, pythMessage); const decodedMessage = decodePythMessage(fromHex(pythMessage)); const pricePayload = decodePriceUpdate(decodedMessage.payload); const pythTimestamp = Number(pricePayload.timestampUs) / 1_000; tx.withdraw( credentialToRewardAddress( lucid.config().network!, scriptHashToCredential(toHex(pythStateDatum.withdraw_script)), ), 0n, serialisePythUpdatesRedeemer([fromHex(pythMessage)]), ); tx.withdraw( credentialToRewardAddress( lucid.config().network!, scriptHashToCredential(toHex(feedValHash)), ), 0n, serialisePythFeedRedeemer({ price: price, auxiliaryData: Core.Data.fromCBORHex(Data.void()), }), ); // 5 minutes - 20 seconds const pythMaxDelay = 280 * 1000; const validFrom = Number(pythTimestamp); const validTo = Number(pythTimestamp + pythMaxDelay); return { interval: { validFrom: validFrom, validTo: validTo, }, referenceInputs: [pythStateUtxo, pfvScriptRefUtxo], }; }, ) .exhaustive(); }