UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

437 lines (395 loc) 12.4 kB
import { Data, LucidEvolution, OutRef, toHex, TxBuilder, UTxO, addAssets, Assets, fromHex, slotToUnixTime, sortUTxOs, getInputIndices, } from '@lucid-evolution/lucid'; import { fromSystemParamsAsset, fromSystemParamsScriptRef, SystemParams, } from '../../types/system-params'; import { matchSingle } from '../../utils/utils'; import { parseInterestCollectionDatum, serialiseInterestCollectionDatum, serialiseInterestCollectionRedeemer, } from './types-new'; import { assetClassValueOf, getInlineDatumOrThrow, mkAssetsOf, } from '@3rd-eye-labs/cardano-offchain-common'; import { createScriptAddress } from '../../utils/lucid-utils'; import { CDPContent, parseCdpDatumOrThrow, serialiseCdpDatum, serialiseCdpRedeemer, } from '../cdp/types-new'; import { parseInterestOracleDatum } from '../interest-oracle/types-new'; import { calculateAccruedInterest, calculateUnitaryInterestSinceOracleLastUpdated, } from '../interest-oracle/helpers'; import { match, P } from 'ts-pattern'; import { array as A, function as F } from 'fp-ts'; import { parseCollateralAssetDatumOrThrow } from '../iasset/types'; import { Multisig } from '../../types/multisig'; type CDPInfo = { utxo: UTxO; datum: CDPContent; accruedInterest: bigint; }; export async function batchCollectInterest( collateralAssetOref: OutRef, interestCollectorOutRef: OutRef, interestOracleOutRef: OutRef, cdpOutRefs: OutRef[], params: SystemParams, lucid: LucidEvolution, currentSlot: number, ): Promise<TxBuilder> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const interestCollectionRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( params.scriptReferences.interestCollectionValidatorRef, ), ]), (_) => new Error('Expected a single interest collection Ref Script UTXO'), ); const cdpRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(params.scriptReferences.cdpValidatorRef), ]), (_) => new Error('Expected a single cdp Ref Script UTXO'), ); const iAssetTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(params.scriptReferences.iAssetTokenPolicyRef), ]), (_) => new Error('Expected a single iasset token policy Ref Script UTXO'), ); const collateralAssetUtxo = matchSingle( await lucid.utxosByOutRef([collateralAssetOref]), (_) => new Error('Expected a single iasset UTXO'), ); const collateralAssetDatum = parseCollateralAssetDatumOrThrow( getInlineDatumOrThrow(collateralAssetUtxo), ); const interestCollectorUtxo: UTxO = matchSingle( await lucid.utxosByOutRef([interestCollectorOutRef]), (_) => new Error('Expected a single interest collector UTXO'), ); // Confirm that the interest collector UTXO is NOT of InterestCollectorDatum type if ( assetClassValueOf( interestCollectorUtxo.assets, fromSystemParamsAsset(params.interestCollectionParams.multisigUtxoNft), ) ) { throw new Error( 'Interest collector UTXO is of InterestCollectorDatum type', ); } const interestOracleUtxo: UTxO = matchSingle( await lucid.utxosByOutRef([interestOracleOutRef]), (_) => new Error('Expected a single interest oracle UTXO'), ); const interestOracleDatum = parseInterestOracleDatum( getInlineDatumOrThrow(interestOracleUtxo), ); const cdpUtxos = await lucid.utxosByOutRef(cdpOutRefs); const sortedCdpUtxos = sortUTxOs(cdpUtxos, 'Canonical'); if (sortedCdpUtxos.length !== cdpOutRefs.length) { throw new Error('Expected certain number of CDPs'); } function getAccruedInterest(cdpDatum: CDPContent): bigint { return match(cdpDatum.cdpFees) .with({ FrozenCDPAccumulatedFees: P.any }, () => { throw new Error('CDP fees wrong'); }) .with({ ActiveCDPInterestTracking: P.select() }, (interest) => { return calculateAccruedInterest( currentTime, interest.unitaryInterestSnapshot, cdpDatum.mintedAmt, interest.lastSettled, interestOracleDatum, ); }) .exhaustive(); } const cdpsInfo: CDPInfo[] = sortedCdpUtxos.map((cdpUtxo) => { const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo)); return { utxo: cdpUtxo, datum: cdpDatum, accruedInterest: getAccruedInterest(cdpDatum), }; }); const accumulatedInterest = F.pipe( cdpsInfo, A.reduce<CDPInfo, bigint>( 0n, (acc, cdpInfo) => acc + cdpInfo.accruedInterest, ), ); const updatedUnitarySnapshot = calculateUnitaryInterestSinceOracleLastUpdated( currentTime, interestOracleDatum, ) + interestOracleDatum.unitaryInterest; const referenceInputs = [ collateralAssetUtxo, interestOracleUtxo, interestCollectionRefScriptUtxo, cdpRefScriptUtxo, iAssetTokenPolicyRefScriptUtxo, ]; const referenceInputIndices = getInputIndices( [collateralAssetUtxo, interestOracleUtxo], referenceInputs, false, ); const validateFrom = slotToUnixTime(network, currentSlot - 1); const validateTo = validateFrom + Number(params.cdpParams.biasTime); const tx = lucid .newTx() .validFrom(validateFrom) .validTo(validateTo) .readFrom(referenceInputs) .collectFrom([interestCollectorUtxo], { kind: 'selected', makeRedeemer: (inputIndices: bigint[]) => { return serialiseInterestCollectionRedeemer({ BatchCollectInterest: { currentTime: currentTime, ownInputIndex: inputIndices[0], collateralAssetRefInputIndex: referenceInputIndices[0], interestOracleRefInputIndex: referenceInputIndices[1], }, }); }, inputs: [interestCollectorUtxo], }) .mintAssets( mkAssetsOf( { currencySymbol: fromHex( params.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: collateralAssetDatum.iasset, }, accumulatedInterest, ), Data.void(), ) .pay.ToContract( createScriptAddress( lucid.config().network!, params.validatorHashes.interestCollectionHash, ), { kind: 'inline', value: Data.void() }, addAssets( interestCollectorUtxo.assets, mkAssetsOf( { currencySymbol: fromHex( params.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: collateralAssetDatum.iasset, }, accumulatedInterest, ), ), ); F.pipe( cdpsInfo, A.reduce<CDPInfo, TxBuilder>(tx, (acc, cdpInfo) => acc .collectFrom([cdpInfo.utxo], { kind: 'selected', makeRedeemer: (inputIndices: bigint[]) => { return serialiseCdpRedeemer({ SettleInterest: { interestCollectorInputIndex: inputIndices[0] }, }); }, inputs: [interestCollectorUtxo], }) .pay.ToContract( cdpInfo.utxo.address, { kind: 'inline', value: serialiseCdpDatum({ ...cdpInfo.datum, mintedAmt: cdpInfo.datum.mintedAmt + cdpInfo.accruedInterest, cdpFees: { ActiveCDPInterestTracking: { lastSettled: currentTime, unitaryInterestSnapshot: updatedUnitarySnapshot, }, }, }), }, cdpInfo.utxo.assets, ), ), ); return tx; } export async function collectInterestTx( value: Assets, lucid: LucidEvolution, sysParams: SystemParams, tx: TxBuilder, interestCollectorOref: OutRef, ): Promise<UTxO> { const interestCollectorUtxo = matchSingle( await lucid.utxosByOutRef([interestCollectorOref]), (_) => new Error('Expected a single interest collector UTXO'), ); const interestCollectorRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.interestCollectionValidatorRef, ), ]), (_) => new Error('Expected a single interest collector Ref Script UTXO'), ); tx.readFrom([interestCollectorRefScriptUtxo]) .collectFrom( [interestCollectorUtxo], serialiseInterestCollectionRedeemer('CollectInterest'), ) .pay.ToContract( interestCollectorUtxo.address, { kind: 'inline', value: Data.void() }, addAssets(interestCollectorUtxo.assets, value), ); return interestCollectorRefScriptUtxo; } export async function distributeInterest( interestCollectorOutRefs: OutRef[], interestAdminOutRef: OutRef, params: SystemParams, lucid: LucidEvolution, ): Promise<TxBuilder> { const interestCollectorUtxos = []; for (const outRef of interestCollectorOutRefs) { interestCollectorUtxos.push( matchSingle( await lucid.utxosByOutRef([outRef]), (_) => new Error('Expected a single UTXO with that reference'), ), ); } const interestAdminUtxo: UTxO = matchSingle( (await lucid.utxosByOutRef([interestAdminOutRef])).filter((utxo) => assetClassValueOf( utxo.assets, fromSystemParamsAsset(params.interestCollectionParams.multisigUtxoNft), ), ), (_) => new Error('Expected a single interest admin UTXO'), ); const interestAdminDatum = parseInterestCollectionDatum( getInlineDatumOrThrow(interestAdminUtxo), ); // Find the script reference UTXO for the interest collection validator const interestCollectionRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( params.scriptReferences.interestCollectionValidatorRef, ), ]), (_) => new Error('Expected a single interest collection Ref Script UTXO'), ); const tx = lucid .newTx() .readFrom([interestCollectionRefScriptUtxo, interestAdminUtxo]) .collectFrom( interestCollectorUtxos, serialiseInterestCollectionRedeemer('Distribute'), ); // TODO: Handle tx contruction for Multisig if ('Signature' in interestAdminDatum.admin_permissions) { tx.addSignerKey( toHex(interestAdminDatum.admin_permissions.Signature.keyHash), ); } else { // TODO: Handle other admin permissions types throw new Error('Unsupported admin permissions type'); } return tx; } export async function updatePermissions( interestAdminOutRef: OutRef, govOref: OutRef, newAdminPermissions: Multisig, expectedSigners: string[], params: SystemParams, lucid: LucidEvolution, ): Promise<TxBuilder> { const interestAdminUtxo: UTxO = matchSingle( (await lucid.utxosByOutRef([interestAdminOutRef])).filter((utxo) => assetClassValueOf( utxo.assets, fromSystemParamsAsset(params.interestCollectionParams.multisigUtxoNft), ), ), (_) => new Error('Expected a single interest admin UTXO'), ); const govUtxo: UTxO = matchSingle( (await lucid.utxosByOutRef([govOref])).filter((utxo) => assetClassValueOf( utxo.assets, fromSystemParamsAsset(params.interestCollectionParams.govAuthTk), ), ), (_) => new Error('Expected a single interest admin UTXO'), ); // Find the script reference UTXO for the interest collection validator const interestCollectionRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( params.scriptReferences.interestCollectionValidatorRef, ), ]), (_) => new Error('Expected a single interest collection Ref Script UTXO'), ); const tx = lucid .newTx() .readFrom([interestCollectionRefScriptUtxo, govUtxo]) .collectFrom( [interestAdminUtxo], serialiseInterestCollectionRedeemer('UpdatePermissions'), ) .pay.ToContract( createScriptAddress( lucid.config().network!, params.validatorHashes.interestCollectionHash, ), { kind: 'inline', value: serialiseInterestCollectionDatum({ admin_permissions: newAdminPermissions, }), }, interestAdminUtxo.assets, ); for (const signer of expectedSigners) { tx.addSignerKey(signer); } return tx; }