UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

349 lines (310 loc) 9.88 kB
import { addAssets, Data, fromHex, LucidEvolution, OutRef, sortUTxOs, toHex, TxBuilder, UTxO, } from '@lucid-evolution/lucid'; import { parseStableswapOrderDatumOrThrow, serialiseStableswapOrderRedeemer, StableswapOrderDatum, } from '../../src/contracts/stableswap/types-new'; import { fromSystemParamsScriptRef, SystemParams, treasuryFeeTx, } from '../../src'; import { addressToBech32, assetClassToUnit, getInlineDatumOrThrow, lovelacesAmt, matchSingle, mkAssetsOf, mkLovelacesOf, } from '@3rd-eye-labs/cardano-offchain-common'; import { isEmpty } from 'fp-ts/lib/Array'; import { array as A, function as F } from 'fp-ts'; import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers'; import { parseStableswapPoolDatumOrThrow, serialiseCdpRedeemer, serialiseStableswapPoolDatum, } from '../../src/contracts/cdp/types-new'; import { createDestinationDatum } from '../../src/contracts/stableswap/helpers'; import { rationalDiv, rationalFloor, rationalFromInt, rationalMul, } from '../../src/types/rational'; type StableswapOrderInfo = { utxo: UTxO; datum: StableswapOrderDatum; suppliedIasset: bigint; suppliedCollateralAsset: bigint; }; export type MutatedBatchProcessStableswapOrdersType = { type: 'exceed-max-execution-fee'; maxExecutionFee: bigint; }; export async function mutatedBatchProcessStableswapOrders( stableswapOrderOrefs: OutRef[], stableswapPoolOref: OutRef, treasuryOref: OutRef, sysParams: SystemParams, lucid: LucidEvolution, type: MutatedBatchProcessStableswapOrdersType, ): Promise<TxBuilder> { const stableswapScriptRefUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.stableswapValidatorRef, ), ]), (_) => new Error('Expected a single Stableswap Ref Script UTXO'), ); const cdpScriptRefUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef), ]), (_) => new Error('Expected a single CDP Ref Script UTXO'), ); const iAssetTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.iAssetTokenPolicyRef, ), ]), (_) => new Error('Expected a single iasset token policy Ref Script UTXO'), ); if (isEmpty(stableswapOrderOrefs)) { throw new Error('At least one order must be provided.'); } const stableswapOrderUtxos = await lucid.utxosByOutRef(stableswapOrderOrefs); const sortedStableswapOrderUtxos = sortUTxOs( stableswapOrderUtxos, 'Canonical', ); if (sortedStableswapOrderUtxos.length !== stableswapOrderOrefs.length) { throw new Error('Expected certain number of orders'); } const mainOrderUtxo = sortedStableswapOrderUtxos[0]; const mainOrderDatum = parseStableswapOrderDatumOrThrow( getInlineDatumOrThrow(mainOrderUtxo), ); const iassetAc = { currencySymbol: fromHex( sysParams.stableswapParams.iassetSymbol.unCurrencySymbol, ), tokenName: mainOrderDatum.iasset, }; const collateralAc = mainOrderDatum.collateralAsset; const ordersInfo: StableswapOrderInfo[] = sortedStableswapOrderUtxos.map( (orderUtxo) => { const orderDatum = parseStableswapOrderDatumOrThrow( getInlineDatumOrThrow(orderUtxo), ); if ( toHex(orderDatum.iasset) != toHex(mainOrderDatum.iasset) || toHex(orderDatum.collateralAsset.currencySymbol) != toHex(mainOrderDatum.collateralAsset.currencySymbol) || toHex(orderDatum.collateralAsset.tokenName) != toHex(mainOrderDatum.collateralAsset.tokenName) ) { throw new Error('Wrong batch of orders'); } const suppliedIasset = orderUtxo.assets[assetClassToUnit(iassetAc)] ?? 0n; const suppliedCollateralAsset = orderUtxo.assets[assetClassToUnit(collateralAc)] ?? 0n; if ( (suppliedIasset != 0n && suppliedCollateralAsset != 0n) || (suppliedIasset == 0n && suppliedCollateralAsset == 0n) ) { throw new Error( 'An order must supply either iAsset or collateral asset', ); } return { utxo: orderUtxo, datum: orderDatum, suppliedIasset: suppliedIasset, suppliedCollateralAsset: suppliedCollateralAsset, }; }, ); const totalSuppliedAssets = F.pipe( ordersInfo, A.reduce<StableswapOrderInfo, [bigint, bigint]>( [0n, 0n], (acc, orderInfo) => [ acc[0] + orderInfo.suppliedIasset, acc[1] + orderInfo.suppliedCollateralAsset, ], ), ); const totalSuppliedIasset = totalSuppliedAssets[0]; const totalSuppliedCollateralAsset = totalSuppliedAssets[1]; const stableswapPoolUtxo = matchSingle( await lucid.utxosByOutRef([stableswapPoolOref]), (_) => new Error('Expected a single cdp UTXO'), ); const stableswapPoolDatum = parseStableswapPoolDatumOrThrow( getInlineDatumOrThrow(stableswapPoolUtxo), ); const redemptionFee = calculateFeeFromRatio( stableswapPoolDatum.redemptionFeeRatio, totalSuppliedIasset, ); const totalEffectiveSuppliedIasset = totalSuppliedIasset - redemptionFee; const collateralAmtChangePool = totalSuppliedCollateralAsset - rationalFloor( rationalMul( rationalFromInt(totalEffectiveSuppliedIasset), stableswapPoolDatum.collateralToIassetRatio, ), ); const amountToMint = rationalFloor( rationalDiv( rationalFromInt(totalSuppliedCollateralAsset), stableswapPoolDatum.collateralToIassetRatio, ), ) - totalEffectiveSuppliedIasset; const mintingFee = calculateFeeFromRatio( stableswapPoolDatum.mintingFeeRatio, amountToMint, ); const tx = lucid .newTx() .readFrom([ stableswapScriptRefUtxo, cdpScriptRefUtxo, iAssetTokenPolicyRefScriptUtxo, ]) .collectFrom([stableswapPoolUtxo], { kind: 'selected', makeRedeemer: (inputIndices) => serialiseCdpRedeemer({ Stableswap: { forwardingInputIndex: inputIndices[0], }, }), inputs: [mainOrderUtxo], }) .pay.ToContract( stableswapPoolUtxo.address, { kind: 'inline', value: serialiseStableswapPoolDatum(stableswapPoolDatum), }, collateralAmtChangePool != 0n ? addAssets( stableswapPoolUtxo.assets, mkAssetsOf(collateralAc, collateralAmtChangePool), ) : stableswapPoolUtxo.assets, ); const fee = mintingFee + redemptionFee; if (fee > 0) { await treasuryFeeTx( iassetAc, fee, 0n, lucid, sysParams, tx, stableswapPoolOref, treasuryOref, ); } if (amountToMint != 0n) { tx.mintAssets(mkAssetsOf(iassetAc, amountToMint), Data.void()); } F.pipe( ordersInfo, A.reduce<StableswapOrderInfo, TxBuilder>(tx, (acc, orderInfo) => { const iassetSupplied = orderInfo.utxo.assets[assetClassToUnit(iassetAc)] ?? 0n; const collateralAssetSupplied = orderInfo.utxo.assets[assetClassToUnit(collateralAc)] ?? 0n; const isMinting = iassetSupplied > 0n ? false : true; const iAssetEquivalentSupplied = isMinting ? rationalFloor( rationalDiv( rationalFromInt(collateralAssetSupplied), stableswapPoolDatum.collateralToIassetRatio, ), ) : iassetSupplied; const effectiveiAssetEquivalentSupplied = isMinting ? iAssetEquivalentSupplied - calculateFeeFromRatio( stableswapPoolDatum.mintingFeeRatio, iAssetEquivalentSupplied, ) : iAssetEquivalentSupplied - calculateFeeFromRatio( stableswapPoolDatum.redemptionFeeRatio, iAssetEquivalentSupplied, ); const amountToReceive = isMinting ? effectiveiAssetEquivalentSupplied : rationalFloor( rationalMul( rationalFromInt(effectiveiAssetEquivalentSupplied), stableswapPoolDatum.collateralToIassetRatio, ), ); const maxExecutionFee = type.type === 'exceed-max-execution-fee' ? type.maxExecutionFee : orderInfo.datum.maxExecutionFee; return acc .collectFrom( [orderInfo.utxo], orderInfo.utxo == mainOrderUtxo ? serialiseStableswapOrderRedeemer('BatchProcessStableswapOrders') : { kind: 'selected', makeRedeemer: (inputIndices: bigint[]) => { return serialiseStableswapOrderRedeemer({ BatchAuxiliary: { ownInputIndex: inputIndices[0], mainOrderInputIndex: inputIndices[1], }, }); }, inputs: [orderInfo.utxo, mainOrderUtxo], }, ) .pay.ToAddressWithData( addressToBech32(orderInfo.datum.destination, lucid.config().network!), { kind: 'inline', value: createDestinationDatum( orderInfo.datum.destinationInlineDatum ?? null, orderInfo.utxo, ), }, addAssets( // Currently, we always take the max execution fee from the order utxo. // This can be improved so that we take the actual execution fee. mkLovelacesOf( lovelacesAmt(orderInfo.utxo.assets) - maxExecutionFee, ), isMinting ? mkAssetsOf(iassetAc, amountToReceive) : mkAssetsOf(collateralAc, amountToReceive), ), ); }), ); return tx; }