Skip to content

perf(bridge-core): trim + overlap the return claim's preflight, tighten its finality poll - #55

Open
Yoni-Starkware wants to merge 1 commit into
mainfrom
perf/claim-preflight
Open

Yoni-Starkware wants to merge 1 commit into
mainfrom
perf/claim-preflight

Conversation

@Yoni-Starkware

@Yoni-Starkware Yoni-Starkware commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

The claim leg — "Adding to your private balance" — measured 21.4s on a real mainnet redeem (claim tx 0x400c77a0…d22e, block 12,816,489). It is the last leg of a return and all of its preflight runs serially after the CCTP attestation lands, so every read is time the user spends watching a transfer that is already funded.

Three changes. None of them alter what the proof commits to.

1. Skip fetchPoolFeeAmount() under the AVNU paymaster

approvePoolFee() already returns undefined immediately when config.paymaster is set (poolFee.ts:39) — the pool's STRK fee is fronted by the relayer/forwarder and repaid through the proof's fee withdraw. So the get_fee_amount() read that precedes it can only ever be discarded. One RPC round trip off the critical path.

The manager path is untouched: it still reads, approves, and seeds the aging anchor from that block (covered by a new test).

2. Start the AVNU fee quote concurrently with the quiescence gate

paymasterBuildLeg's request for this leg is constant — {type:'apply_action', pool_address} plus the configured fee mode — so it depends on nothing the gate produces, yet today it runs strictly behind it inside buildOnce.

It is deliberately not prefetched across any real wait. The quote is a live STRK→pool-fee-token conversion the proof must bake in exactly, so it is dropped and re-quoted on every path that adds delay before the build: the non-quiescent (aging) path, the manager fee-approve path, and every rebuild/rebuildFresh. Its age stays bounded by the gate itself — roughly one discovery round trip.

3. Tighter finality poll for the claim: 700ms → 2.5s (was 1s → 8s)

submitAndTrack's default backoff was tuned for attestation-scale waits, where an 8s ceiling costs nothing. Here nothing follows the claim, and against Starknet block times an 8s step can sit seconds past an acceptance that already happened. The hard timeoutMs is unchanged.

Not done, and why

Carrying a warm discovery registry into the claim build looks like the bigger win — it would make the build's notes scan incremental — but it is unsafe against the current SDK:

  • compiler.ts replaces registry.notes per token with whatever the cursored discovery returns, and a cursored /v1/sync/incoming_state returns only notes past last_note_index. One note arriving between the warm scan and the build therefore wipes the earlier notes from the registry, and the fee withdraw can fail note selection ("Insufficient balance").
  • Removals are invisible incrementally (spent notes are filtered out of the response, not reported), which is precisely what the quiescence gate has to detect — so the gate's own scans cannot go incremental either.

Collapsing the claim's three note scans (gate ×2 + build ×1) plus the channels scan into one needs a discovery/SDK change, not a bridge-core change.

Testing

  • npx vitest run in packages/bridge-core1103 passed (110 files), including 6 new cases in bridgeBack.test.ts: paymaster skips the fee read, manager still reads/approves/anchors, quote overlaps a held-open gate, non-quiescent path re-quotes after aging, rebuild re-quotes, and the poll grid is tighter than the default.
  • npx tsc --noEmit clean, eslint clean.
  • Not live-verified end to end: the wall-clock gain needs a real mainnet return.

🤖 Generated with Claude Code


This change is Reviewable

…en its finality poll

The claim ("Adding to your private balance") is the LAST leg of a return, and all of
its preflight runs serially after the CCTP attestation lands, so every read is time
the user spends watching a transfer that is already funded. Three changes, no change
to what the proof commits to:

1. Skip fetchPoolFeeAmount() under the AVNU paymaster. approvePoolFee() already
   returns undefined immediately when config.paymaster is set (poolFee.ts) — the pool's
   STRK fee is fronted by the relayer/forwarder and repaid via the proof's fee withdraw
   — so the get_fee_amount() read could only ever be discarded. The manager path is
   unchanged: it still reads, approves, and seeds the aging anchor from that block.

2. Start the AVNU fee quote CONCURRENTLY with the quiescence gate. paymasterBuildLeg's
   request for this leg is constant ({type:'apply_action', pool_address} + fee mode), so
   it depends on nothing the gate produces, yet today it runs strictly behind it. The
   quote is deliberately not prefetched across any real wait — it is a live
   STRK -> pool-fee-token conversion the proof must bake in exactly, so it is dropped and
   re-quoted on the non-quiescent (aging) path, the manager fee-approve path, and every
   rebuild/rebuildFresh. Its age stays bounded by the gate, ~one discovery round trip.

3. Track the claim on a 700ms -> 2.5s poll grid instead of submitAndTrack's 1s -> 8s
   default. That default was tuned for attestation-scale waits; here an 8s step can sit
   seconds past an acceptance that already happened, and nothing follows the claim. The
   hard timeoutMs is unchanged.

Not done, and why: carrying a warm discovery registry into the claim build looks like
the bigger win but is unsafe against the current SDK. compiler.ts REPLACES
registry.notes per token with whatever the cursored discovery returns, and a cursored
/v1/sync/incoming_state returns only notes past last_note_index — so one note arriving
between the warm scan and the build wipes the earlier notes from the registry and the
fee withdraw can fail note selection. Removals are invisible incrementally too (spent
notes are filtered out, not reported), which is exactly what the quiescence gate must
detect, so the gate's own scans cannot go incremental either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 5, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches paymaster vs manager fee timing and live AVNU fee quotes before prove; behavior is heavily tested but not live-verified on mainnet.

Overview
Speeds up claimToPool preflight and post-submit tracking on the return leg without changing what proofs commit to.

Paymaster path: Skips fetchPoolFeeAmount() when AVNU is enabled (the pool fee read was useless because approvePoolFee already no-ops). Manager path is unchanged—still read, approve, and anchor from the fee tx.

AVNU fee quote: Starts paymasterBuildLeg in parallel with the quiescence gate so the quote overlaps discovery instead of waiting behind it. The in-flight quote is dropped on non-quiescent aging, manager fee-approve, and rebuilds, then re-quoted so stale STRK→fee-token rates are not baked in.

Claim tracking: submitProvenClaim passes intervalMs 700 and maxIntervalMs 2500 (vs submitAndTrack’s 1s→8s default) while still waiting for ACCEPTED_ON_L2.

Tests: New claimToPool — preflight ordering cases plus hoisted poolFee mocks pin read/approve behavior, concurrent quoting, re-quote paths, and the tighter poll grid.

Reviewed by Cursor Bugbot for commit bed3ea2. Bugbot is set up for automated code reviews on this repo. Configure here.

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.

1 participant