UNPKG

@indigo-labs/dexter

Version:

Customizable Typescript SDK for interacting with Cardano DEXs

244 lines (243 loc) 10.5 kB
import { tokensMatch } from '../utils'; import { DatumParameterKey, MetadataKey, TransactionStatus } from '../constants'; export class SwapRequest { constructor(dexter) { this._swapInAmount = 0n; this._slippagePercent = 1.0; this._withUtxos = []; this._metadata = ''; this._dexter = dexter; } get liquidityPool() { return this._liquidityPool; } get swapInToken() { return this._swapInToken; } get swapOutToken() { return this._swapOutToken; } get swapInAmount() { return this._swapInAmount; } get slippagePercent() { return this._slippagePercent; } forLiquidityPool(liquidityPool) { if (!Object.keys(this._dexter.availableDexs).includes(liquidityPool.dex)) { throw new Error(`DEX ${liquidityPool.dex} provided with the liquidity pool is not available.`); } this._liquidityPool = liquidityPool; return this; } flip() { if (this._swapInToken) { [this._swapInToken, this._swapOutToken] = [this._swapOutToken, this._swapInToken]; this.withSwapOutAmount(this._swapInAmount); } return this; } withMetadata(metadata) { this._metadata = metadata; return this; } withSwapInToken(swapInToken) { if (!this._liquidityPool) { throw new Error('Liquidity pool must be set before providing an input token.'); } if (tokensMatch(swapInToken, this._liquidityPool.assetA)) { this._swapOutToken = this._liquidityPool.assetB; } else if (tokensMatch(swapInToken, this._liquidityPool.assetB)) { this._swapOutToken = this._liquidityPool.assetA; } else { throw new Error("Input token doesn't exist in the set liquidity pool."); } this._swapInToken = swapInToken; return this; } withSwapOutToken(swapOutToken) { if (!this._liquidityPool) { throw new Error('Liquidity pool must be set before providing an input token.'); } if (tokensMatch(swapOutToken, this._liquidityPool.assetA)) { this._swapInToken = this._liquidityPool.assetB; } else if (tokensMatch(swapOutToken, this._liquidityPool.assetB)) { this._swapInToken = this._liquidityPool.assetA; } else { throw new Error("Output token doesn't exist in the set liquidity pool."); } this._swapOutToken = swapOutToken; return this; } withSwapInAmount(swapInAmount) { this._swapInAmount = swapInAmount > 0n ? swapInAmount : 0n; return this; } withSwapOutAmount(swapOutAmount) { if (swapOutAmount <= 0n) { this._swapInAmount = 0n; } if (!this._liquidityPool) { throw new Error('Liquidity pool must be set before setting a swap out amount.'); } this._swapInAmount = this._dexter.availableDexs[this._liquidityPool.dex].estimatedGive(this._liquidityPool, this._swapOutToken, swapOutAmount); return this; } withMinimumReceive(minReceive) { if (minReceive <= 0n) { this._swapInAmount = 0n; } if (!this._liquidityPool) { throw new Error('Liquidity pool must be set before setting a swap out amount.'); } this._swapInAmount = this._dexter.availableDexs[this._liquidityPool.dex].estimatedGive(this._liquidityPool, this._swapOutToken, BigInt(Math.ceil(Number(minReceive) * (1 + (this._slippagePercent / 100))))); return this; } withSlippagePercent(slippagePercent) { if (slippagePercent < 0) { throw new Error('Slippage percent must be zero or above.'); } this._slippagePercent = slippagePercent; return this; } withUtxos(utxos) { if (utxos.length === 0) { throw new Error('Must provide valid UTxOs to use in swap.'); } this._withUtxos = utxos; return this; } getEstimatedReceive(liquidityPool) { const poolToCheck = liquidityPool ?? this._liquidityPool; if (!poolToCheck) { throw new Error('Liquidity pool must be set before calculating the estimated receive.'); } if (!this._swapInToken) { throw new Error('Swap in token must be set before calculating the estimated receive.'); } return this._dexter.availableDexs[this._liquidityPool.dex].estimatedReceive(poolToCheck, this._swapInToken, this._swapInAmount); } getMinimumReceive(liquidityPool) { return BigInt(Math.floor(Number(this.getEstimatedReceive(liquidityPool)) / (1 + (this._slippagePercent / 100)))); } getPriceImpactPercent() { if (!this._liquidityPool) { throw new Error('Liquidity pool must be set before calculating the price impact.'); } if (!this._swapInToken) { throw new Error('Swap in token must be set before calculating the price impact.'); } return this._dexter.availableDexs[this._liquidityPool.dex].priceImpactPercent(this._liquidityPool, this._swapInToken, this._swapInAmount); } getSwapFees() { return this._dexter.availableDexs[this._liquidityPool.dex].swapOrderFees(); } getPaymentsToAddresses() { if (!this._dexter.walletProvider) { throw new Error('Wallet provider must be set before submitting a swap order.'); } if (!this._dexter.walletProvider.isWalletLoaded) { throw new Error('Wallet must be loaded before submitting a swap order.'); } if (!this._liquidityPool) { throw new Error('Liquidity pool must be set before submitting a swap order.'); } if (!this._swapInToken) { throw new Error('Swap in token must be set before submitting a swap order.'); } if (this._swapInAmount <= 0n) { throw new Error('Swap in amount must be set before submitting a swap order.'); } // Standard parameters for a swap order const defaultSwapParameters = { [DatumParameterKey.Address]: this._dexter.walletProvider.address(), [DatumParameterKey.SenderPubKeyHash]: this._dexter.walletProvider.publicKeyHash(), [DatumParameterKey.SenderStakingKeyHash]: this._dexter.walletProvider.stakingKeyHash(), [DatumParameterKey.ReceiverPubKeyHash]: this._dexter.walletProvider.publicKeyHash(), [DatumParameterKey.ReceiverStakingKeyHash]: this._dexter.walletProvider.stakingKeyHash(), [DatumParameterKey.PoolIdentifier]: this._liquidityPool.identifier, [DatumParameterKey.SwapInAmount]: this._swapInAmount, [DatumParameterKey.MinReceive]: this.getMinimumReceive(), [DatumParameterKey.SwapInTokenPolicyId]: this._swapInToken === 'lovelace' ? '' : this._swapInToken.policyId, [DatumParameterKey.SwapInTokenAssetName]: this._swapInToken === 'lovelace' ? '' : this._swapInToken.nameHex, [DatumParameterKey.SwapOutTokenPolicyId]: this._swapOutToken === 'lovelace' ? '' : this._swapOutToken.policyId, [DatumParameterKey.SwapOutTokenAssetName]: this._swapOutToken === 'lovelace' ? '' : this._swapOutToken.nameHex, }; return this._dexter.availableDexs[this._liquidityPool.dex] .buildSwapOrder(this._liquidityPool, defaultSwapParameters, this._withUtxos.map((utxo) => { return { utxo, }; }), this._dexter.dataProvider); } submit() { if (!this._dexter.walletProvider) { throw new Error('Wallet provider must be set before submitting a swap order.'); } if (!this._dexter.walletProvider.isWalletLoaded) { throw new Error('Wallet must be loaded before submitting a swap order.'); } const swapTransaction = this._dexter.walletProvider.createTransaction(); if (!this._dexter.config.shouldSubmitOrders) { return swapTransaction; } this.getPaymentsToAddresses() .then((payToAddresses) => { this.sendSwapOrder(swapTransaction, payToAddresses); }); return swapTransaction; } sendSwapOrder(swapTransaction, payToAddresses) { swapTransaction.status = TransactionStatus.Building; const swapInTokenName = this._swapInToken === 'lovelace' ? 'ADA' : this._swapInToken.assetName; const swapOutTokenName = this._swapOutToken === 'lovelace' ? 'ADA' : this._swapOutToken.assetName; swapTransaction.attachMetadata(MetadataKey.Message, { msg: [ this._metadata !== '' ? this._metadata : `[${this._dexter.config.metadataMsgBranding}] ${this._liquidityPool.dex} ${swapInTokenName} -> ${swapOutTokenName} Swap` ] }); // Build transaction swapTransaction.payToAddresses(payToAddresses) .then(() => { swapTransaction.status = TransactionStatus.Signing; // Sign transaction swapTransaction.sign() .then(() => { swapTransaction.status = TransactionStatus.Submitting; // Submit transaction swapTransaction.submit() .then(() => { swapTransaction.status = TransactionStatus.Submitted; }) .catch((error) => { swapTransaction.error = { step: TransactionStatus.Submitting, reason: 'Failed submitting transaction.', reasonRaw: error, }; swapTransaction.status = TransactionStatus.Errored; }); }) .catch((error) => { swapTransaction.error = { step: TransactionStatus.Signing, reason: 'Failed to sign transaction.', reasonRaw: error, }; swapTransaction.status = TransactionStatus.Errored; }); }) .catch((error) => { swapTransaction.error = { step: TransactionStatus.Building, reason: 'Failed to build transaction.', reasonRaw: error, }; swapTransaction.status = TransactionStatus.Errored; }); } }