Skip to content

Commit 8d454a5

Browse files
committed
Add erc20TokenPeriodTransfer terms builder
1 parent f71db8f commit 8d454a5

5 files changed

Lines changed: 107 additions & 26 deletions

File tree

packages/delegation-core/src/caveats/erc20Streaming.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,18 @@ export function createERC20StreamingTerms(
7171
throw new Error('Invalid tokenAddress: must be a valid address');
7272
}
7373

74-
let tokenAddressHex: string;
74+
let prefixedTokenAddressHex: string;
7575

7676
if (typeof tokenAddress === 'string') {
7777
if (!isHexString(tokenAddress) || tokenAddress.length !== 42) {
7878
throw new Error('Invalid tokenAddress: must be a valid address');
7979
}
80-
tokenAddressHex = tokenAddress.slice(2);
80+
prefixedTokenAddressHex = tokenAddress;
8181
} else {
8282
if (tokenAddress.length !== 20) {
8383
throw new Error('Invalid tokenAddress: must be a valid address');
8484
}
85-
tokenAddressHex = bytesToHex(tokenAddress).slice(2);
85+
prefixedTokenAddressHex = bytesToHex(tokenAddress);
8686
}
8787

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

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

121121
return prepareResult(hexValue, encodingOptions);
122122
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {
2+
defaultOptions,
3+
prepareResult,
4+
type EncodingOptions,
5+
type ResultValue,
6+
} from '../returns';
7+
import type { Hex } from '../types';
8+
import { toHexString } from '../utils';
9+
import { type BytesLike, isHexString, bytesToHex } from '@metamask/utils';
10+
11+
/**
12+
* Terms for configuring a periodic transfer allowance of ERC20 tokens.
13+
*/
14+
export type ERC20TokenPeriodTransferTerms = {
15+
/** The address of the ERC20 token. */
16+
tokenAddress: BytesLike;
17+
/** The maximum amount that can be transferred within each period. */
18+
periodAmount: bigint;
19+
/** The duration of each period in seconds. */
20+
periodDuration: number;
21+
/** Unix timestamp when the first period begins. */
22+
startDate: number;
23+
};
24+
25+
/**
26+
* Creates terms for an ERC20TokenPeriodTransfer caveat that validates that ERC20 token transfers
27+
* do not exceed a specified amount within a given time period. The transferable amount resets at the
28+
* beginning of each period, and any unused tokens are forfeited once the period ends.
29+
*
30+
* @param terms - The terms for the ERC20TokenPeriodTransfer caveat.
31+
* @param encodingOptions - The encoding options for the result.
32+
* @returns The terms as a 128-byte hex string (32 bytes for each parameter).
33+
* @throws Error if any of the numeric parameters are invalid.
34+
*/
35+
export function createERC20TokenPeriodTransferTerms(
36+
terms: ERC20TokenPeriodTransferTerms,
37+
encodingOptions?: EncodingOptions<'hex'>,
38+
): Hex;
39+
export function createERC20TokenPeriodTransferTerms(
40+
terms: ERC20TokenPeriodTransferTerms,
41+
encodingOptions: EncodingOptions<'bytes'>,
42+
): Uint8Array;
43+
/**
44+
* Creates terms for an ERC20TokenPeriodTransfer caveat that validates that ERC20 token transfers
45+
* do not exceed a specified amount within a given time period.
46+
*
47+
* @param terms - The terms for the ERC20TokenPeriodTransfer caveat.
48+
* @param encodingOptions - The encoding options for the result.
49+
* @returns The terms as a 128-byte hex string (32 bytes for each parameter).
50+
* @throws Error if any of the numeric parameters are invalid.
51+
*/
52+
export function createERC20TokenPeriodTransferTerms(
53+
terms: ERC20TokenPeriodTransferTerms,
54+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
55+
): Hex | Uint8Array {
56+
const { tokenAddress, periodAmount, periodDuration, startDate } = terms;
57+
58+
if (!tokenAddress) {
59+
throw new Error('Invalid tokenAddress: must be a valid address');
60+
}
61+
62+
let prefixedTokenAddressHex: string;
63+
64+
if (typeof tokenAddress === 'string') {
65+
if (!isHexString(tokenAddress) || tokenAddress.length !== 42) {
66+
throw new Error('Invalid tokenAddress: must be a valid address');
67+
}
68+
prefixedTokenAddressHex = tokenAddress;
69+
} else {
70+
if (tokenAddress.length !== 20) {
71+
throw new Error('Invalid tokenAddress: must be a valid address');
72+
}
73+
prefixedTokenAddressHex = bytesToHex(tokenAddress);
74+
}
75+
76+
if (periodAmount <= 0n) {
77+
throw new Error('Invalid periodAmount: must be a positive number');
78+
}
79+
80+
if (periodDuration <= 0) {
81+
throw new Error('Invalid periodDuration: must be a positive number');
82+
}
83+
84+
if (startDate <= 0) {
85+
throw new Error('Invalid startDate: must be a positive number');
86+
}
87+
88+
const periodAmountHex = toHexString({ value: periodAmount, size: 32 });
89+
const periodDurationHex = toHexString({ value: periodDuration, size: 32 });
90+
const startDateHex = toHexString({ value: startDate, size: 32 });
91+
92+
const hexValue = `${prefixedTokenAddressHex}${periodAmountHex}${periodDurationHex}${startDateHex}`;
93+
94+
return prepareResult(hexValue, encodingOptions);
95+
}

packages/delegation-core/src/caveats/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { createNativeTokenPeriodTransferTerms } from './nativeTokenPeriodTransfe
44
export { createExactCalldataTerms } from './exactCalldata';
55
export { createNativeTokenStreamingTerms } from './nativeTokenStreaming';
66
export { createERC20StreamingTerms } from './erc20Streaming';
7+
export { createERC20TokenPeriodTransferTerms } from './erc20TokenPeriodTransfer';

packages/delegation-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
createExactCalldataTerms,
1212
createNativeTokenStreamingTerms,
1313
createERC20StreamingTerms,
14+
createERC20TokenPeriodTransferTerms,
1415
} from './caveats';
1516

1617
export {

packages/delegation-toolkit/src/caveatBuilder/erc20PeriodTransferBuilder.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { concat, isAddress, toHex } from 'viem';
21
import type { Address } from 'viem';
32

43
import type { Caveat, DeleGatorEnvironment } from '../types';
4+
import { createERC20TokenPeriodTransferTerms } from '@metamask/delegation-core';
55

66
export const erc20PeriodTransfer = 'erc20PeriodTransfer';
77

@@ -26,28 +26,12 @@ export const erc20PeriodTransferBuilder = (
2626
periodDuration: number,
2727
startDate: number,
2828
): Caveat => {
29-
if (!isAddress(tokenAddress)) {
30-
throw new Error('Invalid tokenAddress: must be a valid address');
31-
}
32-
33-
if (periodAmount <= 0n) {
34-
throw new Error('Invalid periodAmount: must be a positive number');
35-
}
36-
37-
if (periodDuration <= 0) {
38-
throw new Error('Invalid periodDuration: must be a positive number');
39-
}
40-
41-
if (startDate <= 0) {
42-
throw new Error('Invalid startDate: must be a positive number');
43-
}
44-
45-
const terms = concat([
29+
const terms = createERC20TokenPeriodTransferTerms({
4630
tokenAddress,
47-
toHex(periodAmount, { size: 32 }),
48-
toHex(periodDuration, { size: 32 }),
49-
toHex(startDate, { size: 32 }),
50-
]);
31+
periodAmount,
32+
periodDuration,
33+
startDate,
34+
});
5135

5236
const {
5337
caveatEnforcers: { ERC20PeriodTransferEnforcer },

0 commit comments

Comments
 (0)