@indigo-labs/indigo-sdk
Version:
Indigo SDK for interacting with Indigo endpoints via lucid-evolution
1,610 lines (1,493 loc) • 49 kB
text/typescript
// 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;
}