Skip to content

Stop widget sell broadcasts after the ramp start window closes - #1393

Merged
ebma merged 3 commits into
stagingfrom
fix/sell-start-deadline-guard
Sep 30, 2026
Merged

ebma merged 3 commits into
stagingfrom
fix/sell-start-deadline-guard

Conversation

@ebma

@ebma ebma commented Sep 29, 2026

Copy link
Copy Markdown
Member

Why

The API refuses POST /v1/ramp/update and POST /v1/ramp/start 15 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.ts never checked the deadline before broadcasting. The gold app fixed the same gap in 082d0fe58 on fix/gold-review.

What

  • signTransactionsActor refuses each user-wallet broadcast once less than 4 minutes remain before the ramp's expiresAt. The margin is the same as gold's and covers the wallet confirmation, the receipt wait (60 s timeout) and the update/start calls. If expiresAt is missing, the check fails closed. It throws SignRampError(StartWindowClosed), and the catch block now passes that error through instead of turning it into UnknownError.
  • The Error step shows a translated message (en/pt): the sale expired, the funds are still in the wallet, start a new sale.
  • docs/security-spec/03-ramp-engine/ramp-phase-flows.md has a new threat row for this, including what is still not covered.

Corridors covered

Corridor User-wallet tx Guarded
BRL SELL, Base USDC squidRouterNoPermitTransfer yes
BRL SELL, other EVM source squidRouterApprove + squidRouterSwap yes, before each
AlfredPay SELL, token without permit squidRouterNoPermitTransfer, or squidRouterNoPermitApprove + squidRouterNoPermitSwap yes
AlfredPay SELL, permit-capable token squidRouterPermitExecute (EIP-712) no, see below
AssetHub SELL (retired at quote time) assethubToPendulum yes

Permits 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, update is 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's submitUserTransactions has no deadline check either (not touched here).

Tests

  • New sign.actor.test.ts case: the approve prompt runs until 3 minutes remain, so the swap is never sent and update is never called. It fails when run against the actor without the guard.
  • bun lint:fix, bun typecheck, frontend vitest: 26 files / 188 tests pass.

@netlify

netlify Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for vrtx-dashboard canceled.

Name Link
🔨 Latest commit d254a5e
🔍 Latest deploy log https://app.netlify.com/projects/vrtx-dashboard/deploys/6abbd2780e43330008fbac72

@netlify

netlify Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for vortexfi canceled.

Name Link
🔨 Latest commit d254a5e
🔍 Latest deploy log https://app.netlify.com/projects/vortexfi/deploys/6abbd2786c99ee00082bcad4

@netlify

netlify Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for vortex-sandbox ready!

Name Link
🔨 Latest commit d254a5e
🔍 Latest deploy log https://app.netlify.com/projects/vortex-sandbox/deploys/6abbd27881a2db0008f5cb48
😎 Deploy Preview https://deploy-preview-1393--vortex-sandbox.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
ebma force-pushed the fix/sell-start-deadline-guard branch from 7019a8a to d254a5e Compare September 29, 2026 15:00
@ebma
ebma merged commit f915754 into staging Sep 30, 2026
6 checks passed
@ebma
ebma deleted the fix/sell-start-deadline-guard branch September 30, 2026 08:17
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