Skip to content

Commit e9c8f8c

Browse files
committed
When createx402DelegationProvider, resolve the redeemers as the intersection of specified redeemer addresses, and facilitatorAddresses
- if only one of the two is specified, that value is used
1 parent 9d43cae commit e9c8f8c

2 files changed

Lines changed: 60 additions & 20 deletions

File tree

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

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type EnsureRedeemerSufficientlyConstrainedParams = {
3434
redeemerEnforcer: Hex;
3535
caveats: Caveat[];
3636
existingDelegations: Delegation[];
37-
redeemerAddresses: Hex[];
37+
redeemerAddresses: Address[] | undefined;
3838
requireRedeemers: boolean;
3939
};
4040

@@ -58,6 +58,14 @@ type EnsureExpirySufficientlyConstrainedParams = {
5858
expirySeconds: number;
5959
};
6060

61+
/**
62+
* Inputs for resolving allowed redeemer addresses.
63+
*/
64+
type ResolveRedeemerAddressesParams = {
65+
facilitatorAddresses?: Address[];
66+
redeemerAddresses?: Address[];
67+
};
68+
6169
/**
6270
* Resolved context required to build and sign an x402 delegation.
6371
*/
@@ -236,6 +244,42 @@ export const ensureExpirySufficientlyConstrained = ({
236244
return [...caveats, timestampCaveat];
237245
};
238246

247+
/**
248+
* Resolves the allowed redeemer addresses from the union of facilitator specified and caller specified redeemer addresses.
249+
* If either are undefined, the other is returned.
250+
*
251+
* @param options - Redeemer address inputs.
252+
* @param options.facilitatorAddresses - Optional facilitator addresses from the PaymentRequirements.
253+
* @param options.redeemerAddresses - Optional redeemer addresses from the RedeemersConfig.
254+
* @returns The allowed redeemer addresses.
255+
*/
256+
const resolveRedeemerAddresses = ({
257+
facilitatorAddresses,
258+
redeemerAddresses,
259+
}: ResolveRedeemerAddressesParams): Address[] | undefined => {
260+
if (!facilitatorAddresses) {
261+
if (!redeemerAddresses) {
262+
return undefined;
263+
}
264+
return redeemerAddresses;
265+
}
266+
267+
if (!redeemerAddresses) {
268+
return facilitatorAddresses;
269+
}
270+
271+
const normalizedFacilitatorAddresses =
272+
facilitatorAddresses.map(normalizeAddress);
273+
274+
const normalizedRedeemerAddressSet = new Set(
275+
redeemerAddresses.map(normalizeAddress),
276+
);
277+
278+
return normalizedFacilitatorAddresses.filter((address) =>
279+
normalizedRedeemerAddressSet.has(address),
280+
);
281+
};
282+
239283
/**
240284
* Ensures caveats include a sufficiently strict redeemer constraint.
241285
*
@@ -258,9 +302,9 @@ export const ensureRedeemerSufficientlyConstrained = ({
258302
redeemerAddresses,
259303
requireRedeemers,
260304
}: EnsureRedeemerSufficientlyConstrainedParams): Caveat[] => {
261-
const redeemerAddressNormalized = normalizeAddress(redeemerEnforcer);
305+
const redeemerEnforcerNormalized = normalizeAddress(redeemerEnforcer);
262306

263-
if (redeemerAddresses.length === 0) {
307+
if (!redeemerAddresses || redeemerAddresses.length === 0) {
264308
if (!requireRedeemers) {
265309
return caveats;
266310
}
@@ -269,7 +313,7 @@ export const ensureRedeemerSufficientlyConstrained = ({
269313
caveats,
270314
existingDelegations,
271315
({ enforcer }) =>
272-
normalizeAddress(enforcer) === redeemerAddressNormalized,
316+
normalizeAddress(enforcer) === redeemerEnforcerNormalized,
273317
);
274318

275319
if (!hasExistingRedeemerCaveat) {
@@ -288,7 +332,7 @@ export const ensureRedeemerSufficientlyConstrained = ({
288332
caveats,
289333
existingDelegations,
290334
(caveat) => {
291-
if (normalizeAddress(caveat.enforcer) !== redeemerAddressNormalized) {
335+
if (normalizeAddress(caveat.enforcer) !== redeemerEnforcerNormalized) {
292336
return false;
293337
}
294338

@@ -420,17 +464,16 @@ export const resolvex402DelegationCaveats = ({
420464
isScopeOptional: true,
421465
});
422466

423-
const allRedeemerAddresses = new Set(
424-
[...(facilitatorAddresses ?? []), ...(redeemerAddresses ?? [])].map(
425-
normalizeAddress,
426-
),
427-
);
467+
const resolvedRedeemerAddresses = resolveRedeemerAddresses({
468+
facilitatorAddresses,
469+
redeemerAddresses,
470+
});
428471

429472
const caveatsWithRedeemer = ensureRedeemerSufficientlyConstrained({
430473
redeemerEnforcer,
431474
caveats: initialCaveats,
432475
existingDelegations,
433-
redeemerAddresses: Array.from(allRedeemerAddresses),
476+
redeemerAddresses: resolvedRedeemerAddresses,
434477
requireRedeemers,
435478
});
436479

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -448,15 +448,15 @@ describe('x402DelegationProviderUtils', () => {
448448
).toThrow('Expiry seconds must be a positive number');
449449
});
450450

451-
it('enforces redeemer caveats from the union of facilitator and configured redeemer addresses', () => {
451+
it('enforces redeemer caveats from the intersection of facilitator and configured redeemer addresses', () => {
452452
const result = resolvex402DelegationCaveats({
453453
environment: baseEnvironment,
454454
caveatsConfig: [],
455455
existingDelegations: [],
456-
facilitatorAddresses: [facilitatorA],
456+
facilitatorAddresses: [facilitatorA, facilitatorB],
457457
payee,
458458
requireRedeemers: true,
459-
redeemerAddresses: [facilitatorB],
459+
redeemerAddresses: [facilitatorB, facilitatorC],
460460
});
461461

462462
const redeemerCaveat = result.find(
@@ -467,12 +467,9 @@ describe('x402DelegationProviderUtils', () => {
467467
throw new Error('Expected redeemer caveat to be present');
468468
}
469469

470-
expect(decodeRedeemerTerms(redeemerCaveat.terms).redeemers).toEqual(
471-
expect.arrayContaining([facilitatorA, facilitatorB]),
472-
);
473-
expect(decodeRedeemerTerms(redeemerCaveat.terms).redeemers).toHaveLength(
474-
2,
475-
);
470+
expect(decodeRedeemerTerms(redeemerCaveat.terms).redeemers).toEqual([
471+
facilitatorB,
472+
]);
476473
});
477474
});
478475

0 commit comments

Comments
 (0)