Skip to content

Commit 675f4f8

Browse files
committed
Minor refactor of redeemer address resolution, and clarity improvements to error messages.
1 parent 4e1c90b commit 675f4f8

2 files changed

Lines changed: 47 additions & 38 deletions

File tree

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

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type EnsureRedeemerSufficientlyConstrainedParams = {
3434
redeemerEnforcer: Hex;
3535
caveats: Caveat[];
3636
existingDelegations: Delegation[];
37+
facilitatorAddresses: Address[] | undefined;
3738
redeemerAddresses: Address[] | undefined;
3839
requireRedeemers: boolean;
3940
};
@@ -245,13 +246,16 @@ export const ensureExpirySufficientlyConstrained = ({
245246
};
246247

247248
/**
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.
249+
* Resolves allowed redeemer addresses from facilitator and configured redeemer inputs.
250+
*
251+
* If both inputs are provided, returns their intersection.
252+
* If only one input is provided, returns that input.
253+
* If neither input is provided, returns undefined.
250254
*
251255
* @param options - Redeemer address inputs.
252256
* @param options.facilitatorAddresses - Optional facilitator addresses from the PaymentRequirements.
253257
* @param options.redeemerAddresses - Optional redeemer addresses from the RedeemersConfig.
254-
* @returns The allowed redeemer addresses, or undefined if none are specified.
258+
* @returns Resolved allowed redeemer addresses, or undefined when no input is provided.
255259
*/
256260
const resolveRedeemerAddresses = ({
257261
facilitatorAddresses,
@@ -288,40 +292,47 @@ const resolveRedeemerAddresses = ({
288292
* Returns the caveat list unchanged when an existing redeemer caveat is already
289293
* strict enough, or appends a new redeemer caveat scoped to facilitator addresses.
290294
*
291-
* @param options0 - Redeemer constraint evaluation inputs.
292-
* @param options0.redeemerEnforcer - Address of the redeemer enforcer caveat contract.
293-
* @param options0.caveats - Currently resolved caveats for the delegation being created.
294-
* @param options0.existingDelegations - Existing parent-chain delegations to inspect for inherited constraints.
295-
* @param options0.redeemerAddresses - Optional addresses to which redemption should be constrained.
296-
* @param options0.requireRedeemers - Whether at least one redeemer constraint must exist when no redeemer addresses are supplied.
295+
* @param config - Redeemer constraint evaluation inputs.
296+
* @param config.redeemerEnforcer - Address of the redeemer enforcer caveat contract.
297+
* @param config.caveats - Currently resolved caveats for the delegation being created.
298+
* @param config.existingDelegations - Existing parent-chain delegations to inspect for inherited constraints.
299+
* @param config.redeemerAddresses - Optional addresses to which redemption should be constrained.
300+
* @param config.requireRedeemers - Whether at least one redeemer constraint must exist.
297301
* @returns The original caveats when sufficiently constrained, otherwise caveats with a redeemer caveat appended.
298-
* @throws If no facilitator addresses are provided and no redeemer constraint exists.
302+
* @throws If either facilitatorAddresses and/or redeemerAddresses are provided, but the intersection is empty.
303+
* @throws If requireRedeemers is true, but no valid redeemer addresses are provided, and no existing redeemer caveat is found in the existingDelegations.
299304
*/
300-
export const ensureRedeemerSufficientlyConstrained = ({
301-
redeemerEnforcer,
302-
caveats,
303-
existingDelegations,
304-
redeemerAddresses,
305-
requireRedeemers,
306-
}: EnsureRedeemerSufficientlyConstrainedParams): Caveat[] => {
307-
const redeemerEnforcerNormalized = normalizeAddress(redeemerEnforcer);
305+
export const ensureRedeemerSufficientlyConstrained = (
306+
config: EnsureRedeemerSufficientlyConstrainedParams,
307+
): Caveat[] => {
308+
// `redeemerAddresses` is the intersection of `facilitatorAddresses` and `redeemerAddresses`.
309+
// If either is undefined, it implies no constraint is specified by that value, and the other is returned.
310+
// If both are undefined, it implies that no constraint is specified overall.
311+
const redeemerAddresses = resolveRedeemerAddresses({
312+
facilitatorAddresses: config.facilitatorAddresses,
313+
redeemerAddresses: config.redeemerAddresses,
314+
});
308315

316+
// If the result is defined, but zero-length, it implies no redeemers are allowed, which is a non-satisfiable constraint, so an error is thrown.
309317
if (redeemerAddresses?.length === 0) {
310318
throw new Error(
311-
'No valid redeemer addresses were resolved. The intersection of `redeemers.addresses` and `extra.facilitatorAddresses` must be non-empty.',
319+
'No valid redeemer addresses were resolved. If both `redeemers.addresses` and `extra.facilitatorAddresses` are provided, they must overlap. If only one is provided, it must include at least one address.',
312320
);
313321
}
314322

323+
const redeemerEnforcer = normalizeAddress(config.redeemerEnforcer);
324+
325+
const { caveats, existingDelegations } = config;
326+
315327
if (!redeemerAddresses) {
316-
if (!requireRedeemers) {
328+
if (!config.requireRedeemers) {
317329
return caveats;
318330
}
319331

320332
const hasExistingRedeemerCaveat = hasMatchingCaveats(
321333
caveats,
322334
existingDelegations,
323-
({ enforcer }) =>
324-
normalizeAddress(enforcer) === redeemerEnforcerNormalized,
335+
({ enforcer }) => normalizeAddress(enforcer) === redeemerEnforcer,
325336
);
326337

327338
if (!hasExistingRedeemerCaveat) {
@@ -333,22 +344,21 @@ export const ensureRedeemerSufficientlyConstrained = ({
333344
return caveats;
334345
}
335346

336-
const redeemerAddressesNormalized = redeemerAddresses.map(normalizeAddress);
337-
const redeemerAddressesSet = new Set(redeemerAddressesNormalized);
347+
const redeemerAddressesSet = new Set(redeemerAddresses.map(normalizeAddress));
338348

339349
const hasSupersedingRedeemerCaveat = hasMatchingCaveats(
340350
caveats,
341351
existingDelegations,
342352
(caveat) => {
343-
if (normalizeAddress(caveat.enforcer) !== redeemerEnforcerNormalized) {
353+
if (normalizeAddress(caveat.enforcer) !== redeemerEnforcer) {
344354
return false;
345355
}
346356

347357
const allowedRedeemerAddresses = decodeRedeemerTerms(
348358
caveat.terms,
349359
).redeemers.map(normalizeAddress);
350360

351-
// If this redeemer caveat only allows facilitator addresses, it is sufficiently constrained.
361+
// If this redeemer caveat only allows redeemer addresses, it is sufficiently constrained.
352362
return allowedRedeemerAddresses.every((item) =>
353363
redeemerAddressesSet.has(item),
354364
);
@@ -472,16 +482,12 @@ export const resolvex402DelegationCaveats = ({
472482
isScopeOptional: true,
473483
});
474484

475-
const resolvedRedeemerAddresses = resolveRedeemerAddresses({
476-
facilitatorAddresses,
477-
redeemerAddresses,
478-
});
479-
480485
const caveatsWithRedeemer = ensureRedeemerSufficientlyConstrained({
481486
redeemerEnforcer,
487+
facilitatorAddresses,
488+
redeemerAddresses,
482489
caveats: initialCaveats,
483490
existingDelegations,
484-
redeemerAddresses: resolvedRedeemerAddresses,
485491
requireRedeemers,
486492
});
487493

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,11 @@ describe('x402DelegationProviderUtils', () => {
8383
redeemerEnforcer,
8484
caveats: [],
8585
existingDelegations: [],
86+
facilitatorAddresses: undefined,
8687
redeemerAddresses: [],
8788
requireRedeemers: false,
8889
}),
89-
).toThrow(
90-
'No valid redeemer addresses were resolved. The intersection of `redeemers.addresses` and `extra.facilitatorAddresses` must be non-empty.',
91-
);
90+
).toThrow('No valid redeemer addresses were resolved.');
9291
});
9392

9493
it('throws when redeemer addresses are empty and redeemers are required', () => {
@@ -97,12 +96,11 @@ describe('x402DelegationProviderUtils', () => {
9796
redeemerEnforcer,
9897
caveats: [],
9998
existingDelegations: [],
99+
facilitatorAddresses: undefined,
100100
redeemerAddresses: [],
101101
requireRedeemers: true,
102102
}),
103-
).toThrow(
104-
'No valid redeemer addresses were resolved. The intersection of `redeemers.addresses` and `extra.facilitatorAddresses` must be non-empty.',
105-
);
103+
).toThrow('No valid redeemer addresses were resolved.');
106104
});
107105

108106
it('returns caveats unchanged when redeemer addresses are missing and redeemers are optional', () => {
@@ -112,6 +110,7 @@ describe('x402DelegationProviderUtils', () => {
112110
redeemerEnforcer,
113111
caveats: initialCaveats,
114112
existingDelegations: [],
113+
facilitatorAddresses: undefined,
115114
redeemerAddresses: undefined,
116115
requireRedeemers: false,
117116
});
@@ -125,6 +124,7 @@ describe('x402DelegationProviderUtils', () => {
125124
redeemerEnforcer,
126125
caveats: [],
127126
existingDelegations: [],
127+
facilitatorAddresses: undefined,
128128
redeemerAddresses: undefined,
129129
requireRedeemers: true,
130130
}),
@@ -152,6 +152,7 @@ describe('x402DelegationProviderUtils', () => {
152152
},
153153
]),
154154
],
155+
facilitatorAddresses: undefined,
155156
redeemerAddresses: undefined,
156157
requireRedeemers: true,
157158
});
@@ -172,6 +173,7 @@ describe('x402DelegationProviderUtils', () => {
172173
redeemerEnforcer,
173174
caveats: initialCaveats,
174175
existingDelegations: [],
176+
facilitatorAddresses: undefined,
175177
redeemerAddresses: [facilitatorA, facilitatorB],
176178
requireRedeemers: true,
177179
});
@@ -196,6 +198,7 @@ describe('x402DelegationProviderUtils', () => {
196198
},
197199
]),
198200
],
201+
facilitatorAddresses: undefined,
199202
redeemerAddresses: [facilitatorA, facilitatorB],
200203
requireRedeemers: true,
201204
});

0 commit comments

Comments
 (0)