VeilSwap
← All posts
Analysis2026-11-069 min read

Swap Dispute Evidence: Which Clock Counts (2026)

Five clocks run during one swap: your browser, our server, the upstream price lock, the miner's block header and the pool's ledger. We measured each one's error on 6 November 2026 - and only the last decides.

A swap dispute is an argument about clocks, and the clock people quote is usually the wrong one. Between 24 September and 06:05 UTC today our order table collected 151 orders - 123 expired, 21 paid, 5 failed, 2 refunded - spread across 142 deposit addresses. Only 23 of them carry a stored deadline. The other 128 never had one and fell back to a local 30-minute rule. That ratio explains most 'I paid in time' arguments: for five orders out of six there is no upstream deadline to appeal to, and where one exists it is ten minutes, not thirty.

Clock one is the one you can see, and it belongs to your device. The order page never asks the server how much time is left. It takes the deadline the server sent, subtracts your own `Date.now()` once a second, and polls the status every five seconds. The arithmetic is exact; the input is not. A phone five minutes fast shows five extra minutes, a laptop five minutes slow shows five fewer, and the page has no way to know which. That is why a screenshot of a countdown proves almost nothing: it records an opinion about time, produced by the device that is also making the claim.

Clock two is ours, and it is the only one we can put in front of you. Orders are stored in milliseconds, and at 06:04:44 UTC the origin server's wall clock agreed with Cloudflare's `Date` header inside the same second, so the reference is not drifting. Of the 23 orders that carry a stored deadline, 13 ChangeNOW orders ran 599.4-599.9 seconds from creation to deadline (median 599.9) and 10 FixedFloat orders had a median of 599.1 seconds: the same ten minutes from two unrelated pools. One exception deserves naming instead of smoothing over. A FixedFloat order had a deadline bit-for-bit identical to its creation timestamp - a zero-second window - and it still sat in `waiting_deposit` for 2,402.7 seconds, because nothing rewrites a status until something reads the order.

Clock three is the price lock, and it is shorter than the order. At 06:06 UTC a 100 USDT-TRC20 to XMR quote returned `validUntil` 119.8 seconds out. The same endpoint at 200, 500, 1,000, 2,000 and 5,000 USDT returned `validUntil: null` - not a shorter lock, no lock. The quote route also caches for eight seconds server-side, so two clicks inside that window legitimately show the same number. Read the three constants together: the price on your screen expires in two minutes, the order created from it has a ten-minute funding window, and above roughly 130 USDT there is no upstream price promise at all, only a fresh quote at the moment you press the button. (XMR to USDT exit)

Clock four is the chain, and it does not keep your time. A block timestamp is written by whoever mines the block, constrained by two consensus rules and nothing else. Bitcoin: above the median of the previous 11 blocks, and no more than two hours ahead of the node's clock. Litecoin: the same two hours. Zcash: tighter, 90 minutes above the median and two hours against local time. Monero: two hours ahead of the median of the last 60 blocks. Measured at 06:05-06:06 UTC: Bitcoin's newest block was already 1,866 seconds old, its stamp sat 3,436.8 seconds above the 11-block median, and the 15-block interval median was 441.5 seconds; Litecoin's tip was 623 seconds old with 424.5 seconds of median slack; Zcash's newest block was 840 seconds old; Monero's tip was 109 seconds old but 3,847 seconds above its 60-block median. A block timestamp can therefore be an hour away from the moment the coins actually moved. (Zcash vs Monero 2026)

Clock five is the pool's ledger, and it is the one that decides. When a deposit is credited, the provider uses its own arrival time - not your screenshot, not the block header, not our row. That is why 90 of our 123 expired orders carry `local_timeout`: we closed them because our window lapsed, while the provider never said expired at all. It cuts both ways. One paid order created 3 November at 09:28:17 UTC had its final recorded update 259 seconds after its own deadline and still finished paid; a refunded order the same morning went from creation to refunded in about 40 seconds. Late is a decision made upstream, not arithmetic - and the only input you control is the moment of broadcast.

Keep four artefacts if you expect an argument. First, the transaction id copied from your own wallet at the moment of sending, with the wallet's own timestamp: that is the only clock you own end to end. Second, the block height it landed in and that block's timestamp, which places the arrival within about an hour - and note that the order API returns the deadline but never the creation time, so the start of the window has to be captured by you. Third, the order id and deposit address exactly as shown when the order was created. Fourth, a screen recording instead of a screenshot if the deadline itself is contested, because every status poll re-reads the provider's `validUntil` and lets it override the stored value; the number on screen is not guaranteed to be the number you first saw.

Five rules, and one limit that cannot be engineered away. Screenshot the order page in the first minute, while the deposit address and the deadline are on screen together. Send the txid to support in the same message as the order id, not one reply later. Use a wallet that timestamps its broadcasts, because 'I sent it at 06:09' from memory is not evidence. Treat a block timestamp as accurate to about an hour, never to the second. And when the amount is large enough that the quote carries no expiry, assume the price can move between quote and order - a pricing fact, not a clock fact. The limit: no public source says when a pool credited a deposit. We can show `created_at`, `valid_until`, the expiry write time and the block; the provider's internal timestamp stays theirs, and in a real dispute that is the number everyone argues about. Snapshot for this piece: 436 assets across 64 networks at 06:02 UTC, XMR at 617.74 USD, 100 USDT-TRC20 quoting 0.14208343 XMR (703.8 USD per XMR, the locked-rate tier) and 1 XMR quoting 612.143 USDT. Prices move every minute; the structure of the clocks does not. (XMR to USDT exit)

Frequently asked questions

I paid inside the countdown but the order expired. Who is right?

The pool's credit time decides, and it is not published. Of our 123 expired orders, 90 were closed by us (`local_timeout`) rather than by the provider, and the expiry was written a median 74 seconds after the deadline (worst case 40 minutes). Send the txid and ask the provider's own timestamp instead of arguing about a countdown that was computed on your device.

Can the block timestamp prove I sent on time?

No. On 6 November 2026 Bitcoin's newest block stamp sat 3,436.8 seconds above the 11-block median, and consensus allows two hours of future drift; Litecoin allows the same two hours, Zcash 90 minutes above the median and two hours against local time, Monero two hours above a 60-block median. A block stamped an hour behind real time is still valid, so the header dates the coins to a minute at best.

Why does a 100 USDT quote show a deadline but 1,000 does not?

Because the deadline travels with the winning route. At 06:06 UTC the 100 USDT quote returned `validUntil` 119.8 seconds out, while 200, 500, 1,000, 2,000 and 5,000 all returned null - the locked-rate route that wins small tickets publishes an expiry, the multi-route winner at larger sizes does not. The order's own funding window is separate and comes from the pool at creation: 599.4-599.9 seconds across the 23 stored orders.

Keep reading

All articles →

Ready to swap?

No account · No KYC · No email

Exchange now
Swap Dispute Evidence: Which Clock Counts (2026) — VeilSwap