UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

1,610 lines (1,493 loc) 49 kB
// import { OutRef } from '@lucid-evolution/lucid'; import { addAssets, Assets, fromHex, fromText, LucidEvolution, OutRef, paymentCredentialOf, slotToUnixTime, toHex, TxBuilder, UTxO, } from '@lucid-evolution/lucid'; import { matchSingle } from '../../utils/utils'; import { createScriptAddress, getInlineDatumOrThrow, } from '../../utils/lucid-utils'; import { adaAssetClass, assetClassValueOf, isAssetsZero, isSameAssetClass, lovelacesAmt, mkAssetsOf, mkLovelacesOf, negateAssets, } from '@3rd-eye-labs/cardano-offchain-common'; import { Data } from '@lucid-evolution/lucid'; import { pipe } from 'fp-ts/lib/function'; import { array as A, option as O, function as F } from 'fp-ts'; import { match, P } from 'ts-pattern'; import { fromSysParamsCredential, fromSystemParamsAsset, fromSystemParamsScriptRef, SystemParams, } from '../../types/system-params'; import { ONE_SECOND } from '../../utils/time-helpers'; import { parseStakingPositionOrThrow, serialiseStakingDatum, serialiseStakingRedeemer, StakingPosLockedAmt, } from '../staking/types-new'; import { updateStakingLockedAmount } from '../staking/helpers'; import { pollPassQuorum } from '../poll/helpers'; import { collateralAssetCreationDatumHelper, createValueFromWithdrawal, findFirstCollateralAsset, findRelativeCollateralAssetForInsertion, findRelativeIAssetForInsertion, iassetCreationDatumHelper, proposalDeposit, } from './helpers'; import { initSpState } from '../stability-pool/helpers'; import { serialiseVersionRecordDatum } from '../version-registry/types-new'; import { parseGovDatumOrThrow, ProposalContent, serialiseGovDatum, serialiseGovRedeemer, TreasuryWithdrawal, } from './types-new'; import { serialiseStabilityPoolDatum } from '../stability-pool/types-new'; import { parsePollManagerOrThrow, parsePollShardOrThrow, PollShardContent, PollStatus, serialisePollDatum, serialisePollManagerRedeemer, serialisePollShardRedeemer, VoteOption, } from '../poll/types-poll-new'; import { parseExecuteDatumOrThrow, serialiseExecuteDatum, } from '../execute/types-new'; import { addressFromBech32, addressToBech32, } from '@3rd-eye-labs/cardano-offchain-common'; import { serialiseTreasuryRedeemer, serialiseWithdrawalOutputDatum, } from '../treasury/types-new'; import { parseCollateralAssetDatumOrThrow, parseIAssetDatumOrThrow, serialiseIAssetDatum, serialiseIAssetRedeemer, } from '../iasset/types'; import { serialiseCDPCreatorRedeemer } from '../cdp-creator/types-new'; import { serialiseCdpRedeemer, serialiseStableswapPoolDatum, } from '../cdp/types-new'; /** * Returns the new PollId. */ export async function createProposal( proposalContent: ProposalContent, treasuryWithdrawal: TreasuryWithdrawal | null, sysParams: SystemParams, lucid: LucidEvolution, currentSlot: number, govOref: OutRef, /** * This has to be passed only in case of createAsset proposal */ allIAssetOrefs: OutRef[] | undefined, ): Promise<[TxBuilder, bigint]> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const ownAddr = await lucid.wallet().address(); const pkh = paymentCredentialOf(ownAddr); const govRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.governanceValidatorRef, ), ]), (_) => new Error('Expected a single Gov Ref Script UTXO'), ); const pollAuthTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.pollManagerTokenRef, ), ]), (_) => new Error('Expected a single poll auth token policy ref Script UTXO'), ); const govUtxo = matchSingle( await lucid.utxosByOutRef([govOref]), (_) => new Error('Expected a single Gov UTXO'), ); const govDatum = parseGovDatumOrThrow(getInlineDatumOrThrow(govUtxo)); const votingEndTime = currentTime + govDatum.protocolParams.votingPeriod; const expirationTime = votingEndTime + govDatum.protocolParams.expirationPeriod; const proposingEndTime = currentTime + govDatum.protocolParams.proposingPeriod; const pollNftValue = mkAssetsOf( fromSystemParamsAsset(sysParams.govParams.pollToken), 1n, ); const newPollId = govDatum.currentProposal + 1n; const tx = lucid.newTx(); // Add iAsset ref input when Propose asset proposal await match(proposalContent) .with({ ProposeIAsset: { asset: P.select() } }, async (newAsset) => { if (allIAssetOrefs === undefined) { throw new Error('Missing iAsset orefs'); } const relativeIAsset = await findRelativeIAssetForInsertion( newAsset, allIAssetOrefs, lucid, ); pipe( relativeIAsset, O.match( () => { if (govDatum.iassetsCount !== 0n) { throw new Error( 'Has to find relative iAsset when there are iAssets.', ); } }, (relative) => { tx.readFrom([relative.utxo]); }, ), ); }) .otherwise(() => {}); return [ tx .mintAssets(pollNftValue, Data.void()) // Ref scripts .readFrom([govRefScriptUtxo, pollAuthTokenPolicyRefScriptUtxo]) .collectFrom( [govUtxo], serialiseGovRedeemer({ CreatePoll: { content: proposalContent, currentTime: currentTime, proposalOwner: fromHex(pkh.hash), treasuryWithdrawal: treasuryWithdrawal, }, }), ) .pay.ToContract( govUtxo.address, { kind: 'inline', value: serialiseGovDatum({ ...govDatum, currentProposal: govDatum.currentProposal + 1n, activeProposals: govDatum.activeProposals + 1n, }), }, govUtxo.assets, ) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.pollManagerHash), { kind: 'inline', value: serialisePollDatum({ PollManager: { pollId: newPollId, pollOwner: fromHex(pkh.hash), content: proposalContent, treasuryWithdrawal: treasuryWithdrawal, status: { yesVotes: 0n, noVotes: 0n }, votingEndTime: votingEndTime, createdShardsCount: 0n, talliedShardsCount: 0n, totalShardsCount: govDatum.protocolParams.totalShards, proposingEndTime: proposingEndTime, expirationTime: expirationTime, protocolVersion: govDatum.currentVersion, minimumQuorum: govDatum.protocolParams.minimumQuorum, }, }), }, addAssets( pollNftValue, mkAssetsOf( fromSystemParamsAsset(sysParams.govParams.indyAsset), proposalDeposit( govDatum.protocolParams.proposalDeposit, govDatum.activeProposals, ), ), ), ) .validFrom(Number(currentTime) - ONE_SECOND) .validTo( Number(currentTime) + Number(sysParams.govParams.gBiasTime) - ONE_SECOND, ) .addSigner(ownAddr), newPollId, ]; } /** * Builds transaction creating shards of count chunk size. */ export async function createShardsChunks( /** * This gets automatically capped to total shards count. */ chunkSize: bigint, pollManagerOref: OutRef, sysParams: SystemParams, currentSlot: number, lucid: LucidEvolution, ): Promise<TxBuilder> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const ownAddr = await lucid.wallet().address(); const pollManagerUtxo = matchSingle( await lucid.utxosByOutRef([pollManagerOref]), (_) => new Error('Expected a single Poll manager UTXO'), ); const pollManager = parsePollManagerOrThrow( getInlineDatumOrThrow(pollManagerUtxo), ); if (pollManager.createdShardsCount >= pollManager.totalShardsCount) { throw new Error('All shards already created.'); } const pollAuthTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.pollManagerTokenRef, ), ]), (_) => new Error('Expected a single poll auth token policy ref Script UTXO'), ); const pollManagerRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.pollManagerValidatorRef, ), ]), (_) => new Error('Expected a single poll auth token policy ref Script UTXO'), ); const shardsCount = BigInt( Math.min( Number(chunkSize), Number(pollManager.totalShardsCount - pollManager.createdShardsCount), ), ); const pollNft = fromSystemParamsAsset(sysParams.govParams.pollToken); const tx = lucid .newTx() .validFrom(Number(currentTime) - ONE_SECOND) .validTo( Number(currentTime) + Number(sysParams.pollManagerParams.pBiasTime) - ONE_SECOND, ) .mintAssets(mkAssetsOf(pollNft, shardsCount), Data.void()) // Ref scripts .readFrom([pollAuthTokenPolicyRefScriptUtxo, pollManagerRefScriptUtxo]) .collectFrom( [pollManagerUtxo], serialisePollManagerRedeemer({ CreateShards: { currentTime } }), ) .pay.ToContract( pollManagerUtxo.address, { kind: 'inline', value: serialisePollDatum({ PollManager: { ...pollManager, createdShardsCount: pollManager.createdShardsCount + shardsCount, }, }), }, pollManagerUtxo.assets, ) .addSigner(ownAddr); for (let idx = 0; idx < shardsCount; idx++) { tx.pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.pollShardHash), { kind: 'inline', value: serialisePollDatum({ PollShard: { pollId: pollManager.pollId, status: { yesVotes: 0n, noVotes: 0n }, votingEndTime: pollManager.votingEndTime, managerAddress: addressFromBech32(pollManagerUtxo.address), }, }), }, mkAssetsOf(pollNft, 1n), ); } return tx; } /** * Updates both locked amount and poll status based on the vote. */ function voteHelper( stakingPosLockedAmt: StakingPosLockedAmt, pollShard: PollShardContent, voteOption: VoteOption, currentTime: bigint, indyStakedAmt: bigint, ): [StakingPosLockedAmt, PollStatus] { const newPollStatus = match(voteOption) .returnType<PollStatus>() .with('Yes', () => ({ ...pollShard.status, yesVotes: pollShard.status.yesVotes + indyStakedAmt, })) .with('No', () => ({ ...pollShard.status, noVotes: pollShard.status.noVotes + indyStakedAmt, })) .exhaustive(); const newLockedAmt: StakingPosLockedAmt = [ ...updateStakingLockedAmount(stakingPosLockedAmt, currentTime), [ BigInt(pollShard.pollId), { voteAmt: indyStakedAmt, votingEnd: pollShard.votingEndTime }, ], ]; return [newLockedAmt, newPollStatus]; } export async function vote( voteOption: VoteOption, pollShardOref: OutRef, stakingPositionOref: OutRef, sysParams: SystemParams, lucid: LucidEvolution, currentSlot: number, ): Promise<TxBuilder> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const ownAddr = await lucid.wallet().address(); const pollShardRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.pollShardValidatorRef, ), ]), (_) => new Error('Expected a single poll shard ref Script UTXO'), ); const stakingRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(sysParams.scriptReferences.stakingValidatorRef), ]), (_) => new Error('Expected a single staking ref Script UTXO'), ); const pollShardUtxo = matchSingle( await lucid.utxosByOutRef([pollShardOref]), (_) => new Error('Expected a single Poll shard UTXO'), ); const pollShardDatum = parsePollShardOrThrow( getInlineDatumOrThrow(pollShardUtxo), ); const stakingPosUtxo = matchSingle( await lucid.utxosByOutRef([stakingPositionOref]), (_) => new Error('Expected a single staking position UTXO'), ); const stakingPosDatum = parseStakingPositionOrThrow( getInlineDatumOrThrow(stakingPosUtxo), ); const indyStakedAmt = assetClassValueOf( stakingPosUtxo.assets, fromSystemParamsAsset(sysParams.govParams.indyAsset), ); const validityFrom = Number(currentTime) - ONE_SECOND; if ( stakingPosDatum.lockedAmount.find((x) => x[0] === pollShardDatum.pollId) !== undefined ) { throw new Error('Already voted for that proposal.'); } const [newLockedAmt, newVoteStatus] = voteHelper( stakingPosDatum.lockedAmount, pollShardDatum, voteOption, BigInt(validityFrom), indyStakedAmt, ); return lucid .newTx() .validFrom(validityFrom) .validTo(Number(pollShardDatum.votingEndTime) - ONE_SECOND) .readFrom([stakingRefScriptUtxo, pollShardRefScriptUtxo]) .collectFrom([stakingPosUtxo], serialiseStakingRedeemer('Lock')) .collectFrom( [pollShardUtxo], serialisePollShardRedeemer({ Vote: voteOption }), ) .pay.ToContract( pollShardUtxo.address, { kind: 'inline', value: serialisePollDatum({ PollShard: { ...pollShardDatum, status: newVoteStatus, }, }), }, pollShardUtxo.assets, ) .pay.ToContract( stakingPosUtxo.address, { kind: 'inline', value: serialiseStakingDatum({ ...stakingPosDatum, lockedAmount: newLockedAmt, }), }, stakingPosUtxo.assets, ) .addSigner(ownAddr); } export async function mergeShards( pollManagerOref: OutRef, shardsOutRefs: OutRef[], sysParams: SystemParams, lucid: LucidEvolution, currentSlot: number, ): Promise<TxBuilder> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const ownAddr = await lucid.wallet().address(); const pollShardRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.pollShardValidatorRef, ), ]), (_) => new Error('Expected a single poll shard ref Script UTXO'), ); const pollManagerRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.pollManagerValidatorRef, ), ]), (_) => new Error('Expected a single poll shard ref Script UTXO'), ); const pollAuthTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.pollManagerTokenRef, ), ]), (_) => new Error('Expected a single poll auth token policy ref Script UTXO'), ); const pollManagerUtxo = matchSingle( await lucid.utxosByOutRef([pollManagerOref]), (_) => new Error('Expected a single Poll manager UTXO'), ); const pollManagerDatum = parsePollManagerOrThrow( getInlineDatumOrThrow(pollManagerUtxo), ); const shardUtxos = await lucid.utxosByOutRef(shardsOutRefs); const aggregatedStatus: PollStatus = F.pipe( shardUtxos, A.map((utxo) => parsePollShardOrThrow(getInlineDatumOrThrow(utxo))), A.reduce<PollShardContent, PollStatus>( pollManagerDatum.status, (acc, shard) => ({ yesVotes: acc.yesVotes + shard.status.yesVotes, noVotes: acc.noVotes + shard.status.noVotes, }), ), ); const shardsAggregatedAda = A.reduce<UTxO, Assets>({}, (acc, utxo) => addAssets(acc, mkLovelacesOf(lovelacesAmt(utxo.assets))), )(shardUtxos); const pollNft = fromSystemParamsAsset(sysParams.govParams.pollToken); return lucid .newTx() .validFrom(Number(currentTime) - ONE_SECOND) .validTo( Number(currentTime) + Number(sysParams.pollManagerParams.pBiasTime) - ONE_SECOND, ) .readFrom([ pollShardRefScriptUtxo, pollManagerRefScriptUtxo, pollAuthTokenPolicyRefScriptUtxo, ]) .mintAssets(mkAssetsOf(pollNft, -BigInt(shardsOutRefs.length)), Data.void()) .collectFrom( [pollManagerUtxo], serialisePollManagerRedeemer({ MergeShardsManager: { currentTime: currentTime }, }), ) .collectFrom( shardUtxos, serialisePollShardRedeemer({ MergeShards: { currentTime: currentTime, pollManagerRef: { outputIndex: BigInt(pollManagerUtxo.outputIndex), txHash: fromHex(pollManagerUtxo.txHash), }, }, }), ) .pay.ToContract( pollManagerUtxo.address, { kind: 'inline', value: serialisePollDatum({ PollManager: { ...pollManagerDatum, talliedShardsCount: pollManagerDatum.talliedShardsCount + BigInt(shardsOutRefs.length), status: aggregatedStatus, }, }), }, addAssets(pollManagerUtxo.assets, shardsAggregatedAda), ) .addSigner(ownAddr); } export async function endProposal( pollManagerOref: OutRef, govOref: OutRef, sysParams: SystemParams, lucid: LucidEvolution, currentSlot: number, ): Promise<TxBuilder> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const ownAddr = await lucid.wallet().address(); const pollManagerRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.pollManagerValidatorRef, ), ]), (_) => new Error('Expected a single poll shard ref Script UTXO'), ); const govRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.governanceValidatorRef, ), ]), (_) => new Error('Expected a single Gov Ref Script UTXO'), ); const pollAuthTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.pollManagerTokenRef, ), ]), (_) => new Error('Expected a single poll auth token policy ref Script UTXO'), ); const upgradeTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.upgradeTokenRef, ), ]), (_) => new Error('Expected a single upgrade auth token policy ref Script UTXO'), ); const pollManagerUtxo = matchSingle( await lucid.utxosByOutRef([pollManagerOref]), (_) => new Error('Expected a single Poll manager UTXO'), ); const pollManager = parsePollManagerOrThrow( getInlineDatumOrThrow(pollManagerUtxo), ); const govUtxo = matchSingle( await lucid.utxosByOutRef([govOref]), (_) => new Error('Expected a single Gov UTXO'), ); const govDatum = parseGovDatumOrThrow(getInlineDatumOrThrow(govUtxo)); const pollNft = fromSystemParamsAsset(sysParams.govParams.pollToken); const indyAsset = fromSystemParamsAsset( sysParams.pollManagerParams.indyAsset, ); const proposalExpired = currentTime > pollManager.expirationTime; const proposalPassed = !proposalExpired && pollPassQuorum( pollManager.status, govDatum.protocolParams.electorate, pollManager.minimumQuorum, ); const upgradeTokenVal = mkAssetsOf( fromSystemParamsAsset(sysParams.govParams.upgradeToken), 1n, ); const tx = lucid .newTx() .validFrom(Number(currentTime) - ONE_SECOND) .validTo( Number(currentTime) + Number(sysParams.pollManagerParams.pBiasTime) - ONE_SECOND, ) .readFrom([ pollManagerRefScriptUtxo, govRefScriptUtxo, pollAuthTokenPolicyRefScriptUtxo, upgradeTokenPolicyRefScriptUtxo, ]) .collectFrom( [pollManagerUtxo], serialisePollManagerRedeemer({ EndPoll: { currentTime: currentTime } }), ) .collectFrom( [govUtxo], serialiseGovRedeemer({ WitnessEndPoll: { currentTime: currentTime } }), ) .mintAssets(mkAssetsOf(pollNft, -1n), Data.void()) .pay.ToContract( govUtxo.address, { kind: 'inline', value: serialiseGovDatum({ ...govDatum, activeProposals: govDatum.activeProposals - 1n, }), }, govUtxo.assets, ) .addSigner(ownAddr); if (proposalPassed) { tx.pay .ToContract( createScriptAddress(network, sysParams.validatorHashes.executeHash), { kind: 'inline', value: serialiseExecuteDatum({ id: pollManager.pollId, content: pollManager.content, passedTime: currentTime, votingEndTime: pollManager.votingEndTime, protocolVersion: pollManager.protocolVersion, treasuryWithdrawal: pollManager.treasuryWithdrawal, }), }, upgradeTokenVal, ) .mintAssets(upgradeTokenVal, Data.void()); } else { tx.pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.treasuryHash), { kind: 'inline', value: Data.void() }, mkAssetsOf( indyAsset, assetClassValueOf(pollManagerUtxo.assets, indyAsset), ), ); } return tx; } export async function executeProposal( executeOref: OutRef, govOref: OutRef, treasuryWithdrawalOref: OutRef | null, allIAssetOrefs: OutRef[] | null, /** * This for either Propose IAsset, Modify IAsset or Add Collateral asset proposals. */ iAssetOref: OutRef | null, /** * This is when adding new collateral asset. It should be all collateral assets of the given iAsset * where it's adding the new collateral asset. */ allCollateralAssetsOrefsOfIAsset: OutRef[] | null, collateralAssetOref: OutRef | null, stableswapPoolOrCdpCreatorOref: OutRef | null, sysParams: SystemParams, lucid: LucidEvolution, currentSlot: number, ): Promise<TxBuilder> { const network = lucid.config().network!; const currentTime = BigInt(slotToUnixTime(network, currentSlot)); const ownAddr = await lucid.wallet().address(); const govUtxo = matchSingle( await lucid.utxosByOutRef([govOref]), (_) => new Error('Expected a single gov UTXO'), ); const govDatum = parseGovDatumOrThrow(getInlineDatumOrThrow(govUtxo)); const govRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.governanceValidatorRef, ), ]), (_) => new Error('Expected a single Gov Ref Script UTXO'), ); const executeRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(sysParams.scriptReferences.executeValidatorRef), ]), (_) => new Error('Expected a single execute Ref Script UTXO'), ); const upgradeTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.upgradeTokenRef, ), ]), (_) => new Error('Expected a single upgrade auth token policy ref Script UTXO'), ); const executeUtxo = matchSingle( await lucid.utxosByOutRef([executeOref]), (_) => new Error('Expected a single execute UTXO'), ); const executeDatum = parseExecuteDatumOrThrow( getInlineDatumOrThrow(executeUtxo), ); const tx = lucid.newTx(); // Handle treasury withdrawal await pipe( O.fromNullable(executeDatum.treasuryWithdrawal), O.match( () => { if (treasuryWithdrawalOref) { throw new Error('Cannot provide withdrawal oref when no withdrawal.'); } return Promise.resolve(); }, async (withdrawal) => { if (!treasuryWithdrawalOref) { throw new Error('Have to provide withdrawal oref when withdrawal.'); } const treasuryRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.treasuryValidatorRef, ), ]), (_) => new Error('Expected a single Treasury Ref Script UTXO'), ); const treasuryWithdrawalUtxo = matchSingle( await lucid.utxosByOutRef([treasuryWithdrawalOref]), (_) => new Error('Expected a single withdrawal UTXO'), ); const withdrawalVal = createValueFromWithdrawal(withdrawal); const withdrawalChangeVal = addAssets( treasuryWithdrawalUtxo.assets, negateAssets(withdrawalVal), ); if (!isAssetsZero(withdrawalChangeVal)) { tx.pay.ToContract( createScriptAddress( network, sysParams.validatorHashes.treasuryHash, sysParams.treasuryParams.treasuryUtxosStakeCredential ? fromSysParamsCredential( sysParams.treasuryParams.treasuryUtxosStakeCredential, ) : undefined, ), { kind: 'inline', value: Data.void() }, withdrawalChangeVal, ); } tx.readFrom([treasuryRefScriptUtxo]) .collectFrom( [treasuryWithdrawalUtxo], serialiseTreasuryRedeemer('Withdraw'), ) .pay.ToAddressWithData( addressToBech32(withdrawal.destination, lucid.config().network!), { kind: 'inline', value: serialiseWithdrawalOutputDatum([ fromHex(fromText('IndigoTreasuryWithdrawal')), { txHash: fromHex(executeUtxo.txHash), outputIndex: BigInt(executeUtxo.outputIndex), }, ]), }, withdrawalVal, ); }, ), ); await match(executeDatum.content) .with({ ProposeIAsset: P.select() }, async (proposeContent) => { const iassetTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.iAssetAuthTokenRef, ), ]), (_) => new Error( 'Expected a single iasset auth token policy ref Script UTXO', ), ); const stabilityPoolTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies .stabilityPoolAuthTokenRef, ), ]), (_) => new Error('Expected a single SP auth token policy ref Script UTXO'), ); const iassetRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.iassetValidatorRef, ), ]), (_) => new Error('Expected a single IAsset Ref Script UTXO'), ); if (!allIAssetOrefs) { throw new Error('Have to provide all iasset orefs when propose asset.'); } const iassetToReference = await findRelativeIAssetForInsertion( proposeContent.asset, allIAssetOrefs, lucid, ); const { newIAsset, newReferencedIAsset } = iassetCreationDatumHelper( proposeContent, F.pipe( iassetToReference, O.map((i) => i.datum), ), ); const iassetAuthVal = mkAssetsOf( fromSystemParamsAsset(sysParams.executeParams.iAssetToken), 1n, ); const spAuthVal = mkAssetsOf( fromSystemParamsAsset(sysParams.executeParams.stabilityPoolToken), 1n, ); tx.readFrom([ govRefScriptUtxo, iassetRefScriptUtxo, iassetTokenPolicyRefScriptUtxo, stabilityPoolTokenPolicyRefScriptUtxo, ]) .mintAssets(spAuthVal, Data.void()) .mintAssets(iassetAuthVal, Data.void()) .collectFrom([govUtxo], serialiseGovRedeemer('UpgradeGov')) .pay.ToContract( govUtxo.address, { kind: 'inline', value: serialiseGovDatum({ ...govDatum, iassetsCount: govDatum.iassetsCount + 1n, }), }, govUtxo.assets, ) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.iassetHash), { kind: 'inline', value: serialiseIAssetDatum({ IAssetContent: newIAsset }), }, iassetAuthVal, ) .pay.ToContract( createScriptAddress( network, sysParams.validatorHashes.stabilityPoolHash, ), { kind: 'inline', value: serialiseStabilityPoolDatum( { StabilityPool: { iasset: proposeContent.asset, state: initSpState, assetStates: [], }, }, true, ), }, spAuthVal, ); F.pipe( iassetToReference, O.match( () => { // no action }, (i) => F.pipe( newReferencedIAsset, O.match( () => { throw new Error('Expected some referenced iasset.'); }, (newRefIAsset) => { tx.collectFrom([i.utxo], { kind: 'selected', makeRedeemer: (inputIndices) => serialiseIAssetRedeemer({ UpdateOrInsertAsset: { executeInputIdx: inputIndices[0], }, }), inputs: [executeUtxo], }).pay.ToContract( i.utxo.address, { kind: 'inline', value: serialiseIAssetDatum({ IAssetContent: newRefIAsset, }), }, i.utxo.assets, ); }, ), ), ), ); }) .with({ ModifyIAsset: P.select() }, async (modifyContent) => { const iassetRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.iassetValidatorRef, ), ]), (_) => new Error('Expected a single CDP Ref Script UTXO'), ); if (!iAssetOref) { throw new Error('Have to provide iasset oref when modify asset.'); } const iassetUtxo = matchSingle( await lucid.utxosByOutRef([iAssetOref]), (_) => new Error('Expected a single iasset UTXO'), ); const iassetDatum = parseIAssetDatumOrThrow( getInlineDatumOrThrow(iassetUtxo), ); tx.readFrom([iassetRefScriptUtxo]) .collectFrom([iassetUtxo], { kind: 'selected', makeRedeemer: (inputIndices) => serialiseIAssetRedeemer({ UpdateOrInsertAsset: { executeInputIdx: inputIndices[0], }, }), inputs: [executeUtxo], }) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.iassetHash), { kind: 'inline', value: serialiseIAssetDatum({ IAssetContent: { assetName: iassetDatum.assetName, collateralAssetsCount: iassetDatum.collateralAssetsCount, debtMintingFeeRatio: modifyContent.newDebtMintingFeeRatio, liquidationProcessingFeeRatio: modifyContent.newLiquidationProcessingFeeRatio, stabilityPoolWithdrawalFeeRatio: modifyContent.newStabilityPoolWithdrawalFeeRatio, redemptionReimbursementRatio: modifyContent.newRedemptionReimbursementRatio, redemptionProcessingFeeRatio: modifyContent.newRedemptionProcessingFeeRatio, firstIAsset: iassetDatum.firstIAsset, nextIAsset: iassetDatum.nextIAsset, }, }), }, iassetUtxo.assets, ) .readFrom([govUtxo]) .setMinFee(900_000n); }) .with({ AddCollateralAsset: P.select() }, async (addCollateralContent) => { const collateralAssetTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies .collateralAssetTokenRef, ), ]), (_) => new Error( 'Expected a single collateral asset auth token policy ref Script UTXO', ), ); const iassetRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.iassetValidatorRef, ), ]), (_) => new Error('Expected a single iasset Ref Script UTXO'), ); if (!iAssetOref) { throw new Error( 'Have to provide iasset oref when add collateral asset.', ); } const iassetUtxo = matchSingle( await lucid.utxosByOutRef([iAssetOref]), (_) => new Error('Expected a single iasset UTXO'), ); const iassetDatum = parseIAssetDatumOrThrow( getInlineDatumOrThrow(iassetUtxo), ); if (!allCollateralAssetsOrefsOfIAsset) { throw new Error( 'Have to provide all collateral asset orefs when propose asset.', ); } const collateralAssetToReference = await findRelativeCollateralAssetForInsertion( addCollateralContent.collateralAsset, allCollateralAssetsOrefsOfIAsset, lucid, ); const { newCollateralAsset, newReferencedCollateralAsset } = collateralAssetCreationDatumHelper( addCollateralContent, F.pipe( collateralAssetToReference, O.map((i) => i.datum), ), ); const collateralAssetAuthVal = mkAssetsOf( fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken), 1n, ); const firstCollateralAsset = await findFirstCollateralAsset( allCollateralAssetsOrefsOfIAsset, lucid, ); tx.readFrom([ iassetRefScriptUtxo, collateralAssetTokenPolicyRefScriptUtxo, ]); tx.readFrom([govUtxo]) .mintAssets(collateralAssetAuthVal, Data.void()) .collectFrom([iassetUtxo], { kind: 'selected', makeRedeemer: (inputIndices) => serialiseIAssetRedeemer({ UpdateOrInsertAsset: { executeInputIdx: inputIndices[0], }, }), inputs: [executeUtxo], }) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.iassetHash), { kind: 'inline', value: serialiseIAssetDatum({ IAssetContent: { ...iassetDatum, collateralAssetsCount: iassetDatum.collateralAssetsCount + 1n, }, }), }, iassetUtxo.assets, ) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.iassetHash), { kind: 'inline', value: serialiseIAssetDatum({ CollateralAssetContent: newCollateralAsset, }), }, collateralAssetAuthVal, ); F.pipe( collateralAssetToReference, O.match( () => { // no action }, (i) => { F.pipe( newReferencedCollateralAsset, O.match( () => { throw new Error('Expected some referenced collateral asset'); }, (newRefCollateralAsset) => { tx.collectFrom([i.utxo], { kind: 'selected', makeRedeemer: (inputIndices) => serialiseIAssetRedeemer({ UpdateOrInsertAsset: { executeInputIdx: inputIndices[0], }, }), inputs: [executeUtxo], }).pay.ToContract( createScriptAddress( network, sysParams.validatorHashes.iassetHash, ), { kind: 'inline', value: serialiseIAssetDatum({ CollateralAssetContent: newRefCollateralAsset, }), }, i.utxo.assets, ); }, ), ); }, ), ); // Provide proof of existence or non-existence of ADA collateral. if ( !isSameAssetClass(newCollateralAsset.collateralAsset, adaAssetClass) ) { F.pipe( newReferencedCollateralAsset, O.match( () => {}, (referencedAsset) => referencedAsset.firstCollateralAsset ? {} : F.pipe( firstCollateralAsset, O.match( () => {}, (firstAsset) => { tx.readFrom([firstAsset.utxo]); }, ), ), ), ); } }) .with( { UpdateCollateralAsset: P.select() }, async (updateCollateralContent) => { const iassetRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.iassetValidatorRef, ), ]), (_) => new Error('Expected a single iasset Ref Script UTXO'), ); if (!collateralAssetOref) { throw new Error( 'Have to provide collateral asset oref when add collateral asset.', ); } const collateralAssetUtxo = matchSingle( await lucid.utxosByOutRef([collateralAssetOref]), (_) => new Error('Expected a single collateral asset UTXO'), ); const collateralAssetDatum = parseCollateralAssetDatumOrThrow( getInlineDatumOrThrow(collateralAssetUtxo), ); if ( !isSameAssetClass( collateralAssetDatum.collateralAsset, updateCollateralContent.collateralAsset, ) || toHex(collateralAssetDatum.iasset) !== toHex(updateCollateralContent.correspondingIAsset) ) { throw new Error('Wrong collateral asset.'); } tx.readFrom([iassetRefScriptUtxo]) .collectFrom([collateralAssetUtxo], { kind: 'selected', makeRedeemer: (inputIndices) => serialiseIAssetRedeemer({ UpdateOrInsertAsset: { executeInputIdx: inputIndices[0], }, }), inputs: [executeUtxo], }) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.iassetHash), { kind: 'inline', value: serialiseIAssetDatum({ CollateralAssetContent: { ...collateralAssetDatum, priceInfo: updateCollateralContent.newAssetPriceInfo, interestOracleNft: updateCollateralContent.newInterestOracleNft, redemptionRatio: updateCollateralContent.newRedemptionRatio, maintenanceRatio: updateCollateralContent.newMaintenanceRatio, liquidationRatio: updateCollateralContent.newLiquidationRatio, minCollateralAmt: updateCollateralContent.newMinCollateralAmt, }, }), }, collateralAssetUtxo.assets, ) .readFrom([govUtxo]) .setMinFee(900_000n); }, ) .with({ TextProposal: P.any }, () => { tx.readFrom([govUtxo]); }) .with({ ProposeStableswapPool: P.select() }, async (proposeContent) => { const cdpCreatorRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.cdpCreatorValidatorRef, ), ]), (_) => new Error('Expected a single CDP Creator Ref Script UTXO'), ); const cdpTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies.cdpAuthTokenRef, ), ]), (_) => new Error('Expected a single CDP auth token policy ref Script UTXO'), ); if (!iAssetOref) { throw new Error( 'Have to provide iasset oref when proposing stableswap pool.', ); } const iassetUtxo = matchSingle( await lucid.utxosByOutRef([iAssetOref]), (_) => new Error('Expected a single iasset UTXO'), ); const cdpAuthVal = mkAssetsOf( fromSystemParamsAsset(sysParams.executeParams.cdpToken), 1n, ); if (!stableswapPoolOrCdpCreatorOref) { throw new Error( 'Have to provide cdp creator oref when propose stableswap pool.', ); } const cdpCreatorUtxo = matchSingle( await lucid.utxosByOutRef([stableswapPoolOrCdpCreatorOref]), (_) => new Error('Expected a single iasset UTXO'), ); tx.readFrom([ cdpCreatorRefScriptUtxo, cdpTokenPolicyRefScriptUtxo, govUtxo, iassetUtxo, ]) .mintAssets(cdpAuthVal, Data.void()) .collectFrom( [cdpCreatorUtxo], serialiseCDPCreatorRedeemer('CreateStableswapPool'), ) .pay.ToContract( createScriptAddress( network, sysParams.validatorHashes.cdpCreatorHash, ), { kind: 'inline', value: Data.void() }, cdpCreatorUtxo.assets, ) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.cdpHash), { kind: 'inline', value: serialiseStableswapPoolDatum({ iasset: proposeContent.iasset, collateralAsset: proposeContent.collateralAsset, collateralToIassetRatio: proposeContent.collateralToIassetRatio, mintingFeeRatio: proposeContent.mintingFeeRatio, redemptionFeeRatio: proposeContent.redemptionFeeRatio, mintingEnabled: true, redemptionEnabled: true, feeManager: proposeContent.feeManager, minMintOrderAmount: proposeContent.minMintingAmount, minRedemptionOrderAmount: proposeContent.minRedemptionAmount, stableswapValHash: proposeContent.stableswapValHash, }), }, cdpAuthVal, ); }) .with({ ModifyStableswapPool: P.select() }, async (modifyContent) => { const cdpRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef), ]), (_) => new Error('Expected a single CDP Ref Script UTXO'), ); if (!stableswapPoolOrCdpCreatorOref) { throw new Error( 'Have to provide stableswap pool oref when propose stableswap pool.', ); } const stableswapPoolUtxo = matchSingle( await lucid.utxosByOutRef([stableswapPoolOrCdpCreatorOref]), (_) => new Error('Expected a single stableswap pool UTXO'), ); tx.readFrom([govRefScriptUtxo, cdpRefScriptUtxo, govUtxo]) .collectFrom([stableswapPoolUtxo], { kind: 'selected', makeRedeemer: (inputIndices) => serialiseCdpRedeemer({ Stableswap: { forwardingInputIndex: inputIndices[0], }, }), inputs: [executeUtxo], }) .pay.ToContract( createScriptAddress(network, sysParams.validatorHashes.cdpHash), { kind: 'inline', value: serialiseStableswapPoolDatum({ iasset: modifyContent.iasset, collateralAsset: modifyContent.collateralAsset, collateralToIassetRatio: modifyContent.newCollateralToIassetRatio, mintingFeeRatio: modifyContent.newMintingFeeRatio, redemptionFeeRatio: modifyContent.newRedemptionFeeRatio, mintingEnabled: modifyContent.newMintingEnabled, redemptionEnabled: modifyContent.newRedemptionEnabled, feeManager: modifyContent.newFeeManager, minMintOrderAmount: modifyContent.newMinMintingAmount, minRedemptionOrderAmount: modifyContent.newMinRedemptionAmount, stableswapValHash: modifyContent.newStableswapValHash, }), }, stableswapPoolUtxo.assets, ) // This has to be added as otherwise there is the following error: // TxBuilderError: { Complete: RedeemerBuilder: Coin selection had to be updated // after building redeemers, possibly leading to incorrect indices. Try setting // a minimum fee of 1416197 lovelaces. } .setMinFee(1416197n); }) .with( { ModifyProtocolParams: { newParams: P.select() } }, (newProtocolParams) => { tx.readFrom([govRefScriptUtxo]) .collectFrom([govUtxo], serialiseGovRedeemer('UpgradeGov')) .pay.ToContract( govUtxo.address, { kind: 'inline', value: serialiseGovDatum({ ...govDatum, protocolParams: newProtocolParams, }), }, govUtxo.assets, ); }, ) .with({ UpgradeProtocol: P.select() }, async (d) => { const versionRecordTokenPolicyRefScriptUtxo = matchSingle( await lucid.utxosByOutRef([ fromSystemParamsScriptRef( sysParams.scriptReferences.authTokenPolicies .versionRecordTokenPolicyRef, ), ]), (_) => new Error( 'Expected a single version record token policy ref Script UTXO', ), ); const versionRecordNftVal = mkAssetsOf( fromSystemParamsAsset(sysParams.executeParams.versionRecordToken), 1n, ); tx.readFrom([govRefScriptUtxo, versionRecordTokenPolicyRefScriptUtxo]) .mintAssets(versionRecordNftVal, Data.void()) .pay.ToContract( createScriptAddress( network, sysParams.validatorHashes.versionRegistryHash, ), { kind: 'inline', value: serialiseVersionRecordDatum({ upgradeId: d.content.upgradeId, upgradePaths: d.content.upgradePaths.map(([h1, h2]) => [ h1, h2.upgradeSymbol, ]), }), }, versionRecordNftVal, ) .collectFrom([govUtxo], serialiseGovRedeemer('UpgradeGov')) .pay.ToContract( govUtxo.address, { kind: 'inline', value: serialiseGovDatum({ ...govDatum, currentVersion: govDatum.currentVersion + 1n, }), }, govUtxo.assets, ); }) .otherwise(() => {}); tx.readFrom([upgradeTokenPolicyRefScriptUtxo, executeRefScriptUtxo]) .validFrom(Number(currentTime) - ONE_SECOND) .validTo( Number(currentTime) + Number(sysParams.govParams.gBiasTime) - ONE_SECOND, ) .collectFrom([executeUtxo], Data.void()) .mintAssets( mkAssetsOf(fromSystemParamsAsset(sysParams.govParams.upgradeToken), -1n), Data.void(), ) .addSigner(ownAddr) .setMinFee(922932n); return tx; }