fix: request EXACT_OUTPUT for HyperCore perps deposits so the full margin arrives - #9751
Conversation
Unactivated trade-with-token deposits sized to exact margin left users short after HyperLiquid's ~$1 first-credit fee, so the auto-placed order failed. Co-authored-by: Cursor <cursoragent@cursor.com>
Automated fix-bug run — TAT-3400
Worker reportExplanationTrade-with-token deposits size the Relay quote to exact Core already reserved this fee on HyperLiquid withdrawals. This change mirrors that for perps deposits: after Arbitrum USDC is normalized to HyperCore, No exported API or type surface changes. Clients need no coordinated bump beyond consuming the next References
Checklist
|
Link the changelog entry to PR #9751 and cover the HyperLiquid-source deposit-target branch so package coverage thresholds pass. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Relay quotes for HyperCore perps deposits used tradeType EXPECTED_OUTPUT, which only guarantees `target * (1 - slippage)` arrives on the destination. Clients size these deposits at the exact margin required and immediately place an order needing that same margin, so any slippage inside the 0.5% band left the order short and it failed with insufficient margin. HyperCore deposits reach this state because `skipDelegation` is true for them, so they never take the delegation path that already used EXACT_OUTPUT. Request EXACT_OUTPUT for them directly instead. Verified against the live Relay API from $10 to $1000: the guaranteed minimum previously fell short at every size (-$0.05 to -$5.00) and now equals the requested target, at identical source cost. This replaces the previous activation-fee approach, which assumed HyperLiquid deducts ~$1 from the first inbound credit. That premise was not supported by mainnet ledger data, and a flat $1 both over-charged small deposits and under-funded ones above ~$200.
…th-token-has-20-failur # Conflicts: # packages/transaction-pay-controller/CHANGELOG.md
aganglada
left a comment
There was a problem hiding this comment.
Summary
Verdict: Approve — the fix is correct, minimal, and well-supported. A few small nits below.
What it does
HyperCore perps deposits go through Relay with skipDelegation = true (because isHypercore is set in processTransactions). Non-HyperCore flows that don't skip delegation already get EXACT_OUTPUT via the delegation path. HyperCore deposits were the gap: they stayed on EXPECTED_OUTPUT, which only guarantees target × (1 − slippage).
Since clients size deposits to exact marginRequired and immediately place an order needing that margin, the 0.5% slippage band could leave the account short and fail the order leg.
The fix detects normalized HyperCore USDC deposits and requests EXACT_OUTPUT instead.
What works well
-
Root cause is right. The delegation bypass explains why this path differed from other perps flows. The live Relay API table in the PR description ($10–$1000) is strong evidence: guaranteed minimum was short before, exact after, same source cost.
-
Narrow, safe change. No API/type changes.
getTradeTypeis a clean extraction.isHypercoreDepositcorrectly operates on the normalized request (post Arbitrum→HyperCore rewrite) and excludes withdrawals via!request.isHyperliquidSource. -
Better than the activation-fee approach. The pivot away from a flat ~$1 top-up is justified — it would over-charge small deposits and under-fund large ones.
EXACT_OUTPUTaddresses the actual slippage shortfall without padding. -
Tests cover the important cases. HyperCore deposit →
EXACT_OUTPUT; generic quote →EXPECTED_OUTPUTunchanged. -
Aligns with Across. Across already uses
exactOutputfor target-amount flows; Relay was the outlier here.
Issues / nits
1. Orphaned JSDoc (should fix)
The PR inserts isHypercoreDeposit and getTradeType between the existing normalizeRequest JSDoc and the function itself. Move the helpers above the normalizeRequest block, or restore the JSDoc directly above normalizeRequest.
2. Stale inline comment
The comment in getSingleQuote still says "For regular flows with a target amount, use EXPECTED_OUTPUT." HyperCore deposits are now a third case (EXACT_OUTPUT). Worth a one-line update so future readers don't reintroduce the bug.
3. Test could be tighter
The new test uses perpsDepositAndOrder, but the existing "updates request if Arbitrum deposit to Hyperliquid" test already covers perpsDeposit normalization. Consider adding tradeType: 'EXACT_OUTPUT' to that test's objectContaining assertion — or at least add a perpsDeposit case so both PERPS_DEPOSIT_TYPES are covered explicitly.
4. isHypercoreDeposit is broader than perps-only
It keys off normalized target (HyperCore USDC), not transaction.type. Any Relay quote depositing into HyperCore USDC gets EXACT_OUTPUT, not only perps deposits. That's probably fine — a stricter delivery guarantee is reasonable — but worth a conscious choice.
Risks considered (non-blocking)
| Risk | Assessment |
|---|---|
Higher source cost with EXACT_OUTPUT |
PR shows identical currencyIn before/after across sizes — not a concern if Relay behavior holds |
| Post-quote / max-amount flows | Correctly excluded: useExactInput takes precedence |
HyperLiquid withdrawals (isHyperliquidSource) |
Correctly excluded: target is not HyperCore USDC |
| Server strategy same bug | Server still uses ExpectedOutput for HyperCore, but trade-with-token goes through Relay today — out of scope, worth a follow-up if Server is ever routed for perps |
Suggested test plan before merge
- Unit tests pass
- End-to-end trade-with-token deposit at ~$20 margin on Mobile (the reported failure size)
- Confirm
currencyOut.minimumAmount === targetin the Relay quote response for a HyperCore deposit - Regression: standard non-perps Relay quotes still use
EXPECTED_OUTPUT
Bottom line: Ship it after fixing the JSDoc placement. The approach is the right fix for TAT-3400 — you're buying a delivery guarantee at no extra source cost, which is exactly what margin-sized deposits need.
isMaxAmount and isPostQuote are both optional, so useExactInput is boolean | undefined and the build failed on the narrowed parameter type.
Automated pr-complete run — #9751
Worker reportPR #9751 — Comment Triage ReportPR: #9751 Context reloadInherited context: present ( Resolved inherited artifacts: Summary of prior run:
Recipe provenance: Live comment fetch
Triage
No Core compatibility assessmentChanged files touch
Validation this session
Recipe pin correction: the inherited recipe asserted Update — original approach reverted and replacedTriage above found no actionable reviewer comments, and the PR was green. The substantive Live mainnet validation (
Core compatibility assessment (revised)Still non-breaking and still confined to Extension does not yet create Validation (revised)
|
Automated update-branch run — #9751
Worker reportUpdate-Branch Report — PR #9751PR: #9751 — fix: request EXACT_OUTPUT for HyperCore perps deposits so the full margin arrives Selected strategy
What main brought inBranch was 2 commits behind:
62 files changed on the incoming side, mostly release version bumps across package manifests, Conflicts resolvedOne conflict: Both sides added a section under the same
Resolution: kept both, ordered Resolved Files changedConflict resolution touched exactly one file: The PR's own diff against
Merge commit: Downstream compatibilityNo risk introduced by this run.
Validation
Push command usedgit push origin TAT-3400-fix-trade-with-token-has-20-failurRegular push, no Post-push verification
Validation recipe — replaced, not just re-runThe task dir was seeded with an inherited The new
Run result: Supporting artifacts: ResultBranch updated to include The one judgement call beyond mechanical merge work: the inherited recipe was obsolete and was |
…th-token-has-20-failur
Move normalizeRequest's JSDoc back above the function (it was orphaned when isHypercoreDeposit/getTradeType were inserted), update a stale inline comment, and re-anchor the EXACT_OUTPUT changelog entry under Unreleased after merging main moved it into an already-released section.
0bbab89
Automated pr-complete run — #9751
Worker reportPR #9751 — Context Reload & Comment TriageInherited context: present
Root-cause history (important — scope shifted since inherited run)The inherited run (
Live PR comments/review (fetched)See step 4 output below for raw fetch. Prior automated runs already posted to the PR:
Only one Jira/ticket comment thread exists ( Recipe re-validation
Triage table
Real findings: 2 (both fixed). No inline/unresolved review threads exist via the GitHub API (0 inline comments, review is Core compatibility assessmentTouched files:
Recipe / headless validationReplaced and re-run — PASS.
Unplanned fix: merge-queue changelog failure (reported mid-session)The operator flagged a failing merge-queue run: https://github.com/MetaMask/core/actions/runs/30925694739/job/92047377347 —
Root cause: Fix applied this session:
Pushed at operator's explicit request: commit |
Explanation
Trade-with-token perps deposits failed intermittently with insufficient margin on the order leg.
Root cause. Core requested HyperCore perps deposits from Relay with
tradeType: EXPECTED_OUTPUT, which only guaranteestarget * (1 - slippage)arrives at the destination. Clients size these deposits at exactlymarginRequiredand immediately place an order needing that same margin, so any slippage inside the 0.5% band left the account short and the order was rejected.HyperCore deposits end up on this path because
skipDelegationis true for them, so they never take the delegation path that already usedEXACT_OUTPUT.Fix. Request
EXACT_OUTPUTfor HyperCore deposits, so Relay guarantees the full target arrives.Verified against the live Relay API
Same quote request Core builds, across deposit sizes — no mocks. "Guaranteed" is Relay's
currencyOut.minimumAmount; "source cost" is the Arbitrum USDC the user spends (currencyIn).The shortfall reproduced at all six sizes before the change and none after.
Relay quotes the same input amount for both trade types, so the user spends exactly what they spend today — this buys a delivery guarantee at no extra cost, rather than padding the deposit.
Client impact
No exported API or type changes. Mobile HyperCore deposits now deliver the full requested margin at the same source cost. Extension does not yet create
perpsDepositAndOrder, so it is unaffected today but inherits the fix when trade-with-token ships there.References
Checklist
Note
Medium Risk
Changes Relay trade-type selection on the perps deposit path; scope is narrow and covered by new tests, with no public API changes.
Overview
Fixes intermittent insufficient margin on trade-with-token perps by changing how Relay quotes are built for HyperCore USDC deposits.
Relay quote requests now send
tradeType: EXACT_OUTPUTwhen the normalized target is HyperCore USDC (non–Hyperliquid-source), instead ofEXPECTED_OUTPUT. That guarantees the full deposit target arrives so a same-size follow-on order is not short by slippage.EXACT_INPUTfor max/post-quote flows andEXPECTED_OUTPUTfor other targets are unchanged, via newisHypercoreDepositandgetTradeTypehelpers inrelay-quotes.ts. Tests and the package changelog document the behavior.Reviewed by Cursor Bugbot for commit 0bbab89. Bugbot is set up for automated code reviews on this repo. Configure here.