Skip to content

Commit 22f61b9

Browse files
committed
Make MaybeDeferred async
- also refactor types into x402DelegationProviderTypes.ts to simplify internal type dependencies
1 parent 27efa73 commit 22f61b9

5 files changed

Lines changed: 240 additions & 227 deletions

File tree

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

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,25 @@
11
import { parseCaipChainId } from '@metamask/utils';
2-
import type { Account, Hex } from 'viem';
32

4-
import type { Caveats } from '../caveatBuilder';
53
import {
64
createOpenDelegation,
75
encodeDelegations,
86
prepareSignDelegationTypedData,
97
} from '../delegation';
10-
import type { PermissionContext, SmartAccountsEnvironment } from '../types';
8+
import type {
9+
PaymentRequirements,
10+
x402DelegationProvider,
11+
x402DelegationProviderConfig,
12+
x402DelegationProviderPaymentPayload,
13+
} from './x402DelegationProviderTypes';
1114
import { resolveDelegationCreationContext } from './x402DelegationProviderUtils';
1215

13-
/**
14-
* Payment requirement details supplied by an x402 server challenge.
15-
*
16-
* These values are used to scope and construct the delegation that will be
17-
* returned by a {@link x402DelegationProvider}.
18-
*/
19-
export type PaymentRequirements = {
20-
scheme: string;
21-
network: string;
22-
asset: string;
23-
amount: string;
24-
payTo: string;
25-
maxTimeoutSeconds: number;
26-
extra?: Record<string, unknown>;
27-
};
28-
29-
/**
30-
* Encoded delegation response consumed by x402 payment flows.
31-
*
32-
* The payload includes the delegation manager address, the encoded permission
33-
* context to use for execution, and the delegator account that signed it.
34-
*/
35-
export type x402DelegationProviderPaymentPayload = {
36-
delegationManager: Hex;
37-
permissionContext: Hex;
38-
delegator: Hex;
39-
};
40-
41-
/**
42-
* Function that turns payment requirements into a signed delegation payload.
43-
*/
44-
export type x402DelegationProvider = (
45-
paymentRequirements: PaymentRequirements,
46-
) => Promise<x402DelegationProviderPaymentPayload>;
47-
48-
/**
49-
* Value that can be provided eagerly or derived lazily from payment requirements.
50-
*
51-
* @template TResult - Resolved value type.
52-
*/
53-
export type MaybeDeferred<TResult> =
54-
| TResult
55-
| ((requirements: PaymentRequirements) => TResult);
56-
57-
/**
58-
* Configuration used to create a x402DelegationProvider.
59-
*
60-
* `account` is required and is used for signing the delegation.
61-
*/
62-
export type x402DelegationProviderConfig = {
63-
account: Account;
64-
environment: SmartAccountsEnvironment;
65-
from?: Hex;
66-
salt?: Hex;
67-
caveats?: MaybeDeferred<Caveats>;
68-
parentPermissionContext?: MaybeDeferred<PermissionContext>;
69-
};
16+
export type {
17+
MaybeDeferred,
18+
PaymentRequirements,
19+
x402DelegationProvider,
20+
x402DelegationProviderConfig,
21+
x402DelegationProviderPaymentPayload,
22+
} from './x402DelegationProviderTypes';
7023

7124
/**
7225
* Creates a delegation provider function for x402 payment requirements.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { Account, 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>);
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 used to create a x402DelegationProvider.
52+
*
53+
* `account` is required and is used for signing the delegation.
54+
*/
55+
export type x402DelegationProviderConfig = {
56+
account: Account;
57+
environment: SmartAccountsEnvironment;
58+
from?: Hex;
59+
salt?: Hex;
60+
caveats?: MaybeDeferred<Caveats>;
61+
parentPermissionContext?: MaybeDeferred<PermissionContext>;
62+
};

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

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
createRedeemerTerms,
44
decodeRedeemerTerms,
55
} from '@metamask/delegation-core';
6-
import type { Account, Address, Hex } from 'viem';
6+
import type { Address, Hex } from 'viem';
77

88
import type { Caveats } from '../caveatBuilder';
99
import { resolveCaveats } from '../caveatBuilder';
@@ -16,62 +16,46 @@ import type {
1616
PermissionContext,
1717
SmartAccountsEnvironment,
1818
} from '../types';
19+
import type {
20+
MaybeDeferred,
21+
PaymentRequirements,
22+
x402DelegationProviderConfig,
23+
} from './x402DelegationProviderTypes';
1924
import { generateSalt } from '../utils/';
2025

26+
/**
27+
* Inputs for redeemer constraint enforcement.
28+
*/
2129
type EnsureRedeemerSufficientlyConstrainedParams = {
2230
redeemerEnforcer: Hex;
2331
caveats: Caveat[];
2432
existingDelegations: Delegation[];
2533
facilitatorAddresses: Hex[] | undefined;
2634
};
2735

36+
/**
37+
* Inputs for payee constraint enforcement.
38+
*/
2839
type EnsurePayeeSufficientlyConstrainedParams = {
2940
allowedCalldataEnforcer: Hex;
3041
caveats: Caveat[];
3142
existingDelegations: Delegation[];
3243
payee: Hex;
3344
};
3445

35-
type Deferred<TResult, TRequirements> = (
36-
requirements: TRequirements,
37-
) => TResult;
38-
39-
type MaybeDeferred<TResult, TRequirements> =
40-
| TResult
41-
| Deferred<TResult, TRequirements>;
42-
43-
type ResolveDelegationCreationContextRequirements = {
44-
scheme: string;
45-
network: string;
46-
asset: string;
47-
amount: string;
48-
payTo: string;
49-
maxTimeoutSeconds: number;
50-
extra?: Record<string, unknown>;
51-
};
52-
53-
type ResolveDelegationCreationContextConfig = {
54-
account: Account;
55-
environment: SmartAccountsEnvironment;
56-
from?: Hex;
57-
salt?: Hex;
58-
caveats?: MaybeDeferred<
59-
Caveats | undefined,
60-
ResolveDelegationCreationContextRequirements
61-
>;
62-
parentPermissionContext?: MaybeDeferred<
63-
PermissionContext | undefined,
64-
ResolveDelegationCreationContextRequirements
65-
>;
66-
};
67-
46+
/**
47+
* Resolved context required to build and sign an x402 delegation.
48+
*/
6849
export type DelegationCreationContext = {
69-
account: Account;
50+
account: x402DelegationProviderConfig['account'];
7051
delegationManager: Address;
7152
existingDelegations: Delegation[];
7253
createDelegationConfig: Parameters<typeof createOpenDelegation>[0];
7354
};
7455

56+
/**
57+
* Inputs for resolving delegation caveats with x402-specific constraints.
58+
*/
7559
export type Resolvex402DelegationCaveatsParams = {
7660
environment: SmartAccountsEnvironment;
7761
caveatsConfig: Caveats | undefined;
@@ -80,25 +64,58 @@ export type Resolvex402DelegationCaveatsParams = {
8064
payee: Hex;
8165
};
8266

83-
const resolveMaybeDeferred = async <TResult, TRequirements>(
84-
maybeDeferred: MaybeDeferred<TResult, TRequirements> | undefined,
85-
requirements: TRequirements,
67+
/**
68+
* Resolves eager or deferred values against payment requirements.
69+
*
70+
* @param maybeDeferred - Value or async resolver function.
71+
* @param requirements - Payment requirements passed to deferred resolvers.
72+
* @returns The resolved value, or undefined when no value is provided.
73+
*/
74+
const resolveMaybeDeferred = async <TResult>(
75+
maybeDeferred: MaybeDeferred<TResult> | undefined,
76+
requirements: PaymentRequirements,
8677
): Promise<TResult | undefined> => {
8778
if (typeof maybeDeferred === 'function') {
88-
return (maybeDeferred as Deferred<TResult, TRequirements>)(requirements);
79+
const deferred = maybeDeferred as (
80+
deferredRequirements: PaymentRequirements,
81+
) => Promise<TResult>;
82+
return await deferred(requirements);
8983
}
9084

9185
return maybeDeferred;
9286
};
9387

94-
// ERC-20 transfer(to, value), `to` parameter starts at index 4
88+
/**
89+
* ERC-20 `transfer(address to, uint256 value)` calldata index for `to`.
90+
*/
9591
const TRANSFER_PAYEE_INDEX = 4;
9692

93+
/**
94+
* Normalizes an address-like hex string for case-insensitive comparisons.
95+
*
96+
* @param address - Address value to normalize.
97+
* @returns Lowercased hex string.
98+
*/
9799
const normalizeAddress = (address: Hex): string => address.toLowerCase();
98100

101+
/**
102+
* Checks whether every item in `subset` appears in `superset`.
103+
*
104+
* @param subset - Candidate subset values.
105+
* @param superset - Candidate superset values.
106+
* @returns True when `subset` is fully contained in `superset`.
107+
*/
99108
const isSubset = (subset: string[], superset: string[]): boolean =>
100109
subset.every((item) => superset.includes(item));
101110

111+
/**
112+
* Returns whether any caveat in local or inherited delegations matches a predicate.
113+
*
114+
* @param caveats - Current caveat list.
115+
* @param delegations - Existing delegations whose caveats should also be searched.
116+
* @param match - Predicate used to match caveats.
117+
* @returns True when at least one caveat satisfies `match`.
118+
*/
102119
const hasMatchingCaveats = (
103120
caveats: Caveat[],
104121
delegations: Delegation[],
@@ -298,17 +315,15 @@ export const resolvex402DelegationCaveats = ({
298315
* @returns The resolved context used to create and sign a delegation.
299316
*/
300317
export const resolveDelegationCreationContext = async (
301-
config: ResolveDelegationCreationContextConfig,
302-
requirements: ResolveDelegationCreationContextRequirements,
318+
config: x402DelegationProviderConfig,
319+
requirements: PaymentRequirements,
303320
): Promise<DelegationCreationContext> => {
304-
const caveatsConfig = await resolveMaybeDeferred(
321+
const caveatsConfig: Caveats | undefined = await resolveMaybeDeferred(
305322
config.caveats,
306323
requirements,
307324
);
308-
const parentPermissionContext = await resolveMaybeDeferred(
309-
config.parentPermissionContext,
310-
requirements,
311-
);
325+
const parentPermissionContext: PermissionContext | undefined =
326+
await resolveMaybeDeferred(config.parentPermissionContext, requirements);
312327

313328
const { account } = config;
314329
const from = config.from ?? account.address;

0 commit comments

Comments
 (0)