Skip to content

feat: flatten nested redeemDelegations into outer call#29959

Draft
matthewwalsh0 wants to merge 2 commits into
mainfrom
matthewwalsh0/delegation-call-flatten
Draft

feat: flatten nested redeemDelegations into outer call#29959
matthewwalsh0 wants to merge 2 commits into
mainfrom
matthewwalsh0/delegation-call-flatten

Conversation

@matthewwalsh0
Copy link
Copy Markdown
Member

@matthewwalsh0 matthewwalsh0 commented May 11, 2026

Description

Two related changes to mobile's delegation transaction assembly:

1. Central convertTransactionToRedeemDelegations

Mirroring metamask-extension, this PR introduces a single convertTransactionToRedeemDelegations function in app/util/transactions/delegation.ts that all delegation flows route through. It accepts optional caveats, additionalExecutions, delegatee, delegationSignature, and skipAuthorization so each caller can keep its own semantics while sharing one assembly path.

  • getDelegationTransaction is now a thin wrapper that defaults value to 0x0.
  • Delegation7702PublishHook uses the central function, removing ~100 lines of duplicated delegation/execution/caveat assembly.

2. Automatic flattening of nested redeemDelegations

When a delegated transaction has a nested transaction that is itself a redeemDelegations call to the DelegationManager, the previous client wrapped it inside another delegation. The resulting on-chain call had the shape:

redeemDelegations(
  [outerContext],
  [BATCH_DEFAULT_MODE],
  [encodeBatch([
    { target: DelegationManager, callData: redeemDelegations(innerContext, ...) },
    { target: ..., callData: ... },
  ])]
)

This added an entire delegation layer (signature recovery, caveat enforcement, an extra CALL into the DelegationManager) on top of an already self-authorised redemption.

convertTransactionToRedeemDelegations now detects that case and merges the inner call's permissionContexts, modes, and calldatas into the outer call as additional parallel slots:

redeemDelegations(
  [outerContext, innerContext],
  [SINGLE_DEFAULT_MODE, innerMode],
  [encodeSingle(remainingTx), innerCalldata]
)

The outer delegation is rebuilt to authorise only the non-redeemDelegations nested transactions, so its caveats (exactExecution / exactExecutionBatch + limitedCalls(1)) remain tight over the slots the outer delegate actually executes.

Detection is gated on (a) the nested target equalling the chain's DelegationManager address from getDeleGatorEnvironment, (b) the calldata starting with the redeemDelegations selector (0xcef6d209), and (c) successful ABI decode of the (bytes[], bytes32[], bytes[]) payload. Any failure falls back to the existing nested-delegation shape.

Because flattening lives inside convertTransactionToRedeemDelegations, it now applies automatically to every caller, including Delegation7702PublishHook.

Changelog

CHANGELOG entry: null

Related issues

Fixes:

Manual testing steps

```gherkin
Feature: Delegated transaction with a nested redeemDelegations

Scenario: client builds the outer redeemDelegations call
Given a transaction whose nestedTransactions[i] targets the DelegationManager with a redeemDelegations calldata
When getDelegationTransaction runs
Then the returned transaction calls redeemDelegations once
And the bytes[] permissionContexts argument contains the outer delegation plus the inner permissionContext as additional slots
And the bytes32[] modes and bytes[] calldatas arguments are extended in lockstep
And the outer delegation's caveats cover only the remaining (non-redeem) nested transactions
```

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

When a nested transaction in a delegated transaction is itself a
redeemDelegations call, merge its permission contexts, modes, and
calldatas into the outer redeemDelegations call as additional slots
rather than wrapping it inside another delegation. This avoids the
gas cost of an extra delegation layer when the nested call is already
self-authorised.
@metamaskbotv2 metamaskbotv2 Bot added team-confirmations Push issues to confirmations team INVALID-PR-TEMPLATE PR's body doesn't match template labels May 11, 2026
…try point

Mirror the extension by introducing a single convertTransactionToRedeemDelegations
function in app/util/transactions/delegation.ts that handles caveats, executions,
optional EIP-7702 authorization, and unconditional flattening of nested
redeemDelegations.

- getDelegationTransaction is now a thin wrapper that defaults value to 0x0
- Delegation7702PublishHook uses the central function, removing ~100 lines of
  duplicated delegation/execution/caveat assembly
- Flattening of nested redeemDelegations is automatic for every caller
@github-actions github-actions Bot added size-L and removed size-M labels May 17, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeConfirmations
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are focused on EIP-7702 delegation transaction infrastructure:

  1. app/core/Delegation/delegation.ts: Extended encodeRedeemDelegations to support extra contexts/modes/calldatas for flattening nested redeemDelegations calls.

  2. app/util/transactions/delegation.ts: Major refactoring - extracted convertTransactionToRedeemDelegations, added partitionNestedTransactions for flattening nested delegation calls, exported normalizeCallData. The getDelegationTransaction function now delegates to the new function.

  3. app/util/transactions/hooks/delegation-7702-publish.ts: Refactored to use the shared convertTransactionToRedeemDelegations and normalizeCallData, removing duplicated code.

These changes directly affect:

  • EIP-7702 batch transaction flows (sendCalls with smart accounts)
  • Gas fee token payment flows (paying gas with USDC/DAI via delegation)
  • Sponsored transaction relay flows

The E2E tests covering these paths are all under SmokeConfirmations:

  • tests/smoke/confirmations/transactions/7702/batch-transaction.spec.ts - active test for smart account batch transactions
  • tests/smoke/confirmations/transactions/gas-fee-tokens-eip-7702.spec.ts - skipped but tagged SmokeConfirmations
  • tests/smoke/confirmations/transactions/gas-fee-tokens-eip-7702-sponsored.spec.ts - skipped but tagged SmokeConfirmations

The changes don't affect general wallet navigation, account management, network selection, swap/bridge flows, or other areas. The risk is medium because the refactoring is significant but well-tested with unit tests, and the E2E tests for the most critical paths (gas fee tokens) are currently skipped.

Performance Test Selection:
The changes are purely in transaction delegation logic (encoding/decoding, function refactoring). No UI rendering components, list components, account selectors, or app startup code is affected. No performance-sensitive paths are touched.

View GitHub Actions results

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

INVALID-PR-TEMPLATE PR's body doesn't match template size-L team-confirmations Push issues to confirmations team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant