Skip to content

Commit cbf1bcf

Browse files
matthewwalsh0jpuri
andauthored
fix(transaction-pay-controller): account override fixes (#8724)
## Summary Fixes in `transaction-pay-controller`'s Relay strategy that together unblock Money Account deposits via MetaMask Pay (and any other delegation-based deposit flow targeting Arbitrum USDC). 1. Funding-leg recipient should be `transaction.txParams.from`, not `request.from` 2. Arbitrum-USDC → Hypercore rewrite must be gated on `perpsDeposit` Ref: https://consensyssoftware.atlassian.net/browse/CONF-1324 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adjusts Relay quote transaction construction and chain/token rewrite behavior for Arbitrum USDC deposits; mistakes here could misroute funds or produce incorrect quotes for delegation-based flows. > > **Overview** > Fixes Relay quoting for delegation-based flows when `accountOverride` causes `request.from` to differ from the executing delegator. > > Relay now funds the **delegator address** (`transaction.txParams.from`) when building the initial token-transfer leg (falling back to `request.from` if unset), and the Arbitrum-USDC → Hypercore (Hyperliquid) quote rewrite is now **only applied for `TransactionType.perpsDeposit`** (not for other transaction types or post-quote flows). Tests and the changelog are updated to cover these cases. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 882ea79. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Jyoti Puri <jyotipuri@gmail.com>
1 parent 40465f7 commit cbf1bcf

3 files changed

Lines changed: 122 additions & 4 deletions

File tree

packages/transaction-pay-controller/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Fix transaction with accountOverride and targeting Arbitrum USDC ([#8724](https://github.com/MetaMask/core/pull/8724))
13+
- Deliver the Relay-acquired token to `transaction.txParams.from` (the delegator that executes the inner delegation), not `request.from`.
14+
- Gate the Arbitrum-USDC → Hypercore quote rewrite on `transaction.type === TransactionType.perpsDeposit`.
15+
1016
## [22.0.2]
1117

1218
### Changed

packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,68 @@ describe('Relay Quotes Utils', () => {
454454
);
455455
});
456456

457+
it('funds the delegator (transaction.txParams.from) rather than request.from when they differ', async () => {
458+
const delegatorAddress =
459+
'0xabcdef0000000000000000000000000000000001' as Hex;
460+
461+
successfulFetchMock.mockResolvedValue({
462+
ok: true,
463+
json: async () => QUOTE_MOCK,
464+
} as never);
465+
466+
await getRelayQuotes({
467+
accountSupports7702: true,
468+
messenger,
469+
requests: [QUOTE_REQUEST_MOCK],
470+
transaction: {
471+
...TRANSACTION_META_MOCK,
472+
txParams: {
473+
from: delegatorAddress,
474+
data: '0xabc' as Hex,
475+
},
476+
} as TransactionMeta,
477+
});
478+
479+
const body = JSON.parse(
480+
successfulFetchMock.mock.calls[0][1]?.body as string,
481+
);
482+
483+
expect(body.txs[0]).toStrictEqual({
484+
to: QUOTE_REQUEST_MOCK.targetTokenAddress,
485+
data: '0xa9059cbb000000000000000000000000abcdef0000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b',
486+
value: '0x0',
487+
});
488+
});
489+
490+
it('falls back to request.from for the funding recipient when transaction.txParams.from is unset', async () => {
491+
successfulFetchMock.mockResolvedValue({
492+
ok: true,
493+
json: async () => QUOTE_MOCK,
494+
} as never);
495+
496+
await getRelayQuotes({
497+
accountSupports7702: true,
498+
messenger,
499+
requests: [QUOTE_REQUEST_MOCK],
500+
transaction: {
501+
...TRANSACTION_META_MOCK,
502+
txParams: {
503+
data: '0xabc' as Hex,
504+
},
505+
} as TransactionMeta,
506+
});
507+
508+
const body = JSON.parse(
509+
successfulFetchMock.mock.calls[0][1]?.body as string,
510+
);
511+
512+
expect(body.txs[0]).toStrictEqual({
513+
to: QUOTE_REQUEST_MOCK.targetTokenAddress,
514+
data: '0xa9059cbb0000000000000000000000001234567890123456789012345678901234567891000000000000000000000000000000000000000000000000000000000000007b',
515+
value: '0x0',
516+
});
517+
});
518+
457519
it('includes request in quote', async () => {
458520
successfulFetchMock.mockResolvedValue({
459521
ok: true,
@@ -2751,7 +2813,10 @@ describe('Relay Quotes Utils', () => {
27512813
accountSupports7702: true,
27522814
messenger,
27532815
requests: [arbitrumToHyperliquidRequest],
2754-
transaction: TRANSACTION_META_MOCK,
2816+
transaction: {
2817+
...TRANSACTION_META_MOCK,
2818+
type: TransactionType.perpsDeposit,
2819+
},
27552820
});
27562821

27572822
const body = JSON.parse(
@@ -2767,6 +2832,37 @@ describe('Relay Quotes Utils', () => {
27672832
);
27682833
});
27692834

2835+
it('does not convert to Hyperliquid deposit when parent transaction is not a Perps deposit', async () => {
2836+
const arbitrumUsdcRequest: QuoteRequest = {
2837+
...QUOTE_REQUEST_MOCK,
2838+
targetChainId: CHAIN_ID_ARBITRUM,
2839+
targetTokenAddress: ARBITRUM_USDC_ADDRESS,
2840+
};
2841+
2842+
successfulFetchMock.mockResolvedValue({
2843+
ok: true,
2844+
json: async () => QUOTE_MOCK,
2845+
} as never);
2846+
2847+
await getRelayQuotes({
2848+
accountSupports7702: true,
2849+
messenger,
2850+
requests: [arbitrumUsdcRequest],
2851+
transaction: TRANSACTION_META_MOCK,
2852+
});
2853+
2854+
const body = JSON.parse(
2855+
successfulFetchMock.mock.calls[0][1]?.body as string,
2856+
);
2857+
2858+
expect(body).toStrictEqual(
2859+
expect.objectContaining({
2860+
destinationChainId: Number(CHAIN_ID_ARBITRUM),
2861+
destinationCurrency: ARBITRUM_USDC_ADDRESS,
2862+
}),
2863+
);
2864+
});
2865+
27702866
it('does not convert to Hyperliquid deposit for post-quote requests targeting Arbitrum USDC', async () => {
27712867
const postQuoteRequest: QuoteRequest = {
27722868
...QUOTE_REQUEST_MOCK,

packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Interface } from '@ethersproject/abi';
44
import { toHex } from '@metamask/controller-utils';
5+
import { TransactionType } from '@metamask/transaction-controller';
56
import type { TransactionMeta } from '@metamask/transaction-controller';
67
import type { Hex } from '@metamask/utils';
78
import { createModuleLogger } from '@metamask/utils';
@@ -87,7 +88,9 @@ export async function getRelayQuotes(
8788

8889
return hasTargetMinimum || isPostQuote || isExactInputRequest;
8990
})
90-
.map((singleRequest) => normalizeRequest(singleRequest));
91+
.map((singleRequest) =>
92+
normalizeRequest(singleRequest, request.transaction),
93+
);
9194

9295
log('Normalized requests', normalizedRequests);
9396

@@ -346,10 +349,15 @@ async function processTransactions(
346349
requestBody.refundTo = request.from;
347350
}
348351

352+
const fundingRecipient = (transaction.txParams?.from as Hex) ?? request.from;
353+
349354
requestBody.txs = [
350355
{
351356
to: request.targetTokenAddress,
352-
data: buildTokenTransferData(request.from, request.targetAmountMinimum),
357+
data: buildTokenTransferData(
358+
fundingRecipient,
359+
request.targetAmountMinimum,
360+
),
353361
value: '0x0',
354362
},
355363
{
@@ -364,14 +372,22 @@ async function processTransactions(
364372
* Normalizes requests for Relay.
365373
*
366374
* @param request - Quote request to normalize.
375+
* @param transaction - Parent transaction metadata, used to gate
376+
* Hyperliquid-specific rewrites on transaction type.
367377
* @returns Normalized request.
368378
*/
369-
function normalizeRequest(request: QuoteRequest): QuoteRequest {
379+
function normalizeRequest(
380+
request: QuoteRequest,
381+
transaction: TransactionMeta,
382+
): QuoteRequest {
370383
const newRequest = {
371384
...request,
372385
};
373386

387+
const isPerpsDeposit = transaction.type === TransactionType.perpsDeposit;
388+
374389
const isHyperliquidDeposit =
390+
isPerpsDeposit &&
375391
!request.isPostQuote &&
376392
request.targetChainId === CHAIN_ID_ARBITRUM &&
377393
request.targetTokenAddress.toLowerCase() ===

0 commit comments

Comments
 (0)