Skip to content

feat: add support for redelegating from permission contexts#217

Merged
jeffsmale90 merged 14 commits into
mainfrom
cursor/redelegate-permission-context-1c3b
May 11, 2026
Merged

feat: add support for redelegating from permission contexts#217
jeffsmale90 merged 14 commits into
mainfrom
cursor/redelegate-permission-context-1c3b

Conversation

@jeffsmale90

@jeffsmale90 jeffsmale90 commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

📝 Description

This PR implements support for creating redelegations from existing permission contexts (e.g., ERC-7715 responses), significantly improving the developer experience when working with delegation chains.

Previously, developers had to manually decode permission contexts, extract the leaf delegation, sign a new delegation, and re-encode the chain. This PR streamlines that workflow with new APIs and improved type safety.

🔄 What Changed?

Core API Enhancements (createDelegation / createOpenDelegation)

  • Added parentPermissionContext parameter to createDelegation and createOpenDelegation

    • Accepts a PermissionContext (either a Delegation[] or encoded Hex)
    • Automatically extracts the leaf delegation (first element in the chain) to use as the parent authority
    • Uses a discriminated union with parentDelegation and scope so the three options are mutually exclusive at the type level
  • Made scope optional when a parent delegation/permission context exists

    • When redelegating, scope can be inherited from the parent
    • Scope constraints are still enforced through the parent's caveats in the chain
    • An explicit scope override is still supported when you want to add additional scope-derived caveats
  • resolveCaveats now takes hasInheritance

    • Throws Scope is required when the delegation has no inheritance when scope is missing on a root delegation
    • When inheriting, falls back to a base caveat builder (no scope caveats appended)

New Redelegation Actions

Two explicit, high-level convenience functions that handle the full redelegation workflow. They mirror the createDelegation / createOpenDelegation split so callers always get the kind of delegation they expect — no optional to flag deciding the shape of the result.

  • redelegatePermissionContext — creates a redelegation to a specific delegate (to is required).
  • redelegatePermissionContextOpen — creates an open redelegation (delegate is ANY_BENEFICIARY); does not accept to.

Both functions:

  1. Decode the permission context
  2. Extract the leaf delegation as the parent
  3. Create a new delegation (specific or open) inheriting from it
  4. Sign it via signDelegation
  5. Prepend it to the chain and re-encode
  6. Return both the signed delegation and the encoded permissionContext

Shared parameters:

  • account? — defaults to client.account
  • environment — provides DelegationManager and caveat enforcers
  • permissionContext — the context to redelegate from
  • chainId — chain id used for the EIP-712 signature
  • scope? — optional override; inherits from parent when omitted
  • caveats? — additional caveats to apply
  • salt? — optional salt for uniqueness

redelegatePermissionContext additionally requires:

  • to — the address of the new delegate

Client Extension (redelegatePermissionContextActions)

  • Follows the existing SDK pattern (e.g., signDelegationActions)
  • Adds both redelegatePermissionContext and redelegatePermissionContextOpen to the wallet client when .extend()-ed
  • Automatically infers chainId from client.chain.id for both actions (throws Chain ID is required if neither client chain nor chainId is provided)

Type Safety

  • The BaseCreateDelegationOptions type uses a discriminated union so exactly one of the following is allowed:
    • scope (root delegation, no parent)
    • parentDelegation (with optional scope)
    • parentPermissionContext (with optional scope)
  • The two redelegation actions are independent functions with distinct parameter types (RedelegatePermissionContextParameters vs RedelegatePermissionContextOpenParameters), so the choice between specific and open is made at the call site rather than via an optional flag.

🚀 Why?

Issue: #196

Before this PR, redelegating from an ERC-7715 response required manual orchestration:

const erc7715Response = {...};

const decodedContext = decodeDelegations(erc7715Response.context);
const leafDelegation = decodedContext[0];

const newDelegation = createDelegation({
  environment,
  scope: erc20Scope, // had to manually re-specify scope
  to: charlie.address,
  from: bob.address,
  parentDelegation: leafDelegation,
  caveats: [timestampCaveat],
});

const signature = await signDelegation(walletClient, {
  delegation: newDelegation,
  delegationManager: environment.DelegationManager,
  chainId: 11155111,
});

const signed = { ...newDelegation, signature };
const newContext = encodeDelegations([signed, ...decodedContext]);

Now, you can use the parentPermissionContext parameter on createDelegation:

import {
  createDelegation,
  decodeDelegations,
  encodeDelegations,
  signDelegation,
} from '@metamask/smart-accounts-kit';

const erc7715Response = {...};

const redelegation = createDelegation({
  environment,
  from: bob.address,
  to: charlie.address,
  parentPermissionContext: erc7715Response.context,
  caveats: [timestampCaveat],
});

const signature = await signDelegation(walletClient, {
  delegation: redelegation,
  delegationManager: environment.DelegationManager,
  chainId: sepolia.id,
});

const newPermissionContext = encodeDelegations([
  { ...redelegation, signature },
  ...decodeDelegations(erc7715Response.context),
]);

Or, use the dedicated actions to redelegate directly:

import { redelegatePermissionContextActions } from '@metamask/smart-accounts-kit/actions';
import { createWalletClient, http } from 'viem';
import { sepolia } from 'viem/chains';

const erc7715Response = {...};

const walletClient = createWalletClient({
  account: bob,
  chain: sepolia,
  transport: http(),
}).extend(redelegatePermissionContextActions());

const { delegation, permissionContext } =
  await walletClient.redelegatePermissionContext({
    environment,
    permissionContext: erc7715Response.context,
    to: charlie.address,
    caveats: [timestampCaveat],
  });

await sendUserOperationWithDelegation(client, {
  calls: [
    {
      to: contractAddress,
      data: callData,
      permissionContext,
      delegationManager: environment.DelegationManager,
    },
  ],
});

@cursor cursor Bot marked this pull request as ready for review April 29, 2026 01:47
@cursor cursor Bot requested a review from a team as a code owner April 29, 2026 01:47
@jeffsmale90 jeffsmale90 marked this pull request as draft May 3, 2026 21:18
cursoragent and others added 3 commits May 5, 2026 11:02
This implements support for creating redelegations from ERC-7715 permission
contexts, improving the developer experience when working with delegation chains.

Key changes:

1. **Enhanced createDelegation/createOpenDelegation**:
   - Added  parameter to accept PermissionContext directly
   - Made  optional when a parent delegation exists (scope is inherited)
   - Supports both Delegation[] and encoded Hex formats

2. **New redelegatePermissionContext action**:
   - High-level convenience function for the complete redelegation workflow
   - Automatically extracts leaf delegation from permission context
   - Signs the new delegation and returns updated permission context
   - Supports both specific delegate and open delegation (ANY_BENEFICIARY)
   - Can be used standalone or as a client extension via redelegatePermissionContextActions

3. **Improved type safety**:
   - Uses discriminated unions to ensure only one parent source is provided
   - Prevents conflicting parentDelegation and parentPermissionContext parameters

4. **Comprehensive test coverage**:
   - Tests for parentPermissionContext with Delegation arrays and encoded Hex
   - Tests for scope inheritance when no scope is provided
   - Tests for scope override with explicit scope parameter
   - Tests for both standalone and client extension usage patterns

Breaking changes: None. All existing APIs remain unchanged and backward compatible.

Addresses: #196

Co-authored-by: jeffsmale90 <jeffsmale90@users.noreply.github.com>
Co-authored-by: jeffsmale90 <jeffsmale90@users.noreply.github.com>
- Fix import order in redelegatePermissionContext.ts and test files
- Remove forbidden non-null assertion, use explicit check instead
- Apply auto-formatting fixes

Co-authored-by: jeffsmale90 <jeffsmale90@users.noreply.github.com>
@jeffsmale90 jeffsmale90 force-pushed the cursor/redelegate-permission-context-1c3b branch from f6dbdd9 to 5cad4df Compare May 4, 2026 23:02
- resolveParentDelegation accepts config rather than position arguments
- simplifies hasParentDelegation in trackSmartAccountsKitFunctionCall
- refactors tests to enforce strict hierarchy
- adds a new test to ensure an empty permission context is rejected
@jeffsmale90 jeffsmale90 force-pushed the cursor/redelegate-permission-context-1c3b branch from 337d8e4 to fba187e Compare May 6, 2026 23:16
@jeffsmale90 jeffsmale90 marked this pull request as ready for review May 6, 2026 23:24
Comment thread packages/smart-accounts-kit/src/actions/redelegatePermissionContext.ts Outdated
Comment thread packages/smart-accounts-kit/src/actions/redelegatePermissionContext.ts Outdated
Comment thread packages/smart-accounts-kit/src/delegation.ts Outdated
Comment thread packages/smart-accounts-kit/src/delegation.ts Outdated
… params:

- parentDelegation may not be ROOT_AUTHORITY
- resolveCaveats now accepts isScopeOptional rather than hasInheritance
@jeffsmale90 jeffsmale90 force-pushed the cursor/redelegate-permission-context-1c3b branch from 6e6c2b7 to 3fa9795 Compare May 11, 2026 02:23
@jeffsmale90 jeffsmale90 requested a review from mj-kiwi May 11, 2026 02:40

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

Reviewed by Cursor Bugbot for commit 6273f37. Configure here.

@jeffsmale90 jeffsmale90 merged commit 1ab7749 into main May 11, 2026
18 checks passed
@jeffsmale90 jeffsmale90 deleted the cursor/redelegate-permission-context-1c3b branch May 11, 2026 07:14
jeffsmale90 added a commit that referenced this pull request May 12, 2026
jeffsmale90 added a commit that referenced this pull request May 12, 2026
* Initialize Release 29.0.0

* Update Release 29.0.0

* Merge redelegation tickets #217 and #229 in changelog

* Include mention of delegation-core bump in smart-accounts-kit changelog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants