Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions embedded-wallets/authentication/id-token.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ Every JWKS-based verification call **must** include both `issuer` and `audience`

**Who is at risk without `audience` validation?**

| User identifier used by dapp | Exploitable without `aud`? | Reason |
|---|---|---|
| `userId` / `email` / `authConnectionId` | **Yes** | These claims are set by verifier configuration, which an attacker controls via their own project |
| `wallets[].public_key` (app-scoped key) | No | App keys are derived per-project; a different project produces different keys |
| `wallets[].address` (on-chain address) | No | Bound to the user's cryptographic keys, cannot be forged |
| User identifier used by dapp | Exploitable without `aud`? | Reason |
| --------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------ |
| `userId` / `email` / `authConnectionId` | **Yes** | These claims are set by verifier configuration, which an attacker controls via their own project |
| `wallets[].public_key` (app-scoped key) | No | App keys are derived per-project; a different project produces different keys |
| `wallets[].address` (onchain address) | No | Bound to the user's cryptographic keys, cannot be forged |

If your backend matches users by `userId`, `email`, or `authConnectionId` — the common pattern for Web2-style user management — always validate `audience`.

Expand Down
4 changes: 2 additions & 2 deletions embedded-wallets/dashboard/project-settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ jwt.verify(
} else {
console.log('Token verified:', decoded)
}
},
}
)
```

Expand Down Expand Up @@ -210,7 +210,7 @@ jwt.verify(
} else {
console.log('Token verified:', decoded)
}
},
}
)
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const grantedPermissions = await walletClient.requestExecutionPermissions([
{
chainId: chain.id,
expiry,
// The requested permissions will granted to the
// The requested permissions will be granted to the
// session account.
to: sessionAccount.address,
permission: {
Expand All @@ -70,6 +70,7 @@ const grantedPermissions = await walletClient.requestExecutionPermissions([

```typescript
import { createWalletClient, custom } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'

export const walletClient = createWalletClient({
Expand Down Expand Up @@ -111,7 +112,7 @@ const grantedPermissions = await walletClient.requestExecutionPermissions([
{
chainId: chain.id,
expiry,
// The requested permissions will granted to the
// The requested permissions will be granted to the
// session account.
to: sessionAccount.address,
permission: {
Expand Down Expand Up @@ -147,4 +148,3 @@ export const walletClient = createWalletClient({

</TabItem>
</Tabs>

Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const grantedPermissions = await walletClient.requestExecutionPermissions([
import { createWalletClient, custom, createPublicClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { toMetaMaskSmartAccount, Implementation } from '@metamask/smart-accounts-kit'
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'
import { erc7715ProviderActions, erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'
import { sepolia as chain } from 'viem/chains'

// USDC address on Ethereum Sepolia.
Expand All @@ -90,13 +90,11 @@ const publicClient = createPublicClient({
const privateKey = '0x...'
const account = privateKeyToAccount(privateKey)

export const sessionAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [account.address, [], [], []],
deploySalt: '0x',
signer: { account },
})
export const sessionAccount = createWalletClient({
account,
chain,
transport: http(),
}).extend(erc7710WalletActions())

export const walletClient = createWalletClient({
transport: custom(window.ethereum),
Expand All @@ -106,30 +104,16 @@ export const walletClient = createWalletClient({
</TabItem>
</Tabs>

## Decode delegations

The granted permissions object includes a `context` property that represents the encoded delegations.

To create a redelegation, you must first decode these delegations to access the
underlying delegations. To decode the delegations, use the [`decodeDelegations`](../../reference/delegation/index.md#decodedelegations) utility function.

```ts
import { decodeDelegations } from '@metamask/smart-accounts-kit/utils'

const permissionContext = grantedPermissions[0].context

const delegations = decodeDelegations(permissionContext)
const rootDelegation = delegations[0]
```

## Create a redelegation

Create a [redelegation](../../concepts/delegation/overview.md#redelegation) from dapp to a Swap agent.

To create a redelegation, provide the signed delegation as the `parentDelegation` argument when calling [`createDelegation`](../../reference/delegation/index.md#createdelegation).
To create a redelegation, provide the granted permission context as the `permissionContext` argument when calling [`redelegatePermissionContext`](../../reference/erc7710/wallet-client.md#redelegatepermissioncontext).
In the previous step, `sessionAccount` was extended with `erc7710WalletActions`.

This example uses the [`erc20TransferAmount`](../delegation/use-delegation-scopes/spending-limit.md#erc-20-transfer-scope) <GlossaryTerm term="Delegation scope">scope</GlossaryTerm>, allowing
dapp to delegate to a Swap agent the ability to spend 5 USDC on user's behalf.
When you create a redelegation, apply the toolkit's [caveats](../../reference/delegation/caveats.md)
to narrow the Swap agent's authority. In this example, we'll use [`erc20TransferAmount`](../../reference/delegation/caveats.md#erc20transferamount)
enforcer, allowing your dapp to delegate the Swap agent only the ability to spend 5 USDC on the user's behalf.

:::note
When creating a redelegation, you can only narrow the scope of the original authority, not expand it.
Expand All @@ -139,25 +123,35 @@ When creating a redelegation, you can only narrow the scope of the original auth
<TabItem value="redelegation.ts">

```typescript
import { sessionAccount, agentSmartAccount, tokenAddress } from './config.ts'
import { createDelegation, ScopeType } from '@metamask/smart-accounts-kit'
import { sessionAccount, agentAccount, tokenAddress } from './config.ts'
import {
createDelegation,
ScopeType,
getSmartAccountsEnvironment,
Caveats,
CaveatType,
} from '@metamask/smart-accounts-kit'
import { parseUnits } from 'viem'
import { sepolia as chain } from 'viem/chains'

const redelegation = createDelegation({
scope: {
type: ScopeType.Erc20TransferAmount,
const caveats: Caveats = [
{
type: CaveatType.Erc20TransferAmount,
tokenAddress,
// USDC has 6 decimal places.
maxAmount: parseUnits('5', 6),
},
to: agentSmartAccount.address,
from: sessionAccount.address,
// Signed root delegation extracted from Advanced Permissions.
parentDelegation: rootDelegation,
environment: sessionAccount.environment,
})
]

const signedRedelegation = await sessionAccount.signDelegation({ delegation: redelegation })
const environment = getSmartAccountsEnvironment(chain.id)

const { permissionContext: signedPermissionContext } =
await sessionAccount.redelegatePermissionContext({
to: agentAccount.address,
environment,
permissionContext: grantedPermissions[0].context,
caveats,
})
```

</TabItem>
Expand All @@ -166,46 +160,9 @@ const signedRedelegation = await sessionAccount.signDelegation({ delegation: red
```typescript
// Update the existing config to create a smart account for a Swap agent.

const agentAccount = privateKeyToAccount('0x...')

export const agentSmartAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [agentAccount.address, [], [], []],
deploySalt: '0x',
signer: { account: agentAccount },
})
const agentPrivateKey = '0x...'
export const agentAccount = privateKeyToAccount(agentPrivateKey)
```

</TabItem>
</Tabs>

### Limit redelegation using caveats

When you create a redelegation, apply the toolkit's [caveats](../../reference/delegation/caveats.md) to narrow the Swap agent's authority. For example, you can limit the authority so Swap agent can use the delegation only once.

To apply caveats, create the `Delegation` object and use [`createCaveatBuilder`](../../reference/delegation/index.md#createcaveatbuilder).
Use [`hashDelegation`](../../reference/delegation/index.md#hashdelegation) to get the delegation hash, then provide it as the `authority` field.

This example uses the [`limitedCalls`](../../reference/delegation/caveats.md#limitedcalls) caveat with a limit of `1`.

```ts
// Use the config from previous step.
import { sessionAccount, agentSmartAccount, tokenAddress } from './config.ts'
import { CaveatType } from '@metamask/smart-accounts-kit'
import { createCaveatBuilder, hashDelegation } from '@metamask/smart-accounts-kit/utils'

const caveatBuilder = createCaveatBuilder(sessionAccount.environment)

const caveats = caveatBuilder.addCaveat(CaveatType.LimitedCalls, { limit: 1 })

const redelegation: Delegation = {
delegate: sessionAccount.address,
delegator: agentSmartAccount.address,
authority: hashDelegation(rootDelegation),
caveats: caveats.build(),
salt: '0x',
}

const signedRedelegation = await sessionAccount.signDelegation({ delegation: redelegation })
```
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const grantedPermissions = await walletClient.requestExecutionPermissions([

```typescript
import { createWalletClient, custom } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'

export const walletClient = createWalletClient({
Expand Down Expand Up @@ -111,7 +112,7 @@ const grantedPermissions = await walletClient.requestExecutionPermissions([
{
chainId: chain.id,
expiry,
// The requested permissions will granted to the
// The requested permissions will be granted to the
// session account.
to: sessionAccount.address,
permission: {
Expand Down Expand Up @@ -147,4 +148,3 @@ export const walletClient = createWalletClient({

</TabItem>
</Tabs>

139 changes: 138 additions & 1 deletion smart-accounts-kit/reference/erc7710/wallet-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ description: Wallet Client actions reference.
sidebar_label: Wallet Client actions
toc_max_heading_level: 2
keywords:
[ERC-7710, Viem, wallet client, actions, reference, advanced permissions, redeem delegation]
[
ERC-7710,
Viem,
wallet client,
actions,
reference,
advanced permissions,
redeem delegation,
redelegate permission context,
]
---

import Tabs from "@theme/Tabs";
Expand Down Expand Up @@ -82,3 +91,131 @@ const walletClient = createWalletClient({

</TabItem>
</Tabs>

## `redelegatePermissionContext`

Creates a <GlossaryTerm term="Redelegation">redelegation</GlossaryTerm> to a specific <GlossaryTerm term="Delegate account">delegate</GlossaryTerm> from a delegation chain encoded as `Hex` or decoded as [`Delegation`](../types.md#delegation)`[]`.

The action returns [`RedelegatePermissionContextReturnType`](../types.md#redelegatepermissioncontextreturntype).

### Parameters

| Name | Type | Required | Description |
| ------------------- | ------------------------------------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `environment` | [`SmartAccountsEnvironment`](../types.md#smartaccountsenvironment) | Yes | Contract addresses for the <GlossaryTerm term="Delegation Framework" /> on the target chain. |
| `permissionContext` | `PermissionContext` | Yes | Encoded delegation chain (`Hex`) or decoded chain ([`Delegation`](../types.md#delegation)`[]`), leaf first. |
| `chainId` | `number` | No | Chain ID used when signing the delegation. |
| `account` | `Account` \| `Address` | No | Account that signs the redelegation. The default is the Wallet Client's configured account. |
| `scope` | `ScopeConfig` | No | <GlossaryTerm term="Delegation scope">Delegation scope</GlossaryTerm> to restrict the authority of the redelegation. |
| `caveats` | `Caveats` | No | Additional <GlossaryTerm term="Caveat">caveats</GlossaryTerm> to restrict the authority of the redelegation. See [caveats reference](../delegation/caveats.md). |
| `salt` | `Hex` | No | Salt for redelegation. |
| `to` | `Address` | Yes | Address of the delegate for the redelegation. |

### Example

<Tabs>
<TabItem value ="example.ts">

```ts
import { walletClient, publicClient, environment } from './client.ts'

// These properties must be extracted from the permission response. See
// `grantPermissions` action to learn how to request permissions.
const permissionContext = permissionsResponse[0].context

const { permissionContext: redelegatedPermissionContext } =
walletClient.redelegatePermissionContext({
to: 'DELEGATE_ADDRESS',
environment,
permissionContext: permissionContext,
})
```

</TabItem>
<TabItem value ="client.ts">

```ts
import { http, createWalletClient } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia as chain } from 'viem/chains'
import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit'
import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'

// Your session account for requesting and redelegating should be the same.
const privateKey = '0x...'
const account = privateKeyToAccount(privateKey)

export const environment = getSmartAccountsEnvironment(chain.id)

const walletClient = createWalletClient({
account,
transport: http(),
chain,
}).extend(erc7710WalletActions())
```

</TabItem>
</Tabs>

## `redelegatePermissionContextOpen`

Creates an <GlossaryTerm term="Open redelegation">open redelegation</GlossaryTerm> from a delegation chain encoded as `Hex` or decoded as [`Delegation`](../types.md#delegation)`[]`. This allows any account to redeem the inherited permissions.

The action returns [`RedelegatePermissionContextReturnType`](../types.md#redelegatepermissioncontextreturntype).

### Parameters

| Name | Type | Required | Description |
| ------------------- | ------------------------------------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `environment` | [`SmartAccountsEnvironment`](../types.md#smartaccountsenvironment) | Yes | Contract addresses for the <GlossaryTerm term="Delegation Framework" /> on the target chain. |
| `permissionContext` | `PermissionContext` | Yes | Encoded delegation chain (`Hex`) or decoded chain ([`Delegation`](../types.md#delegation)`[]`), leaf first. |
| `chainId` | `number` | No | Chain ID used when signing the delegation. |
| `account` | `Account` \| `Address` | No | Account that signs the redelegation. The default is the Wallet Client's configured account. |
| `scope` | `ScopeConfig` | No | <GlossaryTerm term="Delegation scope">Delegation scope</GlossaryTerm> to restrict the authority of the redelegation. |
| `caveats` | `Caveats` | No | Additional <GlossaryTerm term="Caveat">caveats</GlossaryTerm> to restrict the authority of the redelegation. See [caveats reference](../delegation/caveats.md). |
| `salt` | `Hex` | No | Salt for redelegation. |

### Example

<Tabs>
<TabItem value ="example.ts">

```ts
import { walletClient, publicClient, environment } from './client.ts'

// These properties must be extracted from the permission response. See
// `grantPermissions` action to learn how to request permissions.
const permissionContext = permissionsResponse[0].context

const { permissionContext: redelegatedPermissionContext } =
walletClient.redelegatePermissionContextOpen({
environment,
permissionContext: permissionContext,
})
```

</TabItem>
<TabItem value ="client.ts">

```ts
import { http, createWalletClient } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia as chain } from 'viem/chains'
import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit'
import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'

// Your session account for requesting and redelegating should be the same.
const privateKey = '0x...'
const account = privateKeyToAccount(privateKey)

export const environment = getSmartAccountsEnvironment(chain.id)

const walletClient = createWalletClient({
account,
transport: http(),
chain,
}).extend(erc7710WalletActions())
```

</TabItem>
</Tabs>
Loading
Loading