Skip to content

Commit 327672d

Browse files
authored
Add createx402DelegationProvider to @metamask/smart-accounts-kit/experimental (#248)
* Adds createDelegationProvider utility function * Add generateSalt to @metamask/smart-accounts-kit/utils * Rename DelegationProvider to x402DelegationProvider * Lint fixing * Improves facilitatorAddress / redeemer caveat handling: - if no facilitatorAddresses are specified, at least one redeemer caveat must be either in the parentPermissionContext or specified caveats - if facilitatorAddresses are specified, only add a redeemer caveat if the redeemers are not already constrained to the facilitator addresses * Minor refactor and tidy up * Add payee caveat - only if there isn't an existing caveat constraining the payee - also minor refactor to put various x402 utilities into x402DelegationProviderUtils.ts * Make MaybeDeferred async - also refactor types into x402DelegationProviderTypes.ts to simplify internal type dependencies * Make more configuration parameters MaybeDeferred - also add parseEip155ChainId function * Add expirySeconds parameter, build expiry caveat if required * Make environment parameter optional resolve using getMetaMaskSmartAccountEnvironment if not specified * MaybeDeferred configuration parameters may be syncronous or asyncronous * Remove unused import from @metamask/x402 * Add changelog * Readability pass * Add redeemer config, allowing caller to specify whether redeemer constraint is required, and which addresses should be constrained * Minor fixes from review: - pad payee address to 32 bytes for allowedCalldataTerms - require expiry to be positive number - reject invalid chainId in network string
1 parent feb2f4b commit 327672d

11 files changed

Lines changed: 1890 additions & 0 deletions

File tree

packages/smart-accounts-kit/CHANGELOG.md

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

1010
### Added
1111

12+
- Experimental `createx402DelegationProvider` added to @metamask/smart-accounts-kit/experimental ([#248](https://github.com/MetaMask/smart-accounts-kit/pull/248))
13+
- New `generateSalt` function added to @metamask/smart-accounts-kit/utils ([#248](https://github.com/MetaMask/smart-accounts-kit/pull/248))
1214
- ERC-7715 `token-approval-revocation` permission type ([#226](https://github.com/MetaMask/smart-accounts-kit/pull/226), [#237](https://github.com/MetaMask/smart-accounts-kit/pull/237))
1315
- `CaveatBuilder` for `ApprovalRevocationEnforcer`, deployment address added to `SmartAccountsEnvironment` ([#226](https://github.com/metamask/smart-accounts-kit/pull/226), [#237](https://github.com/MetaMask/smart-accounts-kit/pull/237))
1416
- Helper for decoding revert reasons from delegated execution errors while preserving the original error output ([#245](https://github.com/MetaMask/smart-accounts-kit/pull/245))

packages/smart-accounts-kit/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
"@metamask/delegation-abis": "^1.1.0",
129129
"@metamask/delegation-core": "^2.2.1",
130130
"@metamask/delegation-deployments": "^1.4.0",
131+
"@metamask/utils": "^11.4.0",
131132
"openapi-fetch": "^0.13.5",
132133
"ox": "0.8.1"
133134
},

packages/smart-accounts-kit/src/experimental/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@ export {
55
type Environment,
66
type DelegationStorageConfig,
77
} from './delegationStorage';
8+
export {
9+
createx402DelegationProvider,
10+
parseEip155ChainId,
11+
type x402DelegationProvider,
12+
type x402DelegationProviderConfig,
13+
type x402DelegationProviderPaymentPayload,
14+
type PaymentRequirements,
15+
type MaybeDeferred,
16+
} from './x402DelegationProvider';
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
createOpenDelegation,
3+
encodeDelegations,
4+
prepareSignDelegationTypedData,
5+
} from '../delegation';
6+
import type {
7+
PaymentRequirements,
8+
x402DelegationProvider,
9+
x402DelegationProviderConfig,
10+
x402DelegationProviderPaymentPayload,
11+
} from './x402DelegationProviderTypes';
12+
import {
13+
parseEip155ChainId,
14+
resolveDelegationCreationContext,
15+
} from './x402DelegationProviderUtils';
16+
17+
export type {
18+
MaybeDeferred,
19+
PaymentRequirements,
20+
x402DelegationProvider,
21+
x402DelegationProviderConfig,
22+
x402DelegationProviderPaymentPayload,
23+
} from './x402DelegationProviderTypes';
24+
25+
export { parseEip155ChainId } from './x402DelegationProviderUtils';
26+
27+
/**
28+
* Creates a delegation provider function for x402 payment requirements.
29+
*
30+
* The returned provider resolves deferred config values using incoming payment
31+
* requirements, creates an open delegation, signs it, and returns an encoded
32+
* permission context payload.
33+
*
34+
* @param config - Delegation creation and signing configuration.
35+
* @returns A provider that maps payment requirements to a signed delegation payload.
36+
*/
37+
export function createx402DelegationProvider(
38+
config: x402DelegationProviderConfig,
39+
): x402DelegationProvider {
40+
return async (
41+
requirements: PaymentRequirements,
42+
): Promise<x402DelegationProviderPaymentPayload> => {
43+
const {
44+
account,
45+
delegationManager,
46+
existingDelegations,
47+
createDelegationConfig,
48+
} = await resolveDelegationCreationContext(config, requirements);
49+
50+
const delegation = createOpenDelegation(createDelegationConfig);
51+
52+
const chainId = parseEip155ChainId(requirements.network);
53+
54+
const typedData = prepareSignDelegationTypedData({
55+
delegationManager,
56+
chainId,
57+
delegation,
58+
});
59+
60+
if (!account.signTypedData) {
61+
throw new Error('Account does not support signTypedData');
62+
}
63+
64+
const signature = await account.signTypedData(typedData);
65+
66+
const signedDelegation = {
67+
...delegation,
68+
signature,
69+
};
70+
71+
const permissionContext = encodeDelegations([
72+
signedDelegation,
73+
...existingDelegations,
74+
]);
75+
76+
return {
77+
delegationManager,
78+
permissionContext,
79+
delegator: delegation.delegator,
80+
};
81+
};
82+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { Account, Address, Hex } from 'viem';
2+
3+
import type { Caveats } from '../caveatBuilder';
4+
import type { PermissionContext, SmartAccountsEnvironment } from '../types';
5+
6+
/**
7+
* Payment requirement details supplied by an x402 server challenge.
8+
*
9+
* These values are used to scope and construct the delegation that will be
10+
* returned by a {@link x402DelegationProvider}.
11+
*/
12+
export type PaymentRequirements = {
13+
scheme: string;
14+
network: string;
15+
asset: string;
16+
amount: string;
17+
payTo: string;
18+
maxTimeoutSeconds: number;
19+
extra?: Record<string, unknown>;
20+
};
21+
22+
/**
23+
* Encoded delegation response consumed by x402 payment flows.
24+
*
25+
* The payload includes the delegation manager address, the encoded permission
26+
* context to use for execution, and the delegator account that signed it.
27+
*/
28+
export type x402DelegationProviderPaymentPayload = {
29+
delegationManager: Hex;
30+
permissionContext: Hex;
31+
delegator: Hex;
32+
};
33+
34+
/**
35+
* Value that can be provided eagerly or derived lazily from runtime requirements.
36+
*
37+
* @template TResult - Resolved value type.
38+
*/
39+
export type MaybeDeferred<TResult> =
40+
| TResult
41+
| ((requirements: PaymentRequirements) => Promise<TResult> | TResult);
42+
43+
/**
44+
* Function that turns payment requirements into a signed delegation payload.
45+
*/
46+
export type x402DelegationProvider = (
47+
paymentRequirements: PaymentRequirements,
48+
) => Promise<x402DelegationProviderPaymentPayload>;
49+
50+
/**
51+
* Configuration for redeemer constraint enforcement.
52+
*/
53+
export type RedeemersConfig = {
54+
requireRedeemers: boolean;
55+
addresses?: MaybeDeferred<Address[]>;
56+
};
57+
58+
/**
59+
* Configuration used to create a x402DelegationProvider.
60+
*
61+
* `account` is required and is used for signing the delegation.
62+
*/
63+
export type x402DelegationProviderConfig = {
64+
account: MaybeDeferred<Account>;
65+
environment?: MaybeDeferred<SmartAccountsEnvironment>;
66+
from?: MaybeDeferred<Hex>;
67+
salt?: MaybeDeferred<Hex>;
68+
caveats?: MaybeDeferred<Caveats>;
69+
parentPermissionContext?: MaybeDeferred<PermissionContext>;
70+
expirySeconds?: MaybeDeferred<number>;
71+
redeemers?: MaybeDeferred<RedeemersConfig>;
72+
};

0 commit comments

Comments
 (0)