UNPKG

ndtr-dexter

Version:

Customizable Typescript SDK for interacting with Cardano DEXs

166 lines (165 loc) 7.56 kB
import { LiquidityPool } from './models/liquidity-pool'; import { BaseDex } from './base-dex'; import { DefinitionBuilder } from '../definition-builder'; import { correspondingReserves, tokensMatch } from '../utils'; import { AddressType, DatumParameterKey } from '../constants'; import pool from './definitions/sundaeswap/pool'; import order from './definitions/sundaeswap/order'; import { SundaeSwapApi } from './api/sundaeswap-api'; export class SundaeSwap extends BaseDex { constructor(requestConfig = {}) { super(); this.name = 'SundaeSwap'; /** * On-Chain constants. */ this.orderAddress = 'addr1wxaptpmxcxawvr3pzlhgnpmzz3ql43n2tc8mn3av5kx0yzs09tqh8'; this.poolAddress = 'addr1w9qzpelu9hn45pefc0xr4ac4kdxeswq7pndul2vuj59u8tqaxdznu'; this.lpTokenPolicyId = '0029cb7c88c7567b63d1a512c0ed626aa169688ec980730c0473b913'; this.api = new SundaeSwapApi(this, requestConfig); } async liquidityPoolAddresses(provider) { return Promise.resolve([this.poolAddress]); } async liquidityPools(provider) { const utxos = await provider.utxos(this.poolAddress); return await Promise.all(utxos.map(async (utxo) => { return await this.liquidityPoolFromUtxo(provider, utxo); })) .then((liquidityPools) => { return liquidityPools.filter((liquidityPool) => { return liquidityPool !== undefined; }); }); } 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.id(); return !assetBalanceId.startsWith(this.lpTokenPolicyId); }); // Irrelevant UTxO if (![2, 3].includes(relevantAssets.length)) { 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(this.name, relevantAssets[assetAIndex].asset, relevantAssets[assetBIndex].asset, relevantAssets[assetAIndex].quantity, relevantAssets[assetBIndex].quantity, utxo.address, this.orderAddress, this.orderAddress); // Load additional pool information const lpToken = utxo.assetBalances.find((assetBalance) => { return assetBalance.asset !== 'lovelace' && assetBalance.asset.policyId === this.lpTokenPolicyId; })?.asset; if (lpToken) { lpToken.nameHex = '6c' + lpToken.nameHex; liquidityPool.lpToken = lpToken; } try { const builder = await (new DefinitionBuilder()) .loadDefinition(pool); const datum = await provider.datumValue(utxo.datumHash); const parameters = builder.pullParameters(datum); liquidityPool.identifier = typeof parameters.PoolIdentifier === 'string' ? parameters.PoolIdentifier : ''; liquidityPool.totalLpTokens = typeof parameters.TotalLpTokens === 'number' ? BigInt(parameters.TotalLpTokens) : 0n; liquidityPool.poolFeePercent = typeof parameters.LpFeeNumerator === 'number' && typeof parameters.LpFeeDenominator === 'number' ? (parameters.LpFeeNumerator / parameters.LpFeeDenominator) * 100 : 0; } catch (e) { return liquidityPool; } return liquidityPool; } estimatedGive(liquidityPool, swapOutToken, swapOutAmount) { const [reserveOut, reserveIn] = correspondingReserves(liquidityPool, swapOutToken); const receive = (reserveIn * reserveOut) / (reserveOut - swapOutAmount) - reserveIn; const swapFee = ((receive * BigInt(liquidityPool.poolFeePercent * 100)) + BigInt(10000) - 1n) / 10000n; return receive + swapFee; } estimatedReceive(liquidityPool, swapInToken, swapInAmount) { const [reserveIn, reserveOut] = correspondingReserves(liquidityPool, swapInToken); const swapFee = ((swapInAmount * BigInt(liquidityPool.poolFeePercent * 100)) + BigInt(10000) - 1n) / 10000n; return reserveOut - (reserveIn * reserveOut) / (reserveIn + swapInAmount - swapFee); } priceImpactPercent(liquidityPool, swapInToken, swapInAmount) { const reserveIn = tokensMatch(swapInToken, liquidityPool.assetA) ? liquidityPool.reserveA : liquidityPool.reserveB; return (1 - (Number(reserveIn) / Number(reserveIn + swapInAmount))) * 100; } async buildSwapOrder(liquidityPool, swapParameters) { const scooperFee = this.swapOrderFees().find((fee) => fee.id === 'scooperFee'); const deposit = this.swapOrderFees().find((fee) => fee.id === 'deposit'); if (!scooperFee || !deposit) { return Promise.reject('Parameters for datum are not set.'); } const swapInToken = swapParameters.SwapInTokenPolicyId + swapParameters.SwapInTokenAssetName; const swapOutToken = swapParameters.SwapOutTokenPolicyId + swapParameters.SwapOutTokenAssetName; const swapDirection = [swapInToken, swapOutToken].sort((a, b) => { return a.localeCompare(b); })[0] === swapInToken ? 0 : 1; swapParameters = { ...swapParameters, [DatumParameterKey.ScooperFee]: scooperFee.value, [DatumParameterKey.Action]: swapDirection, }; const datumBuilder = new DefinitionBuilder(); await datumBuilder.loadDefinition(order) .then((builder) => { builder.pushParameters(swapParameters); }); return [ this.buildSwapOrderPayment(swapParameters, { address: this.orderAddress, addressType: AddressType.Contract, assetBalances: [ { asset: 'lovelace', quantity: scooperFee.value + deposit.value, }, ], datum: datumBuilder.getCbor(), }) ]; } async buildCancelSwapOrder(txOutputs, returnAddress) { const relevantUtxo = txOutputs.find((utxo) => { return utxo.address === this.orderAddress; }); if (!relevantUtxo) { return Promise.reject('Unable to find relevant UTxO for cancelling the swap order.'); } return [ { address: returnAddress, addressType: AddressType.Base, assetBalances: relevantUtxo.assetBalances, spendUtxos: [relevantUtxo], } ]; } swapOrderFees() { return [ { id: 'scooperFee', title: 'Scooper Processing Fee', description: 'An ADA fee paid to the Sundae Scooper Network for processing your order.', value: 2500000n, isReturned: false, }, { id: 'deposit', title: 'Deposit', description: 'A small ADA deposit that you will get back when your order is processed or cancelled.', value: 2000000n, isReturned: true, }, ]; } }