Skip to content

feat(transaction-pay-controller): add HyperLiquid source quote support for Perps Withdraw#8285

Open
dan437 wants to merge 2 commits intomainfrom
perps-withdraw-quote
Open

feat(transaction-pay-controller): add HyperLiquid source quote support for Perps Withdraw#8285
dan437 wants to merge 2 commits intomainfrom
perps-withdraw-quote

Conversation

@dan437
Copy link
Contributor

@dan437 dan437 commented Mar 24, 2026

Explanation

Adds HyperLiquid source quote support for Perps Withdraw. With this we will be able to show a quote for Perps Withdraw on a client.

References

Fixes https://consensyssoftware.atlassian.net/browse/CONF-1089

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

Note

Medium Risk
Adds a new isHyperliquidSource flag that changes Relay quote request normalization, fee calculation (gasless), and request schema, which could affect quote correctness for post-quote flows if the override logic is wrong.

Overview
Adds HyperLiquid-source (HyperCore) support to Transaction Pay’s Relay quoting path via a new isHyperliquidSource flag.

When enabled, Relay quote requests are normalized to use HyperCore Perps USDC (including a decimal shift), include protocolVersion: 'v2', use Arbitrum USDC for fiat-rate lookup, and treat source-network fees as gasless (zeroed). Types are expanded to handle non-transaction Relay steps and code paths are updated to only process transaction steps for gas estimation/submission.

Plumbs isHyperliquidSource through controller config and quote-request building, adds HyperCore USDC constants, and extends tests to cover the new behavior.

Written by Cursor Bugbot for commit c6e2d66. This will update automatically on new commits. Configure here.

dan437 added 2 commits March 24, 2026 15:19
…t for Perps Withdraw

Signed-off-by: dan437 <80175477+dan437@users.noreply.github.com>
Signed-off-by: dan437 <80175477+dan437@users.noreply.github.com>
@dan437 dan437 requested a review from a team as a code owner March 24, 2026 14:36
const { steps } = quote.original;
const params = steps.flatMap((step) => step.items).map((item) => item.data);
const txSteps = steps.filter(
(step): step is RelayTransactionStep => step.kind === 'transaction',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was needed because with HyperLiquid withdraw the quote will come back as 2 different kinds:

authorize -> kind: 'signature'
deposit -> kind: 'transaction'

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

const txSteps = steps.filter(
(step): step is RelayTransactionStep => step.kind === 'transaction',
);
const params = txSteps.flatMap((step) => step.items).map((item) => item.data);
Copy link

Choose a reason for hiding this comment

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

Invalid kind guard blocks HyperLiquid signature steps

High Severity

The txSteps filter on lines 314-316 correctly narrows to transaction steps, but the invalidKind guard still scans ALL steps and throws for any non-transaction kind. Since RelaySignatureStep (with kind: 'signature') was added to the steps union for HyperLiquid withdrawal support, any quote containing a signature step will cause submitTransactions to throw "Unsupported step kind: signature", completely blocking HyperLiquid withdrawal submission. There is no isHyperliquidSource check in relay-submit.ts to bypass this.

Additional Locations (1)
Fix in Cursor Fix in Web

export const POLYGON_USDCE_ADDRESS =
'0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174' as Hex;

export const HYPERCORE_USDC_ADDRESS = '0x00000000000000000000000000000000';
Copy link
Member

Choose a reason for hiding this comment

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

These are technically just Relay aliases but I see we already define the chain ID and stablecoin below.

const firstTxStep = quote.original.steps.find(
(step) => step.kind === 'transaction',
);
const firstStepData = firstTxStep?.items[0]?.data as
Copy link
Member

Choose a reason for hiding this comment

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

Minor, could we cast the above firstTxStep if we know the transaction type equates to a specific object?

}

if (request.isHyperliquidSource) {
body.protocolVersion = 'v2';
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed? I thought the default was v2 now as Relay toggled it for our referrer.

const tokenTransferData = nestedTransactions?.find((nestedTx) =>
nestedTx.data?.startsWith(TOKEN_TRANSFER_FOUR_BYTE),
const tokenTransferData = nestedTransactions?.find(
(nestedTx: { data?: Hex }) =>
Copy link
Member

Choose a reason for hiding this comment

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

?


// HyperLiquid withdrawal: source is HyperCore Perps USDC, not Arbitrum.
// Override source chain/token and set protocolVersion=v2 (required by Relay).
if (request.isHyperliquidSource) {
Copy link
Member

Choose a reason for hiding this comment

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

In future, we could inject this at the client level as a constructed payment token but it would be difficult to get the balance, and would only with Relay etc.

export const HYPERCORE_USDC_ADDRESS = '0x00000000000000000000000000000000';

// HyperCore Perps USDC uses 8 decimals vs standard USDC's 6.
export const HYPERCORE_USDC_DECIMAL_SHIFT = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Minor, would it be more accurate to define the decimals so we can calculate the shift based on the tokens we're converting from?

This assumes it is always USDC.

// For HyperLiquid source, the normalized chain/token (HyperCore + Perps USDC)
// won't have a fiat rate entry. Use Arbitrum USDC instead since Perps USDC
// is pegged 1:1.
const sourceChainId = request.isHyperliquidSource
Copy link
Member

Choose a reason for hiding this comment

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

❤️

};

/** HyperLiquid deposit step (sendAsset to Relay solver). */
export type RelayHyperliquidDepositStep = {
Copy link
Member

Choose a reason for hiding this comment

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

Minor, should this also be in the steps type above?

};
status: 'complete' | 'incomplete';
}[];
kind: 'transaction';
Copy link
Member

Choose a reason for hiding this comment

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

Please tell me they don't have a different schema but leave the kind the same and not hyperliquid or something? 😅

* When true, the Relay strategy uses the HyperLiquid 2-step withdrawal
* flow: (1) authorize nonce-mapping, (2) sendAsset to Relay solver.
*/
isHyperliquidSource?: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

Minor, alphabetical?

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