1- import {
2- createRedeemerTerms ,
3- decodeRedeemerTerms ,
4- } from '@metamask/delegation-core' ;
5- import type { Account , Address , Hex } from 'viem' ;
1+ import { parseCaipChainId } from '@metamask/utils' ;
2+ import type { Account , Hex } from 'viem' ;
63
74import type { Caveats } from '../caveatBuilder' ;
8- import { resolveCaveats } from '../caveatBuilder' ;
9- import type { ScopeConfig } from '../caveatBuilder/scope' ;
10- import { ScopeType } from '../constants' ;
115import {
126 createOpenDelegation ,
13- decodeDelegations ,
147 encodeDelegations ,
158 prepareSignDelegationTypedData ,
169} from '../delegation' ;
17- import type {
18- Caveat ,
19- Delegation ,
20- PermissionContext ,
21- SmartAccountsEnvironment ,
22- } from '../types' ;
23- import { generateSalt } from '../utils/' ;
10+ import type { PermissionContext , SmartAccountsEnvironment } from '../types' ;
11+ import { resolveDelegationCreationContext } from './x402DelegationProviderUtils' ;
2412
2513/**
2614 * Payment requirement details supplied by an x402 server challenge.
@@ -45,9 +33,9 @@ export type PaymentRequirements = {
4533 * context to use for execution, and the delegator account that signed it.
4634 */
4735export type x402DelegationProviderPaymentPayload = {
48- delegationManager : `0x${ string } ` ;
49- permissionContext : `0x${ string } ` ;
50- delegator : `0x${ string } ` ;
36+ delegationManager : Hex ;
37+ permissionContext : Hex ;
38+ delegator : Hex ;
5139} ;
5240
5341/**
@@ -57,20 +45,14 @@ export type x402DelegationProvider = (
5745 paymentRequirements : PaymentRequirements ,
5846) => Promise < x402DelegationProviderPaymentPayload > ;
5947
60- type Deferred < TResult > = ( requirements : PaymentRequirements ) => TResult ;
61-
62- type MaybeDeferred < TResult > = TResult | Deferred < TResult > ;
63-
64- const resolveMaybeDeferred = async < TResult > (
65- maybeDeferred : MaybeDeferred < TResult > ,
66- requirements : PaymentRequirements ,
67- ) : Promise < TResult > => {
68- if ( typeof maybeDeferred === 'function' ) {
69- return ( maybeDeferred as Deferred < TResult > ) ( requirements ) ;
70- }
71-
72- return maybeDeferred ;
73- } ;
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 ) ;
7456
7557/**
7658 * Configuration used to create a x402DelegationProvider.
@@ -86,170 +68,6 @@ export type x402DelegationProviderConfig = {
8668 parentPermissionContext ?: MaybeDeferred < PermissionContext > ;
8769} ;
8870
89- type DelegationCreationContext = {
90- account : Account ;
91- delegationManager : Address ;
92- existingDelegations : Delegation [ ] ;
93- createDelegationConfig : Parameters < typeof createOpenDelegation > [ 0 ] ;
94- } ;
95-
96- type Resolvex402DelegationCaveatsParams = {
97- environment : SmartAccountsEnvironment ;
98- caveatsConfig : Caveats | undefined ;
99- existingDelegations : Delegation [ ] ;
100- facilitatorAddresses : Hex [ ] | undefined ;
101- } ;
102-
103- const normalizeAddress = ( address : Hex ) : string => address . toLowerCase ( ) ;
104-
105- const isSubset = ( subset : string [ ] , superset : string [ ] ) : boolean =>
106- subset . every ( ( item ) => superset . includes ( item ) ) ;
107-
108- const resolvex402DelegationCaveats = ( {
109- environment,
110- caveatsConfig,
111- existingDelegations,
112- facilitatorAddresses,
113- } : Resolvex402DelegationCaveatsParams ) : Caveat [ ] => {
114- const {
115- caveatEnforcers : { RedeemerEnforcer : redeemerEnforcer } ,
116- } = environment ;
117-
118- if ( ! redeemerEnforcer ) {
119- throw new Error ( 'RedeemerEnforcer not found in environment' ) ;
120- }
121-
122- const redeemerAddressNormalized = normalizeAddress ( redeemerEnforcer ) ;
123-
124- const caveats = resolveCaveats ( {
125- environment,
126- caveats : caveatsConfig ,
127- // Resolve caveats first so we can append a redeemer caveat when needed.
128- // Scope is still attached later during delegation creation.
129- isScopeOptional : true ,
130- } ) ;
131-
132- const redeemerCaveats = [
133- ...caveats ,
134- ...existingDelegations . flatMap ( ( delegation ) => delegation . caveats ) ,
135- ] . filter (
136- ( { enforcer } ) => normalizeAddress ( enforcer ) === redeemerAddressNormalized ,
137- ) ;
138-
139- const hasExistingRedeemerConstraint = redeemerCaveats . length > 0 ;
140-
141- if ( ! facilitatorAddresses || facilitatorAddresses . length === 0 ) {
142- // Without facilitators, a redeemer constraint must already exist.
143- if ( ! hasExistingRedeemerConstraint ) {
144- throw new Error (
145- 'Redeemer must be constrained, either in the specified `caveats`, `parentPermissionContext`, or the `PaymentRequirements` as `extra.facilitatorAddresses`.' ,
146- ) ;
147- }
148-
149- return caveats ;
150- }
151-
152- const facilitatorAddressesLowerCase =
153- facilitatorAddresses . map ( normalizeAddress ) ;
154-
155- // If an existing redeemer caveat is already within facilitator bounds, no new caveat is needed.
156- const hasSufficientlyConstrainedRedeemerCaveat = redeemerCaveats . some (
157- ( caveat ) => {
158- const allowedRedeemerAddresses = decodeRedeemerTerms (
159- caveat . terms ,
160- ) . redeemers . map ( normalizeAddress ) ;
161-
162- return isSubset ( allowedRedeemerAddresses , facilitatorAddressesLowerCase ) ;
163- } ,
164- ) ;
165-
166- if ( hasSufficientlyConstrainedRedeemerCaveat ) {
167- return caveats ;
168- }
169-
170- const redeemerCaveat : Caveat = {
171- enforcer : redeemerEnforcer ,
172- terms : createRedeemerTerms ( { redeemers : facilitatorAddresses } ) ,
173- args : '0x' ,
174- } ;
175-
176- return [ ...caveats , redeemerCaveat ] ;
177- } ;
178-
179- const resolveDelegationCreationContext = async (
180- config : x402DelegationProviderConfig ,
181- requirements : PaymentRequirements ,
182- ) : Promise < DelegationCreationContext > => {
183- const caveatsConfig = await resolveMaybeDeferred (
184- config . caveats ,
185- requirements ,
186- ) ;
187- const parentPermissionContext = await resolveMaybeDeferred (
188- config . parentPermissionContext ,
189- requirements ,
190- ) ;
191-
192- const { account } = config ;
193- const from = config . from ?? account . address ;
194- const salt = config . salt ?? generateSalt ( ) ;
195-
196- const scope = {
197- type : ScopeType . Erc20TransferAmount ,
198- tokenAddress : requirements . asset as Hex ,
199- maxAmount : BigInt ( requirements . amount ) ,
200- } as ScopeConfig ;
201-
202- const facilitatorAddresses = requirements . extra ?. facilitatorAddresses as
203- | Hex [ ]
204- | undefined ;
205-
206- const existingDelegations = parentPermissionContext
207- ? decodeDelegations ( parentPermissionContext )
208- : [ ] ;
209-
210- const { DelegationManager : delegationManager } = config . environment ;
211- const caveats = resolvex402DelegationCaveats ( {
212- environment : config . environment ,
213- caveatsConfig,
214- existingDelegations,
215- facilitatorAddresses,
216- } ) ;
217-
218- let createDelegationConfig : Parameters < typeof createOpenDelegation > [ 0 ] ;
219-
220- if ( parentPermissionContext ) {
221- const parentDelegation = existingDelegations [ 0 ] ;
222-
223- if ( ! parentDelegation ) {
224- throw new Error ( 'Parent permission context is not a valid delegation' ) ;
225- }
226-
227- createDelegationConfig = {
228- environment : config . environment ,
229- from,
230- caveats,
231- salt,
232- scope,
233- parentDelegation,
234- } ;
235- } else {
236- createDelegationConfig = {
237- environment : config . environment ,
238- from,
239- caveats,
240- salt,
241- scope,
242- } ;
243- }
244-
245- return {
246- account,
247- delegationManager,
248- existingDelegations,
249- createDelegationConfig,
250- } ;
251- } ;
252-
25371/**
25472 * Creates a delegation provider function for x402 payment requirements.
25573 *
@@ -275,8 +93,15 @@ export function createx402DelegationProvider(
27593
27694 const delegation = createOpenDelegation ( createDelegationConfig ) ;
27795
278- // todo: extract chainId from the network parameter
279- const chainId = requirements . network as unknown as number ;
96+ const { namespace, reference } = parseCaipChainId (
97+ requirements . network as `${string } :${string } `,
98+ ) ;
99+
100+ if ( namespace !== 'eip155' ) {
101+ throw new Error ( 'Unsupported chain namespace' ) ;
102+ }
103+
104+ const chainId = parseInt ( reference , 10 ) ;
280105
281106 const typedData = prepareSignDelegationTypedData ( {
282107 delegationManager,
0 commit comments