From b2b6c8651972410cfb04cf6d990ac202df37caad Mon Sep 17 00:00:00 2001 From: runnerelectrode <47902317+runnerelectrode@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:51:38 -0700 Subject: [PATCH 1/3] Reduce polling latency from 1s fixed to 100ms progressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server benchmarks show order processing takes ~200ms, but the frontend used a fixed 1000ms sleep between retry polls. This added up to 5-30s of unnecessary wait time during trade settlement. Changes: - helpers.ts: replace fixed delay with progressive backoff (starts at base delay, grows 1.2x per attempt, caps at base*10). Remove noisy console.log on every retry attempt. - zk/trade.ts: reduce base retry delay from 1000ms to 100ms for all settleOrder, settleOrderSltp, and cancelZkOrder polling loops. - zk/account.ts: reduce base retry delay from 1000ms to 100ms for UTXO and output queries. Polling schedule (100ms base): 100 → 120 → 144 → 173 → ... → 1000ms cap vs old: 1000ms fixed on every attempt. No changes to rendering logic, component state, or API response formats. --- lib/helpers.ts | 12 ++++++++---- lib/zk/account.ts | 4 ++-- lib/zk/trade.ts | 6 +++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/helpers.ts b/lib/helpers.ts index 2025712..7d39a35 100644 --- a/lib/helpers.ts +++ b/lib/helpers.ts @@ -13,6 +13,12 @@ type RetryErrorResponse = { const sleep = (delay: number) => new Promise((res) => setTimeout(res, delay)); +/** Calculate progressive delay: starts at `base`, grows by 1.2x per attempt, caps at `base * 10` */ +function progressiveDelay(base: number, attempt: number): number { + const scaled = base * Math.pow(1.2, attempt); + return Math.min(scaled, base * 10); +} + export async function retry( query: (args: QueryArgs, ...rest: any[]) => QueryReturn, retries: number, @@ -29,12 +35,10 @@ export async function retry( try { tryCount += 1; - console.log(`retrying ${tryCount} / ${retries}`); - const response = await query(args); if (!response) { - await sleep(delay); + await sleep(progressiveDelay(delay, tryCount)); continue; } @@ -58,7 +62,7 @@ export async function retry( break; } - await sleep(delay); + await sleep(progressiveDelay(delay, tryCount)); continue; } catch (err) { console.error("retry >> error", err); diff --git a/lib/zk/account.ts b/lib/zk/account.ts index 7d6f390..7157af7 100644 --- a/lib/zk/account.ts +++ b/lib/zk/account.ts @@ -38,7 +38,7 @@ async function getCoinOutputFromUtxo( const outputResult = await retry< ReturnType, string - >(queryUtxoForOutput, 30, utxoHex, 1000, (outputObj) => + >(queryUtxoForOutput, 30, utxoHex, 100, (outputObj) => Object.hasOwn(outputObj, "out_type") ); @@ -77,7 +77,7 @@ async function getUtxoFromAddress( const utxoDataResult = await retry< ReturnType, string - >(queryUtxoForAddress, 30, address, 1000, (utxoObj) => + >(queryUtxoForAddress, 30, address, 100, (utxoObj) => Object.hasOwn(utxoObj, "output_index") ); diff --git a/lib/zk/trade.ts b/lib/zk/trade.ts index 1fadc24..fa58df1 100644 --- a/lib/zk/trade.ts +++ b/lib/zk/trade.ts @@ -74,7 +74,7 @@ export async function settleOrder( queryTransactionHashes, 30, trade.accountAddress, - 1000, + 100, transactionHashCondition ); @@ -165,7 +165,7 @@ export async function settleOrder( queryTransactionHashes, 30, trade.accountAddress, - 1000, + 100, transactionHashCondition, transactionHashFailCondition ); @@ -293,7 +293,7 @@ export async function settleOrderSltp( queryTransactionHashes, 30, trade.accountAddress, - 1000, + 100, transactionHashCondition ); From 2ec754020281cddb276734fdaa41eb24f1d65a4d Mon Sep 17 00:00:00 2001 From: runnerelectrode <47902317+runnerelectrode@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:09:51 -0700 Subject: [PATCH 2/3] Fix slow open order: reduce waitForUtxoUpdate min wait and poll interval The open order flow was bottlenecked by waitForUtxoUpdate which had a hardcoded 5s minimum wait and 1s poll interval. Server benchmarks show UTXO updates are available in ~1.5-3s, so the 5s floor was wasting time. Changes: - waitForUtxoUpdate.ts: reduce min wait 5000ms -> 1500ms, poll interval 1000ms -> 200ms - market.client.tsx: reduce transaction hash retry delay 1000ms -> 100ms - limit.client.tsx: reduce transaction hash retry delay 1000ms -> 100ms --- app/_components/trade/order/forms/limit.client.tsx | 2 +- app/_components/trade/order/forms/market.client.tsx | 2 +- lib/utils/waitForUtxoUpdate.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/_components/trade/order/forms/limit.client.tsx b/app/_components/trade/order/forms/limit.client.tsx index 761389f..c024a7f 100644 --- a/app/_components/trade/order/forms/limit.client.tsx +++ b/app/_components/trade/order/forms/limit.client.tsx @@ -670,7 +670,7 @@ const OrderLimitForm = () => { queryTransactionHashes, 30, newZkAccount.address, - 1000, + 100, transactionHashCondition, transactionHashFailCondition ); diff --git a/app/_components/trade/order/forms/market.client.tsx b/app/_components/trade/order/forms/market.client.tsx index 76ffb1e..1979693 100644 --- a/app/_components/trade/order/forms/market.client.tsx +++ b/app/_components/trade/order/forms/market.client.tsx @@ -672,7 +672,7 @@ const OrderMarketForm = () => { queryTransactionHashes, 30, newZkAccount.address, - 1000, + 100, transactionHashCondition, transactionHashFailCondition ); diff --git a/lib/utils/waitForUtxoUpdate.ts b/lib/utils/waitForUtxoUpdate.ts index 3cdf55d..369712f 100644 --- a/lib/utils/waitForUtxoUpdate.ts +++ b/lib/utils/waitForUtxoUpdate.ts @@ -2,9 +2,9 @@ import { queryUtxoForAddress } from "@/lib/api/zkos"; import { UtxoData } from "@/lib/types"; const DEFAULT_TIMEOUT_MS = 60_000; -const DEFAULT_POLL_INTERVAL_MS = 1_000; -/** Minimum wait before returning success. Relayer/chain UTXO availability ~5–6s. */ -const DEFAULT_MIN_WAIT_MS = 5_000; +const DEFAULT_POLL_INTERVAL_MS = 200; +/** Minimum wait before returning success. Relayer/chain UTXO availability ~2–6s. */ +const DEFAULT_MIN_WAIT_MS = 1_500; type WaitResult = | { success: true } From e9b6ae60a9925a386c004201582f7fe4d533387a Mon Sep 17 00:00:00 2001 From: runnerelectrode <47902317+runnerelectrode@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:22:48 -0700 Subject: [PATCH 3/3] Add step-by-step progress toasts for open order flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The open order flow has 3 sequential phases that take several seconds each, but the UI only showed a single "Placing order" toast. Users had no feedback during the ~10s wait. Now shows: Step 1/3: Preparing account → Transfer broadcast → on-chain confirmation Step 2/3: Submitting order → sending to relayer Step 3/3: Confirming order → waiting for fill confirmation Applied to both market and limit order forms. --- .../trade/order/forms/limit.client.tsx | 19 +++++++++++++++++-- .../trade/order/forms/market.client.tsx | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/app/_components/trade/order/forms/limit.client.tsx b/app/_components/trade/order/forms/limit.client.tsx index c024a7f..6a6bb74 100644 --- a/app/_components/trade/order/forms/limit.client.tsx +++ b/app/_components/trade/order/forms/limit.client.tsx @@ -477,8 +477,8 @@ const OrderLimitForm = () => { setIsSubmitting(true); toast({ - title: "Placing order", - description: "Order is being placed, please do not close this page.", + title: "Step 1/3: Preparing account", + description: "Transferring funds to a new trading account...", }); const { account: newTradingAccount } = await createZkAccountWithBalance({ @@ -540,6 +540,11 @@ const OrderLimitForm = () => { updatedAddress: updatedTradingAccountAddress, } = privateTxSingleResult.data; + toast({ + title: "Step 1/3: Transfer broadcast", + description: "Waiting for on-chain confirmation...", + }); + const newZkAccount: ZkAccount = { scalar: updatedTradingAccountScalar, type: "Coin", @@ -626,6 +631,11 @@ const OrderLimitForm = () => { const { newZkAccount } = queueResult; const leverageNum = parseInt(leverage || "1", 10); + toast({ + title: "Step 2/3: Submitting order", + description: "Sending limit order to the relayer...", + }); + const { success, msg } = await createZkOrder({ leverage: leverageNum, orderType: "LIMIT", @@ -646,6 +656,11 @@ const OrderLimitForm = () => { if (!data.result || !data.result.id_key) throw "Error with creating limit order"; + toast({ + title: "Step 3/3: Confirming order", + description: "Waiting for order confirmation...", + }); + const transactionHashCondition = ( txHashResult: Awaited> ) => { diff --git a/app/_components/trade/order/forms/market.client.tsx b/app/_components/trade/order/forms/market.client.tsx index 1979693..611023e 100644 --- a/app/_components/trade/order/forms/market.client.tsx +++ b/app/_components/trade/order/forms/market.client.tsx @@ -439,8 +439,8 @@ const OrderMarketForm = () => { setIsSubmitting(true); toast({ - title: "Placing order", - description: "Order is being placed, please do not close this page.", + title: "Step 1/3: Preparing account", + description: "Transferring funds to a new trading account...", }); // Generate the new order account before entering the queue — this is @@ -515,6 +515,11 @@ const OrderMarketForm = () => { updatedAddress: updatedTradingAccountAddress, } = privateTxSingleResult.data; + toast({ + title: "Step 1/3: Transfer broadcast", + description: "Waiting for on-chain confirmation...", + }); + const newZkAccount: ZkAccount = { scalar: updatedTradingAccountScalar, type: "Coin", @@ -605,6 +610,11 @@ const OrderMarketForm = () => { const { newZkAccount } = queueResult; + toast({ + title: "Step 2/3: Submitting order", + description: "Sending trade order to the relayer...", + }); + const { success, msg } = await createZkOrder({ leverage: leverageVal, orderType: "MARKET", @@ -641,6 +651,11 @@ const OrderMarketForm = () => { console.log(data); + toast({ + title: "Step 3/3: Confirming order", + description: "Waiting for order confirmation...", + }); + const transactionHashCondition = ( txHashResult: Awaited> ) => {