UNPKG

@yoroi/swap

Version:
332 lines (322 loc) 11.3 kB
"use strict"; import { Portfolio, Swap } from '@yoroi/types'; import { Dex } from './types'; export const transformersMaker = ({ primaryTokenInfo, address, isPrimaryToken, partner }) => { const fromTokenId = tokenId => { if (tokenId === 'lovelace' || tokenId === 'ADA') { return primaryTokenInfo.id; } // If already in format policyId.hexName, return as is if (tokenId.includes('.')) { return tokenId; } // Steelswap uses hex-encoded format: policyId + hexName (no separator) // PolicyId is always 56 characters, rest is hexName return `${tokenId.slice(0, 56)}.${tokenId.slice(56)}`; }; const toTokenId = tokenId => { if (isPrimaryToken(tokenId)) { return 'lovelace'; } return tokenId.replace('.', ''); }; const parseAssets = assets => { const result = []; for (const asset of assets) { for (const [token, amount] of Object.entries(asset)) { // Amounts are already in decimal format (isFloat=true) result.push({ token, amount }); } } return result; }; return { tokens: { response: res => res.map(({ ticker, name, policyId, policyName, decimals }) => { const hexName = policyName || ''; const id = `${policyId}.${hexName}`; // Filter out primary token (it's already known) // Check if policyId is 'lovelace' or if the constructed id matches primaryTokenInfo.id const isPrimary = policyId === 'lovelace' || id === primaryTokenInfo.id; if (isPrimary) return null; if (decimals === null || decimals === undefined) return null; return { status: Portfolio.Token.Status.Valid, id, ticker: ticker ?? '', name: name ?? ticker ?? '', type: Portfolio.Token.Type.FT, nature: Portfolio.Token.Nature.Secondary, application: Portfolio.Token.Application.General, fingerprint: '', decimals, description: '', originalImage: '', symbol: '', reference: '', tag: '', website: '' }; }).filter(v => !!v) }, orders: { response: res => { const orders = []; for (const orderStatus of res.orders) { for (const swap of orderStatus.swaps) { const submitAssets = swap.submitAssets ? parseAssets(swap.submitAssets) : []; const requestAssets = swap.requestAssets ? parseAssets(swap.requestAssets) : []; const receivedAssets = swap.receivedAssets ? parseAssets(swap.receivedAssets) : []; // Find tokenIn and tokenOut from assets const tokenInAsset = submitAssets[0]; const tokenOutAsset = requestAssets[0]; const receivedAsset = receivedAssets[0]; if (!tokenInAsset || !tokenOutAsset) continue; const tokenIn = fromTokenId(tokenInAsset.token); const tokenOut = fromTokenId(tokenOutAsset.token); const amountIn = tokenInAsset.amount; const expectedAmountOut = tokenOutAsset.amount; const actualAmountOut = receivedAsset?.amount ?? expectedAmountOut; // Map txStatus to our status const statusMap = { txPending: 'open', queued: 'open', executed: 'matched', cancelled: 'canceled' }; const status = statusMap[swap.txStatus] ?? 'open'; orders.push({ status, txHash: swap.submitTxHash, outputIndex: swap.submitTxIndex, tokenIn, tokenOut, updateTxHash: swap.executeTxHash ?? swap.submitTxHash, placedAt: swap.submitTime ? swap.submitTime : undefined, lastUpdate: swap.executeTime ? swap.executeTime : swap.submitTime ? swap.submitTime : undefined, amountIn, actualAmountOut, expectedAmountOut, aggregator: Swap.Aggregator.Steelswap, protocol: toSwapProtocol(swap.dex), customId: `${swap.submitTxHash}#${swap.submitTxIndex}` }); } } return orders; } }, estimate: { request: ({ tokenIn, tokenOut, amountIn, amountOut, slippage: _slippage }) => { const tokenAId = toTokenId(tokenIn); const tokenBId = toTokenId(tokenOut); // Amounts are already in decimal format adjusted by token decimals (isFloat=true) const quantity = amountIn ?? amountOut ?? 0; return { tokenA: tokenAId, tokenB: tokenBId, quantity: Math.round(quantity), predictFromOutputAmount: amountOut !== undefined, ...(partner !== undefined && { partner }), isFloat: true }; }, response: data => { // Handle both SplitOutput and HopSplitOutput const isHopSplit = 'splitGroup' in data; // When isFloat=true, use top-level values for totals (they're in decimal format) // But extract pools from splitGroup if it's a HopSplitOutput const topLevelData = data; const splitOutput = isHopSplit ? data.splitGroup?.[0]?.[0] : data; if (!splitOutput || !splitOutput.pools || splitOutput.pools.length === 0) { throw new Error('Invalid estimate response: missing split output or pools'); } // When isFloat=true, all values are already in decimal format const splits = splitOutput.pools.map(pool => ({ amountIn: pool.quantityA, // Already in decimal format batcherFee: pool.batcherFee, // Already in ADA deposits: pool.deposit, // Already in ADA protocol: toSwapProtocol(pool.dex), expectedOutput: pool.quantityB, // Already in decimal format expectedOutputWithoutSlippage: pool.quantityB, // Already in decimal format fee: pool.volumeFee, // Already in decimal format (isFloat=true) initialPrice: pool.quantityA > 0 ? pool.quantityB / pool.quantityA : 0, finalPrice: pool.quantityA > 0 ? pool.quantityB / pool.quantityA : 0, poolFee: pool.volumeFee, // Already in decimal format (isFloat=true) poolId: pool.poolId, priceDistortion: 0, priceImpact: 0, aggregator: Swap.Aggregator.Steelswap, aggregatorDexKey: pool.dex, aggregatorPoolId: pool.poolId })); // Use top-level values for totals (they're in decimal format when isFloat=true) const totalInput = topLevelData.quantityA; const totalOutput = topLevelData.quantityB; const deposits = topLevelData.totalDeposit; // Already in ADA const totalFee = topLevelData.totalFee + topLevelData.steelswapFee; const batcherFee = topLevelData.totalFee; // Already in ADA const aggregatorFee = topLevelData.steelswapFee; // Already in ADA const netPrice = totalInput > 0 ? totalOutput / totalInput : 0; return { splits, batcherFee, deposits, aggregatorFee, frontendFee: 0, netPrice, priceImpact: 0, totalFee, totalInput, totalOutput, totalOutputWithoutSlippage: totalOutput + topLevelData.bonusOut }; } }, create: { request: ({ tokenIn, tokenOut, amountIn, slippage, inputs }) => { const tokenAId = toTokenId(tokenIn); // AmountIn is already in decimal format adjusted by token decimals (isFloat=true) const quantity = amountIn; return { tokenA: tokenAId, tokenB: toTokenId(tokenOut), quantity: Math.round(quantity), ...(partner !== undefined && { partner }), address, utxos: inputs ?? [], slippage: slippage ? Math.round(slippage * 100) : 0, // Convert percentage to basis points isFloat: true }; }, response: ({ tx, p: _p }) => { return { aggregator: Swap.Aggregator.Steelswap, cbor: tx, splits: [], batcherFee: 0, deposits: 0, aggregatorFee: 0, frontendFee: 0, netPrice: 0, priceImpact: 0, totalFee: 0, totalInput: 0, totalOutput: 0, totalOutputWithoutSlippage: 0 }; } }, cancel: { request: ({ order }) => ({ txList: [{ txHash: order.txHash, txIndex: order.outputIndex ?? 0 }], ttl: 900, ...(partner !== undefined && { partner }) }), response: txHex => ({ cbor: txHex }) } }; }; export const toSwapProtocol = dex => { const dexUpper = dex.toUpperCase(); const dexMap = { CSWAP: Swap.Protocol.Cswap, GENIUSYIELD: Swap.Protocol.Genius, MINSWAP: Swap.Protocol.Minswap_v1, MINSWAPV2: Swap.Protocol.Minswap_v2, MINSWAPV2ROUTER: Swap.Protocol.Minswap_v2, MUESLISWAP: Swap.Protocol.Muesliswap_v2, SPECTRUM: Swap.Protocol.Spectrum_v1, SPLASH: Swap.Protocol.Splash_v1, SPLASHROUTER: Swap.Protocol.Splash_v1, SUNDAESWAP: Swap.Protocol.Sundaeswap_v1, SUNDAESWAPV3: Swap.Protocol.Sundaeswap_v3, VYFI: Swap.Protocol.Vyfi_v1, WINGRIDERS: Swap.Protocol.Wingriders_v1, WINGRIDERSV2: Swap.Protocol.Wingriders_v2 }; return dexMap[dexUpper] ?? Swap.Protocol.Unsupported; }; export const fromSwapProtocol = dex => { const protocolMap = { [Swap.Protocol.Cswap]: Dex.CSWAP, [Swap.Protocol.Genius]: Dex.GeniusYield, [Swap.Protocol.Minswap_v1]: Dex.Minswap, [Swap.Protocol.Minswap_v2]: Dex.MinswapV2, [Swap.Protocol.Minswap_stable]: Dex.MinswapV2, [Swap.Protocol.Muesliswap]: Dex.MuesliSwap, [Swap.Protocol.Muesliswap_v1]: Dex.MuesliSwap, [Swap.Protocol.Muesliswap_v2]: Dex.MuesliSwap, [Swap.Protocol.Muesliswap_clp]: Dex.MuesliSwap, [Swap.Protocol.Muesliswap_orderbook]: Dex.MuesliSwap, [Swap.Protocol.Spectrum_v1]: Dex.Spectrum, [Swap.Protocol.Splash_v1]: Dex.Splash, [Swap.Protocol.Splash_v4]: Dex.Splash, [Swap.Protocol.Splash_v5]: Dex.Splash, [Swap.Protocol.Splash_v6]: Dex.Splash, [Swap.Protocol.Sundaeswap_v1]: Dex.SundaeSwap, [Swap.Protocol.Sundaeswap_v3]: Dex.SundaeSwapV3, [Swap.Protocol.Vyfi_v1]: Dex.VyFi, [Swap.Protocol.Wingriders_v1]: Dex.WingRiders, [Swap.Protocol.Wingriders_v2]: Dex.WingRidersV2, [Swap.Protocol.Wingriders_stable]: Dex.WingRidersV2, [Swap.Protocol.Teddy_v1]: Dex.Unsupported, [Swap.Protocol.Snekfun]: Dex.Unsupported, [Swap.Protocol.Chadswap]: Dex.Unsupported, [Swap.Protocol.Cerra]: Dex.Unsupported, [Swap.Protocol.Unsupported]: Dex.Unsupported }; return protocolMap[dex] ?? Dex.Unsupported; }; export const SteelswapProtocols = Object.values(Dex).filter(p => p !== Dex.Unsupported).map(toSwapProtocol); //# sourceMappingURL=transformers.js.map