@indigo-labs/indigo-sdk
Version:
Indigo SDK for interacting with Indigo endpoints via lucid-evolution
441 lines (410 loc) • 12 kB
text/typescript
import {
Credential,
fromText,
LucidEvolution,
ScriptHash,
toHex,
toText,
UTxO,
} from '@lucid-evolution/lucid';
import {
addrDetails,
adjustPriceToDecimals,
cdpCollateralRatioPercentage,
createScriptAddress,
fromSystemParamsAsset,
matchSingle,
SystemParams,
} from '../../src';
import { option as O, array as A, function as F } from 'fp-ts';
import {
CDPContent,
parseCdpDatum,
parseStableswapPoolDatum,
StableswapPoolContent,
} from '../../src/contracts/cdp/types-new';
import { match, P } from 'ts-pattern';
import {
AssetClass,
assetClassToUnit,
getInlineDatumOrThrow,
getRandomElement,
} from '@3rd-eye-labs/cardano-offchain-common';
import {
InterestOracleDatum,
parseInterestOracleDatum,
} from '../../src/contracts/interest-oracle/types-new';
import { findCollateralAsset, findIAsset } from '../queries/iasset-queries';
import { findStabilityPool } from '../queries/stability-pool-queries';
import { findPriceOracle } from '../price-oracle/price-oracle-queries';
import { findInterestOracle } from '../queries/interest-oracle-queries';
import { findRandomCollector } from '../queries/collector-queries';
import { findGov } from '../gov/governance-queries';
import { findRandomTreasuryUtxoWithOnlyAda } from '../treasury/treasury-queries';
import { parsePriceOracleDatum } from '../../src/contracts/price-oracle/types-new';
import {
CollateralAssetOutput,
IAssetOutput,
} from '../../src/contracts/iasset/types';
import { findRandomNonAdminInterestCollector } from '../interest-collection/interest-collector-queries';
import { LucidContext } from '../test-helpers';
import { Rational } from '../../src/types/rational';
export async function findAllActiveCdps(
lucid: LucidEvolution,
sysParams: SystemParams,
assetAscii: string,
stakeCred?: Credential,
): Promise<{ utxo: UTxO; datum: CDPContent }[]> {
const cdpUtxos = await lucid.utxosAtWithUnit(
createScriptAddress(
lucid.config().network!,
sysParams.validatorHashes.cdpHash,
stakeCred,
),
assetClassToUnit(fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken)),
);
return F.pipe(
cdpUtxos.map((utxo) =>
F.pipe(
O.fromNullable(utxo.datum),
O.flatMap(parseCdpDatum),
O.flatMap((datum) => {
if (toHex(datum.iasset) === fromText(assetAscii) && datum.cdpOwner) {
return O.some({ utxo, datum: datum });
} else {
return O.none;
}
}),
),
),
A.compact,
);
}
export async function findCdp(
lucid: LucidEvolution,
cdpScriptHash: ScriptHash,
cdpNft: AssetClass,
ownerPkh: string,
stakeCred?: Credential,
): Promise<{ utxo: UTxO; datum: CDPContent }> {
const cdpUtxos = await lucid.utxosAtWithUnit(
createScriptAddress(lucid.config().network!, cdpScriptHash, stakeCred),
assetClassToUnit(cdpNft),
);
return matchSingle(
F.pipe(
cdpUtxos.map((utxo) =>
F.pipe(
O.fromNullable(utxo.datum),
O.flatMap(parseCdpDatum),
O.flatMap((datum) => {
if (datum.cdpOwner && toHex(datum.cdpOwner) === ownerPkh) {
return O.some({ utxo, datum: datum });
} else {
return O.none;
}
}),
),
),
A.compact,
),
(res) => new Error('Expected a single CDP UTXO.: ' + JSON.stringify(res)),
);
}
// TODO: use the new variant defined below.
export async function findOwnCdp(
lucid: LucidEvolution,
cdpScriptHash: ScriptHash,
cdpNft: AssetClass,
): Promise<{ utxo: UTxO; datum: CDPContent }> {
const [pkh, skh] = await addrDetails(lucid);
return findCdp(lucid, cdpScriptHash, cdpNft, pkh.hash, skh);
}
export async function findOwnCdpNew(
lucid: LucidEvolution,
sysParams: SystemParams,
): Promise<{ utxo: UTxO; datum: CDPContent }> {
const [pkh, skh] = await addrDetails(lucid);
return findCdp(
lucid,
sysParams.validatorHashes.cdpHash,
fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
pkh.hash,
skh,
);
}
export async function findFrozenCDPs(
lucid: LucidEvolution,
cdpScriptHash: ScriptHash,
cdpNft: AssetClass,
assetAscii: string,
): Promise<{ utxo: UTxO; datum: CDPContent }[]> {
const cdpUtxos = await lucid.utxosAtWithUnit(
createScriptAddress(lucid.config().network!, cdpScriptHash),
assetClassToUnit(cdpNft),
);
return F.pipe(
cdpUtxos.map((utxo) =>
F.pipe(
O.fromNullable(utxo.datum),
O.flatMap(parseCdpDatum),
O.flatMap((datum) => {
if (
datum.cdpOwner == null &&
toHex(datum.iasset) === fromText(assetAscii)
) {
return O.some({ utxo, datum: datum });
} else {
return O.none;
}
}),
),
),
A.compact,
);
}
export async function findStableswapPool(
lucid: LucidEvolution,
cdpScriptHash: ScriptHash,
cdpNft: AssetClass,
iassetName: string,
collateralAsset: AssetClass,
): Promise<{ utxo: UTxO; datum: StableswapPoolContent }> {
const cdpUtxos = await lucid.utxosAtWithUnit(
createScriptAddress(lucid.config().network!, cdpScriptHash),
assetClassToUnit(cdpNft),
);
return matchSingle(
F.pipe(
cdpUtxos.map((utxo) =>
F.pipe(
O.fromNullable(utxo.datum),
O.flatMap(parseStableswapPoolDatum),
O.flatMap((datum) => {
if (
toHex(datum.iasset) === iassetName &&
toHex(datum.collateralAsset.currencySymbol) ===
toHex(collateralAsset.currencySymbol) &&
toHex(datum.collateralAsset.tokenName) ===
toHex(collateralAsset.tokenName)
) {
return O.some({ utxo, datum: datum });
} else {
return O.none;
}
}),
),
),
A.compact,
),
(res) =>
new Error(
'Expected a single stableswap pool UTXO.: ' + JSON.stringify(res),
),
);
}
export async function findAllCdpCreators(
lucid: LucidEvolution,
cdpCreatorScriptHash: string,
cdpCreatorNft: AssetClass,
): Promise<UTxO[]> {
return lucid.utxosAtWithUnit(
createScriptAddress(lucid.config().network!, cdpCreatorScriptHash),
assetClassToUnit(cdpCreatorNft),
);
}
// TODO: replace by the new variant defined below
export async function findRandomCdpCreator(
lucid: LucidEvolution,
cdpCreatorScriptHash: string,
cdpCreatorNft: AssetClass,
): Promise<UTxO> {
const cdpCreatorUtxos = await findAllCdpCreators(
lucid,
cdpCreatorScriptHash,
cdpCreatorNft,
);
return F.pipe(
O.fromNullable(getRandomElement(cdpCreatorUtxos)),
O.match(() => {
throw new Error('Expected some cdp creator UTXOs.');
}, F.identity),
);
}
export async function findRandomCdpCreatorNew(
context: LucidContext,
sysParams: SystemParams,
): Promise<UTxO> {
const cdpCreatorUtxos = await findAllCdpCreators(
context.lucid,
sysParams.validatorHashes.cdpCreatorHash,
fromSystemParamsAsset(sysParams.cdpCreatorParams.cdpCreatorNft),
);
return F.pipe(
O.fromNullable(getRandomElement(cdpCreatorUtxos)),
O.match(() => {
throw new Error('Expected some cdp creator UTXOs.');
}, F.identity),
);
}
export function findPriceOracleFromCollateralAsset(
lucid: LucidEvolution,
collateralAsset: CollateralAssetOutput,
): Promise<UTxO | undefined> {
return match(collateralAsset.datum.priceInfo)
.with({ OracleNft: P.select() }, (oracleNft) =>
findPriceOracle(lucid, oracleNft),
)
.with({ Delisted: P.any }, () => {
throw new Error('Cannot find price oracle as iAsset is delisted');
})
.otherwise(() => Promise.resolve(undefined));
}
export async function findAllNecessaryOrefs(
lucid: LucidEvolution,
sysParams: SystemParams,
iasset: string,
collateralAsset: AssetClass,
): Promise<{
stabilityPoolUtxo: UTxO;
iasset: IAssetOutput;
collateralAsset: CollateralAssetOutput;
cdpCreatorUtxo: UTxO;
interestOracleUtxo: UTxO;
collectorUtxo: UTxO;
interestCollectorUtxo: UTxO;
govUtxo: UTxO;
treasuryUtxo: UTxO;
}> {
const iassetOut = await findIAsset(
lucid,
sysParams.validatorHashes.iassetHash,
fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
iasset,
);
const collateralAssetOut = await findCollateralAsset(
lucid,
sysParams,
fromSystemParamsAsset(sysParams.cdpCreatorParams.collateralAssetAuthTk),
iasset,
collateralAsset,
);
const stabilityPool = await findStabilityPool(lucid, sysParams, iasset);
return {
stabilityPoolUtxo: stabilityPool.utxo,
iasset: iassetOut,
collateralAsset: collateralAssetOut,
cdpCreatorUtxo: await findRandomCdpCreator(
lucid,
sysParams.validatorHashes.cdpCreatorHash,
fromSystemParamsAsset(sysParams.cdpCreatorParams.cdpCreatorNft),
),
interestOracleUtxo: await findInterestOracle(
lucid,
collateralAssetOut.datum.interestOracleNft,
),
collectorUtxo: await findRandomCollector(
lucid,
sysParams.validatorHashes.collectorHash,
),
interestCollectorUtxo: await findRandomNonAdminInterestCollector(
lucid,
sysParams.validatorHashes.interestCollectionHash,
fromSystemParamsAsset(sysParams.interestCollectionParams.multisigUtxoNft),
),
govUtxo: (
await findGov(
lucid,
sysParams.validatorHashes.govHash,
fromSystemParamsAsset(sysParams.govParams.govNFT),
)
).utxo,
treasuryUtxo: await findRandomTreasuryUtxoWithOnlyAda(lucid, sysParams),
};
}
export async function findPrice(
lucid: LucidEvolution,
sysParams: SystemParams,
asset: string,
collateralAsset: AssetClass,
): Promise<Rational> {
const orefs = await findAllNecessaryOrefs(
lucid,
sysParams,
asset,
collateralAsset,
);
const priceIasset = await match(orefs.collateralAsset.datum.priceInfo)
.with({ OracleNft: P.select() }, async (oracleNft) => {
const priceOracleUtxo = matchSingle(
await lucid.utxosByOutRef([await findPriceOracle(lucid, oracleNft)]),
(_) => new Error('Expected a single price oracle UTXO'),
);
return parsePriceOracleDatum(getInlineDatumOrThrow(priceOracleUtxo))
.price;
})
.with({ Delisted: P.select() }, (price) => {
return price.price;
})
.with({ DeferredValidation: P.select() }, (_) => {
throw new Error('Pyth price not implemented');
})
.exhaustive();
return priceIasset;
}
export async function findInterestDatum(
lucid: LucidEvolution,
sysParams: SystemParams,
asset: string,
collateralAsset: AssetClass,
): Promise<InterestOracleDatum> {
const orefs = await findAllNecessaryOrefs(
lucid,
sysParams,
asset,
collateralAsset,
);
const interestOracleUtxo = matchSingle(
await lucid.utxosByOutRef([orefs.interestOracleUtxo]),
(_) => new Error('Expected a single interest oracle UTXO'),
);
return parseInterestOracleDatum(getInlineDatumOrThrow(interestOracleUtxo));
}
export async function findCdpCR(
lucid: LucidEvolution,
sysParams: SystemParams,
cdp: { utxo: UTxO; datum: CDPContent },
slot: number,
): Promise<number> {
const iassetAscii = toText(toHex(cdp.datum.iasset));
const collateralAsset = await findCollateralAsset(
lucid,
sysParams,
fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
iassetAscii,
cdp.datum.collateralAsset,
);
const price = await findPrice(
lucid,
sysParams,
iassetAscii,
cdp.datum.collateralAsset,
);
const adjustedPrice = adjustPriceToDecimals(
collateralAsset.datum.extraDecimals,
price,
);
return cdpCollateralRatioPercentage(
slot,
adjustedPrice,
cdp.utxo,
cdp.datum,
await findInterestDatum(
lucid,
sysParams,
iassetAscii,
cdp.datum.collateralAsset,
),
lucid.config().network!,
);
}