UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

372 lines (339 loc) 10.6 kB
import { LucidEvolution, TxBuilder, OutRef, UTxO, addAssets, slotToUnixTime, Data, fromHex, getInputIndices, } from '@lucid-evolution/lucid'; import { addrDetails, createScriptAddress, getInlineDatumOrThrow, } from '../../utils/lucid-utils'; import { serialiseCdpDatum } from '../cdp/types-new'; import { matchSingle } from '../../utils/utils'; import { fromSystemParamsAsset, fromSystemParamsScriptRef, SystemParams, } from '../../types/system-params'; import { parseInterestOracleDatum } from '../interest-oracle/types-new'; import { serialiseCDPCreatorDatum, serialiseCDPCreatorRedeemer, } from '../cdp-creator/types-new'; import { calculateUnitaryInterestSinceOracleLastUpdated } from '../interest-oracle/helpers'; import { approximateLeverageRedemptions, summarizeActualLeverageRedemptions, calculateLeverageFromCollateralRatio, MAX_REDEMPTIONS_WITH_CDP_OPEN, } from './helpers'; import { parseCollateralAssetDatumOrThrow, parseIAssetDatumOrThrow, } from '../iasset/types'; import { mkAssetsOf } from '@3rd-eye-labs/cardano-offchain-common'; import { RobDatum } from '../rob/types-new'; import { rationalToFloat } from '../../types/rational'; import { buildRedemptionsTx, randomRobsSubsetSatisfyingTargetCollateral, } from '../rob/helpers'; import { treasuryFeeTx } from '../treasury/transactions'; import Decimal from 'decimal.js'; import { fromDecimal } from '../../utils/bigint-utils'; import { calculateFeeFromRatio } from '../../utils/indigo-helpers'; import { retrieveAdjustedPrice } from '../../utils/oracle-helpers'; import { attachOracle } from '../iasset/helpers'; import { OracleIdx } from '../price-oracle/types-new'; export async function leverageCdpWithRob( leverage: number, baseCollateral: bigint, priceOracleOutRef: OutRef | undefined, iassetOutRef: OutRef, collateralAssetOutRef: OutRef, cdpCreatorOref: OutRef, interestOracleOref: OutRef, treasuryOref: OutRef, sysParams: SystemParams, lucid: LucidEvolution, allRobs: [UTxO, RobDatum][], currentSlot: number, pythMessage: string | undefined = undefined, pythStateOref: OutRef | undefined = undefined, ): Promise<TxBuilder> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const [pkh, skh] = await addrDetails(lucid); const robScriptRefUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(sysParams.scriptReferences.robValidatorRef), ]), (_) => new Error('Expected a single ROB Ref Script UTXO'), ); const cdpCreatorRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.cdpCreatorValidatorRef, ), ]), (_) => new Error('Expected a single cdp creator Ref Script UTXO'), ); const cdpAuthTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.cdpAuthTokenRef, ), ]), (_) => new Error('Expected a single cdp auth token policy Ref Script UTXO'), ); const iAssetTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.iAssetTokenPolicyRef, ), ]), (_) => new Error('Expected a single iasset token policy Ref Script UTXO'), ); const cdpCreatorUtxo = matchSingle( await lucid.utxosByOutRef([cdpCreatorOref]), (_) => new Error('Expected a single CDP creator UTXO'), ); const interestOracleUtxo = matchSingle( await lucid.utxosByOutRef([interestOracleOref]), (_) => new Error('Expected a single interest oracle UTXO'), ); const interestOracleDatum = parseInterestOracleDatum( getInlineDatumOrThrow(interestOracleUtxo), ); const iassetUtxo = matchSingle( await lucid.utxosByOutRef([iassetOutRef]), (_) => new Error('Expected a single IAsset UTXO'), ); const iassetDatum = parseIAssetDatumOrThrow( getInlineDatumOrThrow(iassetUtxo), ); const collateralAssetUtxo = matchSingle( await lucid.utxosByOutRef([collateralAssetOutRef]), (_) => new Error('Expected a single collateral asset UTXO'), ); const collateralAssetDatum = parseCollateralAssetDatumOrThrow( getInlineDatumOrThrow(collateralAssetUtxo), ); const [price, _] = await retrieveAdjustedPrice( iassetDatum.assetName, collateralAssetDatum.collateralAsset, collateralAssetDatum.priceInfo, collateralAssetDatum.extraDecimals, priceOracleOutRef, pythMessage, sysParams.pythConfig, lucid, ); const maxLeverage = calculateLeverageFromCollateralRatio( collateralAssetDatum.iasset, collateralAssetDatum.collateralAsset, collateralAssetDatum.maintenanceRatio, baseCollateral, price, iassetDatum.debtMintingFeeRatio, iassetDatum.redemptionReimbursementRatio, allRobs, ); if (!maxLeverage) { throw new Error("Can't calculate max leverage with those parameters."); } const leverageSummary = approximateLeverageRedemptions( baseCollateral, leverage, iassetDatum.redemptionProcessingFeeRatio, iassetDatum.debtMintingFeeRatio, ); if (maxLeverage < leverageSummary.leverage) { throw new Error("Can't use more leverage than max."); } if ( rationalToFloat(leverageSummary.collateralRatio) < rationalToFloat(collateralAssetDatum.maintenanceRatio) ) { throw new Error( "Can't have collateral ratio smaller than maintenance ratio", ); } const redemptionDetails = summarizeActualLeverageRedemptions( leverageSummary.redeemedCollateral, iassetDatum.redemptionReimbursementRatio, price, randomRobsSubsetSatisfyingTargetCollateral( iassetDatum.assetName, collateralAssetDatum.collateralAsset, leverageSummary.redeemedCollateral, price, allRobs, MAX_REDEMPTIONS_WITH_CDP_OPEN, ), ); // payout / (1 - debtMintingFee) = total minted amount const mintedAmt = fromDecimal( Decimal(redemptionDetails.totalIAssetPayout) .div( Decimal(1).minus( Decimal(iassetDatum.debtMintingFeeRatio.numerator).div( iassetDatum.debtMintingFeeRatio.denominator, ), ), ) .floor(), ); const debtMintingFee = calculateFeeFromRatio( iassetDatum.debtMintingFeeRatio, mintedAmt, ); const collateralAmt = redemptionDetails.totalRedeemedCollateral + baseCollateral; const cdpNftVal = mkAssetsOf( fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), 1n, ); const iassetClass = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: iassetDatum.assetName, }; const iassetTokensVal = mkAssetsOf(iassetClass, mintedAmt); const refScripts = [ cdpCreatorRefScriptUtxo, cdpAuthTokenPolicyRefScriptUtxo, iAssetTokenPolicyRefScriptUtxo, robScriptRefUtxo, ]; const tx = lucid .newTx() // Ref scripts .readFrom(refScripts) .mintAssets(cdpNftVal, Data.void()) .mintAssets(iassetTokensVal, Data.void()) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.cdpHash, skh), { kind: 'inline', value: serialiseCdpDatum({ cdpOwner: fromHex(pkh.hash), iasset: iassetDatum.assetName, collateralAsset: collateralAssetDatum.collateralAsset, mintedAmt: mintedAmt, cdpFees: { ActiveCDPInterestTracking: { lastSettled: currentTime, unitaryInterestSnapshot: calculateUnitaryInterestSinceOracleLastUpdated( currentTime, interestOracleDatum, ) + interestOracleDatum.unitaryInterest, }, }, }), }, addAssets( cdpNftVal, mkAssetsOf(collateralAssetDatum.collateralAsset, collateralAmt), ), ) .pay.ToContract( cdpCreatorUtxo.address, { kind: 'inline', value: serialiseCDPCreatorDatum({ creatorInputOref: { outputIndex: BigInt(cdpCreatorUtxo.outputIndex), txHash: fromHex(cdpCreatorUtxo.txHash), }, }), }, cdpCreatorUtxo.assets, ); const treasuryRefScriptUtxo = debtMintingFee > 0n ? await treasuryFeeTx( iassetClass, debtMintingFee, 0n, lucid, sysParams, tx, cdpCreatorUtxo, treasuryOref, ) : undefined; const { interval, referenceInputs: refInputs } = await attachOracle( iassetDatum.assetName, collateralAssetDatum.collateralAsset, collateralAssetDatum.priceInfo, priceOracleOutRef, pythStateOref, pythMessage, sysParams.pythConfig, sysParams.cdpCreatorParams.biasTime, currentSlot, lucid, tx, ); const referenceInputs = [ interestOracleUtxo, iassetUtxo, collateralAssetUtxo, ...refInputs, ]; const refInputsIndices = getInputIndices(referenceInputs, [ ...referenceInputs, ...refScripts, ...(treasuryRefScriptUtxo != null ? [treasuryRefScriptUtxo] : []), ]); const oracleIdx: OracleIdx = priceOracleOutRef !== undefined ? { OracleRefInputIdx: refInputsIndices[3] } : 'OracleVoid'; tx.validFrom(interval.validFrom) .validTo(interval.validTo) .readFrom(referenceInputs) .collectFrom([cdpCreatorUtxo], { kind: 'self', makeRedeemer: (inputIdx) => { return serialiseCDPCreatorRedeemer({ CreateCDP: { cdpOwner: fromHex(pkh.hash), minted: mintedAmt, collateralAmt: collateralAmt, currentTime: currentTime, creatorInputIdx: inputIdx, creatorOutputIdx: 1n, cdpOutputIdx: 0n, iassetRefInputIdx: refInputsIndices[1], collateralAssetRefInputIdx: refInputsIndices[2], interestOracleRefInputIdx: refInputsIndices[0], priceOracleIdx: oracleIdx, }, }); }, }); buildRedemptionsTx( redemptionDetails.redemptions.map((r) => [r.utxo, r.iassetsPayoutAmt]), iassetDatum.assetName, collateralAssetDatum.collateralAsset, price, iassetDatum.redemptionReimbursementRatio, sysParams, tx, 2n + (debtMintingFee > 0n ? 1n : 0n), refInputsIndices[2], refInputsIndices[1], oracleIdx, ); return tx; }