Repository navigation
Stop widget sell broadcasts after the ramp start window closes - #1393
Merged
Merged
Conversation
✅ Deploy Preview for vrtx-dashboard canceled.
|
✅ Deploy Preview for vortexfi canceled.
|
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The guard matches the API deadline contract, preserves permit behavior, and has focused regression coverage.
Review effort: Balanced
Findings: None
What changed in this PR
Prevents late widget SELL broadcasts that could strand funds after the API start deadline.
Changes:
- Adds a four-minute broadcast safety margin with typed-data exemptions.
- Preserves and translates the deadline error.
- Documents the threat and adds regression coverage.
| File | Description |
|---|---|
docs/security-spec/03-ramp-engine/ramp-phase-flows.md |
Documents mitigation and residual risk. |
apps/frontend/src/translations/pt.json |
Adds Portuguese expiration guidance. |
apps/frontend/src/translations/en.json |
Adds English expiration guidance. |
apps/frontend/src/machines/actors/sign.actor.ts |
Blocks unsafe late broadcasts. |
apps/frontend/src/machines/actors/sign.actor.test.ts |
Tests deadline enforcement between transactions. |
apps/frontend/src/components/widget-steps/ErrorStep/index.tsx |
Maps the deadline error to translated guidance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The API refuses to record or start a ramp 15 minutes after registration. A user-wallet transfer or Squid swap broadcast later moves the funds to the ephemeral of a ramp that can never start, leaving them for manual recovery. Refuse each broadcast once less than four minutes remain; typed-data permits move nothing until Vortex executes them, so they stay unguarded.
ebma
force-pushed
the
fix/sell-start-deadline-guard
branch
from
September 29, 2026 15:00
7019a8a to
d254a5e
Compare
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
The API refuses
POST /v1/ramp/updateandPOST /v1/ramp/start15 minutes after registration (assertStartDeadlineNotExceeded). In the widget's SELL signing flow, the user's wallet broadcasts the transactions that move funds to the client-generated ephemeral. If one of those goes out after the deadline, its hash can never be reported, the ramp never starts, and the funds sit on the ephemeral until someone recovers them by hand.sign.actor.tsnever checked the deadline before broadcasting. The gold app fixed the same gap in082d0fe58onfix/gold-review.What
signTransactionsActorrefuses each user-wallet broadcast once less than 4 minutes remain before the ramp'sexpiresAt. The margin is the same as gold's and covers the wallet confirmation, the receipt wait (60 s timeout) and the update/start calls. IfexpiresAtis missing, the check fails closed. It throwsSignRampError(StartWindowClosed), and the catch block now passes that error through instead of turning it intoUnknownError.docs/security-spec/03-ramp-engine/ramp-phase-flows.mdhas a new threat row for this, including what is still not covered.Corridors covered
squidRouterNoPermitTransfersquidRouterApprove+squidRouterSwapsquidRouterNoPermitTransfer, orsquidRouterNoPermitApprove+squidRouterNoPermitSwapsquidRouterPermitExecute(EIP-712)assethubToPendulumPermits are not guarded because signing typed data broadcasts nothing. Vortex's executor/relayer submits the permit only after a successful start. If the user signs late,
updateis refused and no funds move.Known residual
The check runs before each wallet prompt. It catches a slow approve (or Safe co-signing) ahead of the swap, and a signing session restored from localStorage after the window has closed. It cannot catch a single prompt that stays open across the deadline: once the user confirms, the wallet broadcasts. No client-side fix exists for that case.
@vortexfi/sdk'ssubmitUserTransactionshas no deadline check either (not touched here).Tests
sign.actor.test.tscase: the approve prompt runs until 3 minutes remain, so the swap is never sent andupdateis never called. It fails when run against the actor without the guard.bun lint:fix,bun typecheck, frontend vitest: 26 files / 188 tests pass.