Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/delegation-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,33 @@ const terms = createERC20StreamingTerms({
});
```

#### `createERC20TokenPeriodTransferTerms(terms, options?)`

Creates terms for an ERC20TokenPeriodTransfer caveat that validates that ERC20 token transfers do not exceed a specified amount within a given time period.

**Parameters:**
- `terms: ERC20TokenPeriodTransferTerms`
- `tokenAddress: BytesLike` - The address of the ERC20 token.
- `periodAmount: bigint` - The maximum amount that can be transferred within each period.
- `periodDuration: number` - The duration of each period in seconds.
- `startDate: number` - Unix timestamp when the first period begins.
- `options?: EncodingOptions` - Optional encoding options

**Returns:** `Hex | Uint8Array` - 128-byte encoded terms (32 bytes per parameter)

**Example:**
```typescript
import { createERC20TokenPeriodTransferTerms } from '@metamask/delegation-core';

// Allow 100 tokens per day starting from a specific date
const terms = createERC20TokenPeriodTransferTerms({
tokenAddress: '0xA0b86a33E6441E74C65c6BF2A6d73B895B9b34A2',
periodAmount: 100n, // 100 tokens
periodDuration: 86400, // 24 hours in seconds
startDate: 1640995200 // 2022-01-01 00:00:00 UTC
});
```

### Delegation Utilities

#### `encodeDelegations(delegations)`
Expand Down
8 changes: 4 additions & 4 deletions packages/delegation-core/src/caveats/erc20Streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,18 @@ export function createERC20StreamingTerms(
throw new Error('Invalid tokenAddress: must be a valid address');
}

let tokenAddressHex: string;
let prefixedTokenAddressHex: string;

if (typeof tokenAddress === 'string') {
if (!isHexString(tokenAddress) || tokenAddress.length !== 42) {
throw new Error('Invalid tokenAddress: must be a valid address');
}
tokenAddressHex = tokenAddress.slice(2);
prefixedTokenAddressHex = tokenAddress;
} else {
if (tokenAddress.length !== 20) {
throw new Error('Invalid tokenAddress: must be a valid address');
}
tokenAddressHex = bytesToHex(tokenAddress).slice(2);
prefixedTokenAddressHex = bytesToHex(tokenAddress);
}

if (initialAmount < 0n) {
Expand Down Expand Up @@ -116,7 +116,7 @@ export function createERC20StreamingTerms(
const amountPerSecondHex = toHexString({ value: amountPerSecond, size: 32 });
const startTimeHex = toHexString({ value: startTime, size: 32 });

const hexValue = `0x${tokenAddressHex}${initialAmountHex}${maxAmountHex}${amountPerSecondHex}${startTimeHex}`;
const hexValue = `${prefixedTokenAddressHex}${initialAmountHex}${maxAmountHex}${amountPerSecondHex}${startTimeHex}`;

return prepareResult(hexValue, encodingOptions);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { type BytesLike, isHexString, bytesToHex } from '@metamask/utils';

import {
defaultOptions,
prepareResult,
type EncodingOptions,
type ResultValue,
} from '../returns';
import type { Hex } from '../types';
import { toHexString } from '../utils';

/**
* Terms for configuring a periodic transfer allowance of ERC20 tokens.
*/
export type ERC20TokenPeriodTransferTerms = {
/** The address of the ERC20 token. */
tokenAddress: BytesLike;
/** The maximum amount that can be transferred within each period. */
periodAmount: bigint;
/** The duration of each period in seconds. */
periodDuration: number;
/** Unix timestamp when the first period begins. */
startDate: number;
};

/**
* Creates terms for an ERC20TokenPeriodTransfer caveat that validates that ERC20 token transfers
* do not exceed a specified amount within a given time period. The transferable amount resets at the
* beginning of each period, and any unused tokens are forfeited once the period ends.
*
* @param terms - The terms for the ERC20TokenPeriodTransfer caveat.
* @param encodingOptions - The encoding options for the result.
* @returns The terms as a 128-byte hex string (32 bytes for each parameter).
* @throws Error if any of the numeric parameters are invalid.
*/
export function createERC20TokenPeriodTransferTerms(
terms: ERC20TokenPeriodTransferTerms,
encodingOptions?: EncodingOptions<'hex'>,
): Hex;
export function createERC20TokenPeriodTransferTerms(
terms: ERC20TokenPeriodTransferTerms,
encodingOptions: EncodingOptions<'bytes'>,
): Uint8Array;
/**
* Creates terms for an ERC20TokenPeriodTransfer caveat that validates that ERC20 token transfers
* do not exceed a specified amount within a given time period.
*
* @param terms - The terms for the ERC20TokenPeriodTransfer caveat.
* @param encodingOptions - The encoding options for the result.
* @returns The terms as a 128-byte hex string (32 bytes for each parameter).
* @throws Error if any of the numeric parameters are invalid.
*/
export function createERC20TokenPeriodTransferTerms(
terms: ERC20TokenPeriodTransferTerms,
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
): Hex | Uint8Array {
const { tokenAddress, periodAmount, periodDuration, startDate } = terms;

if (!tokenAddress) {
throw new Error('Invalid tokenAddress: must be a valid address');
}
Comment thread
jeffsmale90 marked this conversation as resolved.

let prefixedTokenAddressHex: string;

if (typeof tokenAddress === 'string') {
if (!isHexString(tokenAddress) || tokenAddress.length !== 42) {
throw new Error('Invalid tokenAddress: must be a valid address');
}
prefixedTokenAddressHex = tokenAddress;
} else {
if (tokenAddress.length !== 20) {
throw new Error('Invalid tokenAddress: must be a valid address');
}
prefixedTokenAddressHex = bytesToHex(tokenAddress);
}

if (periodAmount <= 0n) {
throw new Error('Invalid periodAmount: must be a positive number');
}

if (periodDuration <= 0) {
throw new Error('Invalid periodDuration: must be a positive number');
}

if (startDate <= 0) {
throw new Error('Invalid startDate: must be a positive number');
}

const periodAmountHex = toHexString({ value: periodAmount, size: 32 });
const periodDurationHex = toHexString({ value: periodDuration, size: 32 });
const startDateHex = toHexString({ value: startDate, size: 32 });

const hexValue = `${prefixedTokenAddressHex}${periodAmountHex}${periodDurationHex}${startDateHex}`;

return prepareResult(hexValue, encodingOptions);
}
1 change: 1 addition & 0 deletions packages/delegation-core/src/caveats/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { createNativeTokenPeriodTransferTerms } from './nativeTokenPeriodTransfe
export { createExactCalldataTerms } from './exactCalldata';
export { createNativeTokenStreamingTerms } from './nativeTokenStreaming';
export { createERC20StreamingTerms } from './erc20Streaming';
export { createERC20TokenPeriodTransferTerms } from './erc20TokenPeriodTransfer';
1 change: 1 addition & 0 deletions packages/delegation-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export {
createExactCalldataTerms,
createNativeTokenStreamingTerms,
createERC20StreamingTerms,
createERC20TokenPeriodTransferTerms,
} from './caveats';

export {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { concat, isAddress, toHex } from 'viem';
import { createERC20TokenPeriodTransferTerms } from '@metamask/delegation-core';
import type { Address } from 'viem';

import type { Caveat, DeleGatorEnvironment } from '../types';
Expand Down Expand Up @@ -26,28 +26,12 @@ export const erc20PeriodTransferBuilder = (
periodDuration: number,
startDate: number,
): Caveat => {
if (!isAddress(tokenAddress)) {
throw new Error('Invalid tokenAddress: must be a valid address');
}

if (periodAmount <= 0n) {
throw new Error('Invalid periodAmount: must be a positive number');
}

if (periodDuration <= 0) {
throw new Error('Invalid periodDuration: must be a positive number');
}

if (startDate <= 0) {
throw new Error('Invalid startDate: must be a positive number');
}

const terms = concat([
const terms = createERC20TokenPeriodTransferTerms({
tokenAddress,
toHex(periodAmount, { size: 32 }),
toHex(periodDuration, { size: 32 }),
toHex(startDate, { size: 32 }),
]);
periodAmount,
periodDuration,
startDate,
});

const {
caveatEnforcers: { ERC20PeriodTransferEnforcer },
Expand Down
Loading