UNPKG

@indigo-labs/dexter

Version:

Customizable Typescript SDK for interacting with Cardano DEXs

198 lines (197 loc) 10.3 kB
import { LiquidityPool } from './models/liquidity-pool'; import { Asset } from './models/asset'; import { BaseDex } from './base-dex'; import { DefinitionBuilder } from '../definition-builder'; import { correspondingReserves } from '../utils'; import { AddressType, DatumParameterKey } from '../constants'; import order from './definitions/minswap/order'; import { MinswapApi } from './api/minswap-api'; import pool from './definitions/minswap/pool'; export class Minswap extends BaseDex { constructor(requestConfig = {}) { super(); /** * On-Chain constants. */ this.marketOrderAddress = 'addr1wxn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uwc0h43gt'; this.limitOrderAddress = 'addr1zxn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uw6j2c79gy9l76sdg0xwhd7r0c0kna0tycz4y5s6mlenh8pq6s3z70'; this.lpTokenPolicyId = 'e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86'; this.poolNftPolicyId = '0be55d262b29f564998ff81efe21bdc0022621c12f15af08d0f2ddb1'; this.poolValidityAsset = '13aa2accf2e1561723aa26871e071fdf32c867cff7e7d50ad470d62f4d494e53574150'; this.cancelDatum = 'd87a80'; this.orderScript = { type: 'PlutusV1', script: '59014f59014c01000032323232323232322223232325333009300e30070021323233533300b3370e9000180480109118011bae30100031225001232533300d3300e22533301300114a02a66601e66ebcc04800400c5288980118070009bac3010300c300c300c300c300c300c300c007149858dd48008b18060009baa300c300b3754601860166ea80184ccccc0288894ccc04000440084c8c94ccc038cd4ccc038c04cc030008488c008dd718098018912800919b8f0014891ce1317b152faac13426e6a83e06ff88a4d62cce3c1634ab0a5ec133090014a0266008444a00226600a446004602600a601a00626600a008601a006601e0026ea8c03cc038dd5180798071baa300f300b300e3754601e00244a0026eb0c03000c92616300a001375400660106ea8c024c020dd5000aab9d5744ae688c8c0088cc0080080048c0088cc00800800555cf2ba15573e6e1d200201', }; this.api = new MinswapApi(this, requestConfig); } async liquidityPoolAddresses(provider) { const validityAsset = Asset.fromIdentifier(this.poolValidityAsset); const assetAddresses = await provider.assetAddresses(validityAsset); return Promise.resolve([...new Set(assetAddresses.map((assetAddress) => assetAddress.address))]); } async liquidityPools(provider) { const validityAsset = Asset.fromIdentifier(this.poolValidityAsset); const poolAddresses = await this.liquidityPoolAddresses(provider); const addressPromises = poolAddresses.map(async (address) => { const utxos = await provider.utxos(address, validityAsset); return await Promise.all(utxos.map(async (utxo) => { return await this.liquidityPoolFromUtxo(provider, utxo); })) .then((liquidityPools) => { return liquidityPools.filter((liquidityPool) => { return liquidityPool !== undefined; }); }); }); return Promise.all(addressPromises) .then((liquidityPools) => liquidityPools.flat()); } async liquidityPoolFromUtxo(provider, utxo) { if (!utxo.datumHash) { return Promise.resolve(undefined); } const relevantAssets = utxo.assetBalances .filter((assetBalance) => { const assetBalanceId = assetBalance.asset === 'lovelace' ? 'lovelace' : assetBalance.asset.identifier(); return assetBalanceId !== this.poolValidityAsset && !assetBalanceId.startsWith(this.lpTokenPolicyId) && !assetBalanceId.startsWith(this.poolNftPolicyId); }); // Irrelevant UTxO if (relevantAssets.length < 2) { return Promise.resolve(undefined); } // Could be ADA/X or X/X pool const assetAIndex = relevantAssets.length === 2 ? 0 : 1; const assetBIndex = relevantAssets.length === 2 ? 1 : 2; const liquidityPool = new LiquidityPool(Minswap.identifier, relevantAssets[assetAIndex].asset, relevantAssets[assetBIndex].asset, relevantAssets[assetAIndex].quantity, relevantAssets[assetBIndex].quantity, utxo.address, this.marketOrderAddress, this.limitOrderAddress); // Load additional pool information const poolNft = utxo.assetBalances.find((assetBalance) => { return assetBalance.asset !== 'lovelace' && assetBalance.asset.policyId === this.poolNftPolicyId; })?.asset; if (!poolNft) return undefined; liquidityPool.lpToken = new Asset(this.lpTokenPolicyId, poolNft.nameHex); liquidityPool.identifier = liquidityPool.lpToken.identifier(); liquidityPool.poolFeePercent = 0.3; try { liquidityPool.poolFeePercent = 0.3; const builder = await (new DefinitionBuilder()) .loadDefinition(pool); const datum = await provider.datumValue(utxo.datumHash); const parameters = builder.pullParameters(datum); // Ignore Zap orders if (typeof parameters.PoolAssetBPolicyId === 'string' && parameters.PoolAssetBPolicyId === this.lpTokenPolicyId) { return undefined; } liquidityPool.totalLpTokens = typeof parameters.TotalLpTokens === 'number' ? BigInt(parameters.TotalLpTokens) : 0n; } catch (e) { return liquidityPool; } return liquidityPool; } estimatedGive(liquidityPool, swapOutToken, swapOutAmount) { const poolFeeMultiplier = 10000n; const poolFeeModifier = poolFeeMultiplier - BigInt(Math.round((liquidityPool.poolFeePercent / 100) * Number(poolFeeMultiplier))); const [reserveOut, reserveIn] = correspondingReserves(liquidityPool, swapOutToken); const swapInNumerator = swapOutAmount * reserveIn * poolFeeMultiplier; const swapInDenominator = (reserveOut - swapOutAmount) * poolFeeModifier; return swapInNumerator / swapInDenominator + 1n; } estimatedReceive(liquidityPool, swapInToken, swapInAmount) { const poolFeeMultiplier = 10000n; const poolFeeModifier = poolFeeMultiplier - BigInt(Math.round((liquidityPool.poolFeePercent / 100) * Number(poolFeeMultiplier))); const [reserveIn, reserveOut] = correspondingReserves(liquidityPool, swapInToken); const swapOutNumerator = swapInAmount * reserveOut * poolFeeModifier; const swapOutDenominator = swapInAmount * poolFeeModifier + reserveIn * poolFeeMultiplier; return swapOutNumerator / swapOutDenominator; } priceImpactPercent(liquidityPool, swapInToken, swapInAmount) { const poolFeeMultiplier = 10000n; const poolFeeModifier = poolFeeMultiplier - BigInt(Math.round((liquidityPool.poolFeePercent / 100) * Number(poolFeeMultiplier))); const [reserveIn, reserveOut] = correspondingReserves(liquidityPool, swapInToken); const swapOutNumerator = swapInAmount * poolFeeModifier * reserveOut; const swapOutDenominator = swapInAmount * poolFeeModifier + reserveIn * poolFeeMultiplier; const priceImpactNumerator = (reserveOut * swapInAmount * swapOutDenominator * poolFeeModifier) - (swapOutNumerator * reserveIn * poolFeeMultiplier); const priceImpactDenominator = reserveOut * swapInAmount * swapOutDenominator * poolFeeMultiplier; return Number(priceImpactNumerator * 100n) / Number(priceImpactDenominator); } async buildSwapOrder(liquidityPool, swapParameters, spendUtxos = []) { const batcherFee = this.swapOrderFees().find((fee) => fee.id === 'batcherFee'); const deposit = this.swapOrderFees().find((fee) => fee.id === 'deposit'); if (!batcherFee || !deposit) { return Promise.reject('Parameters for datum are not set.'); } swapParameters = { ...swapParameters, [DatumParameterKey.BatcherFee]: batcherFee.value, [DatumParameterKey.DepositFee]: deposit.value, }; const datumBuilder = new DefinitionBuilder(); await datumBuilder.loadDefinition(order) .then((builder) => { builder.pushParameters(swapParameters); }); return [ this.buildSwapOrderPayment(swapParameters, { address: this.marketOrderAddress, addressType: AddressType.Contract, assetBalances: [ { asset: 'lovelace', quantity: batcherFee.value + deposit.value, }, ], datum: datumBuilder.getCbor(), isInlineDatum: false, spendUtxos: spendUtxos, }) ]; } async buildCancelSwapOrder(txOutputs, returnAddress) { const relevantUtxo = txOutputs.find((utxo) => { return [this.marketOrderAddress, this.limitOrderAddress].includes(utxo.address); }); if (!relevantUtxo) { return Promise.reject('Unable to find relevant UTxO for cancelling the swap order.'); } return [ { address: returnAddress, addressType: AddressType.Base, assetBalances: relevantUtxo.assetBalances, isInlineDatum: false, spendUtxos: [{ utxo: relevantUtxo, redeemer: this.cancelDatum, validator: this.orderScript, signer: returnAddress, }], } ]; } swapOrderFees() { return [ { id: 'batcherFee', title: 'Batcher Fee', description: 'Fee paid for the service of off-chain Laminar batcher to process transactions.', value: 2000000n, isReturned: false, }, { id: 'deposit', title: 'Deposit', description: 'This amount of ADA will be held as minimum UTxO ADA and will be returned when your order is processed or cancelled.', value: 2000000n, isReturned: true, }, ]; } } Minswap.identifier = 'Minswap';