From 42751914185694a45283f6d58fc00f43122683e8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 22:39:06 +0000 Subject: [PATCH 01/19] feat: add delegation-based caveat enforcer actions - Add new action functions that accept entire delegation instead of delegation hash and args - Add findMatchingCaveat utility function to extract caveat data from delegation - Add NoMatchingCaveatError and MultipleMatchingCaveatsError for error handling - Add comprehensive test coverage for new functionality - Maintain backward compatibility with existing action functions --- .../src/actions/getCaveatAvailableAmount.ts | 309 +++++++++++++- .../delegation-toolkit/src/actions/index.ts | 15 + .../actions/getCaveatAvailableAmount.test.ts | 395 ++++++++++++++++++ 3 files changed, 718 insertions(+), 1 deletion(-) create mode 100644 packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index fb5da84b..33544fe9 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -5,7 +5,8 @@ import * as ERC20StreamingEnforcer from '../DelegationFramework/ERC20StreamingEn import * as MultiTokenPeriodEnforcer from '../DelegationFramework/MultiTokenPeriodEnforcer'; import * as NativeTokenPeriodTransferEnforcer from '../DelegationFramework/NativeTokenPeriodTransferEnforcer'; import * as NativeTokenStreamingEnforcer from '../DelegationFramework/NativeTokenStreamingEnforcer'; -import type { DeleGatorEnvironment } from '../types'; +import { hashDelegation } from '@metamask/delegation-core'; +import type { DeleGatorEnvironment, Delegation } from '../types'; /** * Shared base params for all enforcer actions @@ -44,6 +45,21 @@ export type NativeTokenPeriodTransferParams = BaseCaveatParams; */ export type NativeTokenStreamingParams = BaseCaveatParams; +/** + * New parameter types that accept the entire delegation + */ +export type BaseDelegationParams = { + delegation: Delegation; + delegationManager?: Address; + enforcerAddress?: Address; +}; + +export type ERC20PeriodTransferDelegationParams = BaseDelegationParams; +export type ERC20StreamingDelegationParams = BaseDelegationParams; +export type MultiTokenPeriodDelegationParams = BaseDelegationParams; +export type NativeTokenPeriodTransferDelegationParams = BaseDelegationParams; +export type NativeTokenStreamingDelegationParams = BaseDelegationParams; + /** * Return type for period-based transfer enforcers */ @@ -60,6 +76,56 @@ export type StreamingResult = { availableAmount: bigint; }; +/** + * Error thrown when no matching caveat is found + */ +export class NoMatchingCaveatError extends Error { + constructor(enforcerName: string) { + super(`No caveat found with enforcer matching ${enforcerName}`); + this.name = 'NoMatchingCaveatError'; + } +} + +/** + * Error thrown when multiple matching caveats are found + */ +export class MultipleMatchingCaveatsError extends Error { + constructor(enforcerName: string) { + super(`Multiple caveats found with enforcer matching ${enforcerName}`); + this.name = 'MultipleMatchingCaveatsError'; + } +} + +/** + * Finds a caveat that matches the specified enforcer address + * @param delegation - The delegation to search + * @param enforcerAddress - The enforcer address to match + * @returns The matching caveat + * @throws NoMatchingCaveatError if no matching caveat is found + * @throws MultipleMatchingCaveatsError if multiple matching caveats are found + */ +function findMatchingCaveat( + delegation: Delegation, + enforcerAddress: Address, +): { terms: Hex; args: Hex } { + const matchingCaveats = delegation.caveats.filter( + (caveat) => caveat.enforcer.toLowerCase() === enforcerAddress.toLowerCase(), + ); + + if (matchingCaveats.length === 0) { + throw new NoMatchingCaveatError(enforcerAddress); + } + + if (matchingCaveats.length > 1) { + throw new MultipleMatchingCaveatsError(enforcerAddress); + } + + return { + terms: matchingCaveats[0].terms, + args: matchingCaveats[0].args, + }; +} + /** * Resolves the delegation manager address from parameters or environment. * @@ -254,6 +320,167 @@ export async function getNativeTokenStreamingEnforcerAvailableAmount( }); } +/** + * Get available amount for ERC20 period transfer enforcer using delegation. + * + * @param client - The viem public client. + * @param environment - The delegator environment. + * @param params - The parameters for the ERC20 period transfer enforcer. + * @returns Promise resolving to the period transfer result. + */ +export async function getErc20PeriodTransferEnforcerAvailableAmountFromDelegation( + client: PublicClient, + environment: DeleGatorEnvironment, + params: ERC20PeriodTransferDelegationParams, +): Promise { + const delegationManager = resolveDelegationManager(params, environment); + const enforcerAddress = resolveEnforcerAddress( + 'ERC20PeriodTransferEnforcer', + params, + environment, + ); + + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + }); +} + +/** + * Get available amount for ERC20 streaming enforcer using delegation. + * + * @param client - The viem public client. + * @param environment - The delegator environment. + * @param params - The parameters for the ERC20 streaming enforcer. + * @returns Promise resolving to the streaming result. + */ +export async function getErc20StreamingEnforcerAvailableAmountFromDelegation( + client: PublicClient, + environment: DeleGatorEnvironment, + params: ERC20StreamingDelegationParams, +): Promise { + const delegationManager = resolveDelegationManager(params, environment); + const enforcerAddress = resolveEnforcerAddress( + 'ERC20StreamingEnforcer', + params, + environment, + ); + + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return ERC20StreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash, + terms, + }); +} + +/** + * Get available amount for multi-token period enforcer using delegation. + * + * @param client - The viem public client. + * @param environment - The delegator environment. + * @param params - The parameters for the multi-token period enforcer. + * @returns Promise resolving to the period transfer result. + */ +export async function getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client: PublicClient, + environment: DeleGatorEnvironment, + params: MultiTokenPeriodDelegationParams, +): Promise { + const delegationManager = resolveDelegationManager(params, environment); + const enforcerAddress = resolveEnforcerAddress( + 'MultiTokenPeriodEnforcer', + params, + environment, + ); + + const delegationHash = hashDelegation(params.delegation); + const { terms, args } = findMatchingCaveat(params.delegation, enforcerAddress); + + return MultiTokenPeriodEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + args, + }); +} + +/** + * Get available amount for native token period transfer enforcer using delegation. + * + * @param client - The viem public client. + * @param environment - The delegator environment. + * @param params - The parameters for the native token period transfer enforcer. + * @returns Promise resolving to the period transfer result. + */ +export async function getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation( + client: PublicClient, + environment: DeleGatorEnvironment, + params: NativeTokenPeriodTransferDelegationParams, +): Promise { + const delegationManager = resolveDelegationManager(params, environment); + const enforcerAddress = resolveEnforcerAddress( + 'NativeTokenPeriodTransferEnforcer', + params, + environment, + ); + + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash, + terms, + }); +} + +/** + * Get available amount for native token streaming enforcer using delegation. + * + * @param client - The viem public client. + * @param environment - The delegator environment. + * @param params - The parameters for the native token streaming enforcer. + * @returns Promise resolving to the streaming result. + */ +export async function getNativeTokenStreamingEnforcerAvailableAmountFromDelegation( + client: PublicClient, + environment: DeleGatorEnvironment, + params: NativeTokenStreamingDelegationParams, +): Promise { + const delegationManager = resolveDelegationManager(params, environment); + const enforcerAddress = resolveEnforcerAddress( + 'NativeTokenStreamingEnforcer', + params, + environment, + ); + + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return NativeTokenStreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash, + terms, + }); +} + /** * Caveat enforcer actions for extending viem clients. * @@ -343,6 +570,86 @@ export const caveatEnforcerActions = params, ); }, + + /** + * Get available amount for ERC20 period transfer enforcer using delegation. + * + * @param params - The parameters for the ERC20 period transfer enforcer. + * @returns Promise resolving to the period transfer result. + */ + getErc20PeriodTransferEnforcerAvailableAmountFromDelegation: async ( + params: ERC20PeriodTransferDelegationParams, + ): Promise => { + return getErc20PeriodTransferEnforcerAvailableAmountFromDelegation( + client, + environment, + params, + ); + }, + + /** + * Get available amount for ERC20 streaming enforcer using delegation. + * + * @param params - The parameters for the ERC20 streaming enforcer. + * @returns Promise resolving to the streaming result. + */ + getErc20StreamingEnforcerAvailableAmountFromDelegation: async ( + params: ERC20StreamingDelegationParams, + ): Promise => { + return getErc20StreamingEnforcerAvailableAmountFromDelegation( + client, + environment, + params, + ); + }, + + /** + * Get available amount for multi-token period enforcer using delegation. + * + * @param params - The parameters for the multi-token period enforcer. + * @returns Promise resolving to the period transfer result. + */ + getMultiTokenPeriodEnforcerAvailableAmountFromDelegation: async ( + params: MultiTokenPeriodDelegationParams, + ): Promise => { + return getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environment, + params, + ); + }, + + /** + * Get available amount for native token period transfer enforcer using delegation. + * + * @param params - The parameters for the native token period transfer enforcer. + * @returns Promise resolving to the period transfer result. + */ + getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation: async ( + params: NativeTokenPeriodTransferDelegationParams, + ): Promise => { + return getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation( + client, + environment, + params, + ); + }, + + /** + * Get available amount for native token streaming enforcer using delegation. + * + * @param params - The parameters for the native token streaming enforcer. + * @returns Promise resolving to the streaming result. + */ + getNativeTokenStreamingEnforcerAvailableAmountFromDelegation: async ( + params: NativeTokenStreamingDelegationParams, + ): Promise => { + return getNativeTokenStreamingEnforcerAvailableAmountFromDelegation( + client, + environment, + params, + ); + }, }); /** diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index e7578b77..3a295074 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -10,6 +10,12 @@ export { getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, + // New delegation-based action functions + getErc20PeriodTransferEnforcerAvailableAmountFromDelegation, + getErc20StreamingEnforcerAvailableAmountFromDelegation, + getMultiTokenPeriodEnforcerAvailableAmountFromDelegation, + getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation, + getNativeTokenStreamingEnforcerAvailableAmountFromDelegation, // Client extension exports createCaveatEnforcerClient, caveatEnforcerActions, @@ -20,9 +26,18 @@ export { type MultiTokenPeriodParams, type NativeTokenPeriodTransferParams, type NativeTokenStreamingParams, + // New delegation-based parameter types + type ERC20PeriodTransferDelegationParams, + type ERC20StreamingDelegationParams, + type MultiTokenPeriodDelegationParams, + type NativeTokenPeriodTransferDelegationParams, + type NativeTokenStreamingDelegationParams, // Result types type PeriodTransferResult, type StreamingResult, + // Error types + type NoMatchingCaveatError, + type MultipleMatchingCaveatsError, } from './getCaveatAvailableAmount'; export { isValid7702Implementation } from './isValid7702Implementation'; diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts new file mode 100644 index 00000000..12fbb174 --- /dev/null +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -0,0 +1,395 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { createPublicClient, http } from 'viem'; +import { mainnet } from 'viem/chains'; +import { hashDelegation } from '@metamask/delegation-core'; +import { + getMultiTokenPeriodEnforcerAvailableAmountFromDelegation, + getErc20PeriodTransferEnforcerAvailableAmountFromDelegation, + getErc20StreamingEnforcerAvailableAmountFromDelegation, + getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation, + getNativeTokenStreamingEnforcerAvailableAmountFromDelegation, + NoMatchingCaveatError, + MultipleMatchingCaveatsError, + type DeleGatorEnvironment, + type Delegation, +} from '../../src'; + +// Mock the contract read functions +vi.mock('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', () => ({ + read: vi.fn(), +})); + +vi.mock('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', () => ({ + read: vi.fn(), +})); + +vi.mock('../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount', () => ({ + read: vi.fn(), +})); + +vi.mock('../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount', () => ({ + read: vi.fn(), +})); + +vi.mock('../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount', () => ({ + read: vi.fn(), +})); + +describe('Delegation-based Caveat Enforcer Actions', () => { + let client: ReturnType; + let environment: DeleGatorEnvironment; + let delegation: Delegation; + + beforeEach(() => { + client = createPublicClient({ + chain: mainnet, + transport: http(), + }); + + environment = { + DelegationManager: '0x1234567890123456789012345678901234567890', + EntryPoint: '0x2345678901234567890123456789012345678901', + SimpleFactory: '0x3456789012345678901234567890123456789012', + implementations: {}, + caveatEnforcers: { + MultiTokenPeriodEnforcer: '0x4567890123456789012345678901234567890123', + ERC20PeriodTransferEnforcer: '0x5678901234567890123456789012345678901234', + ERC20StreamingEnforcer: '0x6789012345678901234567890123456789012345', + NativeTokenPeriodTransferEnforcer: '0x7890123456789012345678901234567890123456', + NativeTokenStreamingEnforcer: '0x8901234567890123456789012345678901234567', + }, + }; + + delegation = { + delegate: '0x1111111111111111111111111111111111111111', + delegator: '0x2222222222222222222222222222222222222222', + authority: '0x0000000000000000000000000000000000000000000000000000000000000000', + caveats: [ + { + enforcer: '0x4567890123456789012345678901234567890123', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + salt: '0x0000000000000000000000000000000000000000000000000000000000000001', + signature: '0xabcdef1234567890', + }; + }); + + describe('getMultiTokenPeriodEnforcerAvailableAmountFromDelegation', () => { + it('should successfully get available amount with matching caveat', async () => { + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash: hashDelegation(delegation), + delegationManager: environment.DelegationManager, + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + }); + + it('should throw NoMatchingCaveatError when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation: delegationWithoutMatchingCaveat }, + ), + ).rejects.toThrow(NoMatchingCaveatError); + }); + + it('should throw MultipleMatchingCaveatsError when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats = { + ...delegation, + caveats: [ + { + enforcer: '0x4567890123456789012345678901234567890123', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x4567890123456789012345678901234567890123', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation: delegationWithMultipleMatchingCaveats }, + ), + ).rejects.toThrow(MultipleMatchingCaveatsError); + }); + + it('should use custom delegation manager when provided', async () => { + const customDelegationManager = '0x9999999999999999999999999999999999999999'; + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + await getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation, delegationManager: customDelegationManager }, + ); + + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash: hashDelegation(delegation), + delegationManager: customDelegationManager, + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + }); + + it('should use custom enforcer address when provided', async () => { + const customEnforcerAddress = '0x9999999999999999999999999999999999999999'; + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + await getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation, enforcerAddress: customEnforcerAddress }, + ); + + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: customEnforcerAddress, + delegationHash: hashDelegation(delegation), + delegationManager: environment.DelegationManager, + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + }); + }); + + describe('getErc20PeriodTransferEnforcerAvailableAmountFromDelegation', () => { + it('should successfully get available amount with matching caveat', async () => { + const delegationWithErc20Caveat = { + ...delegation, + caveats: [ + { + enforcer: '0x5678901234567890123456789012345678901234', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getErc20PeriodTransferEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation: delegationWithErc20Caveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.ERC20PeriodTransferEnforcer, + delegationHash: hashDelegation(delegationWithErc20Caveat), + delegationManager: environment.DelegationManager, + terms: delegationWithErc20Caveat.caveats[0].terms, + }); + }); + }); + + describe('getErc20StreamingEnforcerAvailableAmountFromDelegation', () => { + it('should successfully get available amount with matching caveat', async () => { + const delegationWithErc20StreamingCaveat = { + ...delegation, + caveats: [ + { + enforcer: '0x6789012345678901234567890123456789012345', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + }; + + const { read } = await import('../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getErc20StreamingEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation: delegationWithErc20StreamingCaveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.ERC20StreamingEnforcer, + delegationManager: environment.DelegationManager, + delegationHash: hashDelegation(delegationWithErc20StreamingCaveat), + terms: delegationWithErc20StreamingCaveat.caveats[0].terms, + }); + }); + }); + + describe('getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation', () => { + it('should successfully get available amount with matching caveat', async () => { + const delegationWithNativeTokenCaveat = { + ...delegation, + caveats: [ + { + enforcer: '0x7890123456789012345678901234567890123456', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation: delegationWithNativeTokenCaveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, + delegationHash: hashDelegation(delegationWithNativeTokenCaveat), + delegationManager: environment.DelegationManager, + terms: delegationWithNativeTokenCaveat.caveats[0].terms, + }); + }); + }); + + describe('getNativeTokenStreamingEnforcerAvailableAmountFromDelegation', () => { + it('should successfully get available amount with matching caveat', async () => { + const delegationWithNativeTokenStreamingCaveat = { + ...delegation, + caveats: [ + { + enforcer: '0x8901234567890123456789012345678901234567', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + }; + + const { read } = await import('../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getNativeTokenStreamingEnforcerAvailableAmountFromDelegation( + client, + environment, + { delegation: delegationWithNativeTokenStreamingCaveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.NativeTokenStreamingEnforcer, + delegationManager: environment.DelegationManager, + delegationHash: hashDelegation(delegationWithNativeTokenStreamingCaveat), + terms: delegationWithNativeTokenStreamingCaveat.caveats[0].terms, + }); + }); + }); + + describe('Error handling', () => { + it('should throw error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('should throw error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + MultiTokenPeriodEnforcer: undefined as any, + }, + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow('MultiTokenPeriodEnforcer not found in environment'); + }); + }); +}); \ No newline at end of file From 32ca7cb628f7440dc00937d61ed49d4758353cd3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 22:40:08 +0000 Subject: [PATCH 02/19] feat: move caveat enforcer client to main export --- packages/delegation-toolkit/src/index.ts | 6 + .../test/actions/caveatEnforcerClient.test.ts | 232 ++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts diff --git a/packages/delegation-toolkit/src/index.ts b/packages/delegation-toolkit/src/index.ts index 94a33e28..15228892 100644 --- a/packages/delegation-toolkit/src/index.ts +++ b/packages/delegation-toolkit/src/index.ts @@ -61,3 +61,9 @@ export { redeemDelegations } from './write'; export * as contracts from './contracts'; export * as actions from './actions'; + +// Export the caveat enforcer client +export { + createCaveatEnforcerClient, + type CaveatEnforcerClient, +} from './actions'; diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts new file mode 100644 index 00000000..9506b511 --- /dev/null +++ b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts @@ -0,0 +1,232 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { createPublicClient, http } from 'viem'; +import { mainnet } from 'viem/chains'; +import { hashDelegation } from '@metamask/delegation-core'; +import { + createCaveatEnforcerClient, + type CaveatEnforcerClient, + type DeleGatorEnvironment, + type Delegation, +} from '../../src'; + +// Mock the contract read functions +vi.mock('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', () => ({ + read: vi.fn(), +})); + +vi.mock('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', () => ({ + read: vi.fn(), +})); + +describe('CaveatEnforcerClient', () => { + let client: CaveatEnforcerClient; + let environment: DeleGatorEnvironment; + let delegation: Delegation; + + beforeEach(() => { + const baseClient = createPublicClient({ + chain: mainnet, + transport: http(), + }); + + environment = { + DelegationManager: '0x1234567890123456789012345678901234567890', + EntryPoint: '0x2345678901234567890123456789012345678901', + SimpleFactory: '0x3456789012345678901234567890123456789012', + implementations: {}, + caveatEnforcers: { + MultiTokenPeriodEnforcer: '0x4567890123456789012345678901234567890123', + ERC20PeriodTransferEnforcer: '0x5678901234567890123456789012345678901234', + ERC20StreamingEnforcer: '0x6789012345678901234567890123456789012345', + NativeTokenPeriodTransferEnforcer: '0x7890123456789012345678901234567890123456', + NativeTokenStreamingEnforcer: '0x8901234567890123456789012345678901234567', + }, + }; + + client = createCaveatEnforcerClient({ + client: baseClient, + environment, + }); + + delegation = { + delegate: '0x1111111111111111111111111111111111111111', + delegator: '0x2222222222222222222222222222222222222222', + authority: '0x0000000000000000000000000000000000000000000000000000000000000000', + caveats: [ + { + enforcer: '0x4567890123456789012345678901234567890123', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + salt: '0x0000000000000000000000000000000000000000000000000000000000000001', + signature: '0xabcdef1234567890', + }; + }); + + describe('getMultiTokenPeriodEnforcerAvailableAmountFromDelegation', () => { + it('should successfully get available amount using client', async () => { + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await client.getMultiTokenPeriodEnforcerAvailableAmountFromDelegation({ + delegation, + }); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client: expect.any(Object), + contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash: hashDelegation(delegation), + delegationManager: environment.DelegationManager, + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + }); + + it('should use custom delegation manager when provided', async () => { + const customDelegationManager = '0x9999999999999999999999999999999999999999'; + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + await client.getMultiTokenPeriodEnforcerAvailableAmountFromDelegation({ + delegation, + delegationManager: customDelegationManager, + }); + + expect(read).toHaveBeenCalledWith({ + client: expect.any(Object), + contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash: hashDelegation(delegation), + delegationManager: customDelegationManager, + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + }); + + it('should use custom enforcer address when provided', async () => { + const customEnforcerAddress = '0x9999999999999999999999999999999999999999'; + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + await client.getMultiTokenPeriodEnforcerAvailableAmountFromDelegation({ + delegation, + enforcerAddress: customEnforcerAddress, + }); + + expect(read).toHaveBeenCalledWith({ + client: expect.any(Object), + contractAddress: customEnforcerAddress, + delegationHash: hashDelegation(delegation), + delegationManager: environment.DelegationManager, + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + }); + }); + + describe('getErc20PeriodTransferEnforcerAvailableAmountFromDelegation', () => { + it('should successfully get available amount using client', async () => { + const delegationWithErc20Caveat = { + ...delegation, + caveats: [ + { + enforcer: '0x5678901234567890123456789012345678901234', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await client.getErc20PeriodTransferEnforcerAvailableAmountFromDelegation({ + delegation: delegationWithErc20Caveat, + }); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client: expect.any(Object), + contractAddress: environment.caveatEnforcers.ERC20PeriodTransferEnforcer, + delegationHash: hashDelegation(delegationWithErc20Caveat), + delegationManager: environment.DelegationManager, + terms: delegationWithErc20Caveat.caveats[0].terms, + }); + }); + }); + + describe('Legacy methods still work', () => { + it('should support legacy getMultiTokenPeriodEnforcerAvailableAmount method', async () => { + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await client.getMultiTokenPeriodEnforcerAvailableAmount({ + delegationHash: hashDelegation(delegation), + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client: expect.any(Object), + contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash: hashDelegation(delegation), + delegationManager: environment.DelegationManager, + terms: delegation.caveats[0].terms, + args: delegation.caveats[0].args, + }); + }); + }); + + describe('Client type', () => { + it('should have all expected methods', () => { + expect(client).toHaveProperty('getMultiTokenPeriodEnforcerAvailableAmount'); + expect(client).toHaveProperty('getMultiTokenPeriodEnforcerAvailableAmountFromDelegation'); + expect(client).toHaveProperty('getErc20PeriodTransferEnforcerAvailableAmount'); + expect(client).toHaveProperty('getErc20PeriodTransferEnforcerAvailableAmountFromDelegation'); + expect(client).toHaveProperty('getErc20StreamingEnforcerAvailableAmount'); + expect(client).toHaveProperty('getErc20StreamingEnforcerAvailableAmountFromDelegation'); + expect(client).toHaveProperty('getNativeTokenPeriodTransferEnforcerAvailableAmount'); + expect(client).toHaveProperty('getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation'); + expect(client).toHaveProperty('getNativeTokenStreamingEnforcerAvailableAmount'); + expect(client).toHaveProperty('getNativeTokenStreamingEnforcerAvailableAmountFromDelegation'); + }); + + it('should still have base client methods', () => { + expect(client).toHaveProperty('getBlockNumber'); + expect(client).toHaveProperty('getBalance'); + expect(client).toHaveProperty('readContract'); + }); + }); +}); \ No newline at end of file From 5bc38de7d1d5003349c99f13c22761d96d3e038b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 22:52:44 +0000 Subject: [PATCH 03/19] refactor: modify existing actions to accept delegation instead of creating new ones --- .../src/actions/getCaveatAvailableAmount.ts | 425 ++++++------------ .../delegation-toolkit/src/actions/index.ts | 12 - .../test/actions/caveatEnforcerClient.test.ts | 17 +- .../actions/getCaveatAvailableAmount.test.ts | 42 +- 4 files changed, 158 insertions(+), 338 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 33544fe9..d3b6d271 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -5,7 +5,7 @@ import * as ERC20StreamingEnforcer from '../DelegationFramework/ERC20StreamingEn import * as MultiTokenPeriodEnforcer from '../DelegationFramework/MultiTokenPeriodEnforcer'; import * as NativeTokenPeriodTransferEnforcer from '../DelegationFramework/NativeTokenPeriodTransferEnforcer'; import * as NativeTokenStreamingEnforcer from '../DelegationFramework/NativeTokenStreamingEnforcer'; -import { hashDelegation } from '@metamask/delegation-core'; +import { hashDelegation } from '@metamask/delegation-core/src/delegation'; import type { DeleGatorEnvironment, Delegation } from '../types'; /** @@ -21,45 +21,50 @@ export type BaseCaveatParams = { /** * Parameters for ERC20 period transfer enforcer. */ -export type ERC20PeriodTransferParams = BaseCaveatParams; +export type ERC20PeriodTransferParams = BaseCaveatParams | { + delegation: Delegation; + delegationManager?: Address; + enforcerAddress?: Address; +}; /** * Parameters for ERC20 streaming enforcer. */ -export type ERC20StreamingParams = BaseCaveatParams; +export type ERC20StreamingParams = BaseCaveatParams | { + delegation: Delegation; + delegationManager?: Address; + enforcerAddress?: Address; +}; /** * Parameters for MultiTokenPeriodEnforcer. */ export type MultiTokenPeriodParams = { args: Hex; -} & BaseCaveatParams; +} & BaseCaveatParams | { + delegation: Delegation; + delegationManager?: Address; + enforcerAddress?: Address; +}; /** * Parameters for native token period transfer enforcer. */ -export type NativeTokenPeriodTransferParams = BaseCaveatParams; +export type NativeTokenPeriodTransferParams = BaseCaveatParams | { + delegation: Delegation; + delegationManager?: Address; + enforcerAddress?: Address; +}; /** * Parameters for native token streaming enforcer. */ -export type NativeTokenStreamingParams = BaseCaveatParams; - -/** - * New parameter types that accept the entire delegation - */ -export type BaseDelegationParams = { +export type NativeTokenStreamingParams = BaseCaveatParams | { delegation: Delegation; delegationManager?: Address; enforcerAddress?: Address; }; -export type ERC20PeriodTransferDelegationParams = BaseDelegationParams; -export type ERC20StreamingDelegationParams = BaseDelegationParams; -export type MultiTokenPeriodDelegationParams = BaseDelegationParams; -export type NativeTokenPeriodTransferDelegationParams = BaseDelegationParams; -export type NativeTokenStreamingDelegationParams = BaseDelegationParams; - /** * Return type for period-based transfer enforcers */ @@ -96,6 +101,13 @@ export class MultipleMatchingCaveatsError extends Error { } } +/** + * Type guard to check if params contain a delegation + */ +function hasDelegation(params: any): params is { delegation: Delegation; delegationManager?: Address; enforcerAddress?: Address } { + return 'delegation' in params; +} + /** * Finds a caveat that matches the specified enforcer address * @param delegation - The delegation to search @@ -194,13 +206,26 @@ export async function getErc20PeriodTransferEnforcerAvailableAmount( environment, ); - return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash: params.delegationHash, - delegationManager, - terms: params.terms, - }); + if (hasDelegation(params)) { + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + }); + } else { + return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash: params.delegationHash, + delegationManager, + terms: params.terms, + }); + } } /** @@ -223,13 +248,26 @@ export async function getErc20StreamingEnforcerAvailableAmount( environment, ); - return ERC20StreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash: params.delegationHash, - terms: params.terms, - }); + if (hasDelegation(params)) { + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return ERC20StreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash, + terms, + }); + } else { + return ERC20StreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash: params.delegationHash, + terms: params.terms, + }); + } } /** @@ -252,14 +290,28 @@ export async function getMultiTokenPeriodEnforcerAvailableAmount( environment, ); - return MultiTokenPeriodEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash: params.delegationHash, - delegationManager, - terms: params.terms, - args: params.args, - }); + if (hasDelegation(params)) { + const delegationHash = hashDelegation(params.delegation); + const { terms, args } = findMatchingCaveat(params.delegation, enforcerAddress); + + return MultiTokenPeriodEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + args, + }); + } else { + return MultiTokenPeriodEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash: params.delegationHash, + delegationManager, + terms: params.terms, + args: params.args, + }); + } } /** @@ -282,13 +334,26 @@ export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( environment, ); - return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash: params.delegationHash, - delegationManager, - terms: params.terms, - }); + if (hasDelegation(params)) { + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + }); + } else { + return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash: params.delegationHash, + delegationManager, + terms: params.terms, + }); + } } /** @@ -311,174 +376,26 @@ export async function getNativeTokenStreamingEnforcerAvailableAmount( environment, ); - return NativeTokenStreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash: params.delegationHash, - terms: params.terms, - }); -} - -/** - * Get available amount for ERC20 period transfer enforcer using delegation. - * - * @param client - The viem public client. - * @param environment - The delegator environment. - * @param params - The parameters for the ERC20 period transfer enforcer. - * @returns Promise resolving to the period transfer result. - */ -export async function getErc20PeriodTransferEnforcerAvailableAmountFromDelegation( - client: PublicClient, - environment: DeleGatorEnvironment, - params: ERC20PeriodTransferDelegationParams, -): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'ERC20PeriodTransferEnforcer', - params, - environment, - ); - - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash, - delegationManager, - terms, - }); -} - -/** - * Get available amount for ERC20 streaming enforcer using delegation. - * - * @param client - The viem public client. - * @param environment - The delegator environment. - * @param params - The parameters for the ERC20 streaming enforcer. - * @returns Promise resolving to the streaming result. - */ -export async function getErc20StreamingEnforcerAvailableAmountFromDelegation( - client: PublicClient, - environment: DeleGatorEnvironment, - params: ERC20StreamingDelegationParams, -): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'ERC20StreamingEnforcer', - params, - environment, - ); - - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return ERC20StreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash, - terms, - }); -} - -/** - * Get available amount for multi-token period enforcer using delegation. - * - * @param client - The viem public client. - * @param environment - The delegator environment. - * @param params - The parameters for the multi-token period enforcer. - * @returns Promise resolving to the period transfer result. - */ -export async function getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( - client: PublicClient, - environment: DeleGatorEnvironment, - params: MultiTokenPeriodDelegationParams, -): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'MultiTokenPeriodEnforcer', - params, - environment, - ); - - const delegationHash = hashDelegation(params.delegation); - const { terms, args } = findMatchingCaveat(params.delegation, enforcerAddress); - - return MultiTokenPeriodEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash, - delegationManager, - terms, - args, - }); -} - -/** - * Get available amount for native token period transfer enforcer using delegation. - * - * @param client - The viem public client. - * @param environment - The delegator environment. - * @param params - The parameters for the native token period transfer enforcer. - * @returns Promise resolving to the period transfer result. - */ -export async function getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation( - client: PublicClient, - environment: DeleGatorEnvironment, - params: NativeTokenPeriodTransferDelegationParams, -): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'NativeTokenPeriodTransferEnforcer', - params, - environment, - ); - - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash, - terms, - }); -} - -/** - * Get available amount for native token streaming enforcer using delegation. - * - * @param client - The viem public client. - * @param environment - The delegator environment. - * @param params - The parameters for the native token streaming enforcer. - * @returns Promise resolving to the streaming result. - */ -export async function getNativeTokenStreamingEnforcerAvailableAmountFromDelegation( - client: PublicClient, - environment: DeleGatorEnvironment, - params: NativeTokenStreamingDelegationParams, -): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'NativeTokenStreamingEnforcer', - params, - environment, - ); - - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return NativeTokenStreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash, - terms, - }); + if (hasDelegation(params)) { + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return NativeTokenStreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash, + terms, + }); + } else { + return NativeTokenStreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash: params.delegationHash, + terms: params.terms, + }); + } } /** @@ -570,86 +487,6 @@ export const caveatEnforcerActions = params, ); }, - - /** - * Get available amount for ERC20 period transfer enforcer using delegation. - * - * @param params - The parameters for the ERC20 period transfer enforcer. - * @returns Promise resolving to the period transfer result. - */ - getErc20PeriodTransferEnforcerAvailableAmountFromDelegation: async ( - params: ERC20PeriodTransferDelegationParams, - ): Promise => { - return getErc20PeriodTransferEnforcerAvailableAmountFromDelegation( - client, - environment, - params, - ); - }, - - /** - * Get available amount for ERC20 streaming enforcer using delegation. - * - * @param params - The parameters for the ERC20 streaming enforcer. - * @returns Promise resolving to the streaming result. - */ - getErc20StreamingEnforcerAvailableAmountFromDelegation: async ( - params: ERC20StreamingDelegationParams, - ): Promise => { - return getErc20StreamingEnforcerAvailableAmountFromDelegation( - client, - environment, - params, - ); - }, - - /** - * Get available amount for multi-token period enforcer using delegation. - * - * @param params - The parameters for the multi-token period enforcer. - * @returns Promise resolving to the period transfer result. - */ - getMultiTokenPeriodEnforcerAvailableAmountFromDelegation: async ( - params: MultiTokenPeriodDelegationParams, - ): Promise => { - return getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( - client, - environment, - params, - ); - }, - - /** - * Get available amount for native token period transfer enforcer using delegation. - * - * @param params - The parameters for the native token period transfer enforcer. - * @returns Promise resolving to the period transfer result. - */ - getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation: async ( - params: NativeTokenPeriodTransferDelegationParams, - ): Promise => { - return getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation( - client, - environment, - params, - ); - }, - - /** - * Get available amount for native token streaming enforcer using delegation. - * - * @param params - The parameters for the native token streaming enforcer. - * @returns Promise resolving to the streaming result. - */ - getNativeTokenStreamingEnforcerAvailableAmountFromDelegation: async ( - params: NativeTokenStreamingDelegationParams, - ): Promise => { - return getNativeTokenStreamingEnforcerAvailableAmountFromDelegation( - client, - environment, - params, - ); - }, }); /** diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index 3a295074..e44159bf 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -10,12 +10,6 @@ export { getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, - // New delegation-based action functions - getErc20PeriodTransferEnforcerAvailableAmountFromDelegation, - getErc20StreamingEnforcerAvailableAmountFromDelegation, - getMultiTokenPeriodEnforcerAvailableAmountFromDelegation, - getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation, - getNativeTokenStreamingEnforcerAvailableAmountFromDelegation, // Client extension exports createCaveatEnforcerClient, caveatEnforcerActions, @@ -26,12 +20,6 @@ export { type MultiTokenPeriodParams, type NativeTokenPeriodTransferParams, type NativeTokenStreamingParams, - // New delegation-based parameter types - type ERC20PeriodTransferDelegationParams, - type ERC20StreamingDelegationParams, - type MultiTokenPeriodDelegationParams, - type NativeTokenPeriodTransferDelegationParams, - type NativeTokenStreamingDelegationParams, // Result types type PeriodTransferResult, type StreamingResult, diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts index 9506b511..538f516f 100644 --- a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts +++ b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts @@ -64,7 +64,7 @@ describe('CaveatEnforcerClient', () => { }; }); - describe('getMultiTokenPeriodEnforcerAvailableAmountFromDelegation', () => { + describe('getMultiTokenPeriodEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount using client', async () => { const mockResult = { availableAmount: 1000n, @@ -75,7 +75,7 @@ describe('CaveatEnforcerClient', () => { const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - const result = await client.getMultiTokenPeriodEnforcerAvailableAmountFromDelegation({ + const result = await client.getMultiTokenPeriodEnforcerAvailableAmount({ delegation, }); @@ -101,7 +101,7 @@ describe('CaveatEnforcerClient', () => { const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - await client.getMultiTokenPeriodEnforcerAvailableAmountFromDelegation({ + await client.getMultiTokenPeriodEnforcerAvailableAmount({ delegation, delegationManager: customDelegationManager, }); @@ -127,7 +127,7 @@ describe('CaveatEnforcerClient', () => { const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - await client.getMultiTokenPeriodEnforcerAvailableAmountFromDelegation({ + await client.getMultiTokenPeriodEnforcerAvailableAmount({ delegation, enforcerAddress: customEnforcerAddress, }); @@ -143,7 +143,7 @@ describe('CaveatEnforcerClient', () => { }); }); - describe('getErc20PeriodTransferEnforcerAvailableAmountFromDelegation', () => { + describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount using client', async () => { const delegationWithErc20Caveat = { ...delegation, @@ -165,7 +165,7 @@ describe('CaveatEnforcerClient', () => { const { read } = await import('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - const result = await client.getErc20PeriodTransferEnforcerAvailableAmountFromDelegation({ + const result = await client.getErc20PeriodTransferEnforcerAvailableAmount({ delegation: delegationWithErc20Caveat, }); @@ -212,15 +212,10 @@ describe('CaveatEnforcerClient', () => { describe('Client type', () => { it('should have all expected methods', () => { expect(client).toHaveProperty('getMultiTokenPeriodEnforcerAvailableAmount'); - expect(client).toHaveProperty('getMultiTokenPeriodEnforcerAvailableAmountFromDelegation'); expect(client).toHaveProperty('getErc20PeriodTransferEnforcerAvailableAmount'); - expect(client).toHaveProperty('getErc20PeriodTransferEnforcerAvailableAmountFromDelegation'); expect(client).toHaveProperty('getErc20StreamingEnforcerAvailableAmount'); - expect(client).toHaveProperty('getErc20StreamingEnforcerAvailableAmountFromDelegation'); expect(client).toHaveProperty('getNativeTokenPeriodTransferEnforcerAvailableAmount'); - expect(client).toHaveProperty('getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation'); expect(client).toHaveProperty('getNativeTokenStreamingEnforcerAvailableAmount'); - expect(client).toHaveProperty('getNativeTokenStreamingEnforcerAvailableAmountFromDelegation'); }); it('should still have base client methods', () => { diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts index 12fbb174..aca56d98 100644 --- a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -3,11 +3,11 @@ import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; import { hashDelegation } from '@metamask/delegation-core'; import { - getMultiTokenPeriodEnforcerAvailableAmountFromDelegation, - getErc20PeriodTransferEnforcerAvailableAmountFromDelegation, - getErc20StreamingEnforcerAvailableAmountFromDelegation, - getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation, - getNativeTokenStreamingEnforcerAvailableAmountFromDelegation, + getMultiTokenPeriodEnforcerAvailableAmount, + getErc20PeriodTransferEnforcerAvailableAmount, + getErc20StreamingEnforcerAvailableAmount, + getNativeTokenPeriodTransferEnforcerAvailableAmount, + getNativeTokenStreamingEnforcerAvailableAmount, NoMatchingCaveatError, MultipleMatchingCaveatsError, type DeleGatorEnvironment, @@ -76,7 +76,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; }); - describe('getMultiTokenPeriodEnforcerAvailableAmountFromDelegation', () => { + describe('getMultiTokenPeriodEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { const mockResult = { availableAmount: 1000n, @@ -87,7 +87,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - const result = await getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + const result = await getMultiTokenPeriodEnforcerAvailableAmount( client, environment, { delegation }, @@ -117,7 +117,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; await expect( - getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + getMultiTokenPeriodEnforcerAvailableAmount( client, environment, { delegation: delegationWithoutMatchingCaveat }, @@ -143,7 +143,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; await expect( - getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + getMultiTokenPeriodEnforcerAvailableAmount( client, environment, { delegation: delegationWithMultipleMatchingCaveats }, @@ -162,7 +162,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - await getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + await getMultiTokenPeriodEnforcerAvailableAmount( client, environment, { delegation, delegationManager: customDelegationManager }, @@ -189,7 +189,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - await getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + await getMultiTokenPeriodEnforcerAvailableAmount( client, environment, { delegation, enforcerAddress: customEnforcerAddress }, @@ -206,7 +206,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }); }); - describe('getErc20PeriodTransferEnforcerAvailableAmountFromDelegation', () => { + describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { const delegationWithErc20Caveat = { ...delegation, @@ -228,7 +228,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { const { read } = await import('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - const result = await getErc20PeriodTransferEnforcerAvailableAmountFromDelegation( + const result = await getErc20PeriodTransferEnforcerAvailableAmount( client, environment, { delegation: delegationWithErc20Caveat }, @@ -245,7 +245,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }); }); - describe('getErc20StreamingEnforcerAvailableAmountFromDelegation', () => { + describe('getErc20StreamingEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { const delegationWithErc20StreamingCaveat = { ...delegation, @@ -265,7 +265,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { const { read } = await import('../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - const result = await getErc20StreamingEnforcerAvailableAmountFromDelegation( + const result = await getErc20StreamingEnforcerAvailableAmount( client, environment, { delegation: delegationWithErc20StreamingCaveat }, @@ -282,7 +282,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }); }); - describe('getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation', () => { + describe('getNativeTokenPeriodTransferEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { const delegationWithNativeTokenCaveat = { ...delegation, @@ -304,7 +304,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { const { read } = await import('../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - const result = await getNativeTokenPeriodTransferEnforcerAvailableAmountFromDelegation( + const result = await getNativeTokenPeriodTransferEnforcerAvailableAmount( client, environment, { delegation: delegationWithNativeTokenCaveat }, @@ -321,7 +321,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }); }); - describe('getNativeTokenStreamingEnforcerAvailableAmountFromDelegation', () => { + describe('getNativeTokenStreamingEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { const delegationWithNativeTokenStreamingCaveat = { ...delegation, @@ -341,7 +341,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { const { read } = await import('../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount'); vi.mocked(read).mockResolvedValue(mockResult); - const result = await getNativeTokenStreamingEnforcerAvailableAmountFromDelegation( + const result = await getNativeTokenStreamingEnforcerAvailableAmount( client, environment, { delegation: delegationWithNativeTokenStreamingCaveat }, @@ -366,7 +366,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; await expect( - getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + getMultiTokenPeriodEnforcerAvailableAmount( client, environmentWithoutDelegationManager, { delegation }, @@ -384,7 +384,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; await expect( - getMultiTokenPeriodEnforcerAvailableAmountFromDelegation( + getMultiTokenPeriodEnforcerAvailableAmount( client, environmentWithoutEnforcer, { delegation }, From 9828c5277e3dbeb62d0f3034bfca037180084867 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 22:57:42 +0000 Subject: [PATCH 04/19] refactor: simplify actions to only accept delegation and use environment values --- .../src/actions/getCaveatAvailableAmount.ts | 248 ++++++------------ .../test/actions/caveatEnforcerClient.test.ts | 77 ------ .../actions/getCaveatAvailableAmount.test.ts | 52 ---- 3 files changed, 75 insertions(+), 302 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index d3b6d271..b13330da 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -5,64 +5,42 @@ import * as ERC20StreamingEnforcer from '../DelegationFramework/ERC20StreamingEn import * as MultiTokenPeriodEnforcer from '../DelegationFramework/MultiTokenPeriodEnforcer'; import * as NativeTokenPeriodTransferEnforcer from '../DelegationFramework/NativeTokenPeriodTransferEnforcer'; import * as NativeTokenStreamingEnforcer from '../DelegationFramework/NativeTokenStreamingEnforcer'; -import { hashDelegation } from '@metamask/delegation-core/src/delegation'; +import { hashDelegation } from '../delegation'; import type { DeleGatorEnvironment, Delegation } from '../types'; -/** - * Shared base params for all enforcer actions - */ -export type BaseCaveatParams = { - delegationHash: Hex; - delegationManager?: Address; - enforcerAddress?: Address; - terms: Hex; -}; - /** * Parameters for ERC20 period transfer enforcer. */ -export type ERC20PeriodTransferParams = BaseCaveatParams | { +export type ERC20PeriodTransferParams = { delegation: Delegation; - delegationManager?: Address; - enforcerAddress?: Address; }; /** * Parameters for ERC20 streaming enforcer. */ -export type ERC20StreamingParams = BaseCaveatParams | { +export type ERC20StreamingParams = { delegation: Delegation; - delegationManager?: Address; - enforcerAddress?: Address; }; /** * Parameters for MultiTokenPeriodEnforcer. */ export type MultiTokenPeriodParams = { - args: Hex; -} & BaseCaveatParams | { delegation: Delegation; - delegationManager?: Address; - enforcerAddress?: Address; }; /** * Parameters for native token period transfer enforcer. */ -export type NativeTokenPeriodTransferParams = BaseCaveatParams | { +export type NativeTokenPeriodTransferParams = { delegation: Delegation; - delegationManager?: Address; - enforcerAddress?: Address; }; /** * Parameters for native token streaming enforcer. */ -export type NativeTokenStreamingParams = BaseCaveatParams | { +export type NativeTokenStreamingParams = { delegation: Delegation; - delegationManager?: Address; - enforcerAddress?: Address; }; /** @@ -101,12 +79,7 @@ export class MultipleMatchingCaveatsError extends Error { } } -/** - * Type guard to check if params contain a delegation - */ -function hasDelegation(params: any): params is { delegation: Delegation; delegationManager?: Address; enforcerAddress?: Address } { - return 'delegation' in params; -} + /** * Finds a caveat that matches the specified enforcer address @@ -139,45 +112,30 @@ function findMatchingCaveat( } /** - * Resolves the delegation manager address from parameters or environment. + * Gets the delegation manager address from environment. * - * @param params - The parameters object. - * @param params.delegationManager - The delegation manager address. * @param environment - The delegator environment. - * @returns The resolved delegation manager address. + * @returns The delegation manager address. */ -function resolveDelegationManager( - params: { delegationManager?: Address }, - environment: DeleGatorEnvironment, -): Address { - const delegationManager = - params.delegationManager ?? environment.DelegationManager; - - if (!delegationManager) { +function getDelegationManager(environment: DeleGatorEnvironment): Address { + if (!environment.DelegationManager) { throw new Error('Delegation manager address not found'); } - return delegationManager; + return environment.DelegationManager; } /** - * Resolves the enforcer address from parameters or environment. + * Gets the enforcer address from environment. * * @param enforcerName - The name of the enforcer. - * @param params - The parameters object. - * @param params.enforcerAddress - The enforcer address. * @param environment - The delegator environment. - * @returns The resolved enforcer address. + * @returns The enforcer address. */ -function resolveEnforcerAddress( +function getEnforcerAddress( enforcerName: keyof DeleGatorEnvironment['caveatEnforcers'], - params: { enforcerAddress?: Address }, environment: DeleGatorEnvironment, ): Address { - if (params.enforcerAddress) { - return params.enforcerAddress; - } - const enforcerAddress = environment.caveatEnforcers[enforcerName]; if (!enforcerAddress) { throw new Error(`${enforcerName} not found in environment`); @@ -199,33 +157,22 @@ export async function getErc20PeriodTransferEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: ERC20PeriodTransferParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress( 'ERC20PeriodTransferEnforcer', - params, environment, ); - if (hasDelegation(params)) { - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash, - delegationManager, - terms, - }); - } else { - return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash: params.delegationHash, - delegationManager, - terms: params.terms, - }); - } + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + }); } /** @@ -241,33 +188,22 @@ export async function getErc20StreamingEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: ERC20StreamingParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress( 'ERC20StreamingEnforcer', - params, environment, ); - if (hasDelegation(params)) { - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return ERC20StreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash, - terms, - }); - } else { - return ERC20StreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash: params.delegationHash, - terms: params.terms, - }); - } + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return ERC20StreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash, + terms, + }); } /** @@ -283,35 +219,23 @@ export async function getMultiTokenPeriodEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: MultiTokenPeriodParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress( 'MultiTokenPeriodEnforcer', - params, environment, ); - if (hasDelegation(params)) { - const delegationHash = hashDelegation(params.delegation); - const { terms, args } = findMatchingCaveat(params.delegation, enforcerAddress); - - return MultiTokenPeriodEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash, - delegationManager, - terms, - args, - }); - } else { - return MultiTokenPeriodEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash: params.delegationHash, - delegationManager, - terms: params.terms, - args: params.args, - }); - } + const delegationHash = hashDelegation(params.delegation); + const { terms, args } = findMatchingCaveat(params.delegation, enforcerAddress); + + return MultiTokenPeriodEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + args, + }); } /** @@ -327,33 +251,22 @@ export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: NativeTokenPeriodTransferParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress( 'NativeTokenPeriodTransferEnforcer', - params, environment, ); - if (hasDelegation(params)) { - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash, - delegationManager, - terms, - }); - } else { - return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationHash: params.delegationHash, - delegationManager, - terms: params.terms, - }); - } + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationHash, + delegationManager, + terms, + }); } /** @@ -369,33 +282,22 @@ export async function getNativeTokenStreamingEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: NativeTokenStreamingParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress( 'NativeTokenStreamingEnforcer', - params, environment, ); - if (hasDelegation(params)) { - const delegationHash = hashDelegation(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); - - return NativeTokenStreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash, - terms, - }); - } else { - return NativeTokenStreamingEnforcer.read.getAvailableAmount({ - client, - contractAddress: enforcerAddress, - delegationManager, - delegationHash: params.delegationHash, - terms: params.terms, - }); - } + const delegationHash = hashDelegation(params.delegation); + const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + + return NativeTokenStreamingEnforcer.read.getAvailableAmount({ + client, + contractAddress: enforcerAddress, + delegationManager, + delegationHash, + terms, + }); } /** diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts index 538f516f..ef20d3ab 100644 --- a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts +++ b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts @@ -90,57 +90,7 @@ describe('CaveatEnforcerClient', () => { }); }); - it('should use custom delegation manager when provided', async () => { - const customDelegationManager = '0x9999999999999999999999999999999999999999'; - const mockResult = { - availableAmount: 1000n, - isNewPeriod: true, - currentPeriod: 1n, - }; - const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); - vi.mocked(read).mockResolvedValue(mockResult); - - await client.getMultiTokenPeriodEnforcerAvailableAmount({ - delegation, - delegationManager: customDelegationManager, - }); - - expect(read).toHaveBeenCalledWith({ - client: expect.any(Object), - contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash: hashDelegation(delegation), - delegationManager: customDelegationManager, - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, - }); - }); - - it('should use custom enforcer address when provided', async () => { - const customEnforcerAddress = '0x9999999999999999999999999999999999999999'; - const mockResult = { - availableAmount: 1000n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); - vi.mocked(read).mockResolvedValue(mockResult); - - await client.getMultiTokenPeriodEnforcerAvailableAmount({ - delegation, - enforcerAddress: customEnforcerAddress, - }); - - expect(read).toHaveBeenCalledWith({ - client: expect.any(Object), - contractAddress: customEnforcerAddress, - delegationHash: hashDelegation(delegation), - delegationManager: environment.DelegationManager, - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, - }); - }); }); describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { @@ -180,34 +130,7 @@ describe('CaveatEnforcerClient', () => { }); }); - describe('Legacy methods still work', () => { - it('should support legacy getMultiTokenPeriodEnforcerAvailableAmount method', async () => { - const mockResult = { - availableAmount: 1000n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); - vi.mocked(read).mockResolvedValue(mockResult); - - const result = await client.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash: hashDelegation(delegation), - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, - }); - expect(result).toEqual(mockResult); - expect(read).toHaveBeenCalledWith({ - client: expect.any(Object), - contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash: hashDelegation(delegation), - delegationManager: environment.DelegationManager, - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, - }); - }); - }); describe('Client type', () => { it('should have all expected methods', () => { diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts index aca56d98..f2aa39b3 100644 --- a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -151,59 +151,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { ).rejects.toThrow(MultipleMatchingCaveatsError); }); - it('should use custom delegation manager when provided', async () => { - const customDelegationManager = '0x9999999999999999999999999999999999999999'; - const mockResult = { - availableAmount: 1000n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); - vi.mocked(read).mockResolvedValue(mockResult); - - await getMultiTokenPeriodEnforcerAvailableAmount( - client, - environment, - { delegation, delegationManager: customDelegationManager }, - ); - - expect(read).toHaveBeenCalledWith({ - client, - contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash: hashDelegation(delegation), - delegationManager: customDelegationManager, - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, - }); - }); - it('should use custom enforcer address when provided', async () => { - const customEnforcerAddress = '0x9999999999999999999999999999999999999999'; - const mockResult = { - availableAmount: 1000n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); - vi.mocked(read).mockResolvedValue(mockResult); - - await getMultiTokenPeriodEnforcerAvailableAmount( - client, - environment, - { delegation, enforcerAddress: customEnforcerAddress }, - ); - - expect(read).toHaveBeenCalledWith({ - client, - contractAddress: customEnforcerAddress, - delegationHash: hashDelegation(delegation), - delegationManager: environment.DelegationManager, - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, - }); - }); }); describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { From 309219d345afad2e1ec99559211dea4d73443471 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 23:15:51 +0000 Subject: [PATCH 05/19] refactor: replace custom error types with standard Error constructor --- .../src/actions/getCaveatAvailableAmount.ts | 26 +++---------------- .../delegation-toolkit/src/actions/index.ts | 4 +-- .../actions/getCaveatAvailableAmount.test.ts | 10 +++---- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index b13330da..3ba7d063 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -59,25 +59,7 @@ export type StreamingResult = { availableAmount: bigint; }; -/** - * Error thrown when no matching caveat is found - */ -export class NoMatchingCaveatError extends Error { - constructor(enforcerName: string) { - super(`No caveat found with enforcer matching ${enforcerName}`); - this.name = 'NoMatchingCaveatError'; - } -} -/** - * Error thrown when multiple matching caveats are found - */ -export class MultipleMatchingCaveatsError extends Error { - constructor(enforcerName: string) { - super(`Multiple caveats found with enforcer matching ${enforcerName}`); - this.name = 'MultipleMatchingCaveatsError'; - } -} @@ -86,8 +68,8 @@ export class MultipleMatchingCaveatsError extends Error { * @param delegation - The delegation to search * @param enforcerAddress - The enforcer address to match * @returns The matching caveat - * @throws NoMatchingCaveatError if no matching caveat is found - * @throws MultipleMatchingCaveatsError if multiple matching caveats are found + * @throws Error if no matching caveat is found + * @throws Error if multiple matching caveats are found */ function findMatchingCaveat( delegation: Delegation, @@ -98,11 +80,11 @@ function findMatchingCaveat( ); if (matchingCaveats.length === 0) { - throw new NoMatchingCaveatError(enforcerAddress); + throw new Error(`No caveat found with enforcer matching ${enforcerAddress}`); } if (matchingCaveats.length > 1) { - throw new MultipleMatchingCaveatsError(enforcerAddress); + throw new Error(`Multiple caveats found with enforcer matching ${enforcerAddress}`); } return { diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index e44159bf..9b2d83ec 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -23,9 +23,7 @@ export { // Result types type PeriodTransferResult, type StreamingResult, - // Error types - type NoMatchingCaveatError, - type MultipleMatchingCaveatsError, + } from './getCaveatAvailableAmount'; export { isValid7702Implementation } from './isValid7702Implementation'; diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts index f2aa39b3..28075252 100644 --- a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -8,8 +8,6 @@ import { getErc20StreamingEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, - NoMatchingCaveatError, - MultipleMatchingCaveatsError, type DeleGatorEnvironment, type Delegation, } from '../../src'; @@ -104,7 +102,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }); }); - it('should throw NoMatchingCaveatError when no matching caveat is found', async () => { + it('should throw Error when no matching caveat is found', async () => { const delegationWithoutMatchingCaveat = { ...delegation, caveats: [ @@ -122,10 +120,10 @@ describe('Delegation-based Caveat Enforcer Actions', () => { environment, { delegation: delegationWithoutMatchingCaveat }, ), - ).rejects.toThrow(NoMatchingCaveatError); + ).rejects.toThrow('No caveat found with enforcer matching 0x4567890123456789012345678901234567890123'); }); - it('should throw MultipleMatchingCaveatsError when multiple matching caveats are found', async () => { + it('should throw Error when multiple matching caveats are found', async () => { const delegationWithMultipleMatchingCaveats = { ...delegation, caveats: [ @@ -148,7 +146,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { environment, { delegation: delegationWithMultipleMatchingCaveats }, ), - ).rejects.toThrow(MultipleMatchingCaveatsError); + ).rejects.toThrow('Multiple caveats found with enforcer matching 0x4567890123456789012345678901234567890123'); }); From 0f08e24d70ed5ffe17bf518753ace6dcff208fff Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 23:20:14 +0000 Subject: [PATCH 06/19] refactor: consolidate parameter types into single CaveatEnforcerParams type --- .../src/actions/getCaveatAvailableAmount.ts | 52 +++++-------------- .../delegation-toolkit/src/actions/index.ts | 6 +-- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 3ba7d063..46179fe9 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -9,37 +9,9 @@ import { hashDelegation } from '../delegation'; import type { DeleGatorEnvironment, Delegation } from '../types'; /** - * Parameters for ERC20 period transfer enforcer. + * Parameters for all caveat enforcer actions. */ -export type ERC20PeriodTransferParams = { - delegation: Delegation; -}; - -/** - * Parameters for ERC20 streaming enforcer. - */ -export type ERC20StreamingParams = { - delegation: Delegation; -}; - -/** - * Parameters for MultiTokenPeriodEnforcer. - */ -export type MultiTokenPeriodParams = { - delegation: Delegation; -}; - -/** - * Parameters for native token period transfer enforcer. - */ -export type NativeTokenPeriodTransferParams = { - delegation: Delegation; -}; - -/** - * Parameters for native token streaming enforcer. - */ -export type NativeTokenStreamingParams = { +export type CaveatEnforcerParams = { delegation: Delegation; }; @@ -137,7 +109,7 @@ function getEnforcerAddress( export async function getErc20PeriodTransferEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: ERC20PeriodTransferParams, + params: CaveatEnforcerParams, ): Promise { const delegationManager = getDelegationManager(environment); const enforcerAddress = getEnforcerAddress( @@ -168,7 +140,7 @@ export async function getErc20PeriodTransferEnforcerAvailableAmount( export async function getErc20StreamingEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: ERC20StreamingParams, + params: CaveatEnforcerParams, ): Promise { const delegationManager = getDelegationManager(environment); const enforcerAddress = getEnforcerAddress( @@ -199,7 +171,7 @@ export async function getErc20StreamingEnforcerAvailableAmount( export async function getMultiTokenPeriodEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: MultiTokenPeriodParams, + params: CaveatEnforcerParams, ): Promise { const delegationManager = getDelegationManager(environment); const enforcerAddress = getEnforcerAddress( @@ -231,7 +203,7 @@ export async function getMultiTokenPeriodEnforcerAvailableAmount( export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: NativeTokenPeriodTransferParams, + params: CaveatEnforcerParams, ): Promise { const delegationManager = getDelegationManager(environment); const enforcerAddress = getEnforcerAddress( @@ -262,7 +234,7 @@ export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( export async function getNativeTokenStreamingEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: NativeTokenStreamingParams, + params: CaveatEnforcerParams, ): Promise { const delegationManager = getDelegationManager(environment); const enforcerAddress = getEnforcerAddress( @@ -299,7 +271,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the period transfer result. */ getErc20PeriodTransferEnforcerAvailableAmount: async ( - params: ERC20PeriodTransferParams, + params: CaveatEnforcerParams, ): Promise => { return getErc20PeriodTransferEnforcerAvailableAmount( client, @@ -315,7 +287,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the streaming result. */ getErc20StreamingEnforcerAvailableAmount: async ( - params: ERC20StreamingParams, + params: CaveatEnforcerParams, ): Promise => { return getErc20StreamingEnforcerAvailableAmount( client, @@ -331,7 +303,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the period transfer result. */ getMultiTokenPeriodEnforcerAvailableAmount: async ( - params: MultiTokenPeriodParams, + params: CaveatEnforcerParams, ): Promise => { return getMultiTokenPeriodEnforcerAvailableAmount( client, @@ -347,7 +319,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the period transfer result. */ getNativeTokenPeriodTransferEnforcerAvailableAmount: async ( - params: NativeTokenPeriodTransferParams, + params: CaveatEnforcerParams, ): Promise => { return getNativeTokenPeriodTransferEnforcerAvailableAmount( client, @@ -363,7 +335,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the streaming result. */ getNativeTokenStreamingEnforcerAvailableAmount: async ( - params: NativeTokenStreamingParams, + params: CaveatEnforcerParams, ): Promise => { return getNativeTokenStreamingEnforcerAvailableAmount( client, diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index 9b2d83ec..39d976a9 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -15,11 +15,7 @@ export { caveatEnforcerActions, type CaveatEnforcerClient, // Parameter types - type ERC20PeriodTransferParams, - type ERC20StreamingParams, - type MultiTokenPeriodParams, - type NativeTokenPeriodTransferParams, - type NativeTokenStreamingParams, + type CaveatEnforcerParams, // Result types type PeriodTransferResult, type StreamingResult, From fa47fa7af61aedd335486df21a58d2b3331ee558 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 23:52:36 +0000 Subject: [PATCH 07/19] test: update caveatUtils.test.ts to use new delegation interface --- .../test/caveats/caveatUtils.test.ts | 212 +++++------------- 1 file changed, 60 insertions(+), 152 deletions(-) diff --git a/packages/delegator-e2e/test/caveats/caveatUtils.test.ts b/packages/delegator-e2e/test/caveats/caveatUtils.test.ts index 112b274e..ac78ad3f 100644 --- a/packages/delegator-e2e/test/caveats/caveatUtils.test.ts +++ b/packages/delegator-e2e/test/caveats/caveatUtils.test.ts @@ -132,12 +132,9 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -177,8 +174,7 @@ describe('ERC20PeriodTransferEnforcer', () => { const afterResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -216,12 +212,9 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - const beforeResult = + const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -257,8 +250,7 @@ describe('ERC20PeriodTransferEnforcer', () => { const afterResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -297,13 +289,10 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Should be a new period, availableAmount reset to periodAmount + // Should be a new period, availableAmount reset to periodAmount const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -343,8 +332,7 @@ describe('ERC20PeriodTransferEnforcer', () => { const afterResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -389,13 +377,10 @@ describe('ERC20StreamingEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Check available amount before redemption + // Check available amount before redemption const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -443,8 +428,7 @@ describe('ERC20StreamingEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBeGreaterThanOrEqual( @@ -488,12 +472,9 @@ describe('ERC20StreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - const beforeResult = + const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -528,8 +509,7 @@ describe('ERC20StreamingEnforcer', () => { const afterResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -570,12 +550,9 @@ describe('ERC20StreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - const beforeResult = + const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -623,8 +600,7 @@ describe('ERC20StreamingEnforcer', () => { const afterResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); const expectedAmount = @@ -671,18 +647,14 @@ describe('MultiTokenPeriodEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Prepare args for multi-token enforcer (token selector) + // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; // Check available amount before redemption const beforeResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -725,9 +697,7 @@ describe('MultiTokenPeriodEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); // All tokens were transferred @@ -770,17 +740,13 @@ describe('MultiTokenPeriodEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Prepare args for multi-token enforcer (token selector) + // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; const beforeResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -816,9 +782,7 @@ describe('MultiTokenPeriodEnforcer', () => { const afterResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -860,17 +824,13 @@ describe('MultiTokenPeriodEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Prepare args for multi-token enforcer (token selector) + // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; const beforeResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -910,9 +870,7 @@ describe('MultiTokenPeriodEnforcer', () => { const afterResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -954,13 +912,10 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Check available amount before redemption + // Check available amount before redemption const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -1004,8 +959,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -1044,12 +998,9 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -1081,8 +1032,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { const afterResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -1120,12 +1070,9 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -1161,8 +1108,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { const afterResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -1205,13 +1151,10 @@ describe('NativeTokenStreamingEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Check available amount before redemption + // Check available amount before redemption const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -1255,8 +1198,7 @@ describe('NativeTokenStreamingEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBeGreaterThanOrEqual( @@ -1298,12 +1240,9 @@ describe('NativeTokenStreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -1334,8 +1273,7 @@ describe('NativeTokenStreamingEnforcer', () => { const afterResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -1374,12 +1312,9 @@ describe('NativeTokenStreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -1423,8 +1358,7 @@ describe('NativeTokenStreamingEnforcer', () => { const afterResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); const expectedAmount = @@ -1468,13 +1402,10 @@ describe('Generic caveat utils functionality', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // Use generic method + // Use generic method const result = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(result.availableAmount).toBe(periodAmount); @@ -1510,14 +1441,10 @@ describe('Generic caveat utils functionality', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // This should fail with custom delegation manager since it's not the real one + // This should fail with custom delegation manager since it's not the real one const result = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - delegationManager: customDelegationManager, + delegation: signedDelegation,delegationManager: customDelegationManager, }); // Should still return a result, check structure @@ -1551,11 +1478,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const params = { - delegationHash, - terms: delegation.caveats[0].terms, - }; + const params = { + delegation: signedDelegation,}; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1597,11 +1521,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const params = { - delegationHash, - terms: delegation.caveats[0].terms, - }; + const params = { + delegation: signedDelegation,}; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1642,13 +1563,9 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 + const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 const params = { - delegationHash, - terms: delegation.caveats[0].terms, - args, - }; + delegation: signedDelegation,}; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1687,11 +1604,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const params = { - delegationHash, - terms: delegation.caveats[0].terms, - }; + const params = { + delegation: signedDelegation,}; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1732,11 +1646,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const params = { - delegationHash, - terms: delegation.caveats[0].terms, - }; + const params = { + delegation: signedDelegation,}; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1775,11 +1686,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const customParams = { - delegationHash, - terms: delegation.caveats[0].terms, - delegationManager: aliceSmartAccount.environment.DelegationManager, + const customParams = { + delegation: signedDelegation,delegationManager: aliceSmartAccount.environment.DelegationManager, enforcerAddress: aliceSmartAccount.environment.caveatEnforcers .ERC20PeriodTransferEnforcer, From f34de6c256a0c99bfbf40a590819b320826476c1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 7 Aug 2025 00:09:30 +0000 Subject: [PATCH 08/19] refactor: remove client exports from actions index (moved to main export) --- packages/delegation-toolkit/src/actions/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index 39d976a9..d87bc8b2 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -10,10 +10,6 @@ export { getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, - // Client extension exports - createCaveatEnforcerClient, - caveatEnforcerActions, - type CaveatEnforcerClient, // Parameter types type CaveatEnforcerParams, // Result types From 1fbd944dc9e9792a88b6a7bef59a15ea0ffd2d3c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 7 Aug 2025 00:11:55 +0000 Subject: [PATCH 09/19] refactor: add caveatEnforcerActions back to actions export --- packages/delegation-toolkit/src/actions/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index d87bc8b2..e6b55da2 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -10,6 +10,8 @@ export { getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, + // Action builder + caveatEnforcerActions, // Parameter types type CaveatEnforcerParams, // Result types From 06f21cb7e3b2a3cb5c77b7fd231a6d9b6e80c928 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 7 Aug 2025 00:14:53 +0000 Subject: [PATCH 10/19] refactor: use destructuring in findMatchingCaveat function --- .../src/actions/getCaveatAvailableAmount.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 46179fe9..dfac4671 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -59,9 +59,10 @@ function findMatchingCaveat( throw new Error(`Multiple caveats found with enforcer matching ${enforcerAddress}`); } + const [{ terms, args }] = matchingCaveats; return { - terms: matchingCaveats[0].terms, - args: matchingCaveats[0].args, + terms, + args, }; } From 5b2a35415d0fd1bd43dee056554ba7b8d2df8a03 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 7 Aug 2025 00:17:20 +0000 Subject: [PATCH 11/19] style: remove extra whitespace in getCaveatAvailableAmount.ts --- .../src/actions/getCaveatAvailableAmount.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index dfac4671..3c4cb84d 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -31,10 +31,6 @@ export type StreamingResult = { availableAmount: bigint; }; - - - - /** * Finds a caveat that matches the specified enforcer address * @param delegation - The delegation to search From 5c2e0a53d56b377c6c272ed928912a629f48d36e Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:19:24 +1200 Subject: [PATCH 12/19] Fix tests --- .../src/actions/getCaveatAvailableAmount.ts | 27 +- .../test/actions/caveatEnforcerClient.test.ts | 101 +++--- .../actions/getCaveatAvailableAmount.test.ts | 177 +++++++---- .../test/caveatUtils.test.ts | 295 ++++++++---------- 4 files changed, 319 insertions(+), 281 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 3c4cb84d..57d8193b 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -1,3 +1,4 @@ +import { hashDelegation } from '@metamask/delegation-core'; import type { Address, Hex, PublicClient } from 'viem'; import * as ERC20PeriodTransferEnforcer from '../DelegationFramework/ERC20PeriodTransferEnforcer'; @@ -5,7 +6,6 @@ import * as ERC20StreamingEnforcer from '../DelegationFramework/ERC20StreamingEn import * as MultiTokenPeriodEnforcer from '../DelegationFramework/MultiTokenPeriodEnforcer'; import * as NativeTokenPeriodTransferEnforcer from '../DelegationFramework/NativeTokenPeriodTransferEnforcer'; import * as NativeTokenStreamingEnforcer from '../DelegationFramework/NativeTokenStreamingEnforcer'; -import { hashDelegation } from '../delegation'; import type { DeleGatorEnvironment, Delegation } from '../types'; /** @@ -32,12 +32,12 @@ export type StreamingResult = { }; /** - * Finds a caveat that matches the specified enforcer address - * @param delegation - The delegation to search - * @param enforcerAddress - The enforcer address to match - * @returns The matching caveat - * @throws Error if no matching caveat is found - * @throws Error if multiple matching caveats are found + * Finds a caveat that matches the specified enforcer address. + * @param delegation - The delegation to search. + * @param enforcerAddress - The enforcer address to match. + * @returns The matching caveat. + * @throws Error if no matching caveat is found. + * @throws Error if multiple matching caveats are found. */ function findMatchingCaveat( delegation: Delegation, @@ -48,11 +48,15 @@ function findMatchingCaveat( ); if (matchingCaveats.length === 0) { - throw new Error(`No caveat found with enforcer matching ${enforcerAddress}`); + throw new Error( + `No caveat found with enforcer matching ${enforcerAddress}`, + ); } if (matchingCaveats.length > 1) { - throw new Error(`Multiple caveats found with enforcer matching ${enforcerAddress}`); + throw new Error( + `Multiple caveats found with enforcer matching ${enforcerAddress}`, + ); } const [{ terms, args }] = matchingCaveats; @@ -177,7 +181,10 @@ export async function getMultiTokenPeriodEnforcerAvailableAmount( ); const delegationHash = hashDelegation(params.delegation); - const { terms, args } = findMatchingCaveat(params.delegation, enforcerAddress); + const { terms, args } = findMatchingCaveat( + params.delegation, + enforcerAddress, + ); return MultiTokenPeriodEnforcer.read.getAvailableAmount({ client, diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts index ef20d3ab..f3262ceb 100644 --- a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts +++ b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts @@ -1,22 +1,28 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; -import { hashDelegation } from '@metamask/delegation-core'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + import { createCaveatEnforcerClient, type CaveatEnforcerClient, - type DeleGatorEnvironment, - type Delegation, -} from '../../src'; +} from '../../src/actions/getCaveatAvailableAmount'; +import { getDelegationHashOffchain } from '../../src/delegation'; +import type { DeleGatorEnvironment, Delegation } from '../../src/types'; // Mock the contract read functions -vi.mock('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', () => ({ - read: vi.fn(), -})); - -vi.mock('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', () => ({ - read: vi.fn(), -})); +vi.mock( + '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); describe('CaveatEnforcerClient', () => { let client: CaveatEnforcerClient; @@ -36,10 +42,13 @@ describe('CaveatEnforcerClient', () => { implementations: {}, caveatEnforcers: { MultiTokenPeriodEnforcer: '0x4567890123456789012345678901234567890123', - ERC20PeriodTransferEnforcer: '0x5678901234567890123456789012345678901234', + ERC20PeriodTransferEnforcer: + '0x5678901234567890123456789012345678901234', ERC20StreamingEnforcer: '0x6789012345678901234567890123456789012345', - NativeTokenPeriodTransferEnforcer: '0x7890123456789012345678901234567890123456', - NativeTokenStreamingEnforcer: '0x8901234567890123456789012345678901234567', + NativeTokenPeriodTransferEnforcer: + '0x7890123456789012345678901234567890123456', + NativeTokenStreamingEnforcer: + '0x8901234567890123456789012345678901234567', }, }; @@ -51,7 +60,8 @@ describe('CaveatEnforcerClient', () => { delegation = { delegate: '0x1111111111111111111111111111111111111111', delegator: '0x2222222222222222222222222222222222222222', - authority: '0x0000000000000000000000000000000000000000000000000000000000000000', + authority: + '0x0000000000000000000000000000000000000000000000000000000000000000', caveats: [ { enforcer: '0x4567890123456789012345678901234567890123', @@ -72,30 +82,32 @@ describe('CaveatEnforcerClient', () => { currentPeriod: 1n, }; - const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + const { read } = await import( + '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount' + ); vi.mocked(read).mockResolvedValue(mockResult); const result = await client.getMultiTokenPeriodEnforcerAvailableAmount({ delegation, }); + const delegationHash = getDelegationHashOffchain(delegation); + expect(result).toEqual(mockResult); expect(read).toHaveBeenCalledWith({ client: expect.any(Object), contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash: hashDelegation(delegation), + delegationHash, delegationManager: environment.DelegationManager, - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, + terms: delegation.caveats[0]?.terms, + args: delegation.caveats[0]?.args, }); }); - - }); describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount using client', async () => { - const delegationWithErc20Caveat = { + const delegationWithErc20Caveat: Delegation = { ...delegation, caveats: [ { @@ -112,33 +124,48 @@ describe('CaveatEnforcerClient', () => { currentPeriod: 1n, }; - const { read } = await import('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount'); + const { read } = await import( + '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount' + ); vi.mocked(read).mockResolvedValue(mockResult); - const result = await client.getErc20PeriodTransferEnforcerAvailableAmount({ - delegation: delegationWithErc20Caveat, - }); + const result = await client.getErc20PeriodTransferEnforcerAvailableAmount( + { + delegation: delegationWithErc20Caveat, + }, + ); + + const delegationHash = getDelegationHashOffchain( + delegationWithErc20Caveat, + ); expect(result).toEqual(mockResult); expect(read).toHaveBeenCalledWith({ client: expect.any(Object), - contractAddress: environment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash: hashDelegation(delegationWithErc20Caveat), + contractAddress: + environment.caveatEnforcers.ERC20PeriodTransferEnforcer, + delegationHash, delegationManager: environment.DelegationManager, - terms: delegationWithErc20Caveat.caveats[0].terms, + terms: delegationWithErc20Caveat.caveats[0]?.terms, }); }); }); - - describe('Client type', () => { it('should have all expected methods', () => { - expect(client).toHaveProperty('getMultiTokenPeriodEnforcerAvailableAmount'); - expect(client).toHaveProperty('getErc20PeriodTransferEnforcerAvailableAmount'); + expect(client).toHaveProperty( + 'getMultiTokenPeriodEnforcerAvailableAmount', + ); + expect(client).toHaveProperty( + 'getErc20PeriodTransferEnforcerAvailableAmount', + ); expect(client).toHaveProperty('getErc20StreamingEnforcerAvailableAmount'); - expect(client).toHaveProperty('getNativeTokenPeriodTransferEnforcerAvailableAmount'); - expect(client).toHaveProperty('getNativeTokenStreamingEnforcerAvailableAmount'); + expect(client).toHaveProperty( + 'getNativeTokenPeriodTransferEnforcerAvailableAmount', + ); + expect(client).toHaveProperty( + 'getNativeTokenStreamingEnforcerAvailableAmount', + ); }); it('should still have base client methods', () => { @@ -147,4 +174,4 @@ describe('CaveatEnforcerClient', () => { expect(client).toHaveProperty('readContract'); }); }); -}); \ No newline at end of file +}); diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts index 28075252..68f144f7 100644 --- a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -1,40 +1,56 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import type { PublicClient } from 'viem'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; -import { hashDelegation } from '@metamask/delegation-core'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + import { getMultiTokenPeriodEnforcerAvailableAmount, getErc20PeriodTransferEnforcerAvailableAmount, getErc20StreamingEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, - type DeleGatorEnvironment, - type Delegation, -} from '../../src'; +} from '../../src/actions/getCaveatAvailableAmount'; +import { getDelegationHashOffchain } from '../../src/delegation'; +import type { DeleGatorEnvironment, Delegation } from '../../src/types'; // Mock the contract read functions -vi.mock('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', () => ({ - read: vi.fn(), -})); - -vi.mock('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', () => ({ - read: vi.fn(), -})); - -vi.mock('../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount', () => ({ - read: vi.fn(), -})); - -vi.mock('../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount', () => ({ - read: vi.fn(), -})); - -vi.mock('../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount', () => ({ - read: vi.fn(), -})); +vi.mock( + '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); describe('Delegation-based Caveat Enforcer Actions', () => { - let client: ReturnType; + let client: PublicClient; let environment: DeleGatorEnvironment; let delegation: Delegation; @@ -51,17 +67,21 @@ describe('Delegation-based Caveat Enforcer Actions', () => { implementations: {}, caveatEnforcers: { MultiTokenPeriodEnforcer: '0x4567890123456789012345678901234567890123', - ERC20PeriodTransferEnforcer: '0x5678901234567890123456789012345678901234', + ERC20PeriodTransferEnforcer: + '0x5678901234567890123456789012345678901234', ERC20StreamingEnforcer: '0x6789012345678901234567890123456789012345', - NativeTokenPeriodTransferEnforcer: '0x7890123456789012345678901234567890123456', - NativeTokenStreamingEnforcer: '0x8901234567890123456789012345678901234567', + NativeTokenPeriodTransferEnforcer: + '0x7890123456789012345678901234567890123456', + NativeTokenStreamingEnforcer: + '0x8901234567890123456789012345678901234567', }, }; delegation = { delegate: '0x1111111111111111111111111111111111111111', delegator: '0x2222222222222222222222222222222222222222', - authority: '0x0000000000000000000000000000000000000000000000000000000000000000', + authority: + '0x0000000000000000000000000000000000000000000000000000000000000000', caveats: [ { enforcer: '0x4567890123456789012345678901234567890123', @@ -82,7 +102,9 @@ describe('Delegation-based Caveat Enforcer Actions', () => { currentPeriod: 1n, }; - const { read } = await import('../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount'); + const { read } = await import( + '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount' + ); vi.mocked(read).mockResolvedValue(mockResult); const result = await getMultiTokenPeriodEnforcerAvailableAmount( @@ -91,19 +113,21 @@ describe('Delegation-based Caveat Enforcer Actions', () => { { delegation }, ); + const delegationHash = getDelegationHashOffchain(delegation); + expect(result).toEqual(mockResult); expect(read).toHaveBeenCalledWith({ client, contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash: hashDelegation(delegation), + delegationHash, delegationManager: environment.DelegationManager, - terms: delegation.caveats[0].terms, - args: delegation.caveats[0].args, + terms: delegation.caveats[0]?.terms, + args: delegation.caveats[0]?.args, }); }); it('should throw Error when no matching caveat is found', async () => { - const delegationWithoutMatchingCaveat = { + const delegationWithoutMatchingCaveat: Delegation = { ...delegation, caveats: [ { @@ -115,16 +139,16 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; await expect( - getMultiTokenPeriodEnforcerAvailableAmount( - client, - environment, - { delegation: delegationWithoutMatchingCaveat }, - ), - ).rejects.toThrow('No caveat found with enforcer matching 0x4567890123456789012345678901234567890123'); + getMultiTokenPeriodEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching 0x4567890123456789012345678901234567890123', + ); }); it('should throw Error when multiple matching caveats are found', async () => { - const delegationWithMultipleMatchingCaveats = { + const delegationWithMultipleMatchingCaveats: Delegation = { ...delegation, caveats: [ { @@ -141,20 +165,18 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; await expect( - getMultiTokenPeriodEnforcerAvailableAmount( - client, - environment, - { delegation: delegationWithMultipleMatchingCaveats }, - ), - ).rejects.toThrow('Multiple caveats found with enforcer matching 0x4567890123456789012345678901234567890123'); + getMultiTokenPeriodEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching 0x4567890123456789012345678901234567890123', + ); }); - - }); describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { - const delegationWithErc20Caveat = { + const delegationWithErc20Caveat: Delegation = { ...delegation, caveats: [ { @@ -171,7 +193,9 @@ describe('Delegation-based Caveat Enforcer Actions', () => { currentPeriod: 1n, }; - const { read } = await import('../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount'); + const { read } = await import( + '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount' + ); vi.mocked(read).mockResolvedValue(mockResult); const result = await getErc20PeriodTransferEnforcerAvailableAmount( @@ -183,17 +207,18 @@ describe('Delegation-based Caveat Enforcer Actions', () => { expect(result).toEqual(mockResult); expect(read).toHaveBeenCalledWith({ client, - contractAddress: environment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash: hashDelegation(delegationWithErc20Caveat), + contractAddress: + environment.caveatEnforcers.ERC20PeriodTransferEnforcer, + delegationHash: getDelegationHashOffchain(delegationWithErc20Caveat), delegationManager: environment.DelegationManager, - terms: delegationWithErc20Caveat.caveats[0].terms, + terms: delegationWithErc20Caveat.caveats[0]?.terms, }); }); }); describe('getErc20StreamingEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { - const delegationWithErc20StreamingCaveat = { + const delegationWithErc20StreamingCaveat: Delegation = { ...delegation, caveats: [ { @@ -208,7 +233,9 @@ describe('Delegation-based Caveat Enforcer Actions', () => { availableAmount: 1000n, }; - const { read } = await import('../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount'); + const { read } = await import( + '../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount' + ); vi.mocked(read).mockResolvedValue(mockResult); const result = await getErc20StreamingEnforcerAvailableAmount( @@ -222,15 +249,17 @@ describe('Delegation-based Caveat Enforcer Actions', () => { client, contractAddress: environment.caveatEnforcers.ERC20StreamingEnforcer, delegationManager: environment.DelegationManager, - delegationHash: hashDelegation(delegationWithErc20StreamingCaveat), - terms: delegationWithErc20StreamingCaveat.caveats[0].terms, + delegationHash: getDelegationHashOffchain( + delegationWithErc20StreamingCaveat, + ), + terms: delegationWithErc20StreamingCaveat.caveats[0]?.terms, }); }); }); describe('getNativeTokenPeriodTransferEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { - const delegationWithNativeTokenCaveat = { + const delegationWithNativeTokenCaveat: Delegation = { ...delegation, caveats: [ { @@ -247,7 +276,9 @@ describe('Delegation-based Caveat Enforcer Actions', () => { currentPeriod: 1n, }; - const { read } = await import('../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount'); + const { read } = await import( + '../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount' + ); vi.mocked(read).mockResolvedValue(mockResult); const result = await getNativeTokenPeriodTransferEnforcerAvailableAmount( @@ -259,17 +290,20 @@ describe('Delegation-based Caveat Enforcer Actions', () => { expect(result).toEqual(mockResult); expect(read).toHaveBeenCalledWith({ client, - contractAddress: environment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, - delegationHash: hashDelegation(delegationWithNativeTokenCaveat), + contractAddress: + environment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, + delegationHash: getDelegationHashOffchain( + delegationWithNativeTokenCaveat, + ), delegationManager: environment.DelegationManager, - terms: delegationWithNativeTokenCaveat.caveats[0].terms, + terms: delegationWithNativeTokenCaveat.caveats[0]?.terms, }); }); }); describe('getNativeTokenStreamingEnforcerAvailableAmount with delegation', () => { it('should successfully get available amount with matching caveat', async () => { - const delegationWithNativeTokenStreamingCaveat = { + const delegationWithNativeTokenStreamingCaveat: Delegation = { ...delegation, caveats: [ { @@ -284,7 +318,9 @@ describe('Delegation-based Caveat Enforcer Actions', () => { availableAmount: 1000n, }; - const { read } = await import('../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount'); + const { read } = await import( + '../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount' + ); vi.mocked(read).mockResolvedValue(mockResult); const result = await getNativeTokenStreamingEnforcerAvailableAmount( @@ -296,10 +332,13 @@ describe('Delegation-based Caveat Enforcer Actions', () => { expect(result).toEqual(mockResult); expect(read).toHaveBeenCalledWith({ client, - contractAddress: environment.caveatEnforcers.NativeTokenStreamingEnforcer, + contractAddress: + environment.caveatEnforcers.NativeTokenStreamingEnforcer, delegationManager: environment.DelegationManager, - delegationHash: hashDelegation(delegationWithNativeTokenStreamingCaveat), - terms: delegationWithNativeTokenStreamingCaveat.caveats[0].terms, + delegationHash: getDelegationHashOffchain( + delegationWithNativeTokenStreamingCaveat, + ), + terms: delegationWithNativeTokenStreamingCaveat.caveats[0]?.terms, }); }); }); @@ -338,4 +377,4 @@ describe('Delegation-based Caveat Enforcer Actions', () => { ).rejects.toThrow('MultiTokenPeriodEnforcer not found in environment'); }); }); -}); \ No newline at end of file +}); diff --git a/packages/delegation-toolkit/test/caveatUtils.test.ts b/packages/delegation-toolkit/test/caveatUtils.test.ts index f70ef3a7..99c613e6 100644 --- a/packages/delegation-toolkit/test/caveatUtils.test.ts +++ b/packages/delegation-toolkit/test/caveatUtils.test.ts @@ -5,13 +5,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { randomAddress, randomBytes } from './utils'; import { createCaveatEnforcerClient, - type CaveatEnforcerClient, getErc20PeriodTransferEnforcerAvailableAmount, getErc20StreamingEnforcerAvailableAmount, getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, -} from '../src/actions'; +} from '../src/actions/getCaveatAvailableAmount'; +import type { + CaveatEnforcerParams, + type CaveatEnforcerClient, +} from '../src/actions/getCaveatAvailableAmount'; import { ERC20PeriodTransferEnforcer, MultiTokenPeriodEnforcer, @@ -19,6 +22,7 @@ import { ERC20StreamingEnforcer, NativeTokenStreamingEnforcer, } from '../src/contracts'; +import { getDelegationHashOffchain } from '../src/delegation'; import type { DeleGatorEnvironment } from '../src/types'; // Helper function to generate random bytes32 @@ -29,6 +33,36 @@ describe('Caveat Contract Methods', () => { let mockEnvironment: DeleGatorEnvironment; let caveatClient: CaveatEnforcerClient; + const createParams = (caveat: { + enforcer: string; + terms: Hex; + args?: Hex; + }): CaveatEnforcerParams => { + const enforcer = mockEnvironment.caveatEnforcers[caveat.enforcer]; + if (!enforcer) { + throw new Error(`Enforcer ${caveat.enforcer} not found`); + } + + const { terms, args = '0x' } = caveat; + + return { + delegation: { + delegator: randomAddress(), + delegate: randomAddress(), + authority: randomBytes32(), + caveats: [ + { + enforcer, + terms, + args, + }, + ], + salt: '0x00', + signature: randomBytes32(), + }, + }; + }; + beforeEach(() => { publicClient = createPublicClient({ chain: sepolia, @@ -263,10 +297,21 @@ describe('Caveat Contract Methods', () => { .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const enforcer = + mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer; + + if (!enforcer) { + throw new Error('ERC20PeriodTransferEnforcer not found'); + } + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); const result = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount( @@ -277,9 +322,9 @@ describe('Caveat Contract Methods', () => { client: publicClient, contractAddress: mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash: params.delegationHash, + delegationHash, delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, + terms, }); expect(result).toEqual(mockResult); @@ -296,11 +341,16 @@ describe('Caveat Contract Methods', () => { .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - args: '0x5678' as Hex, - }; + const terms = '0x1234'; + const args = '0x5678'; + + const params = createParams({ + enforcer: 'MultiTokenPeriodEnforcer', + terms, + args, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); const result = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount(params); @@ -309,10 +359,10 @@ describe('Caveat Contract Methods', () => { client: publicClient, contractAddress: mockEnvironment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash: params.delegationHash, + delegationHash, delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, - args: params.args, + terms, + args, }); expect(result).toEqual(mockResult); @@ -327,10 +377,17 @@ describe('Caveat Contract Methods', () => { .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20StreamingEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain({ + ...params.delegation, + salt: 0n, + }); const result = await caveatClient.getErc20StreamingEnforcerAvailableAmount(params); @@ -339,8 +396,8 @@ describe('Caveat Contract Methods', () => { client: publicClient, contractAddress: mockEnvironment.caveatEnforcers.ERC20StreamingEnforcer, delegationManager: mockEnvironment.DelegationManager, - delegationHash: params.delegationHash, - terms: params.terms, + delegationHash, + terms, }); expect(result).toEqual(mockResult); @@ -357,10 +414,14 @@ describe('Caveat Contract Methods', () => { .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x9abc' as Hex, - }; + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'NativeTokenPeriodTransferEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); const result = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount( @@ -371,9 +432,9 @@ describe('Caveat Contract Methods', () => { client: publicClient, contractAddress: mockEnvironment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, - delegationHash: params.delegationHash, + delegationHash, delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, + terms, }); expect(result).toEqual(mockResult); @@ -388,10 +449,14 @@ describe('Caveat Contract Methods', () => { .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'NativeTokenStreamingEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); const result = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount( @@ -403,72 +468,13 @@ describe('Caveat Contract Methods', () => { contractAddress: mockEnvironment.caveatEnforcers.NativeTokenStreamingEnforcer, delegationManager: mockEnvironment.DelegationManager, - delegationHash: params.delegationHash, - terms: params.terms, + delegationHash, + terms, }); expect(result).toEqual(mockResult); }); - it('should use custom delegation manager when provided', async () => { - const mockResult = { - availableAmount: 100n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const customDelegationManager = randomAddress(); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - delegationManager: customDelegationManager, - }; - - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash: params.delegationHash, - delegationManager: customDelegationManager, - terms: params.terms, - }); - }); - - it('should use custom enforcer address when provided', async () => { - const mockResult = { - availableAmount: 100n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const customEnforcerAddress = randomAddress(); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - enforcerAddress: customEnforcerAddress, - }; - - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: customEnforcerAddress, - delegationHash: params.delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, - }); - }); - describe('Error handling', () => { it('should throw error if delegation manager not found', async () => { const environmentWithoutDelegationManager = { @@ -481,10 +487,10 @@ describe('Caveat Contract Methods', () => { environment: environmentWithoutDelegationManager, }); - const params = { - delegationHash: randomBytes32(), + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', terms: '0x1234' as Hex, - }; + }); await expect( clientWithoutDelegationManager.getErc20PeriodTransferEnforcerAvailableAmount( @@ -507,10 +513,10 @@ describe('Caveat Contract Methods', () => { environment: environmentWithoutEnforcer, }); - const params = { - delegationHash: randomBytes32(), + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', terms: '0x1234' as Hex, - }; + }); await expect( clientWithoutEnforcer.getErc20PeriodTransferEnforcerAvailableAmount( @@ -535,10 +541,12 @@ describe('Caveat Contract Methods', () => { .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms, + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -564,10 +572,11 @@ describe('Caveat Contract Methods', () => { .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), + const params = createParams({ + enforcer: 'ERC20StreamingEnforcer', terms: '0x1234' as Hex, - }; + args: '0x5678' as Hex, + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -595,11 +604,11 @@ describe('Caveat Contract Methods', () => { .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), + const params = createParams({ + enforcer: 'MultiTokenPeriodEnforcer', terms: '0x1234' as Hex, args: '0x5678' as Hex, - }; + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -627,10 +636,10 @@ describe('Caveat Contract Methods', () => { .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x9abc' as Hex, - }; + const params = createParams({ + enforcer: 'NativeTokenPeriodTransferEnforcer', + terms: '0x9abc', + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -658,10 +667,10 @@ describe('Caveat Contract Methods', () => { .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const params = createParams({ + enforcer: 'NativeTokenStreamingEnforcer', + terms: '0x1234', + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -678,50 +687,6 @@ describe('Caveat Contract Methods', () => { expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); }); - it('should work identically with custom parameters', async () => { - const mockResult = { - availableAmount: 150n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const customDelegationManager = randomAddress(); - const customEnforcerAddress = randomAddress(); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - delegationManager: customDelegationManager, - enforcerAddress: customEnforcerAddress, - }; - - // Test both approaches with custom parameters - const [clientResult, functionResult] = await Promise.all([ - caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params), - getErc20PeriodTransferEnforcerAvailableAmount( - publicClient, - mockEnvironment, - params, - ), - ]); - - expect(clientResult).toEqual(functionResult); - expect(clientResult).toEqual(mockResult); - expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); - - // Verify both calls used the custom parameters - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: customEnforcerAddress, - delegationHash: params.delegationHash, - delegationManager: customDelegationManager, - terms: params.terms, - }); - }); - it('should handle errors identically', async () => { const mockError = new Error('Contract call failed'); @@ -730,10 +695,10 @@ describe('Caveat Contract Methods', () => { 'getAvailableAmount', ).mockRejectedValue(mockError); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234', + }); // Both approaches should throw the same error await expect( From 5a1ee523868a2d48ce76d91b35bb6c22cca5836e Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:41:55 +1200 Subject: [PATCH 13/19] use getDelegationHashoffchain function instead of delegation-core hashDelegation directly. Use type assertion when destructuring foundCaveats. --- .../src/actions/getCaveatAvailableAmount.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 57d8193b..4c6b6b2b 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -1,4 +1,4 @@ -import { hashDelegation } from '@metamask/delegation-core'; +import { getDelegationHashOffchain } from '../delegation'; import type { Address, Hex, PublicClient } from 'viem'; import * as ERC20PeriodTransferEnforcer from '../DelegationFramework/ERC20PeriodTransferEnforcer'; @@ -59,7 +59,10 @@ function findMatchingCaveat( ); } - const [{ terms, args }] = matchingCaveats; + const [{ terms, args }] = matchingCaveats as unknown as [ + { terms: Hex; args: Hex }, + ]; + return { terms, args, @@ -118,7 +121,7 @@ export async function getErc20PeriodTransferEnforcerAvailableAmount( environment, ); - const delegationHash = hashDelegation(params.delegation); + const delegationHash = getDelegationHashOffchain(params.delegation); const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ @@ -149,7 +152,7 @@ export async function getErc20StreamingEnforcerAvailableAmount( environment, ); - const delegationHash = hashDelegation(params.delegation); + const delegationHash = getDelegationHashOffchain(params.delegation); const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); return ERC20StreamingEnforcer.read.getAvailableAmount({ @@ -180,7 +183,7 @@ export async function getMultiTokenPeriodEnforcerAvailableAmount( environment, ); - const delegationHash = hashDelegation(params.delegation); + const delegationHash = getDelegationHashOffchain(params.delegation); const { terms, args } = findMatchingCaveat( params.delegation, enforcerAddress, @@ -215,7 +218,7 @@ export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( environment, ); - const delegationHash = hashDelegation(params.delegation); + const delegationHash = getDelegationHashOffchain(params.delegation); const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ @@ -246,7 +249,7 @@ export async function getNativeTokenStreamingEnforcerAvailableAmount( environment, ); - const delegationHash = hashDelegation(params.delegation); + const delegationHash = getDelegationHashOffchain(params.delegation); const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); return NativeTokenStreamingEnforcer.read.getAvailableAmount({ From 89628de6688da83875b2d186c67d7abb97bccf78 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:50:24 +1200 Subject: [PATCH 14/19] Fix package exports, straggling tests, and import order. --- .../src/actions/getCaveatAvailableAmount.ts | 2 +- packages/delegation-toolkit/src/index.ts | 2 +- .../test/caveatUtils.test.ts | 7 +- .../test/caveats/caveatUtils.test.ts | 146 ++++-------------- 4 files changed, 37 insertions(+), 120 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 4c6b6b2b..d2e2d95d 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -1,6 +1,6 @@ -import { getDelegationHashOffchain } from '../delegation'; import type { Address, Hex, PublicClient } from 'viem'; +import { getDelegationHashOffchain } from '../delegation'; import * as ERC20PeriodTransferEnforcer from '../DelegationFramework/ERC20PeriodTransferEnforcer'; import * as ERC20StreamingEnforcer from '../DelegationFramework/ERC20StreamingEnforcer'; import * as MultiTokenPeriodEnforcer from '../DelegationFramework/MultiTokenPeriodEnforcer'; diff --git a/packages/delegation-toolkit/src/index.ts b/packages/delegation-toolkit/src/index.ts index 15228892..2afbe9cd 100644 --- a/packages/delegation-toolkit/src/index.ts +++ b/packages/delegation-toolkit/src/index.ts @@ -66,4 +66,4 @@ export * as actions from './actions'; export { createCaveatEnforcerClient, type CaveatEnforcerClient, -} from './actions'; +} from './actions/getCaveatAvailableAmount'; diff --git a/packages/delegation-toolkit/test/caveatUtils.test.ts b/packages/delegation-toolkit/test/caveatUtils.test.ts index 99c613e6..825323f8 100644 --- a/packages/delegation-toolkit/test/caveatUtils.test.ts +++ b/packages/delegation-toolkit/test/caveatUtils.test.ts @@ -13,7 +13,7 @@ import { } from '../src/actions/getCaveatAvailableAmount'; import type { CaveatEnforcerParams, - type CaveatEnforcerClient, + CaveatEnforcerClient, } from '../src/actions/getCaveatAvailableAmount'; import { ERC20PeriodTransferEnforcer, @@ -384,10 +384,7 @@ describe('Caveat Contract Methods', () => { terms, }); - const delegationHash = getDelegationHashOffchain({ - ...params.delegation, - salt: 0n, - }); + const delegationHash = getDelegationHashOffchain(params.delegation); const result = await caveatClient.getErc20StreamingEnforcerAvailableAmount(params); diff --git a/packages/delegator-e2e/test/caveats/caveatUtils.test.ts b/packages/delegator-e2e/test/caveats/caveatUtils.test.ts index ac78ad3f..07ead8fb 100644 --- a/packages/delegator-e2e/test/caveats/caveatUtils.test.ts +++ b/packages/delegator-e2e/test/caveats/caveatUtils.test.ts @@ -3,19 +3,18 @@ import { encodeExecutionCalldatas, encodePermissionContexts, createCaveatBuilder, - getDelegationHashOffchain, } from '@metamask/delegation-toolkit/utils'; import { createDelegation, createExecution, + createCaveatEnforcerClient, + type CaveatEnforcerClient, Implementation, toMetaMaskSmartAccount, ExecutionMode, type MetaMaskSmartAccount, } from '@metamask/delegation-toolkit'; import { - createCaveatEnforcerClient, - type CaveatEnforcerClient, getErc20PeriodTransferEnforcerAvailableAmount, getErc20StreamingEnforcerAvailableAmount, getMultiTokenPeriodEnforcerAvailableAmount, @@ -212,7 +211,7 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const beforeResult = + const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ delegation: signedDelegation, }); @@ -289,7 +288,7 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - // Should be a new period, availableAmount reset to periodAmount + // Should be a new period, availableAmount reset to periodAmount const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ delegation: signedDelegation, @@ -377,7 +376,7 @@ describe('ERC20StreamingEnforcer', () => { }; // Get delegation hash - // Check available amount before redemption + // Check available amount before redemption const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ delegation: signedDelegation, @@ -472,7 +471,7 @@ describe('ERC20StreamingEnforcer', () => { }), }; - const beforeResult = + const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ delegation: signedDelegation, }); @@ -550,7 +549,7 @@ describe('ERC20StreamingEnforcer', () => { }), }; - const beforeResult = + const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ delegation: signedDelegation, }); @@ -647,7 +646,7 @@ describe('MultiTokenPeriodEnforcer', () => { }; // Get delegation hash - // Prepare args for multi-token enforcer (token selector) + // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; @@ -740,7 +739,7 @@ describe('MultiTokenPeriodEnforcer', () => { }), }; - // Prepare args for multi-token enforcer (token selector) + // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; @@ -824,7 +823,7 @@ describe('MultiTokenPeriodEnforcer', () => { }), }; - // Prepare args for multi-token enforcer (token selector) + // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; @@ -912,7 +911,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }; // Get delegation hash - // Check available amount before redemption + // Check available amount before redemption const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ delegation: signedDelegation, @@ -998,7 +997,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }), }; - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ delegation: signedDelegation, }); @@ -1070,7 +1069,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }), }; - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ delegation: signedDelegation, }); @@ -1151,7 +1150,7 @@ describe('NativeTokenStreamingEnforcer', () => { }; // Get delegation hash - // Check available amount before redemption + // Check available amount before redemption const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ delegation: signedDelegation, @@ -1240,7 +1239,7 @@ describe('NativeTokenStreamingEnforcer', () => { }), }; - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ delegation: signedDelegation, }); @@ -1312,7 +1311,7 @@ describe('NativeTokenStreamingEnforcer', () => { }), }; - const beforeResult = + const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ delegation: signedDelegation, }); @@ -1402,7 +1401,7 @@ describe('Generic caveat utils functionality', () => { }; // Get delegation hash - // Use generic method + // Use generic method const result = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ delegation: signedDelegation, @@ -1412,46 +1411,6 @@ describe('Generic caveat utils functionality', () => { expect(result.isNewPeriod).toBe(true); expect(result.currentPeriod).toBeDefined(); }); - - test('should work with custom delegation manager', async () => { - const customDelegationManager = randomAddress(); - const periodAmount = parseEther('2'); - const periodDuration = 3600; - - // Create delegation with custom delegation manager - const delegation = createDelegation({ - to: bobSmartAccount.address, - from: aliceSmartAccount.address, - caveats: createCaveatBuilder(aliceSmartAccount.environment).addCaveat( - 'erc20PeriodTransfer', - { - tokenAddress: erc20TokenAddress, - periodAmount, - periodDuration, - startDate: currentTime, - }, - ), - }); - - const signedDelegation = { - ...delegation, - signature: await aliceSmartAccount.signDelegation({ - delegation, - }), - }; - - // Get delegation hash - // This should fail with custom delegation manager since it's not the real one - const result = - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegation: signedDelegation,delegationManager: customDelegationManager, - }); - - // Should still return a result, check structure - expect(result).toHaveProperty('availableAmount'); - expect(result).toHaveProperty('isNewPeriod'); - expect(result).toHaveProperty('currentPeriod'); - }); }); describe('Individual action functions vs client extension methods', () => { @@ -1478,8 +1437,9 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const params = { - delegation: signedDelegation,}; + const params = { + delegation: signedDelegation, + }; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1521,8 +1481,9 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const params = { - delegation: signedDelegation,}; + const params = { + delegation: signedDelegation, + }; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1563,9 +1524,11 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 + signedDelegation.caveats[0].args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 + const params = { - delegation: signedDelegation,}; + delegation: signedDelegation, + }; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1604,8 +1567,9 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const params = { - delegation: signedDelegation,}; + const params = { + delegation: signedDelegation, + }; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1646,8 +1610,9 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const params = { - delegation: signedDelegation,}; + const params = { + delegation: signedDelegation, + }; // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -1662,49 +1627,4 @@ describe('Individual action functions vs client extension methods', () => { expect(clientResult).toEqual(functionResult); expect(clientResult.availableAmount).toBe(initialAmount); }); - - test('should work with custom parameters (delegation manager and enforcer address)', async () => { - const periodAmount = parseEther('2.5'); - const periodDuration = 3600; - - const delegation = createDelegation({ - to: bobSmartAccount.address, - from: aliceSmartAccount.address, - caveats: createCaveatBuilder(aliceSmartAccount.environment).addCaveat( - 'erc20PeriodTransfer', - { - tokenAddress: erc20TokenAddress, - periodAmount, - periodDuration, - startDate: currentTime, - }, - ), - }); - - const signedDelegation = { - ...delegation, - signature: await aliceSmartAccount.signDelegation({ delegation }), - }; - - const customParams = { - delegation: signedDelegation,delegationManager: aliceSmartAccount.environment.DelegationManager, - enforcerAddress: - aliceSmartAccount.environment.caveatEnforcers - .ERC20PeriodTransferEnforcer, - }; - - // Test both approaches with custom parameters - const [clientResult, functionResult] = await Promise.all([ - caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(customParams), - getErc20PeriodTransferEnforcerAvailableAmount( - publicClient, - aliceSmartAccount.environment, - customParams, - ), - ]); - - expect(clientResult).toEqual(functionResult); - expect(clientResult.availableAmount).toBe(periodAmount); - expect(clientResult.isNewPeriod).toBe(true); - }); }); From b61ee8c700d56e46b37291944a75697f0ead5ab8 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Fri, 8 Aug 2025 10:56:48 +1200 Subject: [PATCH 15/19] Improve internals of getCaveatAvailableAmount actions. Improve validation messages when caveats are not found, or duplicates are found. --- .../src/actions/getCaveatAvailableAmount.ts | 97 ++++++++++++------- .../actions/getCaveatAvailableAmount.test.ts | 4 +- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index d2e2d95d..6c75e304 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -39,23 +39,26 @@ export type StreamingResult = { * @throws Error if no matching caveat is found. * @throws Error if multiple matching caveats are found. */ -function findMatchingCaveat( - delegation: Delegation, - enforcerAddress: Address, -): { terms: Hex; args: Hex } { +function findMatchingCaveat({ + delegation, + enforcerAddress, + enforcerName, +}: { + delegation: Delegation; + enforcerAddress: Address; + enforcerName: keyof DeleGatorEnvironment['caveatEnforcers']; +}): { terms: Hex; args: Hex } { const matchingCaveats = delegation.caveats.filter( (caveat) => caveat.enforcer.toLowerCase() === enforcerAddress.toLowerCase(), ); if (matchingCaveats.length === 0) { - throw new Error( - `No caveat found with enforcer matching ${enforcerAddress}`, - ); + throw new Error(`No caveat found with enforcer matching ${enforcerName}`); } if (matchingCaveats.length > 1) { throw new Error( - `Multiple caveats found with enforcer matching ${enforcerAddress}`, + `Multiple caveats found with enforcer matching ${enforcerName}`, ); } @@ -90,10 +93,13 @@ function getDelegationManager(environment: DeleGatorEnvironment): Address { * @param environment - The delegator environment. * @returns The enforcer address. */ -function getEnforcerAddress( - enforcerName: keyof DeleGatorEnvironment['caveatEnforcers'], - environment: DeleGatorEnvironment, -): Address { +function getEnforcerAddress({ + enforcerName, + environment, +}: { + enforcerName: keyof DeleGatorEnvironment['caveatEnforcers']; + environment: DeleGatorEnvironment; +}): Address { const enforcerAddress = environment.caveatEnforcers[enforcerName]; if (!enforcerAddress) { throw new Error(`${enforcerName} not found in environment`); @@ -115,14 +121,20 @@ export async function getErc20PeriodTransferEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: CaveatEnforcerParams, ): Promise { + const enforcerName = 'ERC20PeriodTransferEnforcer'; + const delegationManager = getDelegationManager(environment); - const enforcerAddress = getEnforcerAddress( - 'ERC20PeriodTransferEnforcer', + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); const delegationHash = getDelegationHashOffchain(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ client, @@ -146,14 +158,19 @@ export async function getErc20StreamingEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: CaveatEnforcerParams, ): Promise { + const enforcerName = 'ERC20StreamingEnforcer'; const delegationManager = getDelegationManager(environment); - const enforcerAddress = getEnforcerAddress( - 'ERC20StreamingEnforcer', + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); const delegationHash = getDelegationHashOffchain(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return ERC20StreamingEnforcer.read.getAvailableAmount({ client, @@ -177,17 +194,19 @@ export async function getMultiTokenPeriodEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: CaveatEnforcerParams, ): Promise { + const enforcerName = 'MultiTokenPeriodEnforcer'; const delegationManager = getDelegationManager(environment); - const enforcerAddress = getEnforcerAddress( - 'MultiTokenPeriodEnforcer', + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); const delegationHash = getDelegationHashOffchain(params.delegation); - const { terms, args } = findMatchingCaveat( - params.delegation, + const { terms, args } = findMatchingCaveat({ + delegation: params.delegation, enforcerAddress, - ); + enforcerName, + }); return MultiTokenPeriodEnforcer.read.getAvailableAmount({ client, @@ -212,14 +231,19 @@ export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: CaveatEnforcerParams, ): Promise { + const enforcerName = 'NativeTokenPeriodTransferEnforcer'; const delegationManager = getDelegationManager(environment); - const enforcerAddress = getEnforcerAddress( - 'NativeTokenPeriodTransferEnforcer', + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); const delegationHash = getDelegationHashOffchain(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ client, @@ -243,14 +267,19 @@ export async function getNativeTokenStreamingEnforcerAvailableAmount( environment: DeleGatorEnvironment, params: CaveatEnforcerParams, ): Promise { + const enforcerName = 'NativeTokenStreamingEnforcer'; const delegationManager = getDelegationManager(environment); - const enforcerAddress = getEnforcerAddress( - 'NativeTokenStreamingEnforcer', + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); const delegationHash = getDelegationHashOffchain(params.delegation); - const { terms } = findMatchingCaveat(params.delegation, enforcerAddress); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return NativeTokenStreamingEnforcer.read.getAvailableAmount({ client, diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts index 68f144f7..7dc8ed5a 100644 --- a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -143,7 +143,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { delegation: delegationWithoutMatchingCaveat, }), ).rejects.toThrow( - 'No caveat found with enforcer matching 0x4567890123456789012345678901234567890123', + 'No caveat found with enforcer matching MultiTokenPeriodEnforcer', ); }); @@ -169,7 +169,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { delegation: delegationWithMultipleMatchingCaveats, }), ).rejects.toThrow( - 'Multiple caveats found with enforcer matching 0x4567890123456789012345678901234567890123', + 'Multiple caveats found with enforcer matching MultiTokenPeriodEnforcer', ); }); }); From 23ba21c03be6d34555bf9d6a23adb3e9b3bf194f Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:12:30 +1200 Subject: [PATCH 16/19] Improve action tests, remove client tests. --- .../test/actions/caveatEnforcerClient.test.ts | 177 --------- .../actions/getCaveatAvailableAmount.test.ts | 372 +++++++++++++++++- 2 files changed, 351 insertions(+), 198 deletions(-) delete mode 100644 packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts deleted file mode 100644 index f3262ceb..00000000 --- a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { createPublicClient, http } from 'viem'; -import { mainnet } from 'viem/chains'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; - -import { - createCaveatEnforcerClient, - type CaveatEnforcerClient, -} from '../../src/actions/getCaveatAvailableAmount'; -import { getDelegationHashOffchain } from '../../src/delegation'; -import type { DeleGatorEnvironment, Delegation } from '../../src/types'; - -// Mock the contract read functions -vi.mock( - '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', - () => ({ - read: vi.fn(), - }), -); - -vi.mock( - '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', - () => ({ - read: vi.fn(), - }), -); - -describe('CaveatEnforcerClient', () => { - let client: CaveatEnforcerClient; - let environment: DeleGatorEnvironment; - let delegation: Delegation; - - beforeEach(() => { - const baseClient = createPublicClient({ - chain: mainnet, - transport: http(), - }); - - environment = { - DelegationManager: '0x1234567890123456789012345678901234567890', - EntryPoint: '0x2345678901234567890123456789012345678901', - SimpleFactory: '0x3456789012345678901234567890123456789012', - implementations: {}, - caveatEnforcers: { - MultiTokenPeriodEnforcer: '0x4567890123456789012345678901234567890123', - ERC20PeriodTransferEnforcer: - '0x5678901234567890123456789012345678901234', - ERC20StreamingEnforcer: '0x6789012345678901234567890123456789012345', - NativeTokenPeriodTransferEnforcer: - '0x7890123456789012345678901234567890123456', - NativeTokenStreamingEnforcer: - '0x8901234567890123456789012345678901234567', - }, - }; - - client = createCaveatEnforcerClient({ - client: baseClient, - environment, - }); - - delegation = { - delegate: '0x1111111111111111111111111111111111111111', - delegator: '0x2222222222222222222222222222222222222222', - authority: - '0x0000000000000000000000000000000000000000000000000000000000000000', - caveats: [ - { - enforcer: '0x4567890123456789012345678901234567890123', - terms: '0x1234567890abcdef', - args: '0xfedcba0987654321', - }, - ], - salt: '0x0000000000000000000000000000000000000000000000000000000000000001', - signature: '0xabcdef1234567890', - }; - }); - - describe('getMultiTokenPeriodEnforcerAvailableAmount with delegation', () => { - it('should successfully get available amount using client', async () => { - const mockResult = { - availableAmount: 1000n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const { read } = await import( - '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount' - ); - vi.mocked(read).mockResolvedValue(mockResult); - - const result = await client.getMultiTokenPeriodEnforcerAvailableAmount({ - delegation, - }); - - const delegationHash = getDelegationHashOffchain(delegation); - - expect(result).toEqual(mockResult); - expect(read).toHaveBeenCalledWith({ - client: expect.any(Object), - contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash, - delegationManager: environment.DelegationManager, - terms: delegation.caveats[0]?.terms, - args: delegation.caveats[0]?.args, - }); - }); - }); - - describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { - it('should successfully get available amount using client', async () => { - const delegationWithErc20Caveat: Delegation = { - ...delegation, - caveats: [ - { - enforcer: '0x5678901234567890123456789012345678901234', - terms: '0x1234567890abcdef', - args: '0x', - }, - ], - }; - - const mockResult = { - availableAmount: 1000n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const { read } = await import( - '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount' - ); - vi.mocked(read).mockResolvedValue(mockResult); - - const result = await client.getErc20PeriodTransferEnforcerAvailableAmount( - { - delegation: delegationWithErc20Caveat, - }, - ); - - const delegationHash = getDelegationHashOffchain( - delegationWithErc20Caveat, - ); - - expect(result).toEqual(mockResult); - expect(read).toHaveBeenCalledWith({ - client: expect.any(Object), - contractAddress: - environment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash, - delegationManager: environment.DelegationManager, - terms: delegationWithErc20Caveat.caveats[0]?.terms, - }); - }); - }); - - describe('Client type', () => { - it('should have all expected methods', () => { - expect(client).toHaveProperty( - 'getMultiTokenPeriodEnforcerAvailableAmount', - ); - expect(client).toHaveProperty( - 'getErc20PeriodTransferEnforcerAvailableAmount', - ); - expect(client).toHaveProperty('getErc20StreamingEnforcerAvailableAmount'); - expect(client).toHaveProperty( - 'getNativeTokenPeriodTransferEnforcerAvailableAmount', - ); - expect(client).toHaveProperty( - 'getNativeTokenStreamingEnforcerAvailableAmount', - ); - }); - - it('should still have base client methods', () => { - expect(client).toHaveProperty('getBlockNumber'); - expect(client).toHaveProperty('getBalance'); - expect(client).toHaveProperty('readContract'); - }); - }); -}); diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts index 7dc8ed5a..87687882 100644 --- a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -49,7 +49,7 @@ vi.mock( }), ); -describe('Delegation-based Caveat Enforcer Actions', () => { +describe('getCaveatAvailableAmount', () => { let client: PublicClient; let environment: DeleGatorEnvironment; let delegation: Delegation; @@ -94,8 +94,8 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }; }); - describe('getMultiTokenPeriodEnforcerAvailableAmount with delegation', () => { - it('should successfully get available amount with matching caveat', async () => { + describe('getMultiTokenPeriodEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { const mockResult = { availableAmount: 1000n, isNewPeriod: true, @@ -126,7 +126,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { }); }); - it('should throw Error when no matching caveat is found', async () => { + it('throws an Error when no matching caveat is found', async () => { const delegationWithoutMatchingCaveat: Delegation = { ...delegation, caveats: [ @@ -147,7 +147,7 @@ describe('Delegation-based Caveat Enforcer Actions', () => { ); }); - it('should throw Error when multiple matching caveats are found', async () => { + it('throws an Error when multiple matching caveats are found', async () => { const delegationWithMultipleMatchingCaveats: Delegation = { ...delegation, caveats: [ @@ -172,10 +172,43 @@ describe('Delegation-based Caveat Enforcer Actions', () => { 'Multiple caveats found with enforcer matching MultiTokenPeriodEnforcer', ); }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + MultiTokenPeriodEnforcer: undefined as any, + }, + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow('MultiTokenPeriodEnforcer not found in environment'); + }); }); - describe('getErc20PeriodTransferEnforcerAvailableAmount with delegation', () => { - it('should successfully get available amount with matching caveat', async () => { + describe('getErc20PeriodTransferEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { const delegationWithErc20Caveat: Delegation = { ...delegation, caveats: [ @@ -214,10 +247,90 @@ describe('Delegation-based Caveat Enforcer Actions', () => { terms: delegationWithErc20Caveat.caveats[0]?.terms, }); }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching ERC20PeriodTransferEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x5678901234567890123456789012345678901234', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x5678901234567890123456789012345678901234', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching ERC20PeriodTransferEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + ERC20PeriodTransferEnforcer: undefined as any, + }, + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow('ERC20PeriodTransferEnforcer not found in environment'); + }); }); - describe('getErc20StreamingEnforcerAvailableAmount with delegation', () => { - it('should successfully get available amount with matching caveat', async () => { + describe('getErc20StreamingEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { const delegationWithErc20StreamingCaveat: Delegation = { ...delegation, caveats: [ @@ -255,10 +368,90 @@ describe('Delegation-based Caveat Enforcer Actions', () => { terms: delegationWithErc20StreamingCaveat.caveats[0]?.terms, }); }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching ERC20StreamingEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x6789012345678901234567890123456789012345', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x6789012345678901234567890123456789012345', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching ERC20StreamingEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + ERC20StreamingEnforcer: undefined as any, + }, + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow('ERC20StreamingEnforcer not found in environment'); + }); }); - describe('getNativeTokenPeriodTransferEnforcerAvailableAmount with delegation', () => { - it('should successfully get available amount with matching caveat', async () => { + describe('getNativeTokenPeriodTransferEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { const delegationWithNativeTokenCaveat: Delegation = { ...delegation, caveats: [ @@ -299,10 +492,100 @@ describe('Delegation-based Caveat Enforcer Actions', () => { terms: delegationWithNativeTokenCaveat.caveats[0]?.terms, }); }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environment, + { + delegation: delegationWithoutMatchingCaveat, + }, + ), + ).rejects.toThrow( + 'No caveat found with enforcer matching NativeTokenPeriodTransferEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x7890123456789012345678901234567890123456', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x7890123456789012345678901234567890123456', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environment, + { + delegation: delegationWithMultipleMatchingCaveats, + }, + ), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching NativeTokenPeriodTransferEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + NativeTokenPeriodTransferEnforcer: undefined as any, + }, + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow( + 'NativeTokenPeriodTransferEnforcer not found in environment', + ); + }); }); - describe('getNativeTokenStreamingEnforcerAvailableAmount with delegation', () => { - it('should successfully get available amount with matching caveat', async () => { + describe('getNativeTokenStreamingEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { const delegationWithNativeTokenStreamingCaveat: Delegation = { ...delegation, caveats: [ @@ -341,17 +624,62 @@ describe('Delegation-based Caveat Enforcer Actions', () => { terms: delegationWithNativeTokenStreamingCaveat.caveats[0]?.terms, }); }); - }); - describe('Error handling', () => { - it('should throw error when delegation manager is not found in environment', async () => { + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getNativeTokenStreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching NativeTokenStreamingEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x8901234567890123456789012345678901234567', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x8901234567890123456789012345678901234567', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getNativeTokenStreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching NativeTokenStreamingEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { const environmentWithoutDelegationManager = { ...environment, DelegationManager: undefined as any, }; await expect( - getMultiTokenPeriodEnforcerAvailableAmount( + getNativeTokenStreamingEnforcerAvailableAmount( client, environmentWithoutDelegationManager, { delegation }, @@ -359,22 +687,24 @@ describe('Delegation-based Caveat Enforcer Actions', () => { ).rejects.toThrow('Delegation manager address not found'); }); - it('should throw error when enforcer is not found in environment', async () => { + it('throws an Error when enforcer is not found in environment', async () => { const environmentWithoutEnforcer = { ...environment, caveatEnforcers: { ...environment.caveatEnforcers, - MultiTokenPeriodEnforcer: undefined as any, + NativeTokenStreamingEnforcer: undefined as any, }, }; await expect( - getMultiTokenPeriodEnforcerAvailableAmount( + getNativeTokenStreamingEnforcerAvailableAmount( client, environmentWithoutEnforcer, { delegation }, ), - ).rejects.toThrow('MultiTokenPeriodEnforcer not found in environment'); + ).rejects.toThrow( + 'NativeTokenStreamingEnforcer not found in environment', + ); }); }); }); From 246694e6be79af53788332fa3affaaa8d9927e52 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:21:54 +1200 Subject: [PATCH 17/19] Move caveatEnforcerClient into it's own file --- .../src/actions/caveatEnforcerClient.ts | 36 ++ .../src/actions/getCaveatAvailableAmount.ts | 24 - .../delegation-toolkit/src/actions/index.ts | 1 - packages/delegation-toolkit/src/index.ts | 3 +- .../test/actions/caveatEnforcerClient.test.ts | 517 ++++++++++++++++++ .../test/caveatUtils.test.ts | 253 +-------- 6 files changed, 559 insertions(+), 275 deletions(-) create mode 100644 packages/delegation-toolkit/src/actions/caveatEnforcerClient.ts create mode 100644 packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts diff --git a/packages/delegation-toolkit/src/actions/caveatEnforcerClient.ts b/packages/delegation-toolkit/src/actions/caveatEnforcerClient.ts new file mode 100644 index 00000000..5fc55cdb --- /dev/null +++ b/packages/delegation-toolkit/src/actions/caveatEnforcerClient.ts @@ -0,0 +1,36 @@ +import type { PublicClient } from 'viem'; + +import type { DeleGatorEnvironment } from '../types'; +import { + caveatEnforcerActions, + type CaveatEnforcerParams, + type PeriodTransferResult, + type StreamingResult, +} from './getCaveatAvailableAmount'; + +/** + * Type for client extended with caveat enforcer actions. + */ +export type CaveatEnforcerClient = PublicClient & + ReturnType>; + +/** + * Create a viem client extended with caveat enforcer actions. + * + * @param params - The parameters object. + * @param params.client - The viem public client. + * @param params.environment - The delegator environment. + * @returns The extended client with caveat enforcer actions. + */ +export function createCaveatEnforcerClient({ + client, + environment, +}: { + client: PublicClient; + environment: DeleGatorEnvironment; +}): CaveatEnforcerClient { + return client.extend(caveatEnforcerActions({ environment })); +} + +// Re-export types for convenience +export type { CaveatEnforcerParams, PeriodTransferResult, StreamingResult }; diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 6c75e304..7fec7d83 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -380,27 +380,3 @@ export const caveatEnforcerActions = ); }, }); - -/** - * Type for client extended with caveat enforcer actions. - */ -export type CaveatEnforcerClient = PublicClient & - ReturnType>; - -/** - * Create a viem client extended with caveat enforcer actions. - * - * @param params - The parameters object. - * @param params.client - The viem public client. - * @param params.environment - The delegator environment. - * @returns The extended client with caveat enforcer actions. - */ -export function createCaveatEnforcerClient({ - client, - environment, -}: { - client: PublicClient; - environment: DeleGatorEnvironment; -}): CaveatEnforcerClient { - return client.extend(caveatEnforcerActions({ environment })); -} diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index e6b55da2..54886165 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -17,7 +17,6 @@ export { // Result types type PeriodTransferResult, type StreamingResult, - } from './getCaveatAvailableAmount'; export { isValid7702Implementation } from './isValid7702Implementation'; diff --git a/packages/delegation-toolkit/src/index.ts b/packages/delegation-toolkit/src/index.ts index 2afbe9cd..d7a830e6 100644 --- a/packages/delegation-toolkit/src/index.ts +++ b/packages/delegation-toolkit/src/index.ts @@ -62,8 +62,7 @@ export * as contracts from './contracts'; export * as actions from './actions'; -// Export the caveat enforcer client export { createCaveatEnforcerClient, type CaveatEnforcerClient, -} from './actions/getCaveatAvailableAmount'; +} from './actions/caveatEnforcerClient'; diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts new file mode 100644 index 00000000..34cf8dfb --- /dev/null +++ b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts @@ -0,0 +1,517 @@ +import { type PublicClient, createPublicClient, http, type Hex } from 'viem'; +import { sepolia } from 'viem/chains'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { randomAddress, randomBytes } from '../utils'; +import { + createCaveatEnforcerClient, + type CaveatEnforcerClient, + type CaveatEnforcerParams, +} from '../../src/actions/caveatEnforcerClient'; +import { + getErc20PeriodTransferEnforcerAvailableAmount, + getErc20StreamingEnforcerAvailableAmount, + getMultiTokenPeriodEnforcerAvailableAmount, + getNativeTokenPeriodTransferEnforcerAvailableAmount, + getNativeTokenStreamingEnforcerAvailableAmount, +} from '../../src/actions/getCaveatAvailableAmount'; +import { + ERC20PeriodTransferEnforcer, + MultiTokenPeriodEnforcer, + NativeTokenPeriodTransferEnforcer, + ERC20StreamingEnforcer, + NativeTokenStreamingEnforcer, +} from '../../src/contracts'; +import { getDelegationHashOffchain } from '../../src/delegation'; +import type { DeleGatorEnvironment } from '../../src/types'; + +// Helper function to generate random bytes32 +const randomBytes32 = (): Hex => randomBytes(32); + +describe('Caveat Enforcer Client', () => { + let publicClient: PublicClient; + let mockEnvironment: DeleGatorEnvironment; + let caveatClient: CaveatEnforcerClient; + + const createParams = (caveat: { + enforcer: string; + terms: Hex; + args?: Hex; + }): CaveatEnforcerParams => { + const enforcer = mockEnvironment.caveatEnforcers[caveat.enforcer]; + if (!enforcer) { + throw new Error(`Enforcer ${caveat.enforcer} not found`); + } + + const { terms, args = '0x' } = caveat; + + return { + delegation: { + delegator: randomAddress(), + delegate: randomAddress(), + authority: randomBytes32(), + caveats: [ + { + enforcer, + terms, + args, + }, + ], + salt: '0x00', + signature: randomBytes32(), + }, + }; + }; + + beforeEach(() => { + publicClient = createPublicClient({ + chain: sepolia, + transport: http(), + }); + + mockEnvironment = { + SimpleFactory: randomAddress(), + EntryPoint: randomAddress(), + DelegationManager: randomAddress(), + implementations: { + HybridDeleGatorImpl: randomAddress(), + MultiSigDeleGatorImpl: randomAddress(), + Stateless7702DeleGatorImpl: randomAddress(), + }, + caveatEnforcers: { + ERC20PeriodTransferEnforcer: randomAddress(), + MultiTokenPeriodEnforcer: randomAddress(), + NativeTokenPeriodTransferEnforcer: randomAddress(), + ERC20StreamingEnforcer: randomAddress(), + NativeTokenStreamingEnforcer: randomAddress(), + // Add other enforcers as needed + }, + } as DeleGatorEnvironment; + + // Create caveat client for tests + caveatClient = createCaveatEnforcerClient({ + client: publicClient, + environment: mockEnvironment, + }); + + // Clear all mocks before each test + vi.clearAllMocks(); + }); + + it('should route to correct enforcer method - ERC20PeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 100n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const enforcer = + mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer; + + if (!enforcer) { + throw new Error('ERC20PeriodTransferEnforcer not found'); + } + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: + mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer, + delegationHash, + delegationManager: mockEnvironment.DelegationManager, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - MultiTokenPeriodEnforcer', async () => { + const mockResult = { + availableAmount: 200n, + isNewPeriod: false, + currentPeriod: 2n, + }; + + const getAvailableAmountSpy = vi + .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + const args = '0x5678'; + + const params = createParams({ + enforcer: 'MultiTokenPeriodEnforcer', + terms, + args, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: mockEnvironment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash, + delegationManager: mockEnvironment.DelegationManager, + terms, + args, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - ERC20StreamingEnforcer', async () => { + const mockResult = { + availableAmount: 500n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20StreamingEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getErc20StreamingEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: mockEnvironment.caveatEnforcers.ERC20StreamingEnforcer, + delegationManager: mockEnvironment.DelegationManager, + delegationHash, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - NativeTokenPeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 300n, + isNewPeriod: true, + currentPeriod: 3n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'NativeTokenPeriodTransferEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount( + params, + ); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: + mockEnvironment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, + delegationHash, + delegationManager: mockEnvironment.DelegationManager, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - NativeTokenStreamingEnforcer', async () => { + const mockResult = { + availableAmount: 600n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'NativeTokenStreamingEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: + mockEnvironment.caveatEnforcers.NativeTokenStreamingEnforcer, + delegationManager: mockEnvironment.DelegationManager, + delegationHash, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + describe('Error handling', () => { + it('should throw error if delegation manager not found', async () => { + const environmentWithoutDelegationManager = { + ...mockEnvironment, + DelegationManager: undefined, + } as unknown as DeleGatorEnvironment; + + const clientWithoutDelegationManager = createCaveatEnforcerClient({ + client: publicClient, + environment: environmentWithoutDelegationManager, + }); + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234' as Hex, + }); + + await expect( + clientWithoutDelegationManager.getErc20PeriodTransferEnforcerAvailableAmount( + params, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('should throw error if enforcer not found in environment', async () => { + const environmentWithoutEnforcer = { + ...mockEnvironment, + caveatEnforcers: { + ...mockEnvironment.caveatEnforcers, + ERC20PeriodTransferEnforcer: undefined, + }, + } as unknown as DeleGatorEnvironment; + + const clientWithoutEnforcer = createCaveatEnforcerClient({ + client: publicClient, + environment: environmentWithoutEnforcer, + }); + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234' as Hex, + }); + + await expect( + clientWithoutEnforcer.getErc20PeriodTransferEnforcerAvailableAmount( + params, + ), + ).rejects.toThrow('ERC20PeriodTransferEnforcer not found in environment'); + }); + }); + + describe('Individual Functions vs Client Extension', () => { + it('should return identical results for ERC20PeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 100n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms, + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params), + getErc20PeriodTransferEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for ERC20StreamingEnforcer', async () => { + const mockResult = { + availableAmount: 500n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'ERC20StreamingEnforcer', + terms: '0x1234' as Hex, + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getErc20StreamingEnforcerAvailableAmount(params), + getErc20StreamingEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for MultiTokenPeriodEnforcer', async () => { + const mockResult = { + availableAmount: 200n, + isNewPeriod: false, + currentPeriod: 2n, + }; + + const getAvailableAmountSpy = vi + .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'MultiTokenPeriodEnforcer', + terms: '0x1234' as Hex, + args: '0x5678' as Hex, + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getMultiTokenPeriodEnforcerAvailableAmount(params), + getMultiTokenPeriodEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for NativeTokenPeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 300n, + isNewPeriod: true, + currentPeriod: 3n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'NativeTokenPeriodTransferEnforcer', + terms: '0x9abc', + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount( + params, + ), + getNativeTokenPeriodTransferEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for NativeTokenStreamingEnforcer', async () => { + const mockResult = { + availableAmount: 600n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'NativeTokenStreamingEnforcer', + terms: '0x1234', + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getNativeTokenStreamingEnforcerAvailableAmount(params), + getNativeTokenStreamingEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should handle errors identically', async () => { + const mockError = new Error('Contract call failed'); + + vi.spyOn( + ERC20PeriodTransferEnforcer.read, + 'getAvailableAmount', + ).mockRejectedValue(mockError); + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234', + }); + + // Both approaches should throw the same error + await expect( + caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params), + ).rejects.toThrow('Contract call failed'); + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ).rejects.toThrow('Contract call failed'); + }); + }); +}); diff --git a/packages/delegation-toolkit/test/caveatUtils.test.ts b/packages/delegation-toolkit/test/caveatUtils.test.ts index 825323f8..69914016 100644 --- a/packages/delegation-toolkit/test/caveatUtils.test.ts +++ b/packages/delegation-toolkit/test/caveatUtils.test.ts @@ -4,17 +4,17 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { randomAddress, randomBytes } from './utils'; import { - createCaveatEnforcerClient, getErc20PeriodTransferEnforcerAvailableAmount, getErc20StreamingEnforcerAvailableAmount, getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, + type CaveatEnforcerParams, } from '../src/actions/getCaveatAvailableAmount'; -import type { - CaveatEnforcerParams, - CaveatEnforcerClient, -} from '../src/actions/getCaveatAvailableAmount'; +import { + createCaveatEnforcerClient, + type CaveatEnforcerClient, +} from '../src/actions/caveatEnforcerClient'; import { ERC20PeriodTransferEnforcer, MultiTokenPeriodEnforcer, @@ -22,7 +22,6 @@ import { ERC20StreamingEnforcer, NativeTokenStreamingEnforcer, } from '../src/contracts'; -import { getDelegationHashOffchain } from '../src/delegation'; import type { DeleGatorEnvironment } from '../src/types'; // Helper function to generate random bytes32 @@ -88,7 +87,6 @@ describe('Caveat Contract Methods', () => { }, } as DeleGatorEnvironment; - // Create caveat client for tests caveatClient = createCaveatEnforcerClient({ client: publicClient, environment: mockEnvironment, @@ -285,247 +283,6 @@ describe('Caveat Contract Methods', () => { }); }); - describe('Caveat Enforcer Client', () => { - it('should route to correct enforcer method - ERC20PeriodTransferEnforcer', async () => { - const mockResult = { - availableAmount: 100n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const enforcer = - mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer; - - if (!enforcer) { - throw new Error('ERC20PeriodTransferEnforcer not found'); - } - - const terms = '0x1234'; - - const params = createParams({ - enforcer: 'ERC20PeriodTransferEnforcer', - terms, - }); - - const delegationHash = getDelegationHashOffchain(params.delegation); - - const result = - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount( - params, - ); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - MultiTokenPeriodEnforcer', async () => { - const mockResult = { - availableAmount: 200n, - isNewPeriod: false, - currentPeriod: 2n, - }; - - const getAvailableAmountSpy = vi - .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const terms = '0x1234'; - const args = '0x5678'; - - const params = createParams({ - enforcer: 'MultiTokenPeriodEnforcer', - terms, - args, - }); - - const delegationHash = getDelegationHashOffchain(params.delegation); - - const result = - await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms, - args, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - ERC20StreamingEnforcer', async () => { - const mockResult = { - availableAmount: 500n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const terms = '0x1234'; - - const params = createParams({ - enforcer: 'ERC20StreamingEnforcer', - terms, - }); - - const delegationHash = getDelegationHashOffchain(params.delegation); - - const result = - await caveatClient.getErc20StreamingEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: mockEnvironment.caveatEnforcers.ERC20StreamingEnforcer, - delegationManager: mockEnvironment.DelegationManager, - delegationHash, - terms, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - NativeTokenPeriodTransferEnforcer', async () => { - const mockResult = { - availableAmount: 300n, - isNewPeriod: true, - currentPeriod: 3n, - }; - - const getAvailableAmountSpy = vi - .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const terms = '0x1234'; - - const params = createParams({ - enforcer: 'NativeTokenPeriodTransferEnforcer', - terms, - }); - - const delegationHash = getDelegationHashOffchain(params.delegation); - - const result = - await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount( - params, - ); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, - delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - NativeTokenStreamingEnforcer', async () => { - const mockResult = { - availableAmount: 600n, - }; - - const getAvailableAmountSpy = vi - .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const terms = '0x1234'; - - const params = createParams({ - enforcer: 'NativeTokenStreamingEnforcer', - terms, - }); - - const delegationHash = getDelegationHashOffchain(params.delegation); - - const result = - await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount( - params, - ); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.NativeTokenStreamingEnforcer, - delegationManager: mockEnvironment.DelegationManager, - delegationHash, - terms, - }); - - expect(result).toEqual(mockResult); - }); - - describe('Error handling', () => { - it('should throw error if delegation manager not found', async () => { - const environmentWithoutDelegationManager = { - ...mockEnvironment, - DelegationManager: undefined, - } as unknown as DeleGatorEnvironment; - - const clientWithoutDelegationManager = createCaveatEnforcerClient({ - client: publicClient, - environment: environmentWithoutDelegationManager, - }); - - const params = createParams({ - enforcer: 'ERC20PeriodTransferEnforcer', - terms: '0x1234' as Hex, - }); - - await expect( - clientWithoutDelegationManager.getErc20PeriodTransferEnforcerAvailableAmount( - params, - ), - ).rejects.toThrow('Delegation manager address not found'); - }); - - it('should throw error if enforcer not found in environment', async () => { - const environmentWithoutEnforcer = { - ...mockEnvironment, - caveatEnforcers: { - ...mockEnvironment.caveatEnforcers, - ERC20PeriodTransferEnforcer: undefined, - }, - } as unknown as DeleGatorEnvironment; - - const clientWithoutEnforcer = createCaveatEnforcerClient({ - client: publicClient, - environment: environmentWithoutEnforcer, - }); - - const params = createParams({ - enforcer: 'ERC20PeriodTransferEnforcer', - terms: '0x1234' as Hex, - }); - - await expect( - clientWithoutEnforcer.getErc20PeriodTransferEnforcerAvailableAmount( - params, - ), - ).rejects.toThrow( - 'ERC20PeriodTransferEnforcer not found in environment', - ); - }); - }); - }); - describe('Individual Functions vs Client Extension', () => { it('should return identical results for ERC20PeriodTransferEnforcer', async () => { const mockResult = { From 0b03514a2887a1884947dd533bf9997768df1e86 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:33:17 +1200 Subject: [PATCH 18/19] Fix jsdoc comments in getCaveatAvailableAmount.ts --- .../src/actions/getCaveatAvailableAmount.ts | 35 ++++++++++++++++--- .../test/actions/caveatEnforcerClient.test.ts | 2 +- .../test/caveatUtils.test.ts | 8 ++--- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index 7fec7d83..b52ad8d2 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -33,8 +33,10 @@ export type StreamingResult = { /** * Finds a caveat that matches the specified enforcer address. - * @param delegation - The delegation to search. - * @param enforcerAddress - The enforcer address to match. + * @param config - The configuration object. + * @param config.delegation - The delegation to search. + * @param config.enforcerAddress - The enforcer address to match. + * @param config.enforcerName - The name of the enforcer. * @returns The matching caveat. * @throws Error if no matching caveat is found. * @throws Error if multiple matching caveats are found. @@ -89,8 +91,9 @@ function getDelegationManager(environment: DeleGatorEnvironment): Address { /** * Gets the enforcer address from environment. * - * @param enforcerName - The name of the enforcer. - * @param environment - The delegator environment. + * @param config - The configuration object. + * @param config.enforcerName - The name of the enforcer. + * @param config.environment - The delegator environment. * @returns The enforcer address. */ function getEnforcerAddress({ @@ -380,3 +383,27 @@ export const caveatEnforcerActions = ); }, }); + +/** + * Type for client extended with caveat enforcer actions. + */ +export type CaveatEnforcerClient = PublicClient & + ReturnType>; + +/** + * Create a viem client extended with caveat enforcer actions. + * + * @param params - The parameters object. + * @param params.client - The viem public client. + * @param params.environment - The delegator environment. + * @returns The extended client with caveat enforcer actions. + */ +export function createCaveatEnforcerClient({ + client, + environment, +}: { + client: PublicClient; + environment: DeleGatorEnvironment; +}): CaveatEnforcerClient { + return client.extend(caveatEnforcerActions({ environment })); +} diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts index 34cf8dfb..a858a19d 100644 --- a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts +++ b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts @@ -2,7 +2,6 @@ import { type PublicClient, createPublicClient, http, type Hex } from 'viem'; import { sepolia } from 'viem/chains'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { randomAddress, randomBytes } from '../utils'; import { createCaveatEnforcerClient, type CaveatEnforcerClient, @@ -24,6 +23,7 @@ import { } from '../../src/contracts'; import { getDelegationHashOffchain } from '../../src/delegation'; import type { DeleGatorEnvironment } from '../../src/types'; +import { randomAddress, randomBytes } from '../utils'; // Helper function to generate random bytes32 const randomBytes32 = (): Hex => randomBytes(32); diff --git a/packages/delegation-toolkit/test/caveatUtils.test.ts b/packages/delegation-toolkit/test/caveatUtils.test.ts index 69914016..d3347584 100644 --- a/packages/delegation-toolkit/test/caveatUtils.test.ts +++ b/packages/delegation-toolkit/test/caveatUtils.test.ts @@ -3,6 +3,10 @@ import { sepolia } from 'viem/chains'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { randomAddress, randomBytes } from './utils'; +import { + createCaveatEnforcerClient, + type CaveatEnforcerClient, +} from '../src/actions/caveatEnforcerClient'; import { getErc20PeriodTransferEnforcerAvailableAmount, getErc20StreamingEnforcerAvailableAmount, @@ -11,10 +15,6 @@ import { getNativeTokenStreamingEnforcerAvailableAmount, type CaveatEnforcerParams, } from '../src/actions/getCaveatAvailableAmount'; -import { - createCaveatEnforcerClient, - type CaveatEnforcerClient, -} from '../src/actions/caveatEnforcerClient'; import { ERC20PeriodTransferEnforcer, MultiTokenPeriodEnforcer, From 8a7843a79bb70b9a367bb1c44a752c11aec372ed Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:13:42 +1200 Subject: [PATCH 19/19] Remove duplicate createCaveatEnforcerClient and type --- .../src/actions/getCaveatAvailableAmount.ts | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index b52ad8d2..43bf4422 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -383,27 +383,3 @@ export const caveatEnforcerActions = ); }, }); - -/** - * Type for client extended with caveat enforcer actions. - */ -export type CaveatEnforcerClient = PublicClient & - ReturnType>; - -/** - * Create a viem client extended with caveat enforcer actions. - * - * @param params - The parameters object. - * @param params.client - The viem public client. - * @param params.environment - The delegator environment. - * @returns The extended client with caveat enforcer actions. - */ -export function createCaveatEnforcerClient({ - client, - environment, -}: { - client: PublicClient; - environment: DeleGatorEnvironment; -}): CaveatEnforcerClient { - return client.extend(caveatEnforcerActions({ environment })); -}