diff --git a/src/lib/components/orders/MarketOrder.svelte b/src/lib/components/orders/MarketOrder.svelte index 7e179495..f7c72238 100644 --- a/src/lib/components/orders/MarketOrder.svelte +++ b/src/lib/components/orders/MarketOrder.svelte @@ -14,6 +14,7 @@ import { createOracleQuotesQuery } from '$lib/queries/oracleQuotes'; import type { CreateQueryResult } from '@tanstack/svelte-query'; import { + DEFAULT_MARKET_ORDER_SLIPPAGE_BPS, executeMarketOrder, filterQuotesForSide, sortQuotesByPrice @@ -46,6 +47,12 @@ const ORDERBOOK_MAX_STALENESS_MS = 20_000; // 20 seconds const PRICE_GUARD_MULTIPLIER = 1.05; // 5% price tolerance for slippage and liquidity checks + const SLIPPAGE_OPTIONS_BPS: number[] = [50, 100, 200, 300]; + let slippageBps = DEFAULT_MARKET_ORDER_SLIPPAGE_BPS; + + function formatSlippageLabel(bpsValue: number): string { + return `${(bpsValue / 100).toFixed(bpsValue % 100 === 0 ? 0 : 1)}%`; + } let oracleQuotesQuery = createOracleQuotesQuery($currentNetwork); $: oracleQuotesQuery = createOracleQuotesQuery($currentNetwork); @@ -490,6 +497,13 @@ } }; + function handleSlippageChange(event: Event) { + const target = event.currentTarget; + if (!(target instanceof HTMLSelectElement)) return; + const next = Number(target.value); + if (Number.isFinite(next)) slippageBps = next; + } + // Calculate how much asset can be bought for a given payment amount using actual orderbook prices function calculateAssetAmountForSpend( paymentAmount: bigint, @@ -815,6 +829,7 @@ orderSide, amount: selectedAmount, inputMode, + slippageBps, assetToken: { address: assetToken.address, decimals: assetToken.decimals, @@ -1001,6 +1016,19 @@

Order Summary

+
+ + +
{#if inputMode === 'spend'}
diff --git a/src/lib/services/marketOrderExecution.ts b/src/lib/services/marketOrderExecution.ts index 07e9bf6e..05b9f0d2 100644 --- a/src/lib/services/marketOrderExecution.ts +++ b/src/lib/services/marketOrderExecution.ts @@ -32,7 +32,9 @@ import { getSignerAddress } from '$lib/services/walletService'; // Safety bounds for market order execution const EMERGENCY_RATIO_MULTIPLIER = '2'; // stricter cap for spend/sell modes -const BUY_EXACT_RATIO_MULTIPLIER = '1.01'; // tighter cap for buy-exact to avoid oversized approvals +const MIN_SLIPPAGE_BPS = 1; +const MAX_SLIPPAGE_BPS = 5_000; +export const DEFAULT_MARKET_ORDER_SLIPPAGE_BPS = 100; const MINIMUM_IO = Float.fromBigint(0n).asHex(); /** @@ -107,6 +109,8 @@ export interface MarketOrderInput { amount: bigint; /** 'amount' = specify asset quantity, 'spend' = specify payment amount (Buy only) */ inputMode?: 'amount' | 'spend'; + /** User-configurable slippage in basis points (100 = 1%). */ + slippageBps?: number; // Tokens assetToken: TokenInfo; @@ -137,6 +141,11 @@ interface OrderInfo { raindexOrder?: RaindexOrder; } +function clampSlippageBps(slippageBps: number): number { + if (!Number.isFinite(slippageBps)) return DEFAULT_MARKET_ORDER_SLIPPAGE_BPS; + return Math.max(MIN_SLIPPAGE_BPS, Math.min(MAX_SLIPPAGE_BPS, Math.round(slippageBps))); +} + function getQuoteMakerAddress(quote: ProcessedQuote): string | null { const ownerFromOrderData = quote.orderData?.owner; if (typeof ownerFromOrderData === 'string' && ownerFromOrderData.length > 0) { @@ -165,6 +174,7 @@ export async function executeMarketOrder(input: MarketOrderInput): Promise