Repository navigation
fix(cctp): pay Circle's Fast Transfer fee instead of offering zero - #210
Merged
Merged
Conversation
Every EVM payment took the slow path. `selectCrypto` built its burn with
`speed: 'fast'` and `maxFee: 0n` hardcoded, and Circle does not reject that
combination — it accepts the burn, then holds the message at
`pending_confirmations` with `delayReason: insufficient_fee` and waits for hard
finality instead. So the checkout promised "8–20 seconds via Circle CCTP V2"
and delivered fifteen-plus minutes, with nothing in our logs to say why. The
demotion is only visible in Iris's own response.
Circle publishes the price per route. For Ethereum → Stellar it is 1 basis
point on the fast tier and nothing on standard:
GET /v2/burn/USDC/fees/0/27
[{"finalityThreshold":1000,"minimumFee":1},
{"finalityThreshold":2000,"minimumFee":0}]
`BurnFeeService` reads that, caches it for five minutes per route, and converts
basis points into a `maxFee` for the amount. The conversion rounds up and never
returns zero for a non-zero rate: 1 bp of a one-cent payment is 0.0001 USDC,
which truncates to nothing in 6-decimal subunits — and a maxFee of zero is the
exact condition being fixed. One extra subunit is cheaper than a quarter-hour
settlement.
When the lookup fails the burn drops to standard finality rather than asking
for fast and being demoted anyway. Slower, but it is what actually happens, and
it is logged.
Verified live: a 15 USDC burn now carries maxFee 1500 subunits at
minFinalityThreshold 1000, where it previously carried 0.
Tests: 317 (+14), covering the rounding floor, the fallback, per-route caching
and the two selectCrypto paths.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why a payment sits at
SOURCE_LOCKEDwithattestation: nullwhile the checkout says "8–20 seconds".The cause
selectCryptobuilt every EVM burn withspeed: 'fast'andmaxFee: 0nhardcoded.Circle does not reject that combination. It accepts the burn, then holds the message at
pending_confirmationsand waits for hard finality instead. The only place this is visible is Iris's own response:{ "status": "pending_confirmations", "cctpVersion": 2, "delayReason": "insufficient_fee" }So every EVM payment took the slow path — fifteen-plus minutes — while the UI promised seconds, and nothing in our logs explained it.
The fix
Circle publishes the price per route:
1 basis point for fast on Ethereum → Stellar; free for standard.
BurnFeeServicereads that, caches per route for five minutes, and converts basis points into amaxFee. Two details that matter:maxFee: 0is the exact condition being fixed. One extra subunit beats a quarter-hour settlement.Verified live
A 15 USDC burn now carries
maxFee: 1500subunits atminFinalityThreshold: 1000. Previously0.Tests: 317 (+14) — the rounding floor, the fallback, per-route caching, and both
selectCryptopaths. 0 lint errors.Note on the in-flight payment
A burn already submitted with
maxFee: 0is not lost — it settles on hard finality (~15–20 min on Sepolia) and the worker will pick it up. This only changes burns built from here on.