UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

621 lines (569 loc) 18.3 kB
import { addAssets, credentialToAddress, Data, fromHex, fromText, LucidEvolution, mintingPolicyToId, PolicyId, SpendingValidator, validatorToScriptHash, } from '@lucid-evolution/lucid'; import { AssetClass, assetClassToUnit, assetClassValueOf, lovelacesAmt, mkAssetsOf, mkLovelacesOf, } from '@3rd-eye-labs/cardano-offchain-common'; import { addrDetails, createScriptAddress, submitTx, } from '../../utils/lucid-utils'; import { runOneShotMintTx } from '../one-shot/transactions'; import { fromSysParamsCredential, getPythFeedConfig, Input, } from '../../types/system-params'; import type { AssetInfo, CollateralAssetInfo, InitialAssetParam, InitialCollateralAssetParam, InitializeOptions, } from './types'; import type { CdpParamsSP, CollectorParamsSP, PythConfig, } from '../../types/system-params'; import type { CDPCreatorParamsSP, TreasuryParamsSP, } from '../../types/system-params'; import type { IAssetParamsSP, StabilityPoolParamsSP, StakingParamsSP, } from '../../types/system-params'; import type { GovParamsSP } from '../../types/system-params'; import type { InterestCollectionDatum } from '../interest-collection/types-new'; import type { PriceOracleParams } from '../price-oracle/types'; import type { InterestOracleParams } from '../interest-oracle/types'; import { mkAuthTokenPolicy } from '../../scripts/auth-token-policy'; import { mkIAssetValidatorFromSP } from '../iasset/scripts'; import { mkCollectorValidatorFromSP } from '../collector/scripts'; import { mkCDPCreatorValidatorFromSP } from '../cdp-creator/scripts'; import { mkTreasuryValidatorFromSP } from '../treasury/scripts'; import { mkStakingValidatorFromSP } from '../staking/scripts'; import { mkGovValidatorFromSP } from '../gov/scripts'; import { mkStabilityPoolValidatorFromSP } from '../stability-pool/scripts'; import { serialiseInterestCollectionDatum } from '../interest-collection/types-new'; import { startPriceOracleTx } from '../price-oracle/transactions'; import { startInterestOracle } from '../interest-oracle/transactions'; import { serialiseStakingDatum } from '../staking/types-new'; import { initSpState } from '../stability-pool/helpers'; import { serialiseStabilityPoolDatum, StabilityPoolContent, } from '../stability-pool/types-new'; import { CollateralAssetContent, IAssetContent, IAssetPriceInfo, serialiseIAssetDatum, } from '../iasset/types'; import { GovDatum, ProtocolParams, serialiseGovDatum } from '../gov/types-new'; import { match } from 'ts-pattern'; import { runAndAwaitTxBuilder } from '../../../tests/test-helpers'; import { serialiseStableswapPoolDatum, StableswapPoolContent, } from '../cdp/types-new'; import { mkCdpValidatorFromSP } from '../cdp/scripts'; const alwaysFailValidatorHash = 'ea84d625650d066e1645e3e81d9c70a73f9ed837bd96dc49850ae744'; /** Default init options used by tests; users must pass explicit options to init(). */ export const DEFAULT_INIT_OPTIONS: InitializeOptions = { numCdpCreators: 2n, numCollectors: 2n, numInterestCollectors: 2n, numTreasuryUtxos: 5n, treasuryIndyAmount: 1_000_000n, totalIndySupply: 35000000000000n, accountCreateFeeLovelaces: 5_000_000n, accountProcessingCooldownMs: 300_000n, accountProcessingBiasTime: 120_000n, interestSettlementCooldown: 432_000_000n, partialRedemptionExtraFeeLovelace: 10_000_000, biasTime: 1200_000n, gBiasTime: 120_000n, interestCollectorUtxoLovelaces: 5_000_000n, }; export const INIT_TOKEN_NAMES = { indy: 'INDY', dao: 'DAO', multisigUtxo: 'INTEREST_COLLECTION_ADMIN', govNft: 'GOV_NFT', pollManager: 'POLL_MANAGER', upgrade: 'UPGRADE', iasset: 'IASSET', collateralAsset: 'COLLATERAL_ASSET', stabilityPool: 'STABILITY_POOL', versionRecord: 'VERSION_RECORD', cdpCreator: 'CDP_CREATOR', cdp: 'CDP', stakingManager: 'STAKING_MANAGER', staking: 'STAKING_POSITION', snapshotEpochToScaleToSum: 'SNAPSHOT_EPOCH_TO_SCALE_TO_SUM', account: 'SP_ACCOUNT', } as const; export async function mintOneTimeToken( lucid: LucidEvolution, tokenName: string, amount: bigint, ): Promise<PolicyId> { const utxos = await lucid.wallet().getUtxos(); return await runOneShotMintTx(lucid, { referenceOutRef: { txHash: utxos[0].txHash, outputIdx: BigInt(utxos[0].outputIndex), }, mintAmounts: [{ tokenName: tokenName, amount: amount }], }); } /** Mint a one-shot token and return its AssetClass. */ export async function mintOneTimeAsset( lucid: LucidEvolution, tokenName: string, amount: bigint, ): Promise<AssetClass> { const policyId = await mintOneTimeToken(lucid, fromText(tokenName), amount); return { currencySymbol: fromHex(policyId), tokenName: fromHex(fromText(tokenName)), }; } /** Build an auth-token AssetClass derived from a parent token. */ export function deriveAuthToken( parent: AssetClass, tokenName: string, ): AssetClass { const policy = mkAuthTokenPolicy(parent, fromText(tokenName)); return { currencySymbol: fromHex(mintingPolicyToId(policy)), tokenName: fromHex(fromText(tokenName)), }; } export async function initScriptRef( lucid: LucidEvolution, validator: SpendingValidator, ): Promise<Input> { const tx = lucid.newTx().pay.ToContract( credentialToAddress(lucid.config().network!, { hash: alwaysFailValidatorHash, type: 'Script', }), undefined, undefined, validator, ); const txHash = await tx .complete() .then((t) => t.sign.withWallet().complete()) .then((t) => t.submit()); await lucid.awaitTx(txHash); return { transactionId: txHash, index: 0 }; } export async function initCollector( lucid: LucidEvolution, collectorParams: CollectorParamsSP, numCollectors: bigint, ): Promise<void> { const tx = lucid.newTx(); for (let i = 0; i < Number(numCollectors); i++) { tx.pay.ToContract( createScriptAddress( lucid.config().network!, validatorToScriptHash(mkCollectorValidatorFromSP(collectorParams)), ), { kind: 'inline', value: Data.void(), }, ); } await submitTx(lucid, tx); } export async function initInterestCollector( lucid: LucidEvolution, interestCollectionValHash: string, multisigUtxoNft: AssetClass, numInterestCollectors: bigint, interestCollectorUtxoLovelaces: bigint, ): Promise<void> { const [pkh, _] = await addrDetails(lucid); const interestCollectionAdminDatum: InterestCollectionDatum = { admin_permissions: { Signature: { keyHash: fromHex(pkh.hash), }, }, }; await submitTx( lucid, lucid.newTx().pay.ToContract( createScriptAddress(lucid.config().network!, interestCollectionValHash), { kind: 'inline', value: serialiseInterestCollectionDatum(interestCollectionAdminDatum), }, mkAssetsOf(multisigUtxoNft, 1n), ), ); const interestCollectionUtxoDeploymentTx = lucid.newTx(); for (let i = 0; i < Number(numInterestCollectors); i++) { interestCollectionUtxoDeploymentTx.pay.ToContract( createScriptAddress(lucid.config().network!, interestCollectionValHash), undefined, mkLovelacesOf(interestCollectorUtxoLovelaces), ); } await submitTx(lucid, interestCollectionUtxoDeploymentTx); } export async function initCDPCreator( lucid: LucidEvolution, cdpCreatorParams: CDPCreatorParamsSP, numCdpCreators: bigint, ): Promise<void> { const tx = lucid.newTx(); for (let i = 0; i < Number(numCdpCreators); i++) { tx.pay.ToContract( credentialToAddress(lucid.config().network!, { hash: validatorToScriptHash( mkCDPCreatorValidatorFromSP(cdpCreatorParams), ), type: 'Script', }), { kind: 'inline', value: Data.void() }, { [cdpCreatorParams.cdpCreatorNft[0].unCurrencySymbol + fromText(cdpCreatorParams.cdpCreatorNft[1].unTokenName)]: 1n, }, ); } await submitTx(lucid, tx); } export async function initTreasury( lucid: LucidEvolution, treasuryParams: TreasuryParamsSP, daoAsset: AssetClass, indyAsset: AssetClass, treasuryIndyAmount: bigint, numTreasuryCollectors: bigint, ): Promise<void> { const treasuryAddr = createScriptAddress( lucid.config().network!, validatorToScriptHash(mkTreasuryValidatorFromSP(treasuryParams)), treasuryParams.treasuryUtxosStakeCredential != null ? fromSysParamsCredential(treasuryParams.treasuryUtxosStakeCredential) : undefined, ); const tx = lucid .newTx() .pay.ToContract( treasuryAddr, { kind: 'inline', value: Data.void() }, addAssets( mkAssetsOf(daoAsset, 1n), mkAssetsOf(indyAsset, treasuryIndyAmount), ), ); for (let i = 0; i < Number(numTreasuryCollectors); i++) { tx.pay.ToContract(treasuryAddr, { kind: 'inline', value: Data.void(), }); } await submitTx(lucid, tx); } export async function initStakingManager( lucid: LucidEvolution, stakingParams: StakingParamsSP, ): Promise<void> { const tx = lucid.newTx().pay.ToContract( createScriptAddress( lucid.config().network!, validatorToScriptHash(mkStakingValidatorFromSP(stakingParams)), ), { kind: 'inline', value: serialiseStakingDatum({ totalStake: 0n, managerSnapshot: { snapshotAda: 0n }, }), }, { [stakingParams.stakingManagerNFT[0].unCurrencySymbol + fromText(stakingParams.stakingManagerNFT[1].unTokenName)]: 1n, }, ); await submitTx(lucid, tx); } export async function handleOracleForCollateralAsset( lucid: LucidEvolution, iasset: InitialAssetParam, collateralAsset: InitialCollateralAssetParam, pythConfig: PythConfig, currentSlot: number, ): Promise<{ info: IAssetPriceInfo; params: PriceOracleParams | undefined }> { return match(collateralAsset.priceOracle) .returnType< Promise<{ info: IAssetPriceInfo; params: PriceOracleParams | undefined }> >() .with({ tag: '_indigo_oracle_nft' }, async (val) => { const [pkh, _] = await addrDetails(lucid); const priceOracleParams: PriceOracleParams = { owner: pkh.hash, biasTime: val.params.biasTime, expirationPeriod: val.params.expirationPeriod, }; const [priceOracleStartTx, priceOracleNft] = await startPriceOracleTx( lucid, val.tokenName, val.startPrice, priceOracleParams, currentSlot, ); await runAndAwaitTxBuilder(lucid, priceOracleStartTx); return { info: { OracleNft: priceOracleNft }, params: priceOracleParams }; }) .with({ tag: '_pyth_oracle_nft' }, () => { return Promise.resolve({ info: { DeferredValidation: { feedValHash: fromHex( getPythFeedConfig( pythConfig, fromHex(fromText(iasset.name)), collateralAsset.collateralAsset, ).pythFeedValHash, ), }, } satisfies IAssetPriceInfo, params: undefined, }); }) .exhaustive(); } export async function initializeAsset( lucid: LucidEvolution, iassetParams: IAssetParamsSP, iassetToken: AssetClass, collateralAssetAuthToken: AssetClass, cdpAuthToken: AssetClass, cdpParams: CdpParamsSP, stableswapValidatorHash: string, stabilityPoolParams: StabilityPoolParamsSP, stabilityPoolToken: AssetClass, asset: InitialAssetParam, pythConfig: PythConfig, currentSlot: number, ): Promise<AssetInfo> { const iassetName = fromHex(fromText(asset.name)); const collateralAssetInfos: CollateralAssetInfo[] = []; for (const collateralAsset of asset.collateralAssets) { const [pkh, _] = await addrDetails(lucid); const interestOracleParams: InterestOracleParams = { owner: pkh.hash, biasTime: collateralAsset.interestOracle.params.biasTime, }; const [startInterestOracleTx, interestOracleNft] = await startInterestOracle( 0n, collateralAsset.interestOracle.initialInterestRate, 0n, interestOracleParams, lucid, collateralAsset.interestOracle.tokenName, ); await submitTx(lucid, startInterestOracleTx); const priceInfo = await handleOracleForCollateralAsset( lucid, asset, collateralAsset, pythConfig, currentSlot, ); const collateralAssetContent: CollateralAssetContent = { iasset: iassetName, collateralAsset: collateralAsset.collateralAsset, extraDecimals: collateralAsset.extraDecimals, priceInfo: priceInfo.info, interestOracleNft: interestOracleNft, redemptionRatio: collateralAsset.redemptionRatio, maintenanceRatio: collateralAsset.maintenanceRatio, liquidationRatio: collateralAsset.liquidationRatio, minCollateralAmt: collateralAsset.minCollateralAmt, firstCollateralAsset: collateralAsset.firstCollateralAsset, nextCollateralAsset: collateralAsset.nextCollateralAsset != null ? collateralAsset.nextCollateralAsset : null, }; const collateralAssetTx = lucid.newTx().pay.ToContract( createScriptAddress( lucid.config().network!, validatorToScriptHash(mkIAssetValidatorFromSP(iassetParams)), ), { kind: 'inline', value: serialiseIAssetDatum({ CollateralAssetContent: collateralAssetContent, }), }, mkAssetsOf(collateralAssetAuthToken, 1n), ); await submitTx(lucid, collateralAssetTx); collateralAssetInfos.push({ collateralAsset: collateralAsset.collateralAsset, interestOracleNft: interestOracleNft, interestOracleParams: interestOracleParams, oracleParams: priceInfo.params, }); } for (const stableswapPool of asset.stablepools) { const stableswapPoolDatum: StableswapPoolContent = { collateralAsset: stableswapPool.collateralAsset, collateralToIassetRatio: stableswapPool.collateralToIassetRatio, mintingFeeRatio: stableswapPool.mintingFeeRatio, redemptionFeeRatio: stableswapPool.redemptionFeeRatio, mintingEnabled: stableswapPool.mintingEnabled, redemptionEnabled: stableswapPool.redemptionEnabled, feeManager: stableswapPool.feeManager ? fromHex(stableswapPool.feeManager) : null, minMintOrderAmount: stableswapPool.minMintOrderAmount, minRedemptionOrderAmount: stableswapPool.minRedemptionOrderAmount, iasset: iassetName, stableswapValHash: fromHex(stableswapValidatorHash), }; await submitTx( lucid, lucid.newTx().pay.ToContract( createScriptAddress( lucid.config().network!, validatorToScriptHash(mkCdpValidatorFromSP(cdpParams)), ), { kind: 'inline', value: serialiseStableswapPoolDatum(stableswapPoolDatum), }, mkAssetsOf(cdpAuthToken, 1n), ), ); } const iassetDatum: IAssetContent = { assetName: iassetName, collateralAssetsCount: BigInt(asset.collateralAssets.length), debtMintingFeeRatio: asset.debtMintingFeeRatio, liquidationProcessingFeeRatio: asset.liquidationProcessingFeeRatio, stabilityPoolWithdrawalFeeRatio: asset.stabilityPoolWithdrawalFeeRatio, redemptionReimbursementRatio: asset.redemptionReimbursementRatio, redemptionProcessingFeeRatio: asset.redemptionProcessingFeeRatio, firstIAsset: asset.firstAsset, nextIAsset: asset.nextAsset ? fromHex(fromText(asset.nextAsset)) : null, }; const assetTx = lucid.newTx().pay.ToContract( createScriptAddress( lucid.config().network!, validatorToScriptHash(mkIAssetValidatorFromSP(iassetParams)), ), { kind: 'inline', value: serialiseIAssetDatum({ IAssetContent: iassetDatum }), }, mkAssetsOf(iassetToken, 1n), ); await submitTx(lucid, assetTx); const stabilityPoolDatum: StabilityPoolContent = { iasset: fromHex(fromText(asset.name)), state: initSpState, assetStates: [], }; const spTx = lucid.newTx().pay.ToContract( credentialToAddress(lucid.config().network!, { hash: validatorToScriptHash( mkStabilityPoolValidatorFromSP(stabilityPoolParams), ), type: 'Script', }), { kind: 'inline', value: serialiseStabilityPoolDatum({ StabilityPool: stabilityPoolDatum }), }, mkAssetsOf(stabilityPoolToken, 1n), ); await submitTx(lucid, spTx); return { iassetTokenNameAscii: asset.name, collateralAssets: collateralAssetInfos, }; } export async function initGovernance( lucid: LucidEvolution, governanceParams: GovParamsSP, govToken: AssetClass, initialAssets: InitialAssetParam[], protocolParams: ProtocolParams, ): Promise<void> { const datum: GovDatum = { currentProposal: 0n, currentVersion: 0n, protocolParams: protocolParams, activeProposals: 0n, iassetsCount: BigInt(initialAssets.length), }; const tx = lucid.newTx().pay.ToContract( credentialToAddress(lucid.config().network!, { hash: validatorToScriptHash(mkGovValidatorFromSP(governanceParams)), type: 'Script', }), { kind: 'inline', value: serialiseGovDatum(datum) }, mkAssetsOf(govToken, 1n), ); await submitTx(lucid, tx); } export async function mintAuthTokenDirect( lucid: LucidEvolution, asset: AssetClass, tokenName: string, amount: bigint, ): Promise<void> { const script = mkAuthTokenPolicy(asset, fromText(tokenName)); const policyId = mintingPolicyToId(script); const address = await lucid.wallet().address(); // Only select utxos that contain exactly 1 asset of the given asset class and sort by lovelaces amount in descending order const utxos = (await lucid.utxosAtWithUnit(address, assetClassToUnit(asset))) .filter((utxo) => assetClassValueOf(utxo.assets, asset) === 1n) .sort((a, b) => Number(lovelacesAmt(b.assets) - lovelacesAmt(a.assets))); if (utxos.length === 0) { throw new Error('No utxos found'); } const utxo = utxos[0]; const tx = lucid .newTx() .attach.MintingPolicy(script) .collectFrom([utxo]) .mintAssets( { [policyId + fromText(tokenName)]: amount, }, Data.void(), ); await submitTx(lucid, tx); }