@indigo-labs/indigo-sdk
Version:
Indigo SDK for interacting with Indigo endpoints via lucid-evolution
176 lines (156 loc) • 5.48 kB
text/typescript
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();
}