Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/lib/components/orders/MarketOrder.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -815,6 +829,7 @@
orderSide,
amount: selectedAmount,
inputMode,
slippageBps,
assetToken: {
address: assetToken.address,
decimals: assetToken.decimals,
Expand Down Expand Up @@ -1001,6 +1016,19 @@
<div class={containerStyles.cardBordered}>
<h4 class="mb-3 text-sm font-medium text-gray-300">Order Summary</h4>
<div class="space-y-2 text-sm">
<div class="flex items-center justify-between">
<label for="market-slippage" class="text-gray-400">Slippage tolerance</label>
<select
id="market-slippage"
value={String(slippageBps)}
on:change={handleSlippageChange}
class="rounded border border-white/10 bg-gray-800 px-2 py-1 text-sm text-gray-200 focus:border-yellow-400/50 focus:outline-none"
>
{#each SLIPPAGE_OPTIONS_BPS as bps (bps)}
<option value={String(Number(bps))}>{formatSlippageLabel(Number(bps))}</option>
{/each}
</select>
</div>
{#if inputMode === 'spend'}
<!-- Spend mode: show spending amount first -->
<div class="flex justify-between">
Expand Down
18 changes: 15 additions & 3 deletions src/lib/services/marketOrderExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -165,6 +174,7 @@ export async function executeMarketOrder(input: MarketOrderInput): Promise<Marke
orderSide,
amount,
inputMode = 'amount',
slippageBps = DEFAULT_MARKET_ORDER_SLIPPAGE_BPS,
assetToken,
paymentToken,
quotes,
Expand Down Expand Up @@ -204,8 +214,10 @@ export async function executeMarketOrder(input: MarketOrderInput): Promise<Marke
return { success: false, error: 'Unable to calculate order price. Please try again.' };
}
const isBuy = orderSide === 'Buy';
const ratioMultiplier =
isBuy && inputMode !== 'spend' ? BUY_EXACT_RATIO_MULTIPLIER : EMERGENCY_RATIO_MULTIPLIER;
const effectiveSlippageBps = clampSlippageBps(slippageBps);
const ratioMultiplier = isBuy
? String(1 + effectiveSlippageBps / 10_000)
: EMERGENCY_RATIO_MULTIPLIER;
Comment on lines +217 to +220

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Honor the selected slippage for Sell orders too.

slippageBps is only used when isBuy; Sell orders still use EMERGENCY_RATIO_MULTIPLIER. Since MarketOrder.svelte exposes the selector for both sides, a user-selected Sell tolerance is silently ignored. Either apply the configured tolerance to Sell execution as well, or hide/disable the selector for Sell orders until supported.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/services/marketOrderExecution.ts` around lines 217 - 220, The code
ignores the user-selected slippage for sell orders by only using slippageBps
when isBuy and otherwise using EMERGENCY_RATIO_MULTIPLIER; change
ratioMultiplier calculation in marketOrderExecution.ts to use the clamped
effectiveSlippageBps for both sides (e.g., derive ratioMultiplier from
effectiveSlippageBps for sells too) or alternatively gate the UI selector in
MarketOrder.svelte; update the ratioMultiplier assignment that references isBuy,
effectiveSlippageBps, clampSlippageBps and EMERGENCY_RATIO_MULTIPLIER so sell
execution honors the configured tolerance (or remove/disable the selector in
MarketOrder.svelte if you prefer not to support sell slippage yet).

const emergencyRatioHex = computeEmergencyRatioHex(
worstFill.quote.ratio as `0x${string}`,
ratioMultiplier
Expand Down
Loading