Skip to content

Commit

Permalink
build stableswap order txs
Browse files Browse the repository at this point in the history
  • Loading branch information
m1n999999 committed Sep 16, 2024
1 parent 7484760 commit f5a4e66
Showing 1 changed file with 357 additions and 7 deletions.
364 changes: 357 additions & 7 deletions src/stableswap.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,384 @@
import { Address, Lucid, Network, Tx, TxComplete, UTxO } from "lucid-cardano";
import {

Check failure on line 1 in src/stableswap.ts

View workflow job for this annotation

GitHub Actions / build-and-test

Run autofix to sort these imports!
Address,
Assets,
Lucid,
TxComplete,
UTxO,
Credential,

Check failure on line 7 in src/stableswap.ts

View workflow job for this annotation

GitHub Actions / build-and-test

'Credential' is defined but never used
Data,
Constr,
} from "lucid-cardano";
import { NetworkEnvironment, NetworkId } from "./types/network";

import { Asset } from "./types/asset";
import { NetworkId } from "./types/network";
import {
BlockfrostAdapter,
FIXED_DEPOSIT_ADA,
MetadataMessage,
StableOrder,
StableswapConstant,
} from ".";
import { DexVersion } from "./batcher-fee-reduction/types.internal";
import { lucidToNetworkEnv } from "./utils/network.internal";
import invariant from "@minswap/tiny-invariant";
import { calculateBatcherFee } from "./batcher-fee-reduction/calculate";

export type CommonOrderOptions = {
sender: Address;
availableUtxos: UTxO[];
lpAsset: Asset;
};

export type ExchangeOptions = CommonOrderOptions & {
export type SwapOptions = CommonOrderOptions & {
type: StableOrder.StepType.SWAP;
assetIn: Asset;
assetInAmount: bigint;
assetInIndex: bigint;
assetOutIndex: bigint;
minimumAssetOut: bigint;
};

export type DepositOptions = CommonOrderOptions & {
type: StableOrder.StepType.DEPOSIT;
assetsAmount: [Asset, bigint][];
minimumLPReceived: bigint;
totalLiquidity: bigint;
};

export type WithdrawOptions = CommonOrderOptions & {
type: StableOrder.StepType.WITHDRAW;
lpAmount: bigint;
minimumAmounts: bigint[];
};

export type WithdrawImbalanceOptions = CommonOrderOptions & {
type: StableOrder.StepType.WITHDRAW_IMBALANCE;
lpAmount: bigint;
withdrawAmounts: bigint[];
};

export type WithdrawOneCoinOptions = CommonOrderOptions & {
type: StableOrder.StepType.ZAP_OUT;
lpAmount: bigint;
assetOutIndex: bigint;
minimumAssetOut: bigint;
};

export type OrderOptions =
| DepositOptions
| WithdrawOptions
| SwapOptions
| WithdrawImbalanceOptions
| WithdrawOneCoinOptions;

export type BuildCancelOrderOptions = {
orderUtxos: UTxO[];
};

export class Stableswap {
private readonly lucid: Lucid;
private readonly network: Network;
private readonly networkId: NetworkId;
private readonly adapter: BlockfrostAdapter;
private readonly networkEnv: NetworkEnvironment;
private readonly dexVersion = DexVersion.STABLESWAP;

constructor(lucid: Lucid) {
constructor(lucid: Lucid, adapter: BlockfrostAdapter) {
this.lucid = lucid;
this.network = lucid.network;
this.networkId =
lucid.network === "Mainnet" ? NetworkId.MAINNET : NetworkId.TESTNET;
this.adapter = adapter;
this.networkEnv = lucidToNetworkEnv(lucid.network);
}

getConfigByLpAsset(lpAsset: Asset): StableswapConstant.Config {
const config = StableswapConstant.CONFIG[this.networkId].find(
(config) => config.lpAsset === Asset.toString(lpAsset)
);
invariant(config, `Invalid Stableswap LP Asset ${Asset.toString(lpAsset)}`);
return config;
}

async buildExchangeOrderTx(options: ExchangeOptions): Promise<TxComplete>{
buildOrderValue(option: OrderOptions): Assets {
const orderAssets: Assets = {};

switch (option.type) {
case StableOrder.StepType.DEPOSIT: {
const { minimumLPReceived, assetsAmount, totalLiquidity } = option;
invariant(
minimumLPReceived > 0n,
"minimum LP received must be non-negative"
);
let sumAmount = 0n;
for (const [asset, amount] of assetsAmount) {
if (totalLiquidity === 0n) {
invariant(
amount > 0n,
"amount must be positive when total liquidity = 0"
);
} else {
invariant(amount >= 0n, "amount must be non-negative");
}
if (amount > 0n) {
orderAssets[Asset.toString(asset)] = amount;
}
sumAmount += amount;
}
invariant(sumAmount > 0n, "sum of amount must be positive");
break;
}
case StableOrder.StepType.SWAP: {
const { assetInAmount, assetIn } = option;
invariant(assetInAmount > 0n, "asset in amount must be positive");
orderAssets[Asset.toString(assetIn)] = assetInAmount;
break;
}
case StableOrder.StepType.WITHDRAW:
case StableOrder.StepType.WITHDRAW_IMBALANCE:
case StableOrder.StepType.ZAP_OUT: {
const { lpAmount, lpAsset } = option;
invariant(lpAmount > 0n, "Lp amount must be positive number");
orderAssets[Asset.toString(lpAsset)] = lpAmount;
break;
}
}

if ("lovelace" in orderAssets) {
orderAssets["lovelace"] += FIXED_DEPOSIT_ADA;
} else {
orderAssets["lovelace"] = FIXED_DEPOSIT_ADA;
}
return orderAssets;
}

buildOrderStep(option: OrderOptions): StableOrder.Step {
switch (option.type) {
case StableOrder.StepType.DEPOSIT: {
const { minimumLPReceived } = option;
invariant(
minimumLPReceived > 0n,
"minimum LP received must be non-negative"
);
return {
type: StableOrder.StepType.DEPOSIT,
minimumLP: minimumLPReceived,
};
}
case StableOrder.StepType.WITHDRAW: {
const { minimumAmounts } = option;
let sumAmount = 0n;
for (const amount of minimumAmounts) {
invariant(amount >= 0n, "minimum amount must be non-negative");
sumAmount += amount;
}
invariant(sumAmount > 0n, "sum of withdaw amount must be positive");
return {
type: StableOrder.StepType.WITHDRAW,
minimumAmounts: minimumAmounts,
};
}
case StableOrder.StepType.SWAP: {
const { lpAsset, assetInIndex, assetOutIndex, minimumAssetOut } =
option;
const poolConfig = this.getConfigByLpAsset(lpAsset);
invariant(
poolConfig,
`Not found Stableswap config matching with LP Asset ${lpAsset.toString()}`
);
const assetLength = BigInt(poolConfig.assets.length);
invariant(
assetInIndex >= 0n && assetInIndex < assetLength,
`Invalid amountInIndex, must be between 0-${assetLength - 1n}`
);
invariant(
assetOutIndex >= 0n && assetOutIndex < assetLength,
`Invalid assetOutIndex, must be between 0-${assetLength - 1n}`
);
invariant(
minimumAssetOut > 0n,
"minimum asset out amount must be positive"
);
return {
type: StableOrder.StepType.SWAP,
assetInIndex: assetInIndex,
assetOutIndex: assetOutIndex,
minimumAssetOut: minimumAssetOut,
};
}
case StableOrder.StepType.WITHDRAW_IMBALANCE: {
const { withdrawAmounts } = option;
let sum = 0n;
for (const amount of withdrawAmounts) {
invariant(amount >= 0n, "withdraw amount must be unsigned number");
sum += amount;
}
invariant(sum > 0n, "sum of withdraw amount must be positive");
return {
type: StableOrder.StepType.WITHDRAW_IMBALANCE,
withdrawAmounts: withdrawAmounts,
};
}
case StableOrder.StepType.ZAP_OUT: {
const { assetOutIndex, minimumAssetOut, lpAsset } = option;
const poolConfig = this.getConfigByLpAsset(lpAsset);
invariant(
poolConfig,
`Not found Stableswap config matching with LP Asset ${lpAsset.toString()}`
);
const assetLength = BigInt(poolConfig.assets.length);
invariant(
minimumAssetOut > 0n,
"Minimum amount out must be positive number"
);
invariant(
assetOutIndex >= 0n && assetOutIndex < assetLength,
`Invalid assetOutIndex, must be between 0-${assetLength - 1n}`
);
return {
type: StableOrder.StepType.ZAP_OUT,
assetOutIndex: assetOutIndex,
minimumAssetOut: minimumAssetOut,
};
}
}
}

private getOrderMetadata(options: OrderOptions): string {
switch (options.type) {
case StableOrder.StepType.SWAP: {
return MetadataMessage.SWAP_EXACT_IN_ORDER;
}
case StableOrder.StepType.DEPOSIT: {
let assetInputCnt = 0;
for (const [_, amount] of options.assetsAmount) {
if (amount > 0) {
assetInputCnt++;
}
}
if (assetInputCnt === 1) {
return MetadataMessage.ZAP_IN_ORDER;
} else {
return MetadataMessage.DEPOSIT_ORDER;
}
}
case StableOrder.StepType.WITHDRAW: {
return MetadataMessage.WITHDRAW_ORDER;
}
case StableOrder.StepType.WITHDRAW_IMBALANCE: {
return MetadataMessage.WITHDRAW_ORDER;
}
case StableOrder.StepType.ZAP_OUT: {
return MetadataMessage.ZAP_OUT_ORDER;
}
}
}

async buildCreateTx(options: OrderOptions): Promise<TxComplete> {
const { sender, availableUtxos, lpAsset } = options;
const config = this.getConfigByLpAsset(lpAsset);
const orderAssets = this.buildOrderValue(options);
const step = this.buildOrderStep(options);
const { batcherFee, reductionAssets } = calculateBatcherFee({
utxos: availableUtxos,
orderAssets,
networkEnv: this.networkEnv,
dexVersion: this.dexVersion,
});
if (orderAssets["lovelace"]) {
orderAssets["lovelace"] += FIXED_DEPOSIT_ADA + batcherFee;
} else {
orderAssets["lovelace"] = FIXED_DEPOSIT_ADA + batcherFee;
}
const datum: StableOrder.Datum = {
sender: sender,
receiver: sender,
receiverDatumHash: undefined,
step: step,
batcherFee: batcherFee,
depositADA: FIXED_DEPOSIT_ADA,
};
const tx = this.lucid
.newTx()
.payToContract(
config.orderAddress,
{
inline: Data.to(StableOrder.Datum.toPlutusData(datum)),
},
orderAssets
)
.payToAddress(sender, reductionAssets)
.addSigner(sender)
.attachMetadata(674, { msg: [this.getOrderMetadata(options)] });
return await tx.complete();
}

getConfigFromStableswapOrderAddress(
address: Address
): StableswapConstant.Config {
const config = StableswapConstant.CONFIG[this.networkId].find((config) => {
return address === config.orderAddress;
});
invariant(config, `Invalid Stableswap Order Address: ${address}`);
return config;
}

getStableswapReferencesScript(
lpAsset: Asset
): StableswapConstant.DeployedScripts {
const refScript =
StableswapConstant.DEPLOYED_SCRIPTS[this.networkId][
Asset.toString(lpAsset)
];
invariant(
refScript,
`Invalid Stableswap LP Asset ${Asset.toString(lpAsset)}`
);
return refScript;
}

async buildCancelOrdersTx(
options: BuildCancelOrderOptions
): Promise<TxComplete> {
const tx = this.lucid.newTx();

const redeemer = Data.to(new Constr(StableOrder.Redeemer.CANCEL_ORDER, []));
for (const utxo of options.orderUtxos) {
const config = this.getConfigFromStableswapOrderAddress(utxo.address);
const referencesScript = this.getStableswapReferencesScript(
Asset.fromString(config.lpAsset)
);
let datum: StableOrder.Datum;
if (utxo.datum) {
const rawDatum = utxo.datum;
datum = StableOrder.Datum.fromPlutusData(
this.networkId,
Data.from(rawDatum)
);
} else if (utxo.datumHash) {
const rawDatum = await this.lucid.datumOf(utxo);
datum = StableOrder.Datum.fromPlutusData(
this.networkId,
rawDatum as Constr<Data>
);
} else {
throw new Error(
"Utxo without Datum Hash or Inline Datum can not be spent"
);
}

const orderRefs = await this.lucid.utxosByOutRef([
referencesScript.order,
]);
invariant(
orderRefs.length === 1,
"cannot find deployed script for V2 Order"
);

const orderRef = orderRefs[0];
tx.readFrom([orderRef])
.collectFrom([utxo], redeemer)
.addSigner(datum.sender)
.attachMetadata(674, { msg: [MetadataMessage.CANCEL_ORDER] });
}
return await tx.complete();
}
}

0 comments on commit f5a4e66

Please sign in to comment.