From 7484760a234fdd7cb5a4a9d77bba21334f8ddb76 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Fri, 6 Sep 2024 14:55:56 +0700 Subject: [PATCH 01/16] wip --- src/stableswap.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/stableswap.ts diff --git a/src/stableswap.ts b/src/stableswap.ts new file mode 100644 index 0000000..287ba85 --- /dev/null +++ b/src/stableswap.ts @@ -0,0 +1,34 @@ +import { Address, Lucid, Network, Tx, TxComplete, UTxO } from "lucid-cardano"; + +import { Asset } from "./types/asset"; +import { NetworkId } from "./types/network"; + +export type CommonOrderOptions = { + sender: Address; + availableUtxos: UTxO[]; + lpAsset: Asset; +}; + +export type ExchangeOptions = CommonOrderOptions & { + assetIn: Asset; + assetInAmount: bigint; + assetInIndex: bigint; + assetOutIndex: bigint; + minimumAssetOut: bigint; +}; +export class Stableswap { + private readonly lucid: Lucid; + private readonly network: Network; + private readonly networkId: NetworkId; + + constructor(lucid: Lucid) { + this.lucid = lucid; + this.network = lucid.network; + this.networkId = + lucid.network === "Mainnet" ? NetworkId.MAINNET : NetworkId.TESTNET; + } + + async buildExchangeOrderTx(options: ExchangeOptions): Promise{ + + } +} From f5a4e66a44159778e4e56ecad150397adea9253b Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Mon, 16 Sep 2024 16:06:09 +0700 Subject: [PATCH 02/16] build stableswap order txs --- src/stableswap.ts | 364 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 357 insertions(+), 7 deletions(-) diff --git a/src/stableswap.ts b/src/stableswap.ts index 287ba85..f959456 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -1,7 +1,27 @@ -import { Address, Lucid, Network, Tx, TxComplete, UTxO } from "lucid-cardano"; +import { + Address, + Assets, + Lucid, + TxComplete, + UTxO, + Credential, + 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; @@ -9,26 +29,356 @@ export type CommonOrderOptions = { 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{ + 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 { + 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 { + 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 + ); + } 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(); } } From f147f1dd1a2af640717411663f738614538c27e2 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 10:55:52 +0700 Subject: [PATCH 03/16] calculation and example --- examples/example.ts | 206 ++++++++++++++++ package-lock.json | 42 +++- package.json | 1 + src/adapter.ts | 15 ++ src/calculate.ts | 516 +++++++++++++++++++++++++++++++++++++++++ src/stableswap.ts | 62 ++--- src/types/constants.ts | 37 +++ 7 files changed, 827 insertions(+), 52 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index 478ed15..a1d1067 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -1,3 +1,5 @@ +// TODO: remove all console.log + import { BlockFrostAPI } from "@blockfrost/blockfrost-js"; import invariant from "@minswap/tiny-invariant"; import BigNumber from "bignumber.js"; @@ -28,8 +30,12 @@ import { NetworkId, OrderV2, PoolV1, + StableOrder, + StableswapCalculation, + StableswapConstant, } from "../src"; import { Slippage } from "../src/utils/slippage.internal"; +import { Stableswap } from "../src/stableswap"; const MIN: Asset = { policyId: "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72", @@ -845,6 +851,206 @@ async function _cancelV2TxExample( }); } +async function _swapStableExample( + lucid: Lucid, + blockfrostAdapter: BlockfrostAdapter, + address: Address, + availableUtxos: UTxO[] +): Promise { + const lpAsset = Asset.fromString( + "d16339238c9e1fb4d034b6a48facb2f97794a9cdb7bc049dd7c49f54646a65642d697573642d76312e342d6c70" + ); + const config = StableswapConstant.getConfigByLpAsset( + lpAsset, + NetworkId.TESTNET + ); + + const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + + const swapAmount = 1_000n; + + const amountOut = StableswapCalculation.calculateSwapAmount({ + inIndex: 0, + outIndex: 1, + amountIn: swapAmount, + amp: pool.amp, + multiples: config.multiples, + datumBalances: pool.datum.balances, + fee: config.fee, + adminFee: config.adminFee, + feeDenominator: config.feeDenominator, + }); + + return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + sender: address, + availableUtxos: availableUtxos, + lpAsset: lpAsset, + type: StableOrder.StepType.SWAP, + assetIn: Asset.fromString(config.assets[0]), + assetInAmount: swapAmount, + assetInIndex: 0n, + assetOutIndex: 1n, + minimumAssetOut: amountOut, + }); +} + +async function _depositStableExample( + lucid: Lucid, + blockfrostAdapter: BlockfrostAdapter, + address: Address, + availableUtxos: UTxO[] +): Promise { + const lpAsset = Asset.fromString( + "d16339238c9e1fb4d034b6a48facb2f97794a9cdb7bc049dd7c49f54646a65642d697573642d76312e342d6c70" + ); + const config = StableswapConstant.getConfigByLpAsset( + lpAsset, + NetworkId.TESTNET + ); + + const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + + const amountIns = [100_000n, 1_000n]; + + const lpAmount = StableswapCalculation.calculateDeposit({ + amountIns: amountIns, + totalLiquidity: pool.totalLiquidity, + amp: pool.amp, + multiples: config.multiples, + datumBalances: pool.datum.balances, + fee: config.fee, + adminFee: config.adminFee, + feeDenominator: config.feeDenominator, + }); + console.log(lpAmount); + return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + sender: address, + availableUtxos: availableUtxos, + lpAsset: lpAsset, + type: StableOrder.StepType.DEPOSIT, + assetsAmount: [ + [Asset.fromString(pool.assets[0]), 100_000n], + [Asset.fromString(pool.assets[1]), 1_000n], + ], + minimumLPReceived: lpAmount, + totalLiquidity: pool.totalLiquidity, + }); +} + +async function _withdrawStableExample( + lucid: Lucid, + blockfrostAdapter: BlockfrostAdapter, + address: Address, + availableUtxos: UTxO[] +): Promise { + const lpAsset = Asset.fromString( + "d16339238c9e1fb4d034b6a48facb2f97794a9cdb7bc049dd7c49f54646a65642d697573642d76312e342d6c70" + ); + const config = StableswapConstant.getConfigByLpAsset( + lpAsset, + NetworkId.TESTNET + ); + + const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + + const lpAmount = 10_000n; + + const amountOuts = StableswapCalculation.calculateWithdraw({ + withdrawalLPAmount: lpAmount, + multiples: config.multiples, + datumBalances: pool.datum.balances, + totalLiquidity: pool.totalLiquidity, + }); + console.log(amountOuts); + return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + sender: address, + availableUtxos: availableUtxos, + lpAsset: lpAsset, + type: StableOrder.StepType.WITHDRAW, + lpAmount: lpAmount, + minimumAmounts: amountOuts, + }); +} + +async function _withdrawImbalanceStableExample( + lucid: Lucid, + blockfrostAdapter: BlockfrostAdapter, + address: Address, + availableUtxos: UTxO[] +): Promise { + const lpAsset = Asset.fromString( + "d16339238c9e1fb4d034b6a48facb2f97794a9cdb7bc049dd7c49f54646a65642d697573642d76312e342d6c70" + ); + const config = StableswapConstant.getConfigByLpAsset( + lpAsset, + NetworkId.TESTNET + ); + + const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + + const withdrawAmounts = [1234n, 5678n]; + + const lpAmount = StableswapCalculation.calculateWithdrawImbalance({ + withdrawAmounts: withdrawAmounts, + totalLiquidity: pool.totalLiquidity, + amp: pool.amp, + multiples: config.multiples, + datumBalances: pool.datum.balances, + fee: config.fee, + adminFee: config.adminFee, + feeDenominator: config.feeDenominator, + }); + console.log(lpAmount); + return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + sender: address, + availableUtxos: availableUtxos, + lpAsset: lpAsset, + type: StableOrder.StepType.WITHDRAW_IMBALANCE, + lpAmount: lpAmount, + withdrawAmounts: withdrawAmounts, + }); +} +async function _zapOutStableExample( + lucid: Lucid, + blockfrostAdapter: BlockfrostAdapter, + address: Address, + availableUtxos: UTxO[] +): Promise { + const lpAsset = Asset.fromString( + "d16339238c9e1fb4d034b6a48facb2f97794a9cdb7bc049dd7c49f54646a65642d697573642d76312e342d6c70" + ); + const config = StableswapConstant.getConfigByLpAsset( + lpAsset, + NetworkId.TESTNET + ); + + const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + + const lpAmount = 12345n; + const outIndex = 0; + const amountOut = StableswapCalculation.calculateZapOut({ + amountLpIn: lpAmount, + outIndex: outIndex, + totalLiquidity: pool.totalLiquidity, + amp: pool.amp, + multiples: config.multiples, + datumBalances: pool.datum.balances, + fee: config.fee, + adminFee: config.adminFee, + feeDenominator: config.feeDenominator, + }); + console.log(lpAmount); + return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + sender: address, + availableUtxos: availableUtxos, + lpAsset: lpAsset, + type: StableOrder.StepType.ZAP_OUT, + lpAmount: lpAmount, + assetOutIndex: BigInt(outIndex), + minimumAssetOut: amountOut, + }); +} + /** * Initialize Lucid Instance for Browser Environment * @param network Network you're working on diff --git a/package-lock.json b/package-lock.json index 85a38ba..39abbef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "big.js": "^6.2.1", "bignumber.js": "^9.1.2", "lucid-cardano": "0.10.10", + "remeda": "^2.12.1", "sha3": "^2.1.4" }, "devDependencies": { @@ -2421,12 +2422,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3626,9 +3627,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -5120,12 +5121,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5894,6 +5895,25 @@ "regexp-tree": "bin/regexp-tree" } }, + "node_modules/remeda": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.12.1.tgz", + "integrity": "sha512-hKFAbxbQe8PMd4+CYO1DYCrCbcZsUSa7e21g7+4co91GBy7BD+Ub6JdaLy76yPOp7PCPTAXRz/9NXtZ9w15jbg==", + "dependencies": { + "type-fest": "^4.26.1" + } + }, + "node_modules/remeda/node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index ed4248a..7ac25ec 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "big.js": "^6.2.1", "bignumber.js": "^9.1.2", "lucid-cardano": "0.10.10", + "remeda": "^2.12.1", "sha3": "^2.1.4" }, "devDependencies": { diff --git a/src/adapter.ts b/src/adapter.ts index c5780cb..4e9ccc1 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -403,6 +403,21 @@ export class BlockfrostAdapter { }; } + public async getStablePoolByLpAsset( + lpAsset: Asset + ): Promise { + const pool = (await this.getAllStablePools()).pools.find( + (pool) => pool.lpAsset === Asset.toString(lpAsset) + ); + invariant( + pool, + `getStablePoolByLpAsset: Can not find pool by LP Asset ${Asset.toString( + lpAsset + )}` + ); + return pool; + } + public async getStablePoolByNFT( nft: Asset ): Promise { diff --git a/src/calculate.ts b/src/calculate.ts index 7d6330e..f6f39d3 100644 --- a/src/calculate.ts +++ b/src/calculate.ts @@ -1,8 +1,10 @@ import Big from "big.js"; +import { zipWith } from "remeda"; import { OrderV2 } from "./types/order"; import { PoolV2 } from "./types/pool"; import { sqrt } from "./utils/sqrt.internal"; +import invariant from "@minswap/tiny-invariant"; /** * Options to calculate Amount Out & Price Impact while swapping exact in @@ -473,3 +475,517 @@ export namespace DexV2Calculation { } } } + +export namespace StableswapCalculation { + export function getD(mulBalances: bigint[], amp: bigint): bigint { + const sumMulBalances = mulBalances.reduce( + (sum, balance) => sum + balance, + 0n + ); + if (sumMulBalances === 0n) { + return 0n; + } + + const length = BigInt(mulBalances.length); + let dPrev = 0n; + let d = sumMulBalances; + const ann = amp * length; + + for (let i = 0; i < 255; i++) { + let dp = d; + for (const mulBalance of mulBalances) { + dp = (dp * d) / (mulBalance * length); + } + dPrev = d; + d = + ((ann * sumMulBalances + dp * length) * d) / + ((ann - 1n) * d + (length + 1n) * dp); + if (d > dPrev) { + if (d - dPrev <= 1n) { + break; + } + } else { + if (dPrev - d <= 1n) { + break; + } + } + } + return d; + } + + export function getY( + i: number, + j: number, + x: bigint, + xp: bigint[], + amp: bigint + ): bigint { + if (i === j || i < 0 || j < 0 || i >= xp.length || j >= xp.length) { + throw Error( + `getY failed: i and j must be different and less than length of xp` + ); + } + const length = BigInt(xp.length); + const d = getD(xp, amp); + let c = d; + let s = 0n; + const ann = amp * length; + + let _x = 0n; + for (let index = 0; index < Number(length); index++) { + if (index === i) { + _x = x; + } else if (index !== j) { + _x = xp[index]; + } else { + continue; + } + s += _x; + c = (c * d) / (_x * length); + } + + c = (c * d) / (ann * length); + const b = s + d / ann; + let yPrev = 0n; + let y = d; + for (let index = 0; index < 255; index++) { + yPrev = y; + y = (y * y + c) / (2n * y + b - d); + if (y > yPrev) { + if (y - yPrev <= 1n) { + break; + } + } else { + if (yPrev - y <= 1n) { + break; + } + } + } + return y; + } + + export function getYD( + i: number, + xp: bigint[], + amp: bigint, + d: bigint + ): bigint { + const length = BigInt(xp.length); + invariant( + 0 <= i && i < xp.length, + `getYD failed: i must be less than length of xp` + ); + let c = d; + let s = 0n; + const ann = amp * length; + + let _x = 0n; + for (let index = 0; index < Number(length); index++) { + if (index !== i) { + _x = xp[index]; + } else { + continue; + } + s += _x; + c = (c * d) / (_x * length); + } + c = (c * d) / (ann * length); + const b = s + d / ann; + let yPrev = 0n; + let y = d; + for (let index = 0; index < 255; index++) { + yPrev = y; + y = (y * y + c) / (2n * y + b - d); + if (y > yPrev) { + if (y - yPrev <= 1n) { + break; + } + } else { + if (yPrev - y <= 1n) { + break; + } + } + } + return y; + } + + export function getDMem( + balances: bigint[], + multiples: bigint[], + amp: bigint + ): bigint { + const mulBalances = zipWith(balances, multiples, (a, b) => a * b); + return getD(mulBalances, amp); + } + + type CommonStableswapCalculationOptions = { + amp: bigint; + multiples: bigint[]; + datumBalances: bigint[]; + fee: bigint; + adminFee: bigint; + feeDenominator: bigint; + }; + + export type StableswapCalculateSwapOptions = + CommonStableswapCalculationOptions & { + inIndex: number; + outIndex: number; + amountIn: bigint; + }; + + export type StableswapCalculateDepositOptions = + CommonStableswapCalculationOptions & { + amountIns: bigint[]; + totalLiquidity: bigint; + }; + + export type StableswapCalculateWithdrawOptions = Omit< + CommonStableswapCalculationOptions, + "amp" | "fee" | "adminFee" | "feeDenominator" + > & { + withdrawalLPAmount: bigint; + totalLiquidity: bigint; + }; + + export type StableswapCalculateWithdrawImbalanceOptions = + CommonStableswapCalculationOptions & { + withdrawAmounts: bigint[]; + totalLiquidity: bigint; + }; + + export type StableswapCalculateZapOutOptions = + CommonStableswapCalculationOptions & { + amountLpIn: bigint; + outIndex: number; + totalLiquidity: bigint; + }; + + export function calculateSwapAmount({ + inIndex, + outIndex, + amountIn, + amp, + multiples, + datumBalances, + fee, + adminFee, + feeDenominator, + }: StableswapCalculateSwapOptions): bigint { + const tempDatumBalances = [...datumBalances]; + + const length = multiples.length; + invariant( + amountIn > 0, + `calculateExchange error: amountIn ${amountIn} must be positive.` + ); + invariant( + 0 <= inIndex && inIndex < length, + `calculateExchange error: inIndex ${inIndex} is not valid, must be within 0-${ + length - 1 + }` + ); + invariant( + 0 <= outIndex && outIndex < length, + `calculateExchange error: outIndex ${outIndex} is not valid, must be within 0-${ + length - 1 + }` + ); + invariant(inIndex !== outIndex, `inIndex must be different from outIndex`); + const mulBalances = zipWith(tempDatumBalances, multiples, (a, b) => a * b); + const mulIn = multiples[inIndex]; + const mulOut = multiples[outIndex]; + const x = mulBalances[inIndex] + amountIn * mulIn; + const y = getY(inIndex, outIndex, x, mulBalances, amp); + + const dy = mulBalances[outIndex] - y; + const dyFee = (dy * fee) / feeDenominator; + const dyAdminFee = (dyFee * adminFee) / feeDenominator; + const amountOut = (dy - dyFee) / mulOut; + const newDatumBalanceOut = (y + (dyFee - dyAdminFee)) / mulOut; + + invariant( + amountOut > 0, + `calculateExchange error: amountIn is too small, amountOut (${amountOut}) must be positive.` + ); + invariant( + newDatumBalanceOut > 0, + `calculateExchange error: newDatumBalanceOut (${newDatumBalanceOut}) must be positive.` + ); + return amountOut; + } + + export function calculateDeposit({ + amountIns, + amp, + multiples, + datumBalances, + totalLiquidity, + fee, + adminFee, + feeDenominator, + }: StableswapCalculateDepositOptions): bigint { + const tempDatumBalances = [...datumBalances]; + const feeAmounts = tempDatumBalances.map((_) => 0n); + const assetVolumes = tempDatumBalances.map((_) => 0n); + const poolVolumes = tempDatumBalances.map((_) => 0n); + + const length = multiples.length; + invariant( + amountIns.length === length, + `calculateDeposit error: amountIns's length ${amountIns.length} is invalid, amountIns's length must be ${length}` + ); + + let newDatumBalances: bigint[] = []; + let newValueBalances: bigint[] = []; + let lpAmount = 0n; + if (totalLiquidity === 0n) { + for (let i = 0; i < length; ++i) { + invariant( + amountIns[i] > 0n, + `calculateDeposit error: amount index ${i} must be positive in case totalLiquidity = 0` + ); + } + newDatumBalances = zipWith(tempDatumBalances, amountIns, (a, b) => a + b); + const d1 = getDMem(newDatumBalances, multiples, amp); + invariant( + d1 > 0, + `calculateDeposit: d1 must be greater than 0 in case totalLiquidity = 0` + ); + lpAmount = d1; + } else { + let sumIns = 0n; + for (let i = 0; i < length; ++i) { + if (amountIns[i] < 0n) { + invariant( + amountIns[i] > 0n, + `calculateDeposit error: amountIns index ${i} must be non-negative` + ); + } + sumIns += amountIns[i]; + } + invariant( + sumIns > 0, + `calculateDeposit error: sum of amountIns must be positive` + ); + + const newDatumBalanceWithoutFee = zipWith( + tempDatumBalances, + amountIns, + (a, b) => a + b + ); + + const d0 = getDMem(tempDatumBalances, multiples, amp); + const d1 = getDMem(newDatumBalanceWithoutFee, multiples, amp); + + invariant( + d1 > d0, + `calculateDeposit: d1 must be greater than d0 in case totalLiquidity > 0, d1: ${d1}, d0: ${d0}` + ); + + const specialFee = (fee * BigInt(length)) / (4n * (BigInt(length) - 1n)); + + const newDatBalancesWithTradingFee: bigint[] = []; + for (let i = 0; i < tempDatumBalances.length; i++) { + const oldBalance = tempDatumBalances[i]; + const newBalance = newDatumBalanceWithoutFee[i]; + + const idealBalance = (d1 * oldBalance) / d0; + let different = 0n; + // In this case, liquidity pool has to swap the amount of other assets to get @different assets[i] + // Because the pool volumes is counted only for destination asset of the swap so we skip the "else" condition + if (newBalance > idealBalance) { + different = newBalance - idealBalance; + poolVolumes[i] = different; + } else { + different = idealBalance - newBalance; + } + assetVolumes[i] = different; + const tradingFeeAmount = (specialFee * different) / feeDenominator; + feeAmounts[i] = tradingFeeAmount; + const adminFeeAmount = (tradingFeeAmount * adminFee) / feeDenominator; + newDatumBalances.push(newBalance - adminFeeAmount); + newDatBalancesWithTradingFee.push(newBalance - tradingFeeAmount); + } + for (let i = 0; i < length; ++i) { + invariant( + newDatBalancesWithTradingFee[i] > 0, + `calculateDeposit error: deposit amount is too small, newDatBalancesWithTradingFee must be positive` + ); + } + const d2 = getDMem(newDatBalancesWithTradingFee, multiples, amp); + lpAmount = (totalLiquidity * (d2 - d0)) / d0; + } + + invariant( + lpAmount > 0, + `calculateDeposit error: deposit amount is too small, lpAmountOut ${lpAmount} must be positive` + ); + return lpAmount; + } + + export function calculateWithdraw({ + withdrawalLPAmount, + multiples, + datumBalances, + totalLiquidity, + }: StableswapCalculateWithdrawOptions): bigint[] { + const tempDatumBalances = [...datumBalances]; + + const length = multiples.length; + invariant( + withdrawalLPAmount > 0, + `calculateWithdraw error: withdrawalLPAmount must be positive` + ); + const amountOuts = tempDatumBalances.map( + (balance) => (balance * withdrawalLPAmount) / totalLiquidity + ); + let sumOuts = 0n; + for (let i = 0; i < length; ++i) { + invariant( + amountOuts[i] >= 0n, + `calculateWithdraw error: amountOuts must be non-negative` + ); + sumOuts += amountOuts[i]; + } + invariant( + sumOuts > 0n, + `calculateWithdraw error: sum of amountOuts must be positive` + ); + + return amountOuts; + } + + export function calculateWithdrawImbalance({ + withdrawAmounts, + amp, + multiples, + datumBalances, + totalLiquidity, + fee, + feeDenominator, + }: StableswapCalculateWithdrawImbalanceOptions): bigint { + const tempDatumBalances = [...datumBalances]; + + const length = multiples.length; + + invariant( + withdrawAmounts.length === length, + `calculateWithdrawImbalance error: withdrawAmounts's length ${withdrawAmounts.length} is invalid, withdrawAmounts's length must be ${length}` + ); + + let sumOuts = 0n; + for (let i = 0; i < length; ++i) { + invariant( + withdrawAmounts[i] >= 0n, + `calculateDeposit error: amountIns must be non-negative` + ); + + sumOuts += withdrawAmounts[i]; + } + invariant( + sumOuts > 0n, + `calculateWithdrawImbalance error: sum of withdrawAmounts must be positive` + ); + + const specialFee = (fee * BigInt(length)) / (4n * (BigInt(length) - 1n)); + + const newDatBalancesWithoutFee = zipWith( + tempDatumBalances, + withdrawAmounts, + (a, b) => a - b + ); + for (let i = 0; i < length; ++i) { + invariant( + newDatBalancesWithoutFee[i] > 0n, + `calculateWithdrawImbalance error: not enough asset index ${i}` + ); + } + const d0 = getDMem(tempDatumBalances, multiples, amp); + const d1 = getDMem(newDatBalancesWithoutFee, multiples, amp); + + const newDatBalancesWithTradingFee = []; + for (let i = 0; i < length; ++i) { + const idealBalance = (d1 * tempDatumBalances[i]) / d0; + let different = 0n; + if (newDatBalancesWithoutFee[i] > idealBalance) { + different = newDatBalancesWithoutFee[i] - idealBalance; + } else { + different = idealBalance - newDatBalancesWithoutFee[i]; + } + const tradingFeeAmount = (specialFee * different) / feeDenominator; + newDatBalancesWithTradingFee.push( + newDatBalancesWithoutFee[i] - tradingFeeAmount + ); + } + for (let i = 0; i < length; ++i) { + invariant( + newDatBalancesWithTradingFee[i] > 0n, + `calculateWithdrawImbalance error: not enough asset index ${i}` + ); + } + + const d2 = getDMem(newDatBalancesWithTradingFee, multiples, amp); + let lpAmount = ((d0 - d2) * totalLiquidity) / d0; + + invariant( + lpAmount > 0n, + `calculateWithdrawImbalance error: required lpAmount ${lpAmount} must be positive` + ); + + lpAmount += 1n; + return lpAmount; + } + + export function calculateZapOut({ + amountLpIn, + outIndex, + amp, + multiples, + datumBalances, + totalLiquidity, + fee, + adminFee, + feeDenominator, + }: StableswapCalculateZapOutOptions): bigint { + const tempDatumBalances = [...datumBalances]; + + const length = multiples.length; + invariant( + amountLpIn > 0, + `calculateZapOut error: amountLpIn ${amountLpIn} must be positive.` + ); + + invariant( + 0 <= outIndex && outIndex < length, + `calculateZapOut error: outIndex ${outIndex} is not valid, must be within 0-${ + length - 1 + }` + ); + + const mulBalances = zipWith(tempDatumBalances, multiples, (a, b) => a * b); + const mulOut = multiples[outIndex]; + const d0 = getD(mulBalances, amp); + const d1 = d0 - (amountLpIn * d0) / totalLiquidity; + const mulBalancesReduced = mulBalances; + // newY is new MulBal[outIndex] + const newYWithoutFee = getYD(outIndex, mulBalances, amp, d1); + const specialFee = (fee * BigInt(length)) / (4n * (BigInt(length) - 1n)); + + const amountOutWithoutFee = + (mulBalances[outIndex] - newYWithoutFee) / mulOut; + for (let i = 0; i < length; ++i) { + const diff = + i === outIndex + ? (mulBalances[i] * d1) / d0 - newYWithoutFee + : mulBalances[i] - (mulBalances[i] * d1) / d0; + mulBalancesReduced[i] -= (diff * specialFee) / feeDenominator; + } + const newY = getYD(outIndex, mulBalancesReduced, amp, d1); + const amountOut = (mulBalancesReduced[outIndex] - newY - 1n) / mulOut; + tempDatumBalances[outIndex] -= + amountOut + + ((amountOutWithoutFee - amountOut) * adminFee) / feeDenominator; + return amountOut; + } +} diff --git a/src/stableswap.ts b/src/stableswap.ts index f959456..f7db531 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -4,7 +4,6 @@ import { Lucid, TxComplete, UTxO, - Credential, Data, Constr, } from "lucid-cardano"; @@ -57,7 +56,7 @@ export type WithdrawImbalanceOptions = CommonOrderOptions & { withdrawAmounts: bigint[]; }; -export type WithdrawOneCoinOptions = CommonOrderOptions & { +export type ZapOutOptions = CommonOrderOptions & { type: StableOrder.StepType.ZAP_OUT; lpAmount: bigint; assetOutIndex: bigint; @@ -69,7 +68,7 @@ export type OrderOptions = | WithdrawOptions | SwapOptions | WithdrawImbalanceOptions - | WithdrawOneCoinOptions; + | ZapOutOptions; export type BuildCancelOrderOptions = { orderUtxos: UTxO[]; @@ -90,14 +89,6 @@ export class Stableswap { 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; - } - buildOrderValue(option: OrderOptions): Assets { const orderAssets: Assets = {}; @@ -179,7 +170,10 @@ export class Stableswap { case StableOrder.StepType.SWAP: { const { lpAsset, assetInIndex, assetOutIndex, minimumAssetOut } = option; - const poolConfig = this.getConfigByLpAsset(lpAsset); + const poolConfig = StableswapConstant.getConfigByLpAsset( + lpAsset, + this.networkId + ); invariant( poolConfig, `Not found Stableswap config matching with LP Asset ${lpAsset.toString()}` @@ -219,7 +213,10 @@ export class Stableswap { } case StableOrder.StepType.ZAP_OUT: { const { assetOutIndex, minimumAssetOut, lpAsset } = option; - const poolConfig = this.getConfigByLpAsset(lpAsset); + const poolConfig = StableswapConstant.getConfigByLpAsset( + lpAsset, + this.networkId + ); invariant( poolConfig, `Not found Stableswap config matching with LP Asset ${lpAsset.toString()}` @@ -274,7 +271,10 @@ export class Stableswap { async buildCreateTx(options: OrderOptions): Promise { const { sender, availableUtxos, lpAsset } = options; - const config = this.getConfigByLpAsset(lpAsset); + const config = StableswapConstant.getConfigByLpAsset( + lpAsset, + this.networkId + ); const orderAssets = this.buildOrderValue(options); const step = this.buildOrderStep(options); const { batcherFee, reductionAssets } = calculateBatcherFee({ @@ -311,30 +311,6 @@ export class Stableswap { 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 { @@ -342,9 +318,13 @@ export class Stableswap { 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) + const config = StableswapConstant.getConfigFromStableswapOrderAddress( + utxo.address, + this.networkId + ); + const referencesScript = StableswapConstant.getStableswapReferencesScript( + Asset.fromString(config.lpAsset), + this.networkId ); let datum: StableOrder.Datum; if (utxo.datum) { diff --git a/src/types/constants.ts b/src/types/constants.ts index 3872994..969a2c5 100644 --- a/src/types/constants.ts +++ b/src/types/constants.ts @@ -1,6 +1,8 @@ import { Address, OutRef, Script } from "lucid-cardano"; import { NetworkId } from "./network"; +import { Asset } from ".."; +import invariant from "@minswap/tiny-invariant"; export namespace DexV1Constant { export const ORDER_BASE_ADDRESS: Record = { @@ -351,6 +353,41 @@ export namespace StableswapConstant { }, }, }; + + export function getConfigByLpAsset( + lpAsset: Asset, + networkId: NetworkId + ): StableswapConstant.Config { + const config = StableswapConstant.CONFIG[networkId].find( + (config) => config.lpAsset === Asset.toString(lpAsset) + ); + invariant(config, `Invalid Stableswap LP Asset ${Asset.toString(lpAsset)}`); + return config; + } + + export function getConfigFromStableswapOrderAddress( + address: Address, + networkId: NetworkId + ): StableswapConstant.Config { + const config = StableswapConstant.CONFIG[networkId].find((config) => { + return address === config.orderAddress; + }); + invariant(config, `Invalid Stableswap Order Address: ${address}`); + return config; + } + + export function getStableswapReferencesScript( + lpAsset: Asset, + networkId: NetworkId + ): StableswapConstant.DeployedScripts { + const refScript = + StableswapConstant.DEPLOYED_SCRIPTS[networkId][Asset.toString(lpAsset)]; + invariant( + refScript, + `Invalid Stableswap LP Asset ${Asset.toString(lpAsset)}` + ); + return refScript; + } } export namespace DexV2Constant { From efd6c2b23d9f69b3895adba30762e922e934b6c4 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 11:10:36 +0700 Subject: [PATCH 04/16] cancel order --- examples/example.ts | 22 ++++++++++++++++++++-- src/stableswap.ts | 2 +- src/types/constants.ts | 6 +++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index a1d1067..c38fb1a 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -1010,6 +1010,7 @@ async function _withdrawImbalanceStableExample( withdrawAmounts: withdrawAmounts, }); } + async function _zapOutStableExample( lucid: Lucid, blockfrostAdapter: BlockfrostAdapter, @@ -1039,7 +1040,7 @@ async function _zapOutStableExample( adminFee: config.adminFee, feeDenominator: config.feeDenominator, }); - console.log(lpAmount); + console.log(amountOut); return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ sender: address, availableUtxos: availableUtxos, @@ -1047,7 +1048,24 @@ async function _zapOutStableExample( type: StableOrder.StepType.ZAP_OUT, lpAmount: lpAmount, assetOutIndex: BigInt(outIndex), - minimumAssetOut: amountOut, + minimumAssetOut: amountOut + 10n, + }); +} + +async function _cancelStableExample( + lucid: Lucid, + blockfrostAdapter: BlockfrostAdapter +): Promise { + const orderUtxos = await lucid.utxosByOutRef([ + { + txHash: + "f1c873201c3638860dc7d831a2266a7b0f46e48674da27fd0bcd1dc0c3835889", + outputIndex: 0, + }, + ]); + invariant(orderUtxos.length === 1, "Can not find order to cancel"); + return new Stableswap(lucid, blockfrostAdapter).buildCancelOrdersTx({ + orderUtxos: orderUtxos, }); } diff --git a/src/stableswap.ts b/src/stableswap.ts index f7db531..93213bf 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -323,7 +323,7 @@ export class Stableswap { this.networkId ); const referencesScript = StableswapConstant.getStableswapReferencesScript( - Asset.fromString(config.lpAsset), + Asset.fromString(config.nftAsset), this.networkId ); let datum: StableOrder.Datum; diff --git a/src/types/constants.ts b/src/types/constants.ts index 969a2c5..aaadcc1 100644 --- a/src/types/constants.ts +++ b/src/types/constants.ts @@ -377,14 +377,14 @@ export namespace StableswapConstant { } export function getStableswapReferencesScript( - lpAsset: Asset, + nftAsset: Asset, networkId: NetworkId ): StableswapConstant.DeployedScripts { const refScript = - StableswapConstant.DEPLOYED_SCRIPTS[networkId][Asset.toString(lpAsset)]; + StableswapConstant.DEPLOYED_SCRIPTS[networkId][Asset.toString(nftAsset)]; invariant( refScript, - `Invalid Stableswap LP Asset ${Asset.toString(lpAsset)}` + `Invalid Stableswap Nft Asset ${Asset.toString(nftAsset)}` ); return refScript; } From c7123dc4afdbabdc2d913448ed73e4b445e5105c Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 11:12:48 +0700 Subject: [PATCH 05/16] formaty --- src/calculate.ts | 3 +-- src/stableswap.ts | 12 ++++++------ src/types/constants.ts | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/calculate.ts b/src/calculate.ts index f6f39d3..6e82c4f 100644 --- a/src/calculate.ts +++ b/src/calculate.ts @@ -1,10 +1,10 @@ +import invariant from "@minswap/tiny-invariant"; import Big from "big.js"; import { zipWith } from "remeda"; import { OrderV2 } from "./types/order"; import { PoolV2 } from "./types/pool"; import { sqrt } from "./utils/sqrt.internal"; -import invariant from "@minswap/tiny-invariant"; /** * Options to calculate Amount Out & Price Impact while swapping exact in @@ -737,7 +737,6 @@ export namespace StableswapCalculation { ); let newDatumBalances: bigint[] = []; - let newValueBalances: bigint[] = []; let lpAmount = 0n; if (totalLiquidity === 0n) { for (let i = 0; i < length; ++i) { diff --git a/src/stableswap.ts b/src/stableswap.ts index 93213bf..7d630fc 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -1,15 +1,14 @@ +import invariant from "@minswap/tiny-invariant"; import { Address, Assets, + Constr, + Data, Lucid, TxComplete, UTxO, - Data, - Constr, } from "lucid-cardano"; -import { NetworkEnvironment, NetworkId } from "./types/network"; -import { Asset } from "./types/asset"; import { BlockfrostAdapter, FIXED_DEPOSIT_ADA, @@ -17,10 +16,11 @@ import { StableOrder, StableswapConstant, } from "."; +import { calculateBatcherFee } from "./batcher-fee-reduction/calculate"; import { DexVersion } from "./batcher-fee-reduction/types.internal"; +import { Asset } from "./types/asset"; +import { NetworkEnvironment, NetworkId } from "./types/network"; import { lucidToNetworkEnv } from "./utils/network.internal"; -import invariant from "@minswap/tiny-invariant"; -import { calculateBatcherFee } from "./batcher-fee-reduction/calculate"; export type CommonOrderOptions = { sender: Address; diff --git a/src/types/constants.ts b/src/types/constants.ts index aaadcc1..bbba78e 100644 --- a/src/types/constants.ts +++ b/src/types/constants.ts @@ -1,8 +1,8 @@ +import invariant from "@minswap/tiny-invariant"; import { Address, OutRef, Script } from "lucid-cardano"; -import { NetworkId } from "./network"; import { Asset } from ".."; -import invariant from "@minswap/tiny-invariant"; +import { NetworkId } from "./network"; export namespace DexV1Constant { export const ORDER_BASE_ADDRESS: Record = { From f2fe51cf2ae340e185cc9ef40b6f042ec5b14cc1 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 14:29:27 +0700 Subject: [PATCH 06/16] add custom receiver+fix batcher fee+remove un-usecd constructor of Stableswap class --- examples/example.ts | 20 +++++++++----------- src/stableswap.ts | 42 +++++++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index c38fb1a..c7c8f32 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -42,6 +42,7 @@ const MIN: Asset = { tokenName: "4d494e", }; + async function main(): Promise { const network: Network = "Preprod"; const blockfrostProjectId = ""; @@ -881,7 +882,7 @@ async function _swapStableExample( feeDenominator: config.feeDenominator, }); - return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, lpAsset: lpAsset, @@ -923,7 +924,7 @@ async function _depositStableExample( feeDenominator: config.feeDenominator, }); console.log(lpAmount); - return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, lpAsset: lpAsset, @@ -962,7 +963,7 @@ async function _withdrawStableExample( totalLiquidity: pool.totalLiquidity, }); console.log(amountOuts); - return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, lpAsset: lpAsset, @@ -1001,7 +1002,7 @@ async function _withdrawImbalanceStableExample( feeDenominator: config.feeDenominator, }); console.log(lpAmount); - return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, lpAsset: lpAsset, @@ -1041,21 +1042,18 @@ async function _zapOutStableExample( feeDenominator: config.feeDenominator, }); console.log(amountOut); - return new Stableswap(lucid, blockfrostAdapter).buildCreateTx({ + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, lpAsset: lpAsset, type: StableOrder.StepType.ZAP_OUT, lpAmount: lpAmount, assetOutIndex: BigInt(outIndex), - minimumAssetOut: amountOut + 10n, + minimumAssetOut: amountOut, }); } -async function _cancelStableExample( - lucid: Lucid, - blockfrostAdapter: BlockfrostAdapter -): Promise { +async function _cancelStableExample(lucid: Lucid): Promise { const orderUtxos = await lucid.utxosByOutRef([ { txHash: @@ -1064,7 +1062,7 @@ async function _cancelStableExample( }, ]); invariant(orderUtxos.length === 1, "Can not find order to cancel"); - return new Stableswap(lucid, blockfrostAdapter).buildCancelOrdersTx({ + return new Stableswap(lucid).buildCancelOrdersTx({ orderUtxos: orderUtxos, }); } diff --git a/src/stableswap.ts b/src/stableswap.ts index 7d630fc..65716e6 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -15,14 +15,17 @@ import { MetadataMessage, StableOrder, StableswapConstant, + V1AndStableswapCustomReceiver, } from "."; import { calculateBatcherFee } from "./batcher-fee-reduction/calculate"; import { DexVersion } from "./batcher-fee-reduction/types.internal"; import { Asset } from "./types/asset"; import { NetworkEnvironment, NetworkId } from "./types/network"; import { lucidToNetworkEnv } from "./utils/network.internal"; +import { buildUtxoToStoreDatum } from "./utils/tx.internal"; export type CommonOrderOptions = { + customReceiver?: V1AndStableswapCustomReceiver; sender: Address; availableUtxos: UTxO[]; lpAsset: Asset; @@ -77,15 +80,13 @@ export type BuildCancelOrderOptions = { export class Stableswap { private readonly lucid: Lucid; private readonly networkId: NetworkId; - private readonly adapter: BlockfrostAdapter; private readonly networkEnv: NetworkEnvironment; private readonly dexVersion = DexVersion.STABLESWAP; - constructor(lucid: Lucid, adapter: BlockfrostAdapter) { + constructor(lucid: Lucid) { this.lucid = lucid; this.networkId = lucid.network === "Mainnet" ? NetworkId.MAINNET : NetworkId.TESTNET; - this.adapter = adapter; this.networkEnv = lucidToNetworkEnv(lucid.network); } @@ -187,6 +188,10 @@ export class Stableswap { assetOutIndex >= 0n && assetOutIndex < assetLength, `Invalid assetOutIndex, must be between 0-${assetLength - 1n}` ); + invariant( + assetInIndex !== assetOutIndex, + `assetOutIndex and amountInIndex must be different` + ); invariant( minimumAssetOut > 0n, "minimum asset out amount must be positive" @@ -270,7 +275,7 @@ export class Stableswap { } async buildCreateTx(options: OrderOptions): Promise { - const { sender, availableUtxos, lpAsset } = options; + const { sender, availableUtxos, lpAsset, customReceiver } = options; const config = StableswapConstant.getConfigByLpAsset( lpAsset, this.networkId @@ -283,15 +288,15 @@ export class Stableswap { networkEnv: this.networkEnv, dexVersion: this.dexVersion, }); - if (orderAssets["lovelace"]) { - orderAssets["lovelace"] += FIXED_DEPOSIT_ADA + batcherFee; + if ("lovelace" in orderAssets) { + orderAssets["lovelace"] += batcherFee; } else { - orderAssets["lovelace"] = FIXED_DEPOSIT_ADA + batcherFee; + orderAssets["lovelace"] = batcherFee; } const datum: StableOrder.Datum = { sender: sender, - receiver: sender, - receiverDatumHash: undefined, + receiver: customReceiver ? customReceiver.receiver : sender, + receiverDatumHash: customReceiver?.receiverDatum?.hash, step: step, batcherFee: batcherFee, depositADA: FIXED_DEPOSIT_ADA, @@ -308,6 +313,21 @@ export class Stableswap { .payToAddress(sender, reductionAssets) .addSigner(sender) .attachMetadata(674, { msg: [this.getOrderMetadata(options)] }); + if (customReceiver && customReceiver.receiverDatum) { + const utxoForStoringDatum = buildUtxoToStoreDatum( + this.lucid, + sender, + customReceiver.receiver, + customReceiver.receiverDatum.datum + ); + if (utxoForStoringDatum) { + tx.payToAddressWithData( + utxoForStoringDatum.address, + utxoForStoringDatum.outputData, + utxoForStoringDatum.assets + ); + } + } return await tx.complete(); } @@ -356,9 +376,9 @@ export class Stableswap { const orderRef = orderRefs[0]; tx.readFrom([orderRef]) .collectFrom([utxo], redeemer) - .addSigner(datum.sender) - .attachMetadata(674, { msg: [MetadataMessage.CANCEL_ORDER] }); + .addSigner(datum.sender); } + tx.attachMetadata(674, { msg: [MetadataMessage.CANCEL_ORDER] }); return await tx.complete(); } } From 0c6995ead2736b69405021a013f10b7f51bc333f Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 14:32:51 +0700 Subject: [PATCH 07/16] format and remove debug --- examples/example.ts | 11 ++++------- src/stableswap.ts | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index c7c8f32..2bfb52a 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -1,5 +1,3 @@ -// TODO: remove all console.log - import { BlockFrostAPI } from "@blockfrost/blockfrost-js"; import invariant from "@minswap/tiny-invariant"; import BigNumber from "bignumber.js"; @@ -42,7 +40,6 @@ const MIN: Asset = { tokenName: "4d494e", }; - async function main(): Promise { const network: Network = "Preprod"; const blockfrostProjectId = ""; @@ -923,7 +920,7 @@ async function _depositStableExample( adminFee: config.adminFee, feeDenominator: config.feeDenominator, }); - console.log(lpAmount); + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, @@ -962,7 +959,7 @@ async function _withdrawStableExample( datumBalances: pool.datum.balances, totalLiquidity: pool.totalLiquidity, }); - console.log(amountOuts); + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, @@ -1001,7 +998,7 @@ async function _withdrawImbalanceStableExample( adminFee: config.adminFee, feeDenominator: config.feeDenominator, }); - console.log(lpAmount); + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, @@ -1041,7 +1038,7 @@ async function _zapOutStableExample( adminFee: config.adminFee, feeDenominator: config.feeDenominator, }); - console.log(amountOut); + return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, diff --git a/src/stableswap.ts b/src/stableswap.ts index 65716e6..b9f4c19 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -10,7 +10,6 @@ import { } from "lucid-cardano"; import { - BlockfrostAdapter, FIXED_DEPOSIT_ADA, MetadataMessage, StableOrder, From 4e1f09998fbaadf0bd351bef4a2a69492a99c0ee Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 17:19:56 +0700 Subject: [PATCH 08/16] support create bulk orders+refactor get stableswap pool by lp asset --- examples/example.ts | 74 +++++++++++++++--------- src/adapter.ts | 52 +++++++++++------ src/calculate.ts | 7 --- src/dex-v2.ts | 5 +- src/stableswap.ts | 138 +++++++++++++++++++++++++++----------------- 5 files changed, 166 insertions(+), 110 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index 2bfb52a..ef66f10 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -882,13 +882,17 @@ async function _swapStableExample( return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, - lpAsset: lpAsset, - type: StableOrder.StepType.SWAP, - assetIn: Asset.fromString(config.assets[0]), - assetInAmount: swapAmount, - assetInIndex: 0n, - assetOutIndex: 1n, - minimumAssetOut: amountOut, + options: [ + { + lpAsset: lpAsset, + type: StableOrder.StepType.SWAP, + assetIn: Asset.fromString(config.assets[0]), + assetInAmount: swapAmount, + assetInIndex: 0n, + assetOutIndex: 1n, + minimumAssetOut: amountOut, + }, + ], }); } @@ -924,14 +928,18 @@ async function _depositStableExample( return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, - lpAsset: lpAsset, - type: StableOrder.StepType.DEPOSIT, - assetsAmount: [ - [Asset.fromString(pool.assets[0]), 100_000n], - [Asset.fromString(pool.assets[1]), 1_000n], + options: [ + { + lpAsset: lpAsset, + type: StableOrder.StepType.DEPOSIT, + assetsAmount: [ + [Asset.fromString(pool.assets[0]), 100_000n], + [Asset.fromString(pool.assets[1]), 1_000n], + ], + minimumLPReceived: lpAmount, + totalLiquidity: pool.totalLiquidity, + }, ], - minimumLPReceived: lpAmount, - totalLiquidity: pool.totalLiquidity, }); } @@ -963,10 +971,14 @@ async function _withdrawStableExample( return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, - lpAsset: lpAsset, - type: StableOrder.StepType.WITHDRAW, - lpAmount: lpAmount, - minimumAmounts: amountOuts, + options: [ + { + lpAsset: lpAsset, + type: StableOrder.StepType.WITHDRAW, + lpAmount: lpAmount, + minimumAmounts: amountOuts, + }, + ], }); } @@ -1002,10 +1014,14 @@ async function _withdrawImbalanceStableExample( return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, - lpAsset: lpAsset, - type: StableOrder.StepType.WITHDRAW_IMBALANCE, - lpAmount: lpAmount, - withdrawAmounts: withdrawAmounts, + options: [ + { + lpAsset: lpAsset, + type: StableOrder.StepType.WITHDRAW_IMBALANCE, + lpAmount: lpAmount, + withdrawAmounts: withdrawAmounts, + }, + ], }); } @@ -1042,11 +1058,15 @@ async function _zapOutStableExample( return new Stableswap(lucid).buildCreateTx({ sender: address, availableUtxos: availableUtxos, - lpAsset: lpAsset, - type: StableOrder.StepType.ZAP_OUT, - lpAmount: lpAmount, - assetOutIndex: BigInt(outIndex), - minimumAssetOut: amountOut, + options: [ + { + lpAsset: lpAsset, + type: StableOrder.StepType.ZAP_OUT, + lpAmount: lpAmount, + assetOutIndex: BigInt(outIndex), + minimumAssetOut: amountOut, + }, + ], }); } diff --git a/src/adapter.ts b/src/adapter.ts index 4e9ccc1..1ca427d 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -362,6 +362,27 @@ export class BlockfrostAdapter { return [priceAB, priceBA]; } + private async parseStablePoolState( + utxo: Awaited>[0] + ): Promise { + let datum: string; + if (utxo.inline_datum) { + datum = utxo.inline_datum; + } else if (utxo.data_hash) { + datum = await this.getDatumByDatumHash(utxo.data_hash); + } else { + throw new Error("Cannot find datum of Stable Pool"); + } + const pool = new StablePool.State( + this.networkId, + utxo.address, + { txHash: utxo.tx_hash, index: utxo.output_index }, + utxo.amount, + datum + ); + return pool; + } + public async getAllStablePools(): Promise<{ pools: StablePool.State[]; errors: unknown[]; @@ -375,21 +396,7 @@ export class BlockfrostAdapter { const utxos = await this.api.addressesUtxosAll(poolAddr); try { for (const utxo of utxos) { - let datum: string; - if (utxo.inline_datum) { - datum = utxo.inline_datum; - } else if (utxo.data_hash) { - datum = await this.getDatumByDatumHash(utxo.data_hash); - } else { - throw new Error("Cannot find datum of Stable Pool"); - } - const pool = new StablePool.State( - this.networkId, - utxo.address, - { txHash: utxo.tx_hash, index: utxo.output_index }, - utxo.amount, - datum - ); + const pool = await this.parseStablePoolState(utxo); pools.push(pool); } } catch (err) { @@ -406,15 +413,22 @@ export class BlockfrostAdapter { public async getStablePoolByLpAsset( lpAsset: Asset ): Promise { - const pool = (await this.getAllStablePools()).pools.find( - (pool) => pool.lpAsset === Asset.toString(lpAsset) + const config = StableswapConstant.CONFIG[this.networkId].find( + (cfg) => cfg.lpAsset === Asset.toString(lpAsset) ); invariant( - pool, - `getStablePoolByLpAsset: Can not find pool by LP Asset ${Asset.toString( + config, + `getStablePoolByLpAsset: Can not find stableswap config by LP Asset ${Asset.toString( lpAsset )}` ); + const poolUtxos = await this.api.addressesUtxosAssetAll( + config.poolAddress, + config.nftAsset + ); + invariant(poolUtxos.length === 1, `Can not find pool utxo in blockchain`); + const poolUtxo = poolUtxos[0]; + const pool = await this.parseStablePoolState(poolUtxo); return pool; } diff --git a/src/calculate.ts b/src/calculate.ts index 6e82c4f..afdf831 100644 --- a/src/calculate.ts +++ b/src/calculate.ts @@ -726,9 +726,6 @@ export namespace StableswapCalculation { feeDenominator, }: StableswapCalculateDepositOptions): bigint { const tempDatumBalances = [...datumBalances]; - const feeAmounts = tempDatumBalances.map((_) => 0n); - const assetVolumes = tempDatumBalances.map((_) => 0n); - const poolVolumes = tempDatumBalances.map((_) => 0n); const length = multiples.length; invariant( @@ -792,16 +789,12 @@ export namespace StableswapCalculation { const idealBalance = (d1 * oldBalance) / d0; let different = 0n; // In this case, liquidity pool has to swap the amount of other assets to get @different assets[i] - // Because the pool volumes is counted only for destination asset of the swap so we skip the "else" condition if (newBalance > idealBalance) { different = newBalance - idealBalance; - poolVolumes[i] = different; } else { different = idealBalance - newBalance; } - assetVolumes[i] = different; const tradingFeeAmount = (specialFee * different) / feeDenominator; - feeAmounts[i] = tradingFeeAmount; const adminFeeAmount = (tradingFeeAmount * adminFee) / feeDenominator; newDatumBalances.push(newBalance - adminFeeAmount); newDatBalancesWithTradingFee.push(newBalance - tradingFeeAmount); diff --git a/src/dex-v2.ts b/src/dex-v2.ts index 2890fe7..7c86a4c 100644 --- a/src/dex-v2.ts +++ b/src/dex-v2.ts @@ -68,7 +68,6 @@ export type CreatePoolV2Options = { export type BulkOrdersOption = { sender: Address; - customReceiver?: V2CustomReceiver; orderOptions: OrderOptions[]; expiredOptions?: OrderV2.ExpirySetting; availableUtxos: UTxO[]; @@ -182,6 +181,7 @@ export type OrderOptions = ( | MultiRoutingOptions ) & { lpAsset: Asset; + customReceiver?: V2CustomReceiver; }; export type CancelBulkOrdersOptions = { @@ -752,7 +752,6 @@ export class DexV2 { async createBulkOrdersTx({ sender, - customReceiver, orderOptions, expiredOptions, availableUtxos, @@ -786,7 +785,7 @@ export class DexV2 { }[] = []; for (let i = 0; i < orderOptions.length; i++) { const option = orderOptions[i]; - const { type, lpAsset } = option; + const { type, lpAsset, customReceiver } = option; const orderAssets = this.buildOrderValue(option); const orderStep = this.buildOrderStep(option, batcherFee); if (type === OrderV2.StepType.SWAP_EXACT_IN && option.isLimitOrder) { diff --git a/src/stableswap.ts b/src/stableswap.ts index b9f4c19..e278636 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -23,14 +23,7 @@ import { NetworkEnvironment, NetworkId } from "./types/network"; import { lucidToNetworkEnv } from "./utils/network.internal"; import { buildUtxoToStoreDatum } from "./utils/tx.internal"; -export type CommonOrderOptions = { - customReceiver?: V1AndStableswapCustomReceiver; - sender: Address; - availableUtxos: UTxO[]; - lpAsset: Asset; -}; - -export type SwapOptions = CommonOrderOptions & { +export type SwapOptions = { type: StableOrder.StepType.SWAP; assetIn: Asset; assetInAmount: bigint; @@ -39,38 +32,48 @@ export type SwapOptions = CommonOrderOptions & { minimumAssetOut: bigint; }; -export type DepositOptions = CommonOrderOptions & { +export type DepositOptions = { type: StableOrder.StepType.DEPOSIT; assetsAmount: [Asset, bigint][]; minimumLPReceived: bigint; totalLiquidity: bigint; }; -export type WithdrawOptions = CommonOrderOptions & { +export type WithdrawOptions = { type: StableOrder.StepType.WITHDRAW; lpAmount: bigint; minimumAmounts: bigint[]; }; -export type WithdrawImbalanceOptions = CommonOrderOptions & { +export type WithdrawImbalanceOptions = { type: StableOrder.StepType.WITHDRAW_IMBALANCE; lpAmount: bigint; withdrawAmounts: bigint[]; }; -export type ZapOutOptions = CommonOrderOptions & { +export type ZapOutOptions = { type: StableOrder.StepType.ZAP_OUT; lpAmount: bigint; assetOutIndex: bigint; minimumAssetOut: bigint; }; -export type OrderOptions = +export type OrderOptions = ( | DepositOptions | WithdrawOptions | SwapOptions | WithdrawImbalanceOptions - | ZapOutOptions; + | ZapOutOptions +) & { + lpAsset: Asset; + customReceiver?: V1AndStableswapCustomReceiver; +}; + +export type BulkOrdersOption = { + options: OrderOptions[]; + sender: Address; + availableUtxos: UTxO[]; +}; export type BuildCancelOrderOptions = { orderUtxos: UTxO[]; @@ -273,60 +276,87 @@ export class Stableswap { } } - async buildCreateTx(options: OrderOptions): Promise { - const { sender, availableUtxos, lpAsset, customReceiver } = options; - const config = StableswapConstant.getConfigByLpAsset( - lpAsset, - this.networkId + async buildCreateTx(options: BulkOrdersOption): Promise { + const { sender, availableUtxos, options: orderOptions } = options; + + invariant( + orderOptions.length > 0, + "Stableswap.buildCreateTx: Need at least 1 order to build" ); - const orderAssets = this.buildOrderValue(options); - const step = this.buildOrderStep(options); + // calculate total order value + const totalOrderAssets: Record = {}; + for (const option of orderOptions) { + const orderAssets = this.buildOrderValue(option); + for (const [asset, amt] of Object.entries(orderAssets)) { + if (asset in totalOrderAssets) { + totalOrderAssets[asset] += amt; + } else { + totalOrderAssets[asset] = amt; + } + } + } + + // calculate batcher fee const { batcherFee, reductionAssets } = calculateBatcherFee({ utxos: availableUtxos, - orderAssets, + orderAssets: totalOrderAssets, networkEnv: this.networkEnv, dexVersion: this.dexVersion, }); - if ("lovelace" in orderAssets) { - orderAssets["lovelace"] += batcherFee; - } else { - orderAssets["lovelace"] = batcherFee; - } - const datum: StableOrder.Datum = { - sender: sender, - receiver: customReceiver ? customReceiver.receiver : sender, - receiverDatumHash: customReceiver?.receiverDatum?.hash, - step: step, - batcherFee: batcherFee, - depositADA: FIXED_DEPOSIT_ADA, - }; - const tx = this.lucid - .newTx() - .payToContract( + + const tx = this.lucid.newTx(); + for (const orderOption of orderOptions) { + const config = StableswapConstant.getConfigByLpAsset( + orderOption.lpAsset, + this.networkId + ); + const { customReceiver } = orderOption; + const orderAssets = this.buildOrderValue(orderOption); + const step = this.buildOrderStep(orderOption); + if ("lovelace" in orderAssets) { + orderAssets["lovelace"] += batcherFee; + } else { + orderAssets["lovelace"] = batcherFee; + } + const datum: StableOrder.Datum = { + sender: sender, + receiver: customReceiver ? customReceiver.receiver : sender, + receiverDatumHash: customReceiver?.receiverDatum?.hash, + step: step, + batcherFee: batcherFee, + depositADA: FIXED_DEPOSIT_ADA, + }; + tx.payToContract( config.orderAddress, { inline: Data.to(StableOrder.Datum.toPlutusData(datum)), }, orderAssets - ) - .payToAddress(sender, reductionAssets) - .addSigner(sender) - .attachMetadata(674, { msg: [this.getOrderMetadata(options)] }); - if (customReceiver && customReceiver.receiverDatum) { - const utxoForStoringDatum = buildUtxoToStoreDatum( - this.lucid, - sender, - customReceiver.receiver, - customReceiver.receiverDatum.datum - ); - if (utxoForStoringDatum) { - tx.payToAddressWithData( - utxoForStoringDatum.address, - utxoForStoringDatum.outputData, - utxoForStoringDatum.assets + ).payToAddress(sender, reductionAssets); + + if (customReceiver && customReceiver.receiverDatum) { + const utxoForStoringDatum = buildUtxoToStoreDatum( + this.lucid, + sender, + customReceiver.receiver, + customReceiver.receiverDatum.datum ); + if (utxoForStoringDatum) { + tx.payToAddressWithData( + utxoForStoringDatum.address, + utxoForStoringDatum.outputData, + utxoForStoringDatum.assets + ); + } } } + tx.attachMetadata(674, { + msg: [ + orderOptions.length > 1 + ? MetadataMessage.MIXED_ORDERS + : this.getOrderMetadata(orderOptions[0]), + ], + }); return await tx.complete(); } From 402e324f5a2eecd71e689f8a9776dbd8b81aadb8 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 17:35:52 +0700 Subject: [PATCH 09/16] more example --- examples/example.ts | 51 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index ef66f10..678b8bf 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -32,8 +32,8 @@ import { StableswapCalculation, StableswapConstant, } from "../src"; -import { Slippage } from "../src/utils/slippage.internal"; import { Stableswap } from "../src/stableswap"; +import { Slippage } from "../src/utils/slippage.internal"; const MIN: Asset = { policyId: "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72", @@ -74,7 +74,6 @@ async function main(): Promise { .signWithPrivateKey("") .complete(); const txId = await signedTx.submit(); - // eslint-disable-next-line no-console console.info(`Transaction submitted successfully: ${txId}`); } @@ -1070,15 +1069,59 @@ async function _zapOutStableExample( }); } +async function _bulkOrderStableExample( + lucid: Lucid, + address: Address, + availableUtxos: UTxO[] +): Promise { + const lpAsset = Asset.fromString( + "d16339238c9e1fb4d034b6a48facb2f97794a9cdb7bc049dd7c49f54646a65642d697573642d76312e342d6c70" + ); + const lpAmount = 12345n; + const outIndex = 0; + const config = StableswapConstant.getConfigByLpAsset( + lpAsset, + NetworkId.TESTNET + ); + + return new Stableswap(lucid).buildCreateTx({ + sender: address, + availableUtxos: availableUtxos, + options: [ + { + lpAsset: lpAsset, + type: StableOrder.StepType.ZAP_OUT, + lpAmount: lpAmount, + assetOutIndex: BigInt(outIndex), + minimumAssetOut: 1n, + }, + { + lpAsset: lpAsset, + type: StableOrder.StepType.SWAP, + assetIn: Asset.fromString(config.assets[0]), + assetInAmount: 1000n, + assetInIndex: 0n, + assetOutIndex: 1n, + minimumAssetOut: 1n, + }, + ], + }); +} + async function _cancelStableExample(lucid: Lucid): Promise { const orderUtxos = await lucid.utxosByOutRef([ { txHash: - "f1c873201c3638860dc7d831a2266a7b0f46e48674da27fd0bcd1dc0c3835889", + "c3ad8e0aa159a22a14088474908e5c23ba6772a6aa82f8250e7e8eaa1016b2d8", + outputIndex: 0, + }, + { + txHash: + "72e57a1fd90bf0b9291a6fa8e04793099d51df7844813689dde67ce3eea03c1f", outputIndex: 0, }, ]); - invariant(orderUtxos.length === 1, "Can not find order to cancel"); + invariant(orderUtxos.length === 2, "Can not find order to cancel"); return new Stableswap(lucid).buildCancelOrdersTx({ orderUtxos: orderUtxos, }); From e42bf9292b0edbd8838cd6fd1e1f097f90ef1275 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 17:38:57 +0700 Subject: [PATCH 10/16] remove unused variable --- src/stableswap.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/stableswap.ts b/src/stableswap.ts index e278636..3e11ad5 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -25,7 +25,6 @@ import { buildUtxoToStoreDatum } from "./utils/tx.internal"; export type SwapOptions = { type: StableOrder.StepType.SWAP; - assetIn: Asset; assetInAmount: bigint; assetInIndex: bigint; assetOutIndex: bigint; @@ -121,9 +120,13 @@ export class Stableswap { break; } case StableOrder.StepType.SWAP: { - const { assetInAmount, assetIn } = option; + const { assetInAmount, assetInIndex, lpAsset } = option; + const poolConfig = StableswapConstant.getConfigByLpAsset( + lpAsset, + this.networkId + ); invariant(assetInAmount > 0n, "asset in amount must be positive"); - orderAssets[Asset.toString(assetIn)] = assetInAmount; + orderAssets[poolConfig.assets[Number(assetInIndex)]] = assetInAmount; break; } case StableOrder.StepType.WITHDRAW: From 0fca8cc88f0424eb52423d44d1b28b79ddcb934f Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 17 Sep 2024 17:41:00 +0700 Subject: [PATCH 11/16] fix build --- examples/example.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index 678b8bf..ecdb79c 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -885,7 +885,6 @@ async function _swapStableExample( { lpAsset: lpAsset, type: StableOrder.StepType.SWAP, - assetIn: Asset.fromString(config.assets[0]), assetInAmount: swapAmount, assetInIndex: 0n, assetOutIndex: 1n, @@ -1079,10 +1078,6 @@ async function _bulkOrderStableExample( ); const lpAmount = 12345n; const outIndex = 0; - const config = StableswapConstant.getConfigByLpAsset( - lpAsset, - NetworkId.TESTNET - ); return new Stableswap(lucid).buildCreateTx({ sender: address, @@ -1098,7 +1093,6 @@ async function _bulkOrderStableExample( { lpAsset: lpAsset, type: StableOrder.StepType.SWAP, - assetIn: Asset.fromString(config.assets[0]), assetInAmount: 1000n, assetInIndex: 0n, assetOutIndex: 1n, From 75046883fb254364dab1db647b227b83c1686035 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 18 Sep 2024 11:10:16 +0700 Subject: [PATCH 12/16] rename function+check empty value --- examples/example.ts | 12 ++++++------ src/stableswap.ts | 7 +++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index ecdb79c..2c6ab11 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -878,7 +878,7 @@ async function _swapStableExample( feeDenominator: config.feeDenominator, }); - return new Stableswap(lucid).buildCreateTx({ + return new Stableswap(lucid).createBulkOrdersTx({ sender: address, availableUtxos: availableUtxos, options: [ @@ -923,7 +923,7 @@ async function _depositStableExample( feeDenominator: config.feeDenominator, }); - return new Stableswap(lucid).buildCreateTx({ + return new Stableswap(lucid).createBulkOrdersTx({ sender: address, availableUtxos: availableUtxos, options: [ @@ -966,7 +966,7 @@ async function _withdrawStableExample( totalLiquidity: pool.totalLiquidity, }); - return new Stableswap(lucid).buildCreateTx({ + return new Stableswap(lucid).createBulkOrdersTx({ sender: address, availableUtxos: availableUtxos, options: [ @@ -1009,7 +1009,7 @@ async function _withdrawImbalanceStableExample( feeDenominator: config.feeDenominator, }); - return new Stableswap(lucid).buildCreateTx({ + return new Stableswap(lucid).createBulkOrdersTx({ sender: address, availableUtxos: availableUtxos, options: [ @@ -1053,7 +1053,7 @@ async function _zapOutStableExample( feeDenominator: config.feeDenominator, }); - return new Stableswap(lucid).buildCreateTx({ + return new Stableswap(lucid).createBulkOrdersTx({ sender: address, availableUtxos: availableUtxos, options: [ @@ -1079,7 +1079,7 @@ async function _bulkOrderStableExample( const lpAmount = 12345n; const outIndex = 0; - return new Stableswap(lucid).buildCreateTx({ + return new Stableswap(lucid).createBulkOrdersTx({ sender: address, availableUtxos: availableUtxos, options: [ diff --git a/src/stableswap.ts b/src/stableswap.ts index 3e11ad5..a1d482e 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -279,7 +279,7 @@ export class Stableswap { } } - async buildCreateTx(options: BulkOrdersOption): Promise { + async createBulkOrdersTx(options: BulkOrdersOption): Promise { const { sender, availableUtxos, options: orderOptions } = options; invariant( @@ -335,7 +335,10 @@ export class Stableswap { inline: Data.to(StableOrder.Datum.toPlutusData(datum)), }, orderAssets - ).payToAddress(sender, reductionAssets); + ); + if (Object.keys(reductionAssets).length !== 0) { + tx.payToAddress(sender, reductionAssets); + } if (customReceiver && customReceiver.receiverDatum) { const utxoForStoringDatum = buildUtxoToStoreDatum( From 7e5ba9cca39494b714e93884e24b28e8c746a627 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 18 Sep 2024 15:42:19 +0700 Subject: [PATCH 13/16] add comment --- examples/example.ts | 9 +++++++++ src/calculate.ts | 31 +++++++++++++++++++++++++++++++ src/stableswap.ts | 7 +++++++ 3 files changed, 47 insertions(+) diff --git a/examples/example.ts b/examples/example.ts index 2c6ab11..5db30a7 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -866,6 +866,9 @@ async function _swapStableExample( const swapAmount = 1_000n; + // This pool have 2 assets in config. They are [tDJED, tiUSD]. + // Index-0 Asset is tDJED. Index-1 Asset is tiUSD. + // This order swap 1_000n tDJED to ... tiUSD. const amountOut = StableswapCalculation.calculateSwapAmount({ inIndex: 0, outIndex: 1, @@ -910,6 +913,8 @@ async function _depositStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + // This pool have 2 assets in config. They are [tDJED, tiUSD]. + // This order deposit 100_000n tDJED and 1_000n tiUSD to pool. const amountIns = [100_000n, 1_000n]; const lpAmount = StableswapCalculation.calculateDeposit({ @@ -998,6 +1003,8 @@ async function _withdrawImbalanceStableExample( const withdrawAmounts = [1234n, 5678n]; + // This pool have 2 assets in config. They are [tDJED, tiUSD]. + // This order withdraw exactly 1234n tDJED and 5678n tiUSD from pool. const lpAmount = StableswapCalculation.calculateWithdrawImbalance({ withdrawAmounts: withdrawAmounts, totalLiquidity: pool.totalLiquidity, @@ -1039,6 +1046,8 @@ async function _zapOutStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + // This pool have 2 assets in config. They are [tDJED, tiUSD]. + // This order withdraw xxx tiUSD by 12345 Lp Asset from pool. const lpAmount = 12345n; const outIndex = 0; const amountOut = StableswapCalculation.calculateZapOut({ diff --git a/src/calculate.ts b/src/calculate.ts index afdf831..d7aa73d 100644 --- a/src/calculate.ts +++ b/src/calculate.ts @@ -627,6 +627,11 @@ export namespace StableswapCalculation { feeDenominator: bigint; }; + /** + * @property {number} inIndex - index of asset in config assets that you want to swap + * @property {bigint} amountIn - amount of asset that you want to swap + * @property {number} outIndex - index of asset in config assets that you want to receive + */ export type StableswapCalculateSwapOptions = CommonStableswapCalculationOptions & { inIndex: number; @@ -634,6 +639,10 @@ export namespace StableswapCalculation { amountIn: bigint; }; + /** + * @property {bigint[]} amountIns - amount of assets that you want to deposit ordering by assets in config + * @property {bigint} totalLiquidity - amount of asset that you want to swap + */ export type StableswapCalculateDepositOptions = CommonStableswapCalculationOptions & { amountIns: bigint[]; @@ -648,12 +657,19 @@ export namespace StableswapCalculation { totalLiquidity: bigint; }; + /** + * @property {bigint[]} withdrawAmounts - exactly amount of assets that you want to withdraw ordering by assets in config + */ export type StableswapCalculateWithdrawImbalanceOptions = CommonStableswapCalculationOptions & { withdrawAmounts: bigint[]; totalLiquidity: bigint; }; + /** + * @property {bigint} amountLpIn - exactly LP amount that you want to withdraw + * @property {number} outIndex - index of asset that you want to zap out in config assets + */ export type StableswapCalculateZapOutOptions = CommonStableswapCalculationOptions & { amountLpIn: bigint; @@ -661,6 +677,9 @@ export namespace StableswapCalculation { totalLiquidity: bigint; }; + /** + * @returns amount of asset that you want to receive. + */ export function calculateSwapAmount({ inIndex, outIndex, @@ -715,6 +734,9 @@ export namespace StableswapCalculation { return amountOut; } + /** + * @returns amount of liquidity asset you receive. + */ export function calculateDeposit({ amountIns, amp, @@ -816,6 +838,9 @@ export namespace StableswapCalculation { return lpAmount; } + /** + * @returns amounts of asset you can receive ordering by config assets + */ export function calculateWithdraw({ withdrawalLPAmount, multiples, @@ -848,6 +873,9 @@ export namespace StableswapCalculation { return amountOuts; } + /** + * @returns lp asset amount you need to provide to receive exactly amount of assets in the pool + */ export function calculateWithdrawImbalance({ withdrawAmounts, amp, @@ -929,6 +957,9 @@ export namespace StableswapCalculation { return lpAmount; } + /** + * @returns amount asset amount you want receive + */ export function calculateZapOut({ amountLpIn, outIndex, diff --git a/src/stableswap.ts b/src/stableswap.ts index a1d482e..57f4512 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -23,6 +23,10 @@ import { NetworkEnvironment, NetworkId } from "./types/network"; import { lucidToNetworkEnv } from "./utils/network.internal"; import { buildUtxoToStoreDatum } from "./utils/tx.internal"; +/** + * @property {bigint} assetInIndex - Index of asset you want to swap in config assets + * @property {bigint} assetOutIndex - Index of asset you want to receive in config assets + */ export type SwapOptions = { type: StableOrder.StepType.SWAP; assetInAmount: bigint; @@ -50,6 +54,9 @@ export type WithdrawImbalanceOptions = { withdrawAmounts: bigint[]; }; +/** + * @property {bigint} assetOutIndex - Index of asset you want to receive in config assets + */ export type ZapOutOptions = { type: StableOrder.StepType.ZAP_OUT; lpAmount: bigint; From ad82e017207634b738798f4bab7a4ea8fc059095 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 18 Sep 2024 16:24:07 +0700 Subject: [PATCH 14/16] fix duplicated paying reductionAssets --- src/stableswap.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stableswap.ts b/src/stableswap.ts index 57f4512..3198fbb 100644 --- a/src/stableswap.ts +++ b/src/stableswap.ts @@ -343,9 +343,6 @@ export class Stableswap { }, orderAssets ); - if (Object.keys(reductionAssets).length !== 0) { - tx.payToAddress(sender, reductionAssets); - } if (customReceiver && customReceiver.receiverDatum) { const utxoForStoringDatum = buildUtxoToStoreDatum( @@ -363,6 +360,9 @@ export class Stableswap { } } } + if (Object.keys(reductionAssets).length !== 0) { + tx.payToAddress(sender, reductionAssets); + } tx.attachMetadata(674, { msg: [ orderOptions.length > 1 From 4fce42b48e3bda250d71b8f36f108ed694b52567 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 18 Sep 2024 16:32:22 +0700 Subject: [PATCH 15/16] grammar --- examples/example.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index 5db30a7..c793ade 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -866,9 +866,9 @@ async function _swapStableExample( const swapAmount = 1_000n; - // This pool have 2 assets in config. They are [tDJED, tiUSD]. + // This pool has 2 assets in its config. They are [tDJED, tiUSD]. // Index-0 Asset is tDJED. Index-1 Asset is tiUSD. - // This order swap 1_000n tDJED to ... tiUSD. + // This order swaps 1_000n tDJED to ... tiUSD. const amountOut = StableswapCalculation.calculateSwapAmount({ inIndex: 0, outIndex: 1, @@ -913,8 +913,8 @@ async function _depositStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); - // This pool have 2 assets in config. They are [tDJED, tiUSD]. - // This order deposit 100_000n tDJED and 1_000n tiUSD to pool. + // This pool has 2 assets in its config. They are [tDJED, tiUSD]. + // This order deposits 100_000n tDJED and 1_000n tiUSD into the pool. const amountIns = [100_000n, 1_000n]; const lpAmount = StableswapCalculation.calculateDeposit({ @@ -1003,8 +1003,8 @@ async function _withdrawImbalanceStableExample( const withdrawAmounts = [1234n, 5678n]; - // This pool have 2 assets in config. They are [tDJED, tiUSD]. - // This order withdraw exactly 1234n tDJED and 5678n tiUSD from pool. + // This pool has 2 assets in its config. They are [tDJED, tiUSD]. + // This order withdraws exactly 1234n tDJED and 5678n tiUSD from the pool. const lpAmount = StableswapCalculation.calculateWithdrawImbalance({ withdrawAmounts: withdrawAmounts, totalLiquidity: pool.totalLiquidity, @@ -1046,8 +1046,8 @@ async function _zapOutStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); - // This pool have 2 assets in config. They are [tDJED, tiUSD]. - // This order withdraw xxx tiUSD by 12345 Lp Asset from pool. + // This pool has 2 assets in its config. They are [tDJED, tiUSD]. + // This order withdraws xxx tiUSD by 12345 Lp Assets from the pool. const lpAmount = 12345n; const outIndex = 0; const amountOut = StableswapCalculation.calculateZapOut({ From 86a125d1fb47e92733f777331533335349efd483 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 18 Sep 2024 17:48:31 +0700 Subject: [PATCH 16/16] write test and fix return type of getStablePoolByLpAsset --- examples/example.ts | 10 ++++++++++ src/adapter.ts | 34 ++++++++++------------------------ test/adapter.test.ts | 23 +++++++++++++++++++++++ 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/examples/example.ts b/examples/example.ts index c793ade..fe80136 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -864,6 +864,8 @@ async function _swapStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + invariant(pool, `Can not find pool by lp asset ${Asset.toString(lpAsset)}`); + const swapAmount = 1_000n; // This pool has 2 assets in its config. They are [tDJED, tiUSD]. @@ -913,6 +915,8 @@ async function _depositStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + invariant(pool, `Can not find pool by lp asset ${Asset.toString(lpAsset)}`); + // This pool has 2 assets in its config. They are [tDJED, tiUSD]. // This order deposits 100_000n tDJED and 1_000n tiUSD into the pool. const amountIns = [100_000n, 1_000n]; @@ -962,6 +966,8 @@ async function _withdrawStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + invariant(pool, `Can not find pool by lp asset ${Asset.toString(lpAsset)}`); + const lpAmount = 10_000n; const amountOuts = StableswapCalculation.calculateWithdraw({ @@ -1001,6 +1007,8 @@ async function _withdrawImbalanceStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + invariant(pool, `Can not find pool by lp asset ${Asset.toString(lpAsset)}`); + const withdrawAmounts = [1234n, 5678n]; // This pool has 2 assets in its config. They are [tDJED, tiUSD]. @@ -1046,6 +1054,8 @@ async function _zapOutStableExample( const pool = await blockfrostAdapter.getStablePoolByLpAsset(lpAsset); + invariant(pool, `Can not find pool by lp asset ${Asset.toString(lpAsset)}`); + // This pool has 2 assets in its config. They are [tDJED, tiUSD]. // This order withdraws xxx tiUSD by 12345 Lp Assets from the pool. const lpAmount = 12345n; diff --git a/src/adapter.ts b/src/adapter.ts index 1ca427d..3b97280 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -412,7 +412,7 @@ export class BlockfrostAdapter { public async getStablePoolByLpAsset( lpAsset: Asset - ): Promise { + ): Promise { const config = StableswapConstant.CONFIG[this.networkId].find( (cfg) => cfg.lpAsset === Asset.toString(lpAsset) ); @@ -426,10 +426,11 @@ export class BlockfrostAdapter { config.poolAddress, config.nftAsset ); - invariant(poolUtxos.length === 1, `Can not find pool utxo in blockchain`); - const poolUtxo = poolUtxos[0]; - const pool = await this.parseStablePoolState(poolUtxo); - return pool; + if (poolUtxos.length === 1) { + const poolUtxo = poolUtxos[0]; + return await this.parseStablePoolState(poolUtxo); + } + return null; } public async getStablePoolByNFT( @@ -443,29 +444,14 @@ export class BlockfrostAdapter { `Cannot find Stable Pool having NFT ${Asset.toString(nft)}` ); } - const utxos = await this.api.addressesUtxosAssetAll( + const poolUtxos = await this.api.addressesUtxosAssetAll( poolAddress, Asset.toString(nft) ); - for (const utxo of utxos) { - let datum: string; - if (utxo.inline_datum) { - datum = utxo.inline_datum; - } else if (utxo.data_hash) { - datum = await this.getDatumByDatumHash(utxo.data_hash); - } else { - throw new Error("Cannot find datum of Stable Pool"); - } - const pool = new StablePool.State( - this.networkId, - utxo.address, - { txHash: utxo.tx_hash, index: utxo.output_index }, - utxo.amount, - datum - ); - return pool; + if (poolUtxos.length === 1) { + const poolUtxo = poolUtxos[0]; + return await this.parseStablePoolState(poolUtxo); } - return null; } diff --git a/test/adapter.test.ts b/test/adapter.test.ts index fc0a220..87f2b95 100644 --- a/test/adapter.test.ts +++ b/test/adapter.test.ts @@ -142,6 +142,29 @@ test("getAllStablePools", async () => { expect(mainnetPools.length === numberOfStablePoolsMainnet); }); +test("getStablePoolByLPAsset", async () => { + const testnetCfgs = StableswapConstant.CONFIG[NetworkId.TESTNET]; + const mainnetCfgs = StableswapConstant.CONFIG[NetworkId.MAINNET]; + + for (const cfg of testnetCfgs) { + const pool = await adapterTestnet.getStablePoolByLpAsset( + Asset.fromString(cfg.lpAsset) + ); + expect(pool).not.toBeNull(); + expect(pool?.nft).toEqual(cfg.nftAsset); + expect(pool?.assets).toEqual(cfg.assets); + } + + for (const cfg of mainnetCfgs) { + const pool = await adapterMainnet.getStablePoolByLpAsset( + Asset.fromString(cfg.lpAsset) + ); + expect(pool).not.toBeNull(); + expect(pool?.nft).toEqual(cfg.nftAsset); + expect(pool?.assets).toEqual(cfg.assets); + } +}); + test("getStablePoolByNFT", async () => { const testnetCfgs = StableswapConstant.CONFIG[NetworkId.TESTNET]; const mainnetCfgs = StableswapConstant.CONFIG[NetworkId.MAINNET];