From e9cefe355d527c3f59a24cdfecdb3dddbbf91ee6 Mon Sep 17 00:00:00 2001 From: h2physics Date: Thu, 4 Jul 2024 10:54:16 +0700 Subject: [PATCH 1/3] Integrate Stableswap and DEX V2 Liquidity Pool --- package-lock.json | 72 ++++- package.json | 3 +- src/adapter.ts | 253 ++++++++++++++++-- src/constants.ts | 52 ---- src/dex.ts | 55 ++-- src/index.ts | 2 +- src/types/constants.ts | 427 ++++++++++++++++++++++++++++++ src/types/order.ts | 458 ++++++++++++++++---------------- src/types/pool.internal.ts | 7 +- src/types/pool.ts | 526 +++++++++++++++++++++++++++---------- src/types/tx.internal.ts | 8 + src/utils/hash.internal.ts | 7 + test/adapter.test.ts | 135 ++++++++-- test/order.test.ts | 84 +++--- test/pool.test.ts | 18 +- 15 files changed, 1556 insertions(+), 551 deletions(-) delete mode 100644 src/constants.ts create mode 100644 src/types/constants.ts create mode 100644 src/utils/hash.internal.ts diff --git a/package-lock.json b/package-lock.json index 149e140..2c63be0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@blockfrost/blockfrost-js": "^5.3.1", "@minswap/tiny-invariant": "^1.2.0", "big.js": "^6.1.1", - "lucid-cardano": "^0.10.1" + "lucid-cardano": "^0.10.1", + "sha3": "^2.1.4" }, "devDependencies": { "@types/big.js": "^6.1.3", @@ -2066,6 +2067,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bech32": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", @@ -2185,6 +2205,29 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3877,6 +3920,25 @@ "node": ">=10.17.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -6253,6 +6315,14 @@ "node": ">=10" } }, + "node_modules/sha3": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", + "dependencies": { + "buffer": "6.0.3" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index c142f11..68fa9eb 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "@blockfrost/blockfrost-js": "^5.3.1", "@minswap/tiny-invariant": "^1.2.0", "big.js": "^6.1.1", - "lucid-cardano": "^0.10.1" + "lucid-cardano": "^0.10.1", + "sha3": "^2.1.4" }, "devDependencies": { "@types/big.js": "^6.1.3", diff --git a/src/adapter.ts b/src/adapter.ts index 99f4300..6182f6e 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -6,12 +6,24 @@ import { PaginationOptions } from "@blockfrost/blockfrost-js/lib/types"; import invariant from "@minswap/tiny-invariant"; import Big from "big.js"; -import { POOL_NFT_POLICY_ID, POOL_SCRIPT_HASH } from "./constants"; -import { PoolHistory, PoolState } from "./types/pool"; -import { checkValidPoolOutput, isValidPoolOutput } from "./types/pool.internal"; +import { Asset } from "./types/asset"; +import { + DexV1Constant, + DexV2Constant, + StableswapConstant, +} from "./types/constants"; +import { NetworkId } from "./types/network"; +import { PoolV1, PoolV2, StablePool } from "./types/pool"; +import { + checkValidPoolOutput, + isValidPoolOutput, + normalizeAssets, +} from "./types/pool.internal"; +import { TxHistory } from "./types/tx.internal"; import { getScriptHashFromAddress } from "./utils/address-utils.internal"; export type BlockfrostAdapterOptions = { + networkId: NetworkId; blockFrost: BlockFrostAPI; }; @@ -24,7 +36,7 @@ export type GetPoolByIdParams = { }; export type GetPoolPriceParams = { - pool: PoolState; + pool: PoolV1.State; decimalsA?: number; decimalsB?: number; }; @@ -37,26 +49,36 @@ export type GetPoolInTxParams = { txHash: string; }; +export type GetStablePoolInTxParams = { + networkId: NetworkId; + txHash: string; +}; + export class BlockfrostAdapter { private readonly api: BlockFrostAPI; + private readonly networkId: NetworkId; - constructor({ blockFrost }: BlockfrostAdapterOptions) { + constructor({ networkId, blockFrost }: BlockfrostAdapterOptions) { + this.networkId = networkId; this.api = blockFrost; } /** * @returns The latest pools or empty array if current page is after last page */ - public async getPools({ + public async getV1Pools({ page, count = 100, order = "asc", - }: GetPoolsParams): Promise { - const utxos = await this.api.addressesUtxos(POOL_SCRIPT_HASH, { - count, - order, - page, - }); + }: GetPoolsParams): Promise { + const utxos = await this.api.addressesUtxos( + DexV1Constant.POOL_SCRIPT_HASH, + { + count, + order, + page, + } + ); return utxos .filter((utxo) => isValidPoolOutput(utxo.address, utxo.amount, utxo.data_hash) @@ -66,7 +88,7 @@ export class BlockfrostAdapter { utxo.data_hash, `expect pool to have datum hash, got ${utxo.data_hash}` ); - return new PoolState( + return new PoolV1.State( utxo.address, { txHash: utxo.tx_hash, index: utxo.output_index }, utxo.amount, @@ -79,12 +101,12 @@ export class BlockfrostAdapter { * Get a specific pool by its ID. * @param {Object} params - The parameters. * @param {string} params.pool - The pool ID. This is the asset name of a pool's NFT and LP tokens. It can also be acquired by calling pool.id. - * @returns {PoolState | null} - Returns the pool or null if not found. + * @returns {PoolV1.State | null} - Returns the pool or null if not found. */ - public async getPoolById({ + public async getV1PoolById({ id, - }: GetPoolByIdParams): Promise { - const nft = `${POOL_NFT_POLICY_ID}${id}`; + }: GetPoolByIdParams): Promise { + const nft = `${DexV1Constant.POOL_NFT_POLICY_ID}${id}`; const nftTxs = await this.api.assetsTransactions(nft, { count: 1, page: 1, @@ -93,23 +115,23 @@ export class BlockfrostAdapter { if (nftTxs.length === 0) { return null; } - return this.getPoolInTx({ txHash: nftTxs[0].tx_hash }); + return this.getV1PoolInTx({ txHash: nftTxs[0].tx_hash }); } - public async getPoolHistory({ + public async getV1PoolHistory({ id, page = 1, count = 100, order = "desc", - }: GetPoolHistoryParams): Promise { - const nft = `${POOL_NFT_POLICY_ID}${id}`; + }: GetPoolHistoryParams): Promise { + const nft = `${DexV1Constant.POOL_NFT_POLICY_ID}${id}`; const nftTxs = await this.api.assetsTransactions(nft, { count, page, order, }); return nftTxs.map( - (tx): PoolHistory => ({ + (tx): TxHistory => ({ txHash: tx.tx_hash, txIndex: tx.tx_index, blockHeight: tx.block_height, @@ -122,14 +144,15 @@ export class BlockfrostAdapter { * Get pool state in a transaction. * @param {Object} params - The parameters. * @param {string} params.txHash - The transaction hash containing pool output. One of the way to acquire is by calling getPoolHistory. - * @returns {PoolState} - Returns the pool state or null if the transaction doesn't contain pool. + * @returns {PoolV1.State} - Returns the pool state or null if the transaction doesn't contain pool. */ - public async getPoolInTx({ + public async getV1PoolInTx({ txHash, - }: GetPoolInTxParams): Promise { + }: GetPoolInTxParams): Promise { const poolTx = await this.api.txsUtxos(txHash); const poolUtxo = poolTx.outputs.find( - (o) => getScriptHashFromAddress(o.address) === POOL_SCRIPT_HASH + (o) => + getScriptHashFromAddress(o.address) === DexV1Constant.POOL_SCRIPT_HASH ); if (!poolUtxo) { return null; @@ -139,7 +162,7 @@ export class BlockfrostAdapter { poolUtxo.data_hash, `expect pool to have datum hash, got ${poolUtxo.data_hash}` ); - return new PoolState( + return new PoolV1.State( poolUtxo.address, { txHash: txHash, index: poolUtxo.output_index }, poolUtxo.amount, @@ -170,7 +193,7 @@ export class BlockfrostAdapter { * @param {string} [params.decimalsB] - The decimals of assetB in pool, if undefined then query from Blockfrost. * @returns {[string, string]} - Returns a pair of asset A/B price and B/A price, adjusted to decimals. */ - public async getPoolPrice({ + public async getV1PoolPrice({ pool, decimalsA, decimalsB, @@ -196,4 +219,178 @@ export class BlockfrostAdapter { const scriptsDatum = await this.api.scriptsDatumCbor(datumHash); return scriptsDatum.cbor; } + + public async getAllV2Pools(): Promise<{ + pools: PoolV2.State[]; + errors: unknown[]; + }> { + const v2Config = DexV2Constant.CONFIG[this.networkId]; + const utxos = await this.api.addressesUtxosAssetAll( + v2Config.poolScriptHashBech32, + v2Config.poolAuthenAsset + ); + + const pools: PoolV2.State[] = []; + const errors: unknown[] = []; + for (const utxo of utxos) { + try { + if (!utxo.inline_datum) { + throw new Error(`Cannot find datum of Pool V2, tx: ${utxo.tx_hash}`); + } + const pool = new PoolV2.State( + this.networkId, + utxo.address, + { txHash: utxo.tx_hash, index: utxo.output_index }, + utxo.amount, + utxo.inline_datum + ); + pools.push(pool); + } catch (err) { + errors.push(err); + } + } + return { + pools: pools, + errors: errors, + }; + } + + public async getV2Pools({ + page, + count = 100, + order = "asc", + }: GetPoolsParams): Promise<{ + pools: PoolV2.State[]; + errors: unknown[]; + }> { + const v2Config = DexV2Constant.CONFIG[this.networkId]; + const utxos = await this.api.addressesUtxosAsset( + v2Config.poolScriptHashBech32, + v2Config.poolAuthenAsset, + { + count, + order, + page, + } + ); + + const pools: PoolV2.State[] = []; + const errors: unknown[] = []; + for (const utxo of utxos) { + try { + if (!utxo.inline_datum) { + throw new Error(`Cannot find datum of Pool V2, tx: ${utxo.tx_hash}`); + } + const pool = new PoolV2.State( + this.networkId, + utxo.address, + { txHash: utxo.tx_hash, index: utxo.output_index }, + utxo.amount, + utxo.inline_datum + ); + pools.push(pool); + } catch (err) { + errors.push(err); + } + } + return { + pools: pools, + errors: errors, + }; + } + + public async getV2PoolByPair( + assetA: Asset, + assetB: Asset + ): Promise { + const [normalizedAssetA, normalizedAssetB] = normalizeAssets( + Asset.toString(assetA), + Asset.toString(assetB) + ); + const { pools: allPools } = await this.getAllV2Pools(); + return ( + allPools.find( + (pool) => + pool.assetA === normalizedAssetA && pool.assetB === normalizedAssetB + ) ?? null + ); + } + + public async getAllStablePools(): Promise<{ + pools: StablePool.State[]; + errors: unknown[]; + }> { + const poolAddresses = StableswapConstant.CONFIG[this.networkId].map( + (cfg) => cfg.poolAddress + ); + const pools: StablePool.State[] = []; + const errors: unknown[] = []; + for (const poolAddr of poolAddresses) { + 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 + ); + pools.push(pool); + } + } catch (err) { + errors.push(err); + } + } + + return { + pools: pools, + errors: errors, + }; + } + + public async getStablePoolByNFT( + nft: Asset + ): Promise { + const poolAddress = StableswapConstant.CONFIG[this.networkId].find( + (cfg) => cfg.nftAsset === Asset.toString(nft) + )?.poolAddress; + if (!poolAddress) { + throw new Error( + `Cannot find Stable Pool having NFT ${Asset.toString(nft)}` + ); + } + const utxos = 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; + } + + return null; + } } diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index b241032..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Address, Script } from "lucid-cardano"; - -import { NetworkId } from "./types/network"; - -export const ORDER_BASE_ADDRESS: Record = { - [NetworkId.TESTNET]: - "addr_test1zzn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uwurajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upq932hcy", - [NetworkId.MAINNET]: - "addr1zxn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uw6j2c79gy9l76sdg0xwhd7r0c0kna0tycz4y5s6mlenh8pq6s3z70", -}; - -export const POOL_SCRIPT_HASH = - "script1uychk9f04tqngfhx4qlqdlug5ntzen3uzc62kzj7cyesjk0d9me"; - -export const FACTORY_POLICY_ID = - "13aa2accf2e1561723aa26871e071fdf32c867cff7e7d50ad470d62f"; -export const FACTORY_ASSET_NAME = "4d494e53574150"; -export const LP_POLICY_ID = - "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86"; -export const POOL_NFT_POLICY_ID = - "0be55d262b29f564998ff81efe21bdc0022621c12f15af08d0f2ddb1"; -export const ORDER_SCRIPT: Script = { - type: "PlutusV1", - script: - "59014f59014c01000032323232323232322223232325333009300e30070021323233533300b3370e9000180480109118011bae30100031225001232533300d3300e22533301300114a02a66601e66ebcc04800400c5288980118070009bac3010300c300c300c300c300c300c300c007149858dd48008b18060009baa300c300b3754601860166ea80184ccccc0288894ccc04000440084c8c94ccc038cd4ccc038c04cc030008488c008dd718098018912800919b8f0014891ce1317b152faac13426e6a83e06ff88a4d62cce3c1634ab0a5ec133090014a0266008444a00226600a446004602600a601a00626600a008601a006601e0026ea8c03cc038dd5180798071baa300f300b300e3754601e00244a0026eb0c03000c92616300a001375400660106ea8c024c020dd5000aab9d5744ae688c8c0088cc0080080048c0088cc00800800555cf2ba15573e6e1d200201", -}; - -export const BATCHER_FEE_REDUCTION_SUPPORTED_ASSET: Record< - number, - [string, string] -> = { - [NetworkId.MAINNET]: [ - "29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c64d494e", // MIN - "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d866aa2153e1ae896a95539c9d62f76cedcdabdcdf144e564b8955f609d660cf6a2", // ADA-MIN LP - ], - [NetworkId.TESTNET]: [ - "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed724d494e", // MIN - "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d863bb0079303c57812462dec9de8fb867cef8fd3768de7f12c77f6f0dd80381d0d", // ADA-MIN LP - ], -}; - -export enum MetadataMessage { - DEPOSIT_ORDER = "SDK Minswap: Deposit Order", - CANCEL_ORDER = "SDK Minswap: Cancel Order", - ZAP_IN_ORDER = "SDK Minswap: Zap Order", - SWAP_EXACT_IN_ORDER = "SDK Minswap: Swap Exact In Order", - SWAP_EXACT_IN_LIMIT_ORDER = "SDK Minswap: Swap Exact In Limit Order", - SWAP_EXACT_OUT_ORDER = "SDK Minswap: Swap Exact Out Order", - WITHDRAW_ORDER = "SDK Minswap: Withdraw Order", -} - -export const FIXED_DEPOSIT_ADA = 2_000_000n; diff --git a/src/dex.ts b/src/dex.ts index f1831df..2ffe33b 100644 --- a/src/dex.ts +++ b/src/dex.ts @@ -12,16 +12,15 @@ import { } from "lucid-cardano"; import { getBatcherFee } from "./batcher-fee-reduction/configs.internal"; +import { Asset } from "./types/asset"; import { BATCHER_FEE_REDUCTION_SUPPORTED_ASSET, + DexV1Constant, FIXED_DEPOSIT_ADA, MetadataMessage, - ORDER_BASE_ADDRESS, - ORDER_SCRIPT, -} from "./constants"; -import { Asset } from "./types/asset"; +} from "./types/constants"; import { NetworkId } from "./types/network"; -import { OrderDatum, OrderRedeemer, OrderStepType } from "./types/order"; +import { OrderV1 } from "./types/order"; /** * Common options for build Minswap transaction @@ -156,12 +155,12 @@ export class Dex { } else { orderAssets["lovelace"] = FIXED_DEPOSIT_ADA + batcherFee; } - const datum: OrderDatum = { + const datum: OrderV1.Datum = { sender: sender, receiver: sender, receiverDatumHash: undefined, step: { - type: OrderStepType.SWAP_EXACT_IN, + type: OrderV1.StepType.SWAP_EXACT_IN, desiredAsset: assetOut, minimumReceived: minimumAmountOut, }, @@ -171,8 +170,8 @@ export class Dex { const tx = this.lucid .newTx() .payToContract( - ORDER_BASE_ADDRESS[this.networkId], - Data.to(OrderDatum.toPlutusData(datum)), + DexV1Constant.ORDER_BASE_ADDRESS[this.networkId], + Data.to(OrderV1.Datum.toPlutusData(datum)), orderAssets ) .payToAddress(sender, reductionAssets) @@ -212,12 +211,12 @@ export class Dex { } else { orderAssets["lovelace"] = FIXED_DEPOSIT_ADA + batcherFee; } - const datum: OrderDatum = { + const datum: OrderV1.Datum = { sender: sender, receiver: sender, receiverDatumHash: undefined, step: { - type: OrderStepType.SWAP_EXACT_OUT, + type: OrderV1.StepType.SWAP_EXACT_OUT, desiredAsset: assetOut, expectedReceived: expectedAmountOut, }, @@ -228,8 +227,8 @@ export class Dex { return await this.lucid .newTx() .payToContract( - ORDER_BASE_ADDRESS[this.networkId], - Data.to(OrderDatum.toPlutusData(datum)), + DexV1Constant.ORDER_BASE_ADDRESS[this.networkId], + Data.to(OrderV1.Datum.toPlutusData(datum)), orderAssets ) .payToAddress(sender, reductionAssets) @@ -262,12 +261,12 @@ export class Dex { } else { orderAssets["lovelace"] = FIXED_DEPOSIT_ADA + batcherFee; } - const datum: OrderDatum = { + const datum: OrderV1.Datum = { sender: sender, receiver: sender, receiverDatumHash: undefined, step: { - type: OrderStepType.WITHDRAW, + type: OrderV1.StepType.WITHDRAW, minimumAssetA: minimumAssetAReceived, minimumAssetB: minimumAssetBReceived, }, @@ -277,8 +276,8 @@ export class Dex { return await this.lucid .newTx() .payToContract( - ORDER_BASE_ADDRESS[this.networkId], - Data.to(OrderDatum.toPlutusData(datum)), + DexV1Constant.ORDER_BASE_ADDRESS[this.networkId], + Data.to(OrderV1.Datum.toPlutusData(datum)), orderAssets ) .payToAddress(sender, reductionAssets) @@ -308,12 +307,12 @@ export class Dex { } else { orderAssets["lovelace"] = FIXED_DEPOSIT_ADA + batcherFee; } - const datum: OrderDatum = { + const datum: OrderV1.Datum = { sender: sender, receiver: sender, receiverDatumHash: undefined, step: { - type: OrderStepType.ZAP_IN, + type: OrderV1.StepType.ZAP_IN, desiredAsset: assetOut, minimumLP: minimumLPReceived, }, @@ -324,8 +323,8 @@ export class Dex { return await this.lucid .newTx() .payToContract( - ORDER_BASE_ADDRESS[this.networkId], - Data.to(OrderDatum.toPlutusData(datum)), + DexV1Constant.ORDER_BASE_ADDRESS[this.networkId], + Data.to(OrderV1.Datum.toPlutusData(datum)), orderAssets ) .payToAddress(sender, reductionAssets) @@ -359,12 +358,12 @@ export class Dex { } else { orderAssets["lovelace"] = FIXED_DEPOSIT_ADA + batcherFee; } - const datum: OrderDatum = { + const datum: OrderV1.Datum = { sender: sender, receiver: sender, receiverDatumHash: undefined, step: { - type: OrderStepType.DEPOSIT, + type: OrderV1.StepType.DEPOSIT, minimumLP: minimumLPReceived, }, batcherFee: batcherFee, @@ -373,8 +372,8 @@ export class Dex { return await this.lucid .newTx() .payToContract( - ORDER_BASE_ADDRESS[this.networkId], - Data.to(OrderDatum.toPlutusData(datum)), + DexV1Constant.ORDER_BASE_ADDRESS[this.networkId], + Data.to(OrderV1.Datum.toPlutusData(datum)), orderAssets ) .payToAddress(sender, reductionAssets) @@ -387,13 +386,13 @@ export class Dex { options: BuildCancelOrderOptions ): Promise { const { orderUtxo } = options; - const redeemer = Data.to(new Constr(OrderRedeemer.CANCEL_ORDER, [])); + const redeemer = Data.to(new Constr(OrderV1.Redeemer.CANCEL_ORDER, [])); const rawDatum = orderUtxo.datum; invariant( rawDatum, `Cancel Order requires Order UTxOs along with its CBOR Datum` ); - const orderDatum = OrderDatum.fromPlutusData( + const orderDatum = OrderV1.Datum.fromPlutusData( this.networkId, Data.from(rawDatum) as Constr ); @@ -401,7 +400,7 @@ export class Dex { .newTx() .collectFrom([orderUtxo], redeemer) .addSigner(orderDatum.sender) - .attachSpendingValidator(ORDER_SCRIPT) + .attachSpendingValidator(DexV1Constant.ORDER_SCRIPT) .attachMetadata(674, { msg: [MetadataMessage.CANCEL_ORDER] }) .complete(); } diff --git a/src/index.ts b/src/index.ts index ef6acb2..a273057 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ export * from "./adapter"; export * from "./calculate"; -export * from "./constants"; export * from "./dex"; export * from "./types/asset"; +export * from "./types/constants"; export * from "./types/network"; export * from "./types/order"; export * from "./types/pool"; diff --git a/src/types/constants.ts b/src/types/constants.ts new file mode 100644 index 0000000..d0fc570 --- /dev/null +++ b/src/types/constants.ts @@ -0,0 +1,427 @@ +import { Address, OutRef,Script } from "lucid-cardano"; + +import { NetworkId } from "./network"; + +export namespace DexV1Constant { + export const ORDER_BASE_ADDRESS: Record = { + [NetworkId.TESTNET]: + "addr_test1zzn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uwurajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upq932hcy", + [NetworkId.MAINNET]: + "addr1zxn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uw6j2c79gy9l76sdg0xwhd7r0c0kna0tycz4y5s6mlenh8pq6s3z70", + }; + + export const POOL_SCRIPT_HASH = + "script1uychk9f04tqngfhx4qlqdlug5ntzen3uzc62kzj7cyesjk0d9me"; + + export const FACTORY_POLICY_ID = + "13aa2accf2e1561723aa26871e071fdf32c867cff7e7d50ad470d62f"; + export const FACTORY_ASSET_NAME = "4d494e53574150"; + export const LP_POLICY_ID = + "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86"; + export const POOL_NFT_POLICY_ID = + "0be55d262b29f564998ff81efe21bdc0022621c12f15af08d0f2ddb1"; + export const ORDER_SCRIPT: Script = { + type: "PlutusV1", + script: + "59014f59014c01000032323232323232322223232325333009300e30070021323233533300b3370e9000180480109118011bae30100031225001232533300d3300e22533301300114a02a66601e66ebcc04800400c5288980118070009bac3010300c300c300c300c300c300c300c007149858dd48008b18060009baa300c300b3754601860166ea80184ccccc0288894ccc04000440084c8c94ccc038cd4ccc038c04cc030008488c008dd718098018912800919b8f0014891ce1317b152faac13426e6a83e06ff88a4d62cce3c1634ab0a5ec133090014a0266008444a00226600a446004602600a601a00626600a008601a006601e0026ea8c03cc038dd5180798071baa300f300b300e3754601e00244a0026eb0c03000c92616300a001375400660106ea8c024c020dd5000aab9d5744ae688c8c0088cc0080080048c0088cc00800800555cf2ba15573e6e1d200201", + }; +} + +export namespace StableswapConstant { + export type Config = { + orderAddress: Address; + poolAddress: Address; + nftAsset: string; + lpAsset: string; + assets: string[]; + multiples: bigint[]; + fee: bigint; + adminFee: bigint; + feeDenominator: bigint; + } + + export type DeployedScripts = { + order: OutRef, + pool: OutRef, + lp: OutRef, + poolBatching: OutRef + } + + export const CONFIG: Record = { + [NetworkId.TESTNET]: [ + { + orderAddress: "addr_test1zq8spknltt6yyz2505rhc5lqw89afc4anhu4u0347n5dz8urajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upqa63kst", + poolAddress: "addr_test1zr3hs60rn9x49ahuduuzmnlhnema0jsl4d3ujrf3cmurhmvrajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upqcgz9yc", + nftAsset: "06fe1ba957728130154154d5e5b25a7b533ebe6c4516356c0aa69355646a65642d697573642d76312e342d6c70", + lpAsset: "d16339238c9e1fb4d034b6a48facb2f97794a9cdb7bc049dd7c49f54646a65642d697573642d76312e342d6c70", + assets: [ + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed7274444a4544", + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed727469555344" + ], + multiples: [1n, 1n], + fee: 1000000n, + adminFee: 5000000000n, + feeDenominator: 10000000000n + }, + { + orderAddress: "addr_test1zp3mf7r63u8km2d69kh6v2axlvl04yunmmj67vprljuht4urajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upqhelj6n", + poolAddress: "addr_test1zzc8ar93kgntz3lv95uauhe29kj4yj84mxhg5v9dqj4k7p5rajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upqujv25l", + nftAsset: "06fe1ba957728130154154d5e5b25a7b533ebe6c4516356c0aa69355757364632d757364742d76312e342d6c70", + lpAsset: "8db03e0cc042a5f82434123a0509f590210996f1c7410c94f913ac48757364632d757364742d76312e342d6c70", + assets: [ + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed727455534443", + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed727455534454" + ], + multiples: [1n, 1n], + fee: 1000000n, + adminFee: 5000000000n, + feeDenominator: 10000000000n + }, + { + orderAddress: "addr_test1zqpmw0kkgm6fp9x0asq5vwuaccweeqdv3edhwckqr2gnvzurajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upq9z8vxj", + poolAddress: "addr_test1zqh2uv0wvrtt579e92q35ktkzcj3lj3nzdm3xjpsdack3q5rajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upqud27a8", + nftAsset: "06fe1ba957728130154154d5e5b25a7b533ebe6c4516356c0aa69355646a65642d697573642d6461692d76312e342d6c70", + lpAsset: "492fd7252d5914c9f5acb7eeb6b905b3a65b9a952c2300de34eb86c5646a65642d697573642d6461692d76312e342d6c70", + assets: [ + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed7274444a4544", + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed727469555344", + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed7274444149" + ], + multiples: [1n, 1n, 1n], + fee: 1000000n, + adminFee: 5000000000n, + feeDenominator: 10000000000n + } + ], + [NetworkId.MAINNET]: [ + { + orderAddress: "addr1w9xy6edqv9hkptwzewns75ehq53nk8t73je7np5vmj3emps698n9g", + poolAddress: "addr1wy7kkcpuf39tusnnyga5t2zcul65dwx9yqzg7sep3cjscesx2q5m5", + nftAsset: "5d4b6afd3344adcf37ccef5558bb87f522874578c32f17160512e398444a45442d695553442d534c50", + lpAsset: "2c07095028169d7ab4376611abef750623c8f955597a38cd15248640444a45442d695553442d534c50", + assets: [ + "8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd61446a65644d6963726f555344", + "f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988069555344" + ], + multiples: [1n, 1n], + fee: 1000000n, + adminFee: 5000000000n, + feeDenominator: 10000000000n, + }, + { + orderAddress: "addr1w93d8cuht3hvqt2qqfjqgyek3gk5d6ss2j93e5sh505m0ng8cmze2", + poolAddress: "addr1wx8d45xlfrlxd7tctve8xgdtk59j849n00zz2pgyvv47t8sxa6t53", + nftAsset: "d97fa91daaf63559a253970365fb219dc4364c028e5fe0606cdbfff9555344432d444a45442d534c50", + lpAsset: "ac49e0969d76ed5aa9e9861a77be65f4fc29e9a979dc4c37a99eb8f4555344432d444a45442d534c50", + assets: [ + "25c5de5f5b286073c593edfd77b48abc7a48e5a4f3d4cd9d428ff93555534443", + "8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd61446a65644d6963726f555344" + ], + multiples: [1n, 100n], + fee: 1000000n, + adminFee: 5000000000n, + feeDenominator: 10000000000n + }, + { + orderAddress: "addr1wxtv9k2lcum5pmcc4wu44a5tufulszahz84knff87wcawycez9lug", + poolAddress: "addr1w9520fyp6g3pjwd0ymfy4v2xka54ek6ulv4h8vce54zfyfcm2m0sm", + nftAsset: "96402c6f5e7a04f16b4d6f500ab039ff5eac5d0226d4f88bf5523ce85553444d2d695553442d534c50", + lpAsset: "31f92531ac9f1af3079701fab7c66ce997eb07988277ee5b9d6403015553444d2d695553442d534c50", + assets: [ + "c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d", + "f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988069555344" + ], + multiples: [1n, 1n], + fee: 1000000n, + adminFee: 5000000000n, + feeDenominator: 10000000000n + }, + { + orderAddress: "addr1wxr9ppdymqgw6g0hvaaa7wc6j0smwh730ujx6lczgdynehsguav8d", + poolAddress: "addr1wxxdvtj6y4fut4tmu796qpvy2xujtd836yg69ahat3e6jjcelrf94", + nftAsset: "07b0869ed7488657e24ac9b27b3f0fb4f76757f444197b2a38a15c3c444a45442d5553444d2d534c50", + lpAsset: "5b042cf53c0b2ce4f30a9e743b4871ad8c6dcdf1d845133395f55a8e444a45442d5553444d2d534c50", + assets: [ + "8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd61446a65644d6963726f555344", + "c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d" + ], + multiples: [1n, 1n], + fee: 1000000n, + adminFee: 5000000000n, + feeDenominator: 10000000000n + } + ] + } + + export const DEPLOYED_SCRIPTS: Record> = { + [NetworkId.TESTNET]: { + "06fe1ba957728130154154d5e5b25a7b533ebe6c4516356c0aa69355646a65642d697573642d76312e342d6c70": { + order: { + "txHash": "527e421bc3eb8b9e5ec0a9ad214bb9b76148f57b9a5a8cbd83a51264f943e91d", + "outputIndex": 0, + }, + pool: { + "txHash": "527e421bc3eb8b9e5ec0a9ad214bb9b76148f57b9a5a8cbd83a51264f943e91d", + "outputIndex": 1, + }, + lp: { + "txHash": "527e421bc3eb8b9e5ec0a9ad214bb9b76148f57b9a5a8cbd83a51264f943e91d", + "outputIndex": 2, + }, + poolBatching: { + "txHash": "527e421bc3eb8b9e5ec0a9ad214bb9b76148f57b9a5a8cbd83a51264f943e91d", + "outputIndex": 3, + } + }, + "06fe1ba957728130154154d5e5b25a7b533ebe6c4516356c0aa69355757364632d757364742d76312e342d6c70": { + order: { + "txHash": "cf699550642c8ffc1673d1e5d56d8562ca7c7f5c0b513a8428c3f52cdcc8fdb7", + "outputIndex": 0, + }, + pool: { + "txHash": "cf699550642c8ffc1673d1e5d56d8562ca7c7f5c0b513a8428c3f52cdcc8fdb7", + "outputIndex": 1, + }, + lp: { + "txHash": "cf699550642c8ffc1673d1e5d56d8562ca7c7f5c0b513a8428c3f52cdcc8fdb7", + "outputIndex": 2, + }, + poolBatching: { + "txHash": "cf699550642c8ffc1673d1e5d56d8562ca7c7f5c0b513a8428c3f52cdcc8fdb7", + "outputIndex": 3, + } + }, + "06fe1ba957728130154154d5e5b25a7b533ebe6c4516356c0aa69355646a65642d697573642d6461692d76312e342d6c70": { + order: { + "txHash": "a8ab602259654697c85e2f61752d34cdb631f314eaeded0676fee6f6be70afe7", + "outputIndex": 0, + }, + pool: { + "txHash": "a8ab602259654697c85e2f61752d34cdb631f314eaeded0676fee6f6be70afe7", + "outputIndex": 1, + }, + lp: { + "txHash": "a8ab602259654697c85e2f61752d34cdb631f314eaeded0676fee6f6be70afe7", + "outputIndex": 2, + }, + poolBatching: { + "txHash": "a8ab602259654697c85e2f61752d34cdb631f314eaeded0676fee6f6be70afe7", + "outputIndex": 3, + } + } + }, + [NetworkId.MAINNET]: { + "5d4b6afd3344adcf37ccef5558bb87f522874578c32f17160512e398444a45442d695553442d534c50": { + order: { + "txHash": "20227174ec2f7853a71a02c435d063b3bf63851d4e0ad9a0c09250a087a6577e", + "outputIndex": 0, + }, + pool: { + "txHash": "20227174ec2f7853a71a02c435d063b3bf63851d4e0ad9a0c09250a087a6577e", + "outputIndex": 1, + }, + lp: { + "txHash": "20227174ec2f7853a71a02c435d063b3bf63851d4e0ad9a0c09250a087a6577e", + "outputIndex": 2, + }, + poolBatching: { + "txHash": "20227174ec2f7853a71a02c435d063b3bf63851d4e0ad9a0c09250a087a6577e", + "outputIndex": 3, + } + }, + "d97fa91daaf63559a253970365fb219dc4364c028e5fe0606cdbfff9555344432d444a45442d534c50": { + order: { + "txHash": "8b880e77a726e76e5dd585cda2c4c2ac93f1cfccc06910f00550fb820ae1fc54", + "outputIndex": 0, + }, + pool: { + "txHash": "8b880e77a726e76e5dd585cda2c4c2ac93f1cfccc06910f00550fb820ae1fc54", + "outputIndex": 1, + }, + lp: { + "txHash": "8b880e77a726e76e5dd585cda2c4c2ac93f1cfccc06910f00550fb820ae1fc54", + "outputIndex": 2, + }, + poolBatching: { + "txHash": "8b880e77a726e76e5dd585cda2c4c2ac93f1cfccc06910f00550fb820ae1fc54", + "outputIndex": 3, + } + }, + "96402c6f5e7a04f16b4d6f500ab039ff5eac5d0226d4f88bf5523ce85553444d2d695553442d534c50": { + order: { + "txHash": "48019a931af442e1eedab6c5b52b3069cf6eadb2483a2131f517e62fddfd5662", + "outputIndex": 0, + }, + pool: { + "txHash": "48019a931af442e1eedab6c5b52b3069cf6eadb2483a2131f517e62fddfd5662", + "outputIndex": 1, + }, + lp: { + "txHash": "48019a931af442e1eedab6c5b52b3069cf6eadb2483a2131f517e62fddfd5662", + "outputIndex": 2, + }, + poolBatching: { + "txHash": "48019a931af442e1eedab6c5b52b3069cf6eadb2483a2131f517e62fddfd5662", + "outputIndex": 3, + } + }, + "07b0869ed7488657e24ac9b27b3f0fb4f76757f444197b2a38a15c3c444a45442d5553444d2d534c50": { + order: { + "txHash": "dddccee9cd58cbf712f2ff2c49ea20537db681a333c701106aa13cd57aee3873", + "outputIndex": 0, + }, + pool: { + "txHash": "dddccee9cd58cbf712f2ff2c49ea20537db681a333c701106aa13cd57aee3873", + "outputIndex": 1, + }, + lp: { + "txHash": "dddccee9cd58cbf712f2ff2c49ea20537db681a333c701106aa13cd57aee3873", + "outputIndex": 2, + }, + poolBatching: { + "txHash": "dddccee9cd58cbf712f2ff2c49ea20537db681a333c701106aa13cd57aee3873", + "outputIndex": 3, + } + } + }, + } +} + +export namespace DexV2Constant { + export type Config = { + factoryAsset: string; + poolAuthenAsset: string; + globalSettingAsset: string; + lpPolicyId: string; + globalSettingScriptHash: string; + orderScriptHash: string; + poolScriptHash: string; + poolScriptHashBech32: string; + poolCreationAddress: Address; + factoryScriptHash: string; + expiredOrderCancelAddress: string; + poolBatchingAddress: string; + } + + export type DeployedScripts = { + order: OutRef, + pool: OutRef, + factory: OutRef, + authen: OutRef, + poolBatching: OutRef, + expiredOrderCancellation: OutRef + } + + export const CONFIG: Record = { + [NetworkId.TESTNET]: { + factoryAsset: "d6aae2059baee188f74917493cf7637e679cd219bdfbbf4dcbeb1d0b4d5346", + poolAuthenAsset: "d6aae2059baee188f74917493cf7637e679cd219bdfbbf4dcbeb1d0b4d5350", + globalSettingAsset: "d6aae2059baee188f74917493cf7637e679cd219bdfbbf4dcbeb1d0b4d534753", + lpPolicyId: "d6aae2059baee188f74917493cf7637e679cd219bdfbbf4dcbeb1d0b", + globalSettingScriptHash: "d6aae2059baee188f74917493cf7637e679cd219bdfbbf4dcbeb1d0b", + orderScriptHash: "da9525463841173ad1230b1d5a1b5d0a3116bbdeb4412327148a1b7a", + poolScriptHash: "d6ba9b7509eac866288ff5072d2a18205ac56f744bc82dcd808cb8fe", + poolScriptHashBech32: "script166afkagfatyxv2y075rj62scypdv2mm5f0yzmnvq3ju0uqqmszv", + poolCreationAddress: "addr_test1zrtt4xm4p84vse3g3l6swtf2rqs943t0w39ustwdszxt3l5rajt8r8wqtygrfduwgukk73m5gcnplmztc5tl5ngy0upqhns793", + factoryScriptHash: "6e23fe172b5b50e2ad59aded9ee8d488f74c7f4686f91b032220adad", + expiredOrderCancelAddress: "stake_test17rytpnrpxax5p8leepgjx9cq8ecedgly6jz4xwvvv4kvzfqz6sgpf", + poolBatchingAddress: "stake_test17rann6nth9675m0y5tz32u3rfhzcfjymanxqnfyexsufu5glcajhf", + }, + [NetworkId.MAINNET]: { + factoryAsset: "", + poolAuthenAsset: "", + globalSettingAsset: "", + lpPolicyId: "", + globalSettingScriptHash: "", + orderScriptHash: "", + poolScriptHash: "", + poolScriptHashBech32: "", + poolCreationAddress: "", + factoryScriptHash: "", + expiredOrderCancelAddress: "", + poolBatchingAddress: "", + } + } + + export const DEPLOYED_SCRIPTS: Record = { + [NetworkId.TESTNET]: { + order: { + txHash: "8c98f0530cba144d264fbd2731488af25257d7ce6a0cd1586fc7209363724f03", + outputIndex: 0 + }, + pool: { + txHash: "9f30b1c3948a009ceebda32d0b1d25699674b2eaf8b91ef029a43bfc1073ce28", + outputIndex: 0 + }, + factory: { + txHash: "9741d59656e9ad54f197b0763482eede9a6fa1616c4547797eee6617f92a1396", + outputIndex: 0 + }, + authen: { + txHash: "c429b8ee27e5761ba8714e26e3a5899886cd28d136d43e969d4bc1acf0f72d4a", + outputIndex: 0 + }, + poolBatching: { + txHash: "b0a6c5512735c7a183a167eed035ac75c191d6ff5be9736dfa1f1f02f7ae5dbc", + outputIndex: 0, + }, + expiredOrderCancellation: { + txHash: "ee718dd86e3cb89e802aa8b2be252fccf6f15263f4a26b5f478c5135c40264c6", + outputIndex: 0 + } + }, + [NetworkId.MAINNET]: { + order: { + txHash: "", + outputIndex: 0 + }, + pool: { + txHash: "", + outputIndex: 0 + }, + factory: { + txHash: "", + outputIndex: 0 + }, + authen: { + txHash: "", + outputIndex: 0 + }, + poolBatching: { + txHash: "", + outputIndex: 0, + }, + expiredOrderCancellation: { + txHash: "", + outputIndex: 0 + } + } + } +} + + +export const BATCHER_FEE_REDUCTION_SUPPORTED_ASSET: Record< + number, + [string, string] +> = { + [NetworkId.MAINNET]: [ + "29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c64d494e", // MIN + "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d866aa2153e1ae896a95539c9d62f76cedcdabdcdf144e564b8955f609d660cf6a2", // ADA-MIN LP + ], + [NetworkId.TESTNET]: [ + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed724d494e", // MIN + "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d863bb0079303c57812462dec9de8fb867cef8fd3768de7f12c77f6f0dd80381d0d", // ADA-MIN LP + ], +}; + +export enum MetadataMessage { + DEPOSIT_ORDER = "SDK Minswap: Deposit Order", + CANCEL_ORDER = "SDK Minswap: Cancel Order", + ZAP_IN_ORDER = "SDK Minswap: Zap Order", + SWAP_EXACT_IN_ORDER = "SDK Minswap: Swap Exact In Order", + SWAP_EXACT_IN_LIMIT_ORDER = "SDK Minswap: Swap Exact In Limit Order", + SWAP_EXACT_OUT_ORDER = "SDK Minswap: Swap Exact Out Order", + WITHDRAW_ORDER = "SDK Minswap: Withdraw Order", +} + +export const FIXED_DEPOSIT_ADA = 2_000_000n; diff --git a/src/types/order.ts b/src/types/order.ts index 2e63f63..a3e4e0d 100644 --- a/src/types/order.ts +++ b/src/types/order.ts @@ -4,249 +4,251 @@ import { AddressPlutusData } from "./address.internal"; import { Asset } from "./asset"; import { NetworkId } from "./network"; -export enum OrderStepType { - SWAP_EXACT_IN = 0, - SWAP_EXACT_OUT, - DEPOSIT, - WITHDRAW, - ZAP_IN, -} +export namespace OrderV1 { + export enum StepType { + SWAP_EXACT_IN = 0, + SWAP_EXACT_OUT, + DEPOSIT, + WITHDRAW, + ZAP_IN, + } -export type SwapExactInStep = { - type: OrderStepType.SWAP_EXACT_IN; - desiredAsset: Asset; - minimumReceived: bigint; -}; + export type SwapExactIn = { + type: StepType.SWAP_EXACT_IN; + desiredAsset: Asset; + minimumReceived: bigint; + }; -export type SwapExactOutStep = { - type: OrderStepType.SWAP_EXACT_OUT; - desiredAsset: Asset; - expectedReceived: bigint; -}; + export type SwapExactOut = { + type: StepType.SWAP_EXACT_OUT; + desiredAsset: Asset; + expectedReceived: bigint; + }; -export type DepositStep = { - type: OrderStepType.DEPOSIT; - minimumLP: bigint; -}; + export type Deposit = { + type: StepType.DEPOSIT; + minimumLP: bigint; + }; -export type WithdrawStep = { - type: OrderStepType.WITHDRAW; - minimumAssetA: bigint; - minimumAssetB: bigint; -}; + export type Withdraw = { + type: StepType.WITHDRAW; + minimumAssetA: bigint; + minimumAssetB: bigint; + }; -export type ZapInStep = { - type: OrderStepType.ZAP_IN; - desiredAsset: Asset; - minimumLP: bigint; -}; + export type ZapIn = { + type: StepType.ZAP_IN; + desiredAsset: Asset; + minimumLP: bigint; + }; -export type OrderStep = - | SwapExactInStep - | SwapExactOutStep - | DepositStep - | WithdrawStep - | ZapInStep; + export type Step = + | SwapExactIn + | SwapExactOut + | Deposit + | Withdraw + | ZapIn; -export type OrderDatum = { - sender: Address; - receiver: Address; - receiverDatumHash?: string; - step: OrderStep; - batcherFee: bigint; - depositADA: bigint; -}; + export type Datum = { + sender: Address; + receiver: Address; + receiverDatumHash?: string; + step: Step; + batcherFee: bigint; + depositADA: bigint; + }; -export namespace OrderDatum { - export function toPlutusData(datum: OrderDatum): Constr { - const { - sender, - receiver, - receiverDatumHash, - step, - batcherFee, - depositADA, - } = datum; - const senderConstr = AddressPlutusData.toPlutusData(sender); - const receiverConstr = AddressPlutusData.toPlutusData(receiver); - const receiverDatumHashConstr = receiverDatumHash - ? new Constr(0, [receiverDatumHash]) - : new Constr(1, []); - let datumConstr: Constr; - switch (step.type) { - case OrderStepType.SWAP_EXACT_IN: { - datumConstr = new Constr(0, [ - senderConstr, - receiverConstr, - receiverDatumHashConstr, - new Constr(OrderStepType.SWAP_EXACT_IN, [ - Asset.toPlutusData(step.desiredAsset), - step.minimumReceived, - ]), - batcherFee, - depositADA, - ]); - break; - } - case OrderStepType.SWAP_EXACT_OUT: { - datumConstr = new Constr(0, [ - senderConstr, - receiverConstr, - receiverDatumHashConstr, - new Constr(OrderStepType.SWAP_EXACT_OUT, [ - Asset.toPlutusData(step.desiredAsset), - step.expectedReceived, - ]), - batcherFee, - depositADA, - ]); - break; - } - case OrderStepType.DEPOSIT: { - datumConstr = new Constr(0, [ - senderConstr, - receiverConstr, - receiverDatumHashConstr, - new Constr(OrderStepType.DEPOSIT, [step.minimumLP]), - batcherFee, - depositADA, - ]); - break; - } - case OrderStepType.WITHDRAW: { - datumConstr = new Constr(0, [ - senderConstr, - receiverConstr, - receiverDatumHashConstr, - new Constr(OrderStepType.WITHDRAW, [ - step.minimumAssetA, - step.minimumAssetB, - ]), - batcherFee, - depositADA, - ]); - break; - } - case OrderStepType.ZAP_IN: { - datumConstr = new Constr(0, [ - senderConstr, - receiverConstr, - receiverDatumHashConstr, - new Constr(OrderStepType.ZAP_IN, [ - Asset.toPlutusData(step.desiredAsset), - step.minimumLP, - ]), - batcherFee, - depositADA, - ]); - break; + export namespace Datum { + export function toPlutusData(datum: Datum): Constr { + const { + sender, + receiver, + receiverDatumHash, + step, + batcherFee, + depositADA, + } = datum; + const senderConstr = AddressPlutusData.toPlutusData(sender); + const receiverConstr = AddressPlutusData.toPlutusData(receiver); + const receiverDatumHashConstr = receiverDatumHash + ? new Constr(0, [receiverDatumHash]) + : new Constr(1, []); + let datumConstr: Constr; + switch (step.type) { + case StepType.SWAP_EXACT_IN: { + datumConstr = new Constr(0, [ + senderConstr, + receiverConstr, + receiverDatumHashConstr, + new Constr(StepType.SWAP_EXACT_IN, [ + Asset.toPlutusData(step.desiredAsset), + step.minimumReceived, + ]), + batcherFee, + depositADA, + ]); + break; + } + case StepType.SWAP_EXACT_OUT: { + datumConstr = new Constr(0, [ + senderConstr, + receiverConstr, + receiverDatumHashConstr, + new Constr(StepType.SWAP_EXACT_OUT, [ + Asset.toPlutusData(step.desiredAsset), + step.expectedReceived, + ]), + batcherFee, + depositADA, + ]); + break; + } + case StepType.DEPOSIT: { + datumConstr = new Constr(0, [ + senderConstr, + receiverConstr, + receiverDatumHashConstr, + new Constr(StepType.DEPOSIT, [step.minimumLP]), + batcherFee, + depositADA, + ]); + break; + } + case StepType.WITHDRAW: { + datumConstr = new Constr(0, [ + senderConstr, + receiverConstr, + receiverDatumHashConstr, + new Constr(StepType.WITHDRAW, [ + step.minimumAssetA, + step.minimumAssetB, + ]), + batcherFee, + depositADA, + ]); + break; + } + case StepType.ZAP_IN: { + datumConstr = new Constr(0, [ + senderConstr, + receiverConstr, + receiverDatumHashConstr, + new Constr(StepType.ZAP_IN, [ + Asset.toPlutusData(step.desiredAsset), + step.minimumLP, + ]), + batcherFee, + depositADA, + ]); + break; + } } - } - - return datumConstr; - } - export function fromPlutusData( - networkId: NetworkId, - data: Constr - ): OrderDatum { - if (data.index !== 0) { - throw new Error(`Index of Order Datum must be 0, actual: ${data.index}`); + return datumConstr; } - const sender = AddressPlutusData.fromPlutusData( - networkId, - data.fields[0] as Constr - ); - const receiver = AddressPlutusData.fromPlutusData( - networkId, - data.fields[1] as Constr - ); - let receiverDatumHash: string | undefined = undefined; - const maybeReceiverDatumHash = data.fields[2] as Constr; - switch (maybeReceiverDatumHash.index) { - case 0: { - receiverDatumHash = maybeReceiverDatumHash.fields[0] as string; - break; - } - case 1: { - receiverDatumHash = undefined; - break; - } - default: { - throw new Error( - `Index of Receiver Datum Hash must be 0 or 1, actual: ${maybeReceiverDatumHash.index}` - ); - } - } - let step: OrderStep; - const orderStepConstr = data.fields[3] as Constr; - switch (orderStepConstr.index) { - case OrderStepType.SWAP_EXACT_IN: { - step = { - type: OrderStepType.SWAP_EXACT_IN, - desiredAsset: Asset.fromPlutusData( - orderStepConstr.fields[0] as Constr - ), - minimumReceived: orderStepConstr.fields[1] as bigint, - }; - break; - } - case OrderStepType.SWAP_EXACT_OUT: { - step = { - type: OrderStepType.SWAP_EXACT_OUT, - desiredAsset: Asset.fromPlutusData( - orderStepConstr.fields[0] as Constr - ), - expectedReceived: orderStepConstr.fields[1] as bigint, - }; - break; - } - case OrderStepType.DEPOSIT: { - step = { - type: OrderStepType.DEPOSIT, - minimumLP: orderStepConstr.fields[0] as bigint, - }; - break; - } - case OrderStepType.WITHDRAW: { - step = { - type: OrderStepType.WITHDRAW, - minimumAssetA: orderStepConstr.fields[0] as bigint, - minimumAssetB: orderStepConstr.fields[1] as bigint, - }; - break; + + export function fromPlutusData( + networkId: NetworkId, + data: Constr + ): Datum { + if (data.index !== 0) { + throw new Error(`Index of Order Datum must be 0, actual: ${data.index}`); } - case OrderStepType.ZAP_IN: { - step = { - type: OrderStepType.ZAP_IN, - desiredAsset: Asset.fromPlutusData( - orderStepConstr.fields[0] as Constr - ), - minimumLP: orderStepConstr.fields[1] as bigint, - }; - break; + const sender = AddressPlutusData.fromPlutusData( + networkId, + data.fields[0] as Constr + ); + const receiver = AddressPlutusData.fromPlutusData( + networkId, + data.fields[1] as Constr + ); + let receiverDatumHash: string | undefined = undefined; + const maybeReceiverDatumHash = data.fields[2] as Constr; + switch (maybeReceiverDatumHash.index) { + case 0: { + receiverDatumHash = maybeReceiverDatumHash.fields[0] as string; + break; + } + case 1: { + receiverDatumHash = undefined; + break; + } + default: { + throw new Error( + `Index of Receiver Datum Hash must be 0 or 1, actual: ${maybeReceiverDatumHash.index}` + ); + } } - default: { - throw new Error( - `Index of Order Step must be in 0-4, actual: ${orderStepConstr.index}` - ); + let step: Step; + const orderStepConstr = data.fields[3] as Constr; + switch (orderStepConstr.index) { + case StepType.SWAP_EXACT_IN: { + step = { + type: StepType.SWAP_EXACT_IN, + desiredAsset: Asset.fromPlutusData( + orderStepConstr.fields[0] as Constr + ), + minimumReceived: orderStepConstr.fields[1] as bigint, + }; + break; + } + case StepType.SWAP_EXACT_OUT: { + step = { + type: StepType.SWAP_EXACT_OUT, + desiredAsset: Asset.fromPlutusData( + orderStepConstr.fields[0] as Constr + ), + expectedReceived: orderStepConstr.fields[1] as bigint, + }; + break; + } + case StepType.DEPOSIT: { + step = { + type: StepType.DEPOSIT, + minimumLP: orderStepConstr.fields[0] as bigint, + }; + break; + } + case StepType.WITHDRAW: { + step = { + type: StepType.WITHDRAW, + minimumAssetA: orderStepConstr.fields[0] as bigint, + minimumAssetB: orderStepConstr.fields[1] as bigint, + }; + break; + } + case StepType.ZAP_IN: { + step = { + type: StepType.ZAP_IN, + desiredAsset: Asset.fromPlutusData( + orderStepConstr.fields[0] as Constr + ), + minimumLP: orderStepConstr.fields[1] as bigint, + }; + break; + } + default: { + throw new Error( + `Index of Order Step must be in 0-4, actual: ${orderStepConstr.index}` + ); + } } - } - const batcherFee = data.fields[4] as bigint; - const depositADA = data.fields[5] as bigint; - return { - sender: sender, - receiver: receiver, - receiverDatumHash: receiverDatumHash, - step: step, - batcherFee: batcherFee, - depositADA: depositADA, - }; + const batcherFee = data.fields[4] as bigint; + const depositADA = data.fields[5] as bigint; + return { + sender: sender, + receiver: receiver, + receiverDatumHash: receiverDatumHash, + step: step, + batcherFee: batcherFee, + depositADA: depositADA, + }; + } } -} -export enum OrderRedeemer { - APPLY_ORDER = 0, - CANCEL_ORDER, + export enum Redeemer { + APPLY_ORDER = 0, + CANCEL_ORDER, + } } diff --git a/src/types/pool.internal.ts b/src/types/pool.internal.ts index 0b10c05..28d1bc1 100644 --- a/src/types/pool.internal.ts +++ b/src/types/pool.internal.ts @@ -1,10 +1,9 @@ import invariant from "@minswap/tiny-invariant"; import { Address, Constr, Data } from "lucid-cardano"; -import { FACTORY_ASSET_NAME, FACTORY_POLICY_ID } from "../constants"; -import { POOL_SCRIPT_HASH } from "../constants"; import { getScriptHashFromAddress } from "../utils/address-utils.internal"; import { AddressPlutusData } from "./address.internal"; +import { DexV1Constant } from "./constants"; import { NetworkId } from "./network"; import { Value } from "./tx.internal"; @@ -80,13 +79,13 @@ export function checkValidPoolOutput( datumHash: string | null ): void { invariant( - getScriptHashFromAddress(poolAddress) === POOL_SCRIPT_HASH, + getScriptHashFromAddress(poolAddress) === DexV1Constant.POOL_SCRIPT_HASH, `invalid pool address: ${poolAddress}` ); // must have 1 factory token if ( value.find( - ({ unit }) => unit === `${FACTORY_POLICY_ID}${FACTORY_ASSET_NAME}` + ({ unit }) => unit === `${DexV1Constant.FACTORY_POLICY_ID}${DexV1Constant.FACTORY_ASSET_NAME}` )?.quantity !== "1" ) { throw new Error(`expect pool to have 1 factory token`); diff --git a/src/types/pool.ts b/src/types/pool.ts index 7d7b495..a7376d1 100644 --- a/src/types/pool.ts +++ b/src/types/pool.ts @@ -1,169 +1,415 @@ import invariant from "@minswap/tiny-invariant"; -import { Constr, Data } from "lucid-cardano"; +import { Constr, Credential, Data } from "lucid-cardano"; -import { - FACTORY_POLICY_ID, - LP_POLICY_ID, - POOL_NFT_POLICY_ID, -} from "../constants"; +import { sha3 } from "../utils/hash.internal"; +import { LucidCredential } from "./address.internal"; import { Asset } from "./asset"; +import { + DexV1Constant, + DexV2Constant, + StableswapConstant, +} from "./constants"; import { NetworkId } from "./network"; import { normalizeAssets, PoolFeeSharing } from "./pool.internal"; import { TxIn, Value } from "./tx.internal"; -/** - * Represents state of a pool UTxO. The state could be latest state or a historical state. - */ -export class PoolState { - /** The transaction hash and output index of the pool UTxO */ - public readonly address: string; - public readonly txIn: TxIn; - public readonly value: Value; - public readonly datumHash: string; - public readonly assetA: string; - public readonly assetB: string; - - constructor(address: string, txIn: TxIn, value: Value, datumHash: string) { - this.address = address; - this.txIn = txIn; - this.value = value; - this.datumHash = datumHash; - - const nft = value.find(({ unit }) => unit.startsWith(POOL_NFT_POLICY_ID)); - invariant(nft, "pool doesn't have NFT"); - const poolId = nft.unit.slice(56); - // validate and memoize assetA and assetB - const relevantAssets = value.filter( - ({ unit }) => - !unit.startsWith(FACTORY_POLICY_ID) && // factory token - !unit.endsWith(poolId) // NFT and LP tokens from profit sharing - ); - switch (relevantAssets.length) { - case 2: { - // ADA/A pool - this.assetA = "lovelace"; - const nonADAAssets = relevantAssets.filter( - ({ unit }) => unit !== "lovelace" - ); - invariant(nonADAAssets.length === 1, "pool must have 1 non-ADA asset"); - this.assetB = nonADAAssets[0].unit; - break; +export namespace PoolV1 { + /** + * Represents state of a pool UTxO. The state could be latest state or a historical state. + */ + export class State { + /** The transaction hash and output index of the pool UTxO */ + public readonly address: string; + public readonly txIn: TxIn; + public readonly value: Value; + public readonly datumHash: string; + public readonly assetA: string; + public readonly assetB: string; + + constructor(address: string, txIn: TxIn, value: Value, datumHash: string) { + this.address = address; + this.txIn = txIn; + this.value = value; + this.datumHash = datumHash; + + const nft = value.find(({ unit }) => unit.startsWith(DexV1Constant.POOL_NFT_POLICY_ID)); + invariant(nft, "pool doesn't have NFT"); + const poolId = nft.unit.slice(56); + // validate and memoize assetA and assetB + const relevantAssets = value.filter( + ({ unit }) => + !unit.startsWith(DexV1Constant.FACTORY_POLICY_ID) && // factory token + !unit.endsWith(poolId) // NFT and LP tokens from profit sharing + ); + switch (relevantAssets.length) { + case 2: { + // ADA/A pool + this.assetA = "lovelace"; + const nonADAAssets = relevantAssets.filter( + ({ unit }) => unit !== "lovelace" + ); + invariant(nonADAAssets.length === 1, "pool must have 1 non-ADA asset"); + this.assetB = nonADAAssets[0].unit; + break; + } + case 3: { + // A/B pool + const nonADAAssets = relevantAssets.filter( + ({ unit }) => unit !== "lovelace" + ); + invariant(nonADAAssets.length === 2, "pool must have 1 non-ADA asset"); + [this.assetA, this.assetB] = normalizeAssets( + nonADAAssets[0].unit, + nonADAAssets[1].unit + ); + break; + } + default: + throw new Error( + "pool must have 2 or 3 assets except factory, NFT and LP tokens" + ); + } + } + + get nft(): string { + const nft = this.value.find(({ unit }) => + unit.startsWith(DexV1Constant.POOL_NFT_POLICY_ID) + ); + invariant(nft, "pool doesn't have NFT"); + return nft.unit; + } + + get id(): string { + // a pool's ID is the NFT's asset name + return this.nft.slice(DexV1Constant.POOL_NFT_POLICY_ID.length); + } + + get assetLP(): string { + return `${DexV1Constant.LP_POLICY_ID}${this.id}`; + } + + get reserveA(): bigint { + return BigInt( + this.value.find(({ unit }) => unit === this.assetA)?.quantity ?? "0" + ); + } + + get reserveB(): bigint { + return BigInt( + this.value.find(({ unit }) => unit === this.assetB)?.quantity ?? "0" + ); + } + } + + export type Datum = { + assetA: Asset; + assetB: Asset; + totalLiquidity: bigint; + rootKLast: bigint; + feeSharing?: PoolFeeSharing; + }; + + export namespace Datum { + export function toPlutusData(datum: Datum): Constr { + const { assetA, assetB, totalLiquidity, rootKLast, feeSharing } = datum; + return new Constr(0, [ + Asset.toPlutusData(assetA), + Asset.toPlutusData(assetB), + totalLiquidity, + rootKLast, + feeSharing + ? new Constr(0, [PoolFeeSharing.toPlutusData(feeSharing)]) + : new Constr(1, []), + ]); + } + + export function fromPlutusData( + networkId: NetworkId, + data: Constr + ): Datum { + if (data.index !== 0) { + throw new Error(`Index of Pool Datum must be 0, actual: ${data.index}`); } - case 3: { - // A/B pool - const nonADAAssets = relevantAssets.filter( - ({ unit }) => unit !== "lovelace" - ); - invariant(nonADAAssets.length === 2, "pool must have 1 non-ADA asset"); - [this.assetA, this.assetB] = normalizeAssets( - nonADAAssets[0].unit, - nonADAAssets[1].unit - ); - break; + let feeSharing: PoolFeeSharing | undefined = undefined; + const maybeFeeSharingConstr = data.fields[4] as Constr; + switch (maybeFeeSharingConstr.index) { + case 0: { + feeSharing = PoolFeeSharing.fromPlutusData( + networkId, + maybeFeeSharingConstr.fields[0] as Constr + ); + break; + } + case 1: { + feeSharing = undefined; + break; + } + default: { + throw new Error( + `Index of Pool Fee Sharing must be 0 or 1, actual: ${maybeFeeSharingConstr.index}` + ); + } } - default: - throw new Error( - "pool must have 2 or 3 assets except factory, NFT and LP tokens" - ); + return { + assetA: Asset.fromPlutusData(data.fields[0] as Constr), + assetB: Asset.fromPlutusData(data.fields[1] as Constr), + totalLiquidity: data.fields[2] as bigint, + rootKLast: data.fields[3] as bigint, + feeSharing: feeSharing, + }; } } +} + +export namespace StablePool { + export class State { + public readonly address: string; + public readonly txIn: TxIn; + public readonly value: Value; + public readonly datumCbor: string; + public readonly datum: Datum; + public readonly config: StableswapConstant.Config + + constructor( + networkId: NetworkId, + address: string, + txIn: TxIn, + value: Value, + datum: string + ) { + this.address = address + this.txIn = txIn + this.value = value + this.datumCbor = datum + this.datum = Datum.fromPlutusData(Data.from(datum)) + const allConfigs = StableswapConstant.CONFIG[networkId] + const config = allConfigs.find((cfg) => cfg.poolAddress === address) + if (!config) { + throw new Error("Invalid Stable Pool address") + } + this.config = config + if (!value.find((v) => v.unit === config.nftAsset && v.quantity === "1")) { + throw new Error("Cannot find the Pool NFT in the value") + } + } + + get assets(): string[] { + return this.config.assets + } + + get nft(): string { + return this.config.nftAsset + } + + get lpAsset(): string { + return this.config.lpAsset + } + + get reserves(): bigint[] { + return this.datum.balances + } + + get totalLiquidity(): bigint { + return this.datum.totalLiquidity + } + + get orderHash(): string { + return this.datum.orderHash + } + + get amp(): bigint { + return this.datum.amplificationCoefficient + } - get nft(): string { - const nft = this.value.find(({ unit }) => - unit.startsWith(POOL_NFT_POLICY_ID) - ); - invariant(nft, "pool doesn't have NFT"); - return nft.unit; + get id(): string { + return this.nft + } } - get id(): string { - // a pool's ID is the NFT's asset name - return this.nft.slice(POOL_NFT_POLICY_ID.length); + export type Datum = { + balances: bigint[]; + totalLiquidity: bigint; + amplificationCoefficient: bigint; + orderHash: string; } - get assetLP(): string { - return `${LP_POLICY_ID}${this.id}`; + export namespace Datum { + export function toPlutusData(datum: Datum): Constr { + const { balances, totalLiquidity, amplificationCoefficient, orderHash } = datum; + return new Constr(0, [ + balances, + totalLiquidity, + amplificationCoefficient, + orderHash + ]); + } + + export function fromPlutusData(data: Constr): Datum { + if (data.index !== 0) { + throw new Error(`Index of Pool Datum must be 0, actual: ${data.index}`); + } + return { + balances: data.fields[0] as bigint[], + totalLiquidity: data.fields[1] as bigint, + amplificationCoefficient: data.fields[2] as bigint, + orderHash: data.fields[3] as string + } + } } +} - get reserveA(): bigint { - return BigInt( - this.value.find(({ unit }) => unit === this.assetA)?.quantity ?? "0" - ); +export namespace PoolV2 { + export function computeLPAssetName(assetA: Asset, assetB: Asset): string { + const k1 = sha3(assetA.policyId + assetA.tokenName); + const k2 = sha3(assetB.policyId + assetB.tokenName); + return sha3(k1 + k2); } + export class State { + public readonly address: string; + public readonly txIn: TxIn; + public readonly value: Value; + public readonly datumRaw: string; + public readonly datum: Datum; + public readonly config: DexV2Constant.Config + public readonly lpAsset: Asset; + public readonly authenAsset: Asset; + constructor( + networkId: NetworkId, + address: string, + txIn: TxIn, + value: Value, + datum: string + ) { + this.address = address + this.txIn = txIn + this.value = value + this.datumRaw = datum + this.datum = Datum.fromPlutusData(Data.from(datum)) + this.config = DexV2Constant.CONFIG[networkId] + this.lpAsset = { + policyId: this.config.lpPolicyId, + tokenName: computeLPAssetName(this.datum.assetA, this.datum.assetB) + } + this.authenAsset = Asset.fromString(this.config.poolAuthenAsset) + if (!value.find((v) => v.unit === this.config.poolAuthenAsset && v.quantity === "1")) { + throw new Error("Cannot find the Pool Authentication Asset in the value") + } + } + + get assetA(): string { + return Asset.toString(this.datum.assetA) + } + + get assetB(): string { + return Asset.toString(this.datum.assetB) + } + + get totalLiquidity(): bigint { + return this.datum.totalLiquidity + } + + get reserveA(): bigint { + return this.datum.reserveA + } + + get reserve(): bigint { + return this.datum.reserveB + } + + get feeA(): bigint { + return this.datum.baseFee.feeANumerator + } - get reserveB(): bigint { - return BigInt( - this.value.find(({ unit }) => unit === this.assetB)?.quantity ?? "0" - ); + get feeB(): bigint { + return this.datum.baseFee.feeBNumerator + } + + get feeShare(): bigint | undefined { + return this.datum.feeSharingNumerator + } } -} -/** - * Represents a historical point of a pool. - */ -export type PoolHistory = { - txHash: string; - /** Transaction index within the block */ - txIndex: number; - blockHeight: number; - time: Date; -}; - -export type PoolDatum = { - assetA: Asset; - assetB: Asset; - totalLiquidity: bigint; - rootKLast: bigint; - feeSharing?: PoolFeeSharing; -}; - -export namespace PoolDatum { - export function toPlutusData(datum: PoolDatum): Constr { - const { assetA, assetB, totalLiquidity, rootKLast, feeSharing } = datum; - return new Constr(0, [ - Asset.toPlutusData(assetA), - Asset.toPlutusData(assetB), - totalLiquidity, - rootKLast, - feeSharing - ? new Constr(0, [PoolFeeSharing.toPlutusData(feeSharing)]) - : new Constr(1, []), - ]); + export type Datum = { + poolBatchingStakeCredential: Credential; + assetA: Asset; + assetB: Asset; + totalLiquidity: bigint; + reserveA: bigint; + reserveB: bigint; + baseFee: { + feeANumerator: bigint; + feeBNumerator: bigint; + }; + feeSharingNumerator?: bigint; + allowDynamicFee: boolean; } - export function fromPlutusData( - networkId: NetworkId, - data: Constr - ): PoolDatum { - if (data.index !== 0) { - throw new Error(`Index of Pool Datum must be 0, actual: ${data.index}`); - } - let feeSharing: PoolFeeSharing | undefined = undefined; - const maybeFeeSharingConstr = data.fields[4] as Constr; - switch (maybeFeeSharingConstr.index) { - case 0: { - feeSharing = PoolFeeSharing.fromPlutusData( - networkId, - maybeFeeSharingConstr.fields[0] as Constr - ); - break; - } - case 1: { - feeSharing = undefined; - break; + export namespace Datum { + export function toPlutusData(datum: Datum): Constr { + const { + poolBatchingStakeCredential, + assetA, + assetB, + totalLiquidity, + reserveA, + reserveB, + baseFee, + feeSharingNumerator, + allowDynamicFee + } = datum; + return new Constr(0, [ + LucidCredential.toPlutusData(poolBatchingStakeCredential), + Asset.toPlutusData(assetA), + Asset.toPlutusData(assetB), + totalLiquidity, + reserveA, + reserveB, + baseFee.feeANumerator, + baseFee.feeBNumerator, + feeSharingNumerator !== undefined + ? new Constr(0, [feeSharingNumerator]) + : new Constr(1, []), + new Constr(allowDynamicFee ? 1 : 0, []) + ]); + } + + export function fromPlutusData(data: Constr): Datum { + if (data.index !== 0) { + throw new Error(`Index of Pool Datum must be 0, actual: ${data.index}`); } - default: { - throw new Error( - `Index of Pool Fee Sharing must be 0 or 1, actual: ${maybeFeeSharingConstr.index}` - ); + let feeSharingNumerator: bigint | undefined = undefined; + const maybeFeeSharingConstr = data.fields[8] as Constr; + switch (maybeFeeSharingConstr.index) { + case 0: { + feeSharingNumerator = maybeFeeSharingConstr.fields[0] as bigint + break; + } + case 1: { + feeSharingNumerator = undefined; + break; + } + default: { + throw new Error( + `Index of Pool Fee Sharing must be 0 or 1, actual: ${maybeFeeSharingConstr.index}` + ); + } } + const allowDynamicFeeConstr = data.fields[9] as Constr; + const allowDynamicFee = allowDynamicFeeConstr.index === 1; + return { + poolBatchingStakeCredential: LucidCredential.fromPlutusData(data.fields[0] as Constr), + assetA: Asset.fromPlutusData(data.fields[1] as Constr), + assetB: Asset.fromPlutusData(data.fields[2] as Constr), + totalLiquidity: data.fields[3] as bigint, + reserveA: data.fields[4] as bigint, + reserveB: data.fields[5] as bigint, + baseFee: { + feeANumerator: data.fields[6] as bigint, + feeBNumerator: data.fields[7] as bigint + }, + feeSharingNumerator: feeSharingNumerator, + allowDynamicFee: allowDynamicFee + }; } - return { - assetA: Asset.fromPlutusData(data.fields[0] as Constr), - assetB: Asset.fromPlutusData(data.fields[1] as Constr), - totalLiquidity: data.fields[2] as bigint, - rootKLast: data.fields[3] as bigint, - feeSharing: feeSharing, - }; } } diff --git a/src/types/tx.internal.ts b/src/types/tx.internal.ts index 46cb2b2..336e02d 100644 --- a/src/types/tx.internal.ts +++ b/src/types/tx.internal.ts @@ -7,3 +7,11 @@ export type TxIn = { txHash: string; index: number; }; + +export type TxHistory = { + txHash: string; + /** Transaction index within the block */ + txIndex: number; + blockHeight: number; + time: Date; +} \ No newline at end of file diff --git a/src/utils/hash.internal.ts b/src/utils/hash.internal.ts new file mode 100644 index 0000000..17df03a --- /dev/null +++ b/src/utils/hash.internal.ts @@ -0,0 +1,7 @@ +import { SHA3 } from "sha3"; + +export function sha3(hex: string): string { + const hash = new SHA3(256); + hash.update(hex, "hex"); + return hash.digest("hex"); +} \ No newline at end of file diff --git a/test/adapter.test.ts b/test/adapter.test.ts index 713b8f0..fc0a220 100644 --- a/test/adapter.test.ts +++ b/test/adapter.test.ts @@ -1,7 +1,13 @@ import { BlockFrostAPI } from "@blockfrost/blockfrost-js"; import { jest } from "@jest/globals"; -import { BlockfrostAdapter } from "../src"; +import { + ADA, + Asset, + BlockfrostAdapter, + NetworkId, + StableswapConstant, +} from "../src"; function mustGetEnv(key: string): string { const val = process.env[key]; @@ -11,11 +17,25 @@ function mustGetEnv(key: string): string { return val; } -const MIN = "29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c64d494e"; -const MIN_ADA_POOL_ID = +const MIN_TESTNET = + "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed724d494e"; +const MIN_MAINNET = + "29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c64d494e"; +const MIN_ADA_POOL_V1_ID_TESTNET = + "3bb0079303c57812462dec9de8fb867cef8fd3768de7f12c77f6f0dd80381d0d"; +const MIN_ADA_POOL_V1_ID_MAINNET = "6aa2153e1ae896a95539c9d62f76cedcdabdcdf144e564b8955f609d660cf6a2"; -const adapter = new BlockfrostAdapter({ +const adapterTestnet = new BlockfrostAdapter({ + networkId: NetworkId.TESTNET, + blockFrost: new BlockFrostAPI({ + projectId: mustGetEnv("BLOCKFROST_PROJECT_ID_TESTNET"), + network: "preprod", + }), +}); + +const adapterMainnet = new BlockfrostAdapter({ + networkId: NetworkId.MAINNET, blockFrost: new BlockFrostAPI({ projectId: mustGetEnv("BLOCKFROST_PROJECT_ID_MAINNET"), network: "mainnet", @@ -27,12 +47,14 @@ beforeAll(() => { }); test("getAssetDecimals", async () => { - expect(await adapter.getAssetDecimals("lovelace")).toBe(6); - expect(await adapter.getAssetDecimals(MIN)).toBe(6); + expect(await adapterTestnet.getAssetDecimals("lovelace")).toBe(6); + expect(await adapterTestnet.getAssetDecimals(MIN_TESTNET)).toBe(0); + expect(await adapterMainnet.getAssetDecimals("lovelace")).toBe(6); + expect(await adapterMainnet.getAssetDecimals(MIN_MAINNET)).toBe(6); }); -test("getPoolPrice", async () => { - const pools = await adapter.getPools({ +async function testPoolPrice(adapter: BlockfrostAdapter): Promise { + const pools = await adapter.getV1Pools({ page: 1, }); expect(pools.length).toBeGreaterThan(0); @@ -40,26 +62,105 @@ test("getPoolPrice", async () => { for (let i = 0; i < 5; i++) { const idx = Math.floor(Math.random() * pools.length); const pool = pools[idx]; - const [priceAB, priceBA] = await adapter.getPoolPrice({ pool }); + const [priceAB, priceBA] = await adapter.getV1PoolPrice({ pool }); // product of 2 prices must be approximately equal to 1 // abs(priceAB * priceBA - 1) <= epsilon expect(priceAB.mul(priceBA).sub(1).abs().toNumber()).toBeLessThanOrEqual( 1e-6 ); } +} + +test("getPoolPrice", async () => { + await testPoolPrice(adapterTestnet); + await testPoolPrice(adapterMainnet); }, 10000); -test("getPoolById", async () => { - const pool = await adapter.getPoolById({ id: MIN_ADA_POOL_ID }); - expect(pool).not.toBeNull(); - expect(pool?.assetA).toEqual("lovelace"); - expect(pool?.assetB).toEqual(MIN); +test("getV1PoolById", async () => { + const adaMINTestnet = await adapterTestnet.getV1PoolById({ + id: MIN_ADA_POOL_V1_ID_TESTNET, + }); + expect(adaMINTestnet).not.toBeNull(); + expect(adaMINTestnet?.assetA).toEqual("lovelace"); + expect(adaMINTestnet?.assetB).toEqual(MIN_TESTNET); + + const adaMINMainnet = await adapterMainnet.getV1PoolById({ + id: MIN_ADA_POOL_V1_ID_MAINNET, + }); + expect(adaMINMainnet).not.toBeNull(); + expect(adaMINMainnet?.assetA).toEqual("lovelace"); + expect(adaMINMainnet?.assetB).toEqual(MIN_MAINNET); }); -test("get prices of last 5 states of MIN/ADA pool", async () => { - const history = await adapter.getPoolHistory({ id: MIN_ADA_POOL_ID }); +async function testPriceHistory( + adapter: BlockfrostAdapter, + id: string +): Promise { + const history = await adapter.getV1PoolHistory({ id: id }); for (let i = 0; i < Math.min(5, history.length); i++) { - const pool = await adapter.getPoolInTx({ txHash: history[i].txHash }); + const pool = await adapter.getV1PoolInTx({ txHash: history[i].txHash }); expect(pool?.txIn.txHash).toEqual(history[i].txHash); } +} + +test("get prices of last 5 states of MIN/ADA pool", async () => { + await testPriceHistory(adapterTestnet, MIN_ADA_POOL_V1_ID_TESTNET); + await testPriceHistory(adapterMainnet, MIN_ADA_POOL_V1_ID_MAINNET); +}); + +test("getV2PoolByPair", async () => { + const pool = await adapterTestnet.getV2PoolByPair(ADA, { + policyId: "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72", + tokenName: "4d494e", + }); + expect(pool).not.toBeNull(); + expect(pool?.assetA).toEqual("lovelace"); + expect(pool?.assetB).toEqual(MIN_TESTNET); +}); + +test("getAllV2Pools", async () => { + const { pools } = await adapterTestnet.getAllV2Pools(); + expect(pools.length > 0); +}); + +test("getV2Pools", async () => { + const { pools } = await adapterTestnet.getV2Pools({ + page: 1, + }); + expect(pools.length > 0); +}); + +test("getAllStablePools", async () => { + const numberOfStablePoolsTestnet = + StableswapConstant.CONFIG[NetworkId.TESTNET].length; + const numberOfStablePoolsMainnet = + StableswapConstant.CONFIG[NetworkId.MAINNET].length; + const { pools: testnetPools } = await adapterTestnet.getAllStablePools(); + expect(testnetPools.length === numberOfStablePoolsTestnet); + + const { pools: mainnetPools } = await adapterMainnet.getAllStablePools(); + expect(mainnetPools.length === numberOfStablePoolsMainnet); +}); + +test("getStablePoolByNFT", async () => { + const testnetCfgs = StableswapConstant.CONFIG[NetworkId.TESTNET]; + const mainnetCfgs = StableswapConstant.CONFIG[NetworkId.MAINNET]; + + for (const cfg of testnetCfgs) { + const pool = await adapterTestnet.getStablePoolByNFT( + Asset.fromString(cfg.nftAsset) + ); + 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.getStablePoolByNFT( + Asset.fromString(cfg.nftAsset) + ); + expect(pool).not.toBeNull(); + expect(pool?.nft).toEqual(cfg.nftAsset); + expect(pool?.assets).toEqual(cfg.assets); + } }); diff --git a/test/order.test.ts b/test/order.test.ts index 3335b03..148fcb3 100644 --- a/test/order.test.ts +++ b/test/order.test.ts @@ -2,10 +2,10 @@ import JSONBig from "json-bigint"; import { Address, Data } from "lucid-cardano"; import { FIXED_BATCHER_FEE } from "../src/batcher-fee-reduction/configs.internal"; -import { FIXED_DEPOSIT_ADA } from "../src/constants"; +import { FIXED_DEPOSIT_ADA } from "../src/types/constants"; import { Asset } from "../src/types/asset"; import { NetworkId } from "../src/types/network"; -import { OrderDatum, OrderStepType } from "../src/types/order"; +import { OrderV1 } from "../src/types/order"; let testSender: Address; let testReceiver: Address; @@ -27,12 +27,12 @@ beforeAll(() => { }); test("SwapExactIn Order to PlutusData Converter", () => { - const order1: OrderDatum = { + const order1: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: testReceiverDatumHash, step: { - type: OrderStepType.SWAP_EXACT_IN, + type: OrderV1.StepType.SWAP_EXACT_IN, desiredAsset: testAsset, minimumReceived: 10n, }, @@ -40,12 +40,12 @@ test("SwapExactIn Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const order2: OrderDatum = { + const order2: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: undefined, step: { - type: OrderStepType.SWAP_EXACT_IN, + type: OrderV1.StepType.SWAP_EXACT_IN, desiredAsset: testAsset, minimumReceived: 10n, }, @@ -53,25 +53,25 @@ test("SwapExactIn Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const convertedOrder1 = OrderDatum.fromPlutusData( + const convertedOrder1 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order1))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order1))) ); - const convertedOrder2 = OrderDatum.fromPlutusData( + const convertedOrder2 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order2))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order2))) ); expect(JSONBig.stringify(order1)).toEqual(JSONBig.stringify(convertedOrder1)); expect(JSONBig.stringify(order2)).toEqual(JSONBig.stringify(convertedOrder2)); }); test("SwapExactOut Order to PlutusData Converter", () => { - const order1: OrderDatum = { + const order1: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: testReceiverDatumHash, step: { - type: OrderStepType.SWAP_EXACT_OUT, + type: OrderV1.StepType.SWAP_EXACT_OUT, desiredAsset: testAsset, expectedReceived: 10n, }, @@ -79,12 +79,12 @@ test("SwapExactOut Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const order2: OrderDatum = { + const order2: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: undefined, step: { - type: OrderStepType.SWAP_EXACT_OUT, + type: OrderV1.StepType.SWAP_EXACT_OUT, desiredAsset: testAsset, expectedReceived: 10n, }, @@ -92,62 +92,62 @@ test("SwapExactOut Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const convertedOrder1 = OrderDatum.fromPlutusData( + const convertedOrder1 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order1))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order1))) ); - const convertedOrder2 = OrderDatum.fromPlutusData( + const convertedOrder2 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order2))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order2))) ); expect(JSONBig.stringify(order1)).toEqual(JSONBig.stringify(convertedOrder1)); expect(JSONBig.stringify(order2)).toEqual(JSONBig.stringify(convertedOrder2)); }); test("Deposit Order to PlutusData Converter", () => { - const order1: OrderDatum = { + const order1: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: testReceiverDatumHash, step: { - type: OrderStepType.DEPOSIT, + type: OrderV1.StepType.DEPOSIT, minimumLP: 10n, }, batcherFee: FIXED_BATCHER_FEE, depositADA: FIXED_DEPOSIT_ADA, }; - const order2: OrderDatum = { + const order2: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: undefined, step: { - type: OrderStepType.DEPOSIT, + type: OrderV1.StepType.DEPOSIT, minimumLP: 10n, }, batcherFee: FIXED_BATCHER_FEE, depositADA: FIXED_DEPOSIT_ADA, }; - const convertedOrder1 = OrderDatum.fromPlutusData( + const convertedOrder1 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order1))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order1))) ); - const convertedOrder2 = OrderDatum.fromPlutusData( + const convertedOrder2 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order2))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order2))) ); expect(JSONBig.stringify(order1)).toEqual(JSONBig.stringify(convertedOrder1)); expect(JSONBig.stringify(order2)).toEqual(JSONBig.stringify(convertedOrder2)); }); test("Withdraw Order to PlutusData Converter", () => { - const order1: OrderDatum = { + const order1: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: testReceiverDatumHash, step: { - type: OrderStepType.WITHDRAW, + type: OrderV1.StepType.WITHDRAW, minimumAssetA: 10n, minimumAssetB: 11n, }, @@ -155,12 +155,12 @@ test("Withdraw Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const order2: OrderDatum = { + const order2: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: undefined, step: { - type: OrderStepType.WITHDRAW, + type: OrderV1.StepType.WITHDRAW, minimumAssetA: 10n, minimumAssetB: 11n, }, @@ -168,25 +168,25 @@ test("Withdraw Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const convertedOrder1 = OrderDatum.fromPlutusData( + const convertedOrder1 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order1))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order1))) ); - const convertedOrder2 = OrderDatum.fromPlutusData( + const convertedOrder2 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order2))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order2))) ); expect(JSONBig.stringify(order1)).toEqual(JSONBig.stringify(convertedOrder1)); expect(JSONBig.stringify(order2)).toEqual(JSONBig.stringify(convertedOrder2)); }); test("Zap Order to PlutusData Converter", () => { - const order1: OrderDatum = { + const order1: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: testReceiverDatumHash, step: { - type: OrderStepType.ZAP_IN, + type: OrderV1.StepType.ZAP_IN, desiredAsset: testAsset, minimumLP: 11n, }, @@ -194,12 +194,12 @@ test("Zap Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const order2: OrderDatum = { + const order2: OrderV1.Datum = { sender: testSender, receiver: testReceiver, receiverDatumHash: undefined, step: { - type: OrderStepType.ZAP_IN, + type: OrderV1.StepType.ZAP_IN, desiredAsset: testAsset, minimumLP: 11n, }, @@ -207,13 +207,13 @@ test("Zap Order to PlutusData Converter", () => { depositADA: FIXED_DEPOSIT_ADA, }; - const convertedOrder1 = OrderDatum.fromPlutusData( + const convertedOrder1 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order1))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order1))) ); - const convertedOrder2 = OrderDatum.fromPlutusData( + const convertedOrder2 = OrderV1.Datum.fromPlutusData( networkId, - Data.from(Data.to(OrderDatum.toPlutusData(order2))) + Data.from(Data.to(OrderV1.Datum.toPlutusData(order2))) ); expect(JSONBig.stringify(order1)).toEqual(JSONBig.stringify(convertedOrder1)); expect(JSONBig.stringify(order2)).toEqual(JSONBig.stringify(convertedOrder2)); diff --git a/test/pool.test.ts b/test/pool.test.ts index d0f4bc5..96cc6a6 100644 --- a/test/pool.test.ts +++ b/test/pool.test.ts @@ -3,7 +3,7 @@ import { Data } from "lucid-cardano"; import { NetworkId } from "../src"; import { ADA, Asset } from "../src/types/asset"; -import { PoolDatum, PoolState } from "../src/types/pool"; +import { PoolV1 } from "../src/types/pool"; import { isValidPoolOutput, PoolFeeSharing } from "../src/types/pool.internal"; import { TxIn, Value } from "../src/types/tx.internal"; @@ -36,8 +36,8 @@ test("can handle pool with one side being LP tokens", () => { isValidPoolOutput(PREPROD_POOL_ADDRESS, value, datumHash) ).toBeTruthy(); expect( - new PoolState(PREPROD_POOL_ADDRESS, txIn, value, datumHash) - ).toBeInstanceOf(PoolState); + new PoolV1.State(PREPROD_POOL_ADDRESS, txIn, value, datumHash) + ).toBeInstanceOf(PoolV1.State); }); test("Fee Sharing to PlutusData Converter", () => { @@ -74,7 +74,7 @@ test("Pool Datum to PlutusData Converter", () => { policyId: "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72", tokenName: "4d494e", }; - const poolDatum1: PoolDatum = { + const poolDatum1: PoolV1.Datum = { assetA: assetA, assetB: assetB, totalLiquidity: 100000n, @@ -86,7 +86,7 @@ test("Pool Datum to PlutusData Converter", () => { }, }; - const poolDatum2: PoolDatum = { + const poolDatum2: PoolV1.Datum = { assetA: assetA, assetB: assetB, totalLiquidity: 100000n, @@ -94,13 +94,13 @@ test("Pool Datum to PlutusData Converter", () => { feeSharing: undefined, }; - const convertedPoolDatum1 = PoolDatum.fromPlutusData( + const convertedPoolDatum1 = PoolV1.Datum.fromPlutusData( NetworkId.TESTNET, - Data.from(Data.to(PoolDatum.toPlutusData(poolDatum1))) + Data.from(Data.to(PoolV1.Datum.toPlutusData(poolDatum1))) ); - const convertedPoolDatum2 = PoolDatum.fromPlutusData( + const convertedPoolDatum2 = PoolV1.Datum.fromPlutusData( NetworkId.TESTNET, - Data.from(Data.to(PoolDatum.toPlutusData(poolDatum2))) + Data.from(Data.to(PoolV1.Datum.toPlutusData(poolDatum2))) ); expect(JSONBig.stringify(poolDatum1)).toEqual( From dccde8fce71ecfea4eeccc0e8a5cb8b4d508b844 Mon Sep 17 00:00:00 2001 From: h2physics Date: Thu, 4 Jul 2024 14:59:38 +0700 Subject: [PATCH 2/3] minor --- src/types/pool.ts | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/types/pool.ts b/src/types/pool.ts index a7376d1..53ef0ca 100644 --- a/src/types/pool.ts +++ b/src/types/pool.ts @@ -13,6 +13,8 @@ import { NetworkId } from "./network"; import { normalizeAssets, PoolFeeSharing } from "./pool.internal"; import { TxIn, Value } from "./tx.internal"; +export const DEFAULT_POOL_V2_TRADING_FEE_DENOMINATOR = 10000n; + export namespace PoolV1 { /** * Represents state of a pool UTxO. The state could be latest state or a historical state. @@ -312,20 +314,33 @@ export namespace PoolV2 { return this.datum.reserveA } - get reserve(): bigint { + get reserveB(): bigint { return this.datum.reserveB } - get feeA(): bigint { - return this.datum.baseFee.feeANumerator + get feeA(): [bigint, bigint] { + return [ + this.datum.baseFee.feeANumerator, + DEFAULT_POOL_V2_TRADING_FEE_DENOMINATOR + ] } - get feeB(): bigint { - return this.datum.baseFee.feeBNumerator + get feeB(): [bigint, bigint] { + return [ + this.datum.baseFee.feeBNumerator, + DEFAULT_POOL_V2_TRADING_FEE_DENOMINATOR + ] } - get feeShare(): bigint | undefined { - return this.datum.feeSharingNumerator + get feeShare(): [bigint, bigint] | undefined { + if (this.datum.feeSharingNumerator !== undefined) { + return [ + this.datum.feeSharingNumerator, + DEFAULT_POOL_V2_TRADING_FEE_DENOMINATOR + ] + } else { + return undefined + } } } From 738a9753ba839bf1d212352855b9928c45392035 Mon Sep 17 00:00:00 2001 From: Nguyen Le Vu Long Date: Thu, 4 Jul 2024 16:02:20 +0700 Subject: [PATCH 3/3] update CI Signed-off-by: Nguyen Le Vu Long --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 225b965..98bc5ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,8 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 18.x - name: Install dependencies @@ -24,5 +24,6 @@ jobs: run: npm run test env: BLOCKFROST_PROJECT_ID_MAINNET: ${{ secrets.BLOCKFROST_PROJECT_ID_MAINNET }} + BLOCKFROST_PROJECT_ID_TESTNET: ${{ secrets.BLOCKFROST_PROJECT_ID_TESTNET }} - name: Check format & lint run: npm run check-format