Skip to content

fix(perps): [Bug]: Perps - Error when selecting imported account ApiRequestError: Multi-sig required - #9769

Merged
abretonc7s merged 4 commits into
mainfrom
TAT-3214-fix-bug-perps-error-when-selecting
Aug 4, 2026
Merged

fix(perps): [Bug]: Perps - Error when selecting imported account ApiRequestError: Multi-sig required#9769
abretonc7s merged 4 commits into
mainfrom
TAT-3214-fix-bug-perps-error-when-selecting

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Explanation

Opening the Perps tab with a HyperLiquid multi-sig account raised
ApiRequestError: Multi-sig required, on every entry.

Perps initialization runs HyperLiquidProvider.#ensureUnifiedAccountEnabled,
which migrates an account on a legacy abstraction mode to unifiedAccount. For
default / disabled accounts that is a silent agent-signed
agentSetAbstraction write. HyperLiquid rejects every single-signer
exchange write for an account that has been converted to multi-sig, so this
write could never succeed — but nothing classified the account shape before the
write, and nothing classified the rejection after it. It fell through to the
generic failure handler, which caches nothing, sets the retry flag (so
#ensureReady drops its memoized promise), and reports the error. The result
was a permanent error for these accounts and recurring Sentry noise.

Two changes:

  • HyperLiquidProvider now reads infoClient.userToMultiSigSigners immediately
    before the migration write. For a multi-sig account it skips the write, emits
    Perp Account Setup with status: not_applicable /
    error_message: multi_sig_account, and records
    { attempted: true, enabled: false } in TradingReadinessCache — a final
    state, so the attempt is not repeated. The lookup only runs when a migration
    write would otherwise be made, so accounts already on unifiedAccount /
    portfolioMargin, deferred dexAbstraction accounts (including hardware
    wallets at init) and unknown modes pay no extra round trip. The probe fails
    open on a transient info-API error, so it can never block migration for a
    normal single-signer account.
  • The new isHyperLiquidMultiSigRequiredError classifier (exported from
    @metamask/perps-controller/utils/*) is applied in the same method's catch,
    covering the case where the account is converted between the lookup and the
    write, and the case where the probe failed open. It matches case-insensitively
    and accepts both multi-sig required and multisig required; only the
    hyphenated form has been observed in the wild (the ticket, and the mapping
    added in fix(perps): harden HyperLiquid cancel readiness and map account-mode errors #9750), so the unhyphenated variant is defensive rather than
    documented venue behaviour.

Unified account mode stays off for these accounts by design; HIP-3 collateral
continues through the existing programmatic transfer fallback. Behaviour for
single-signer accounts is unchanged apart from the one pre-write lookup.

Note for a follow-up (out of scope here): the other user-scoped exchange writes
reached from #ensureReadyForTradingapproveBuilderFee and setReferrer
will hit the same rejection at trade time for these accounts, and
#setReferralCode still routes it to the error logger.

How this was verified

Reproduced and fixed against a real HyperLiquid account. A testnet account
was transiently converted to multi-sig and the real PerpsController was driven
through #ensureReady#ensureUnifiedAccountEnabled from two isolated
checkouts. Only the checked-out source differed:

Run Multi-sig required logged Unified Account setup failed migration skipped
main yes yes no
this branch no no yes

Baseline logged Unified Account setup failed {…,"error":"Multi-sig required"}
and forwarded it to the error logger — the reported bug. This branch logged
Multi-sig account, skipping unified account migration {…,"mode":"default"} and
raised no error. The account was restored afterwards (signer set null,
abstraction default, funds returned) and verified against its pre-mutation
snapshot. Testnet only; no order was placed, cancelled or edited.

Unit regression net (independently diff-sensitive). Reverting only
HyperLiquidProvider.ts and errorUtils.ts, leaving the tests in place, fails
exactly three assertions of 60 — the migration skip, the readiness-cache state,
and logger.error receiving 1 call where 0 is expected — and passes again when
restored. The classifier target fails separately with
isHyperLiquidMultiSigRequiredError is not a function.

Live mainnet read path (getAccountState / getPositions, read-only) was
also exercised to confirm no regression to controller initialization and reads.
That tier is explicitly not diff-sensitive for this change and is not offered
as evidence for the fix.

Remaining limitation: the multi-sig runs are captured artifacts rather than a
replayable recipe step, because restoring the account to single-sig is the
required end state. A repeatable version needs a permanently multi-sig testnet
fixture account and a read-only ensure-ready action in the harness.

Relationship to #9750

#9750 (TAT-3633, now merged) maps
the same HyperLiquid rejection to a typed EXCHANGE_MULTI_SIG_REQUIRED code on
the order and cancel paths. This PR stops the account-setup write from being
attempted at all, which is what produces the error on Perps tab entry. The two
are complementary and neither subsumes the other.

HyperLiquidProvider.ts auto-merges cleanly — that PR touches ~511, 2393,
3439–3463, 3711, 3737, 4061–4096, this one 136, 785, 980, 1075 — and the test
files are disjoint. packages/perps-controller/CHANGELOG.md does conflict
now that #9750 has merged: both add entries to the same Unreleased sections.
git merge-tree --write-tree against current main reports
CONFLICT (content) on that file alone. It needs a trivial resolution (keep
both sets of entries) before merge.

Optional follow-up for whichever merges second: #mapError's static table keys
only on 'multi-sig required', while the classifier added here also matches the
unhyphenated spelling. #mapError already calls isHyperLiquidUserNotFoundError
as a function for the analogous EXCHANGE_ACCOUNT_NOT_FOUND case, so
isHyperLiquidMultiSigRequiredError could be reused there instead of keeping two
matchers. Not a blocker for either PR.

The blocking review comment on #9750 about a dropped cancel status error is a
separate cancelOrder response-shape issue and does not affect this change.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Screenshots/Recordings

LIVE diff-sensitive Core proof for TAT-3214. A HyperLiquid TESTNET account (dev3) was transiently converted to multi-sig and the real PerpsController was driven through #ensureReady -> #ensureUnifiedAccountEnabled from two isolated checkouts: origin/main logged the genuine 'Multi-sig required' rejection, this branch skipped the write. The account was restored and verified against its pre-mutation snapshot (signers null, mode default, funds returned). Supporting tiers: live mainnet read-only non-regression, and a re-runnable unit regression net. No mainnet mutation, no trading, no visual evidence.

@abretonc7s
abretonc7s requested review from a team as code owners August 3, 2026 18:49
abretonc7s added a commit that referenced this pull request Aug 3, 2026
Both Unreleased entries referenced (TAT-3214) with no PR link, unlike every
neighbouring entry. Add ([#9769](#9769))
now that the pull request exists.

Refs TAT-3214
abretonc7s added a commit that referenced this pull request Aug 3, 2026
Both Unreleased entries referenced (TAT-3214) with no PR link, unlike every
neighbouring entry. Add ([#9769](#9769))
now that the pull request exists.

Refs TAT-3214
Opening the Perps tab with a HyperLiquid multi-sig account raised
`ApiRequestError: Multi-sig required` on every entry.
`#ensureUnifiedAccountEnabled` migrates legacy abstraction modes with a
single-signer `agentSetAbstraction` / `userSetAbstraction` write, which
HyperLiquid rejects outright for a converted multi-sig account. Nothing
classified the account shape before the write or the rejection after it,
so it fell through to the generic failure handler: no cache entry, retry
flag set, error reported.

Query `userToMultiSigSigners` immediately before the migration write and
skip it for a multi-sig account, recording the setup as not applicable
and caching `{ attempted: true, enabled: false }` so it is not retried.
The lookup only runs when a write would otherwise be made, so accounts
already on a compatible mode and deferred accounts pay no extra round
trip, and it fails open so a transient info-API error cannot block
migration for a single-signer account.

Add `isHyperLiquidMultiSigRequiredError` and apply it in the same
method's catch block, covering the conversion racing the write and the
failed-open probe. Both `multi-sig required` and `multisig required`
spellings are matched.

Fixes TAT-3214
The Unreleased entry documented the classifier as exported from
`@metamask/perps-controller/utils`, but the package exports map declares
`./utils/*` with no bare `./utils`, so that specifier fails to resolve
with ERR_PACKAGE_PATH_NOT_EXPORTED. Point it at `utils/*`, matching the
neighbouring floorToSizeDecimals entry.

Refs TAT-3214
Both the changelog entry and the isHyperLiquidMultiSigRequiredError JSDoc
asserted that HyperLiquid returns the unhyphenated "multisig required"
spelling. Only the hyphenated "Multi-sig required" has been observed, and
the mapping table added in #9750 carries that key alone.

Matching both spellings is unchanged and still correct; only the stated
justification was wrong. Documentation only, no behaviour change.

Refs TAT-3214
Both Unreleased entries referenced (TAT-3214) with no PR link, unlike every
neighbouring entry. Add ([#9769](#9769))
now that the pull request exists.

Refs TAT-3214
@abretonc7s
abretonc7s force-pushed the TAT-3214-fix-bug-perps-error-when-selecting branch from c70c277 to 72c372b Compare August 3, 2026 20:27
@abretonc7s
abretonc7s added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 6e16298 Aug 4, 2026
46 checks passed
@abretonc7s
abretonc7s deleted the TAT-3214-fix-bug-perps-error-when-selecting branch August 4, 2026 09:37
@abretonc7s abretonc7s changed the title fix(perps): skip unified migration for HyperLiquid multi-sig accounts fix(perps): [Bug]: Perps - Error when selecting imported account ApiRequestError: Multi-sig required Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants