Skip to content

fix: keep block scanner awake while orders await confirmation#220

Merged
v03413 merged 1 commit into
v03413:mainfrom
zshuang15:fix/scanner-confirming-orders
Jul 20, 2026
Merged

fix: keep block scanner awake while orders await confirmation#220
v03413 merged 1 commit into
v03413:mainfrom
zshuang15:fix/scanner-confirming-orders

Conversation

@zshuang15

Copy link
Copy Markdown
Contributor

Problem

On EVM chains an order can get stuck in OrderStatusConfirming forever and never fire its notify — the payment is on-chain and has far more than enough confirmations, but the merchant is never credited. It happens when block_offset_confirm is enabled and the confirming order is the only pending order (typical for low-traffic merchants).

Root cause

Block scanning is demand-driven. syncBreak() halts the forward scan once hasLookbackOrders() finds no "receivable" orders, and receivableOrderStatuses() only returned {Waiting, Expired}:

func receivableOrderStatuses() []int {
	return []int{model.OrderStatusWaiting, model.OrderStatusExpired}
}

When an order transitions Waiting → Confirming and it is the only pending order, hasLookbackOrders() becomes false, the forward scan stops, and the in-memory chainBlockNum freezes.

With block_offset_confirm enabled, tradeConfirmHandle() gates confirmation on chainBlockNum - RefBlockNum >= ConfirmedOffset (e.g. 40 on Polygon). A frozen chainBlockNum therefore never reaches the threshold, so the order stays Confirming indefinitely — no notify, merchant never credited. It does not recover across restarts, because with no waiting/expired order the scanner correctly stays idle.

(With block_offset_confirm disabled the confirmation gate is skipped, so this only bites deployments that enable confirmations on an EVM chain — which is exactly the safe choice against reorgs.)

Fix

Include OrderStatusConfirming in receivableOrderStatuses() so the scanner keeps advancing while any order is awaiting confirmation.

Validation

Reproduced on Polygon (block_offset_confirm=1, ConfirmedOffset=40): a lone confirming order backed by a real, on-chain-confirmed USDT transfer (500+ confirmations) stayed in Confirming forever on an unpatched build — the scanner was idle and chainBlockNum was frozen. With this change the scanner stays awake, chainBlockNum advances, tradeConfirmHandle() promotes the order to success, and the notify fires.

🤖 Generated with Claude Code

On EVM chains an order can stall forever in OrderStatusConfirming and never
fire its notify when block_offset_confirm is enabled, even though the payment
is on-chain and has far more than enough confirmations.

Block scanning is demand-driven: syncBreak() halts the forward scan once
hasLookbackOrders() finds no "receivable" orders, and receivableOrderStatuses()
only listed {Waiting, Expired}. When an order moves Waiting -> Confirming and it
is the only pending order, the scanner stops and the in-memory chainBlockNum
freezes. With block_offset_confirm enabled, tradeConfirmHandle() requires
chainBlockNum - RefBlockNum >= ConfirmedOffset, so a frozen chainBlockNum means
the order never reaches the threshold and stays Confirming indefinitely: no
notify is sent and the merchant is never credited. Low-traffic merchants hit
this on essentially every order.

Include OrderStatusConfirming in receivableOrderStatuses() so the scanner keeps
advancing while any order is awaiting confirmation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@v03413
v03413 merged commit bf068e1 into v03413:main Jul 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants