Skip to content

Commit 43d0661

Browse files
committed
Add caveat terms builders to core package, and use them in the caveatBuilders.
- exactCalldata - nativeTokenPeriodTransfer - nativeTokenStreaming - timestamp - valueLte - erc20TokenStreaming
1 parent b996faa commit 43d0661

21 files changed

Lines changed: 2986 additions & 150 deletions
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { type BytesLike, bytesToHex, isHexString } from '@metamask/utils';
2+
3+
import {
4+
defaultOptions,
5+
prepareResult,
6+
type EncodingOptions,
7+
type ResultValue,
8+
} from '../returns';
9+
import type { Hex } from '../types';
10+
import { toHexString } from '../utils';
11+
12+
// Upper bound for timestamps (January 1, 10000 CE)
13+
const TIMESTAMP_UPPER_BOUND_SECONDS = 253402300799;
14+
15+
/**
16+
* Terms for configuring a linear streaming allowance of ERC20 tokens.
17+
*/
18+
export type ERC20StreamingTerms = {
19+
/** The address of the ERC20 token contract. */
20+
tokenAddress: BytesLike;
21+
/** The initial amount available immediately. */
22+
initialAmount: bigint;
23+
/** The maximum total amount that can be transferred. */
24+
maxAmount: bigint;
25+
/** The rate at which allowance increases per second. */
26+
amountPerSecond: bigint;
27+
/** Unix timestamp when streaming begins. */
28+
startTime: number;
29+
};
30+
31+
/**
32+
* Creates terms for the ERC20Streaming caveat, configuring a linear
33+
* streaming allowance of ERC20 tokens.
34+
*
35+
* @param terms - The terms for the ERC20Streaming caveat.
36+
* @param encodingOptions - The encoding options for the result.
37+
* @returns Hex-encoded terms for the caveat (160 bytes).
38+
* @throws Error if tokenAddress is invalid.
39+
* @throws Error if initialAmount is negative.
40+
* @throws Error if maxAmount is not positive.
41+
* @throws Error if maxAmount is less than initialAmount.
42+
* @throws Error if amountPerSecond is not positive.
43+
* @throws Error if startTime is not positive.
44+
* @throws Error if startTime exceeds upper bound.
45+
*/
46+
export function createERC20StreamingTerms(
47+
terms: ERC20StreamingTerms,
48+
encodingOptions?: EncodingOptions<'hex'>,
49+
): Hex;
50+
export function createERC20StreamingTerms(
51+
terms: ERC20StreamingTerms,
52+
encodingOptions: EncodingOptions<'bytes'>,
53+
): Uint8Array;
54+
/**
55+
* Creates terms for the ERC20Streaming caveat, configuring a linear
56+
* streaming allowance of ERC20 tokens.
57+
*
58+
* @param terms - The terms for the ERC20Streaming caveat.
59+
* @param encodingOptions - The encoding options for the result.
60+
* @returns The terms as a 160-byte hex string.
61+
* @throws Error if any of the parameters are invalid.
62+
*/
63+
export function createERC20StreamingTerms(
64+
terms: ERC20StreamingTerms,
65+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
66+
): Hex | Uint8Array {
67+
const { tokenAddress, initialAmount, maxAmount, amountPerSecond, startTime } =
68+
terms;
69+
70+
if (!tokenAddress) {
71+
throw new Error('Invalid tokenAddress: must be a valid address');
72+
}
73+
74+
let tokenAddressHex: string;
75+
76+
if (typeof tokenAddress === 'string') {
77+
if (!isHexString(tokenAddress) || tokenAddress.length !== 42) {
78+
throw new Error('Invalid tokenAddress: must be a valid address');
79+
}
80+
tokenAddressHex = tokenAddress.slice(2);
81+
} else {
82+
if (tokenAddress.length !== 20) {
83+
throw new Error('Invalid tokenAddress: must be a valid address');
84+
}
85+
tokenAddressHex = bytesToHex(tokenAddress).slice(2);
86+
}
87+
88+
if (initialAmount < 0n) {
89+
throw new Error('Invalid initialAmount: must be greater than zero');
90+
}
91+
92+
if (maxAmount <= 0n) {
93+
throw new Error('Invalid maxAmount: must be a positive number');
94+
}
95+
96+
if (maxAmount < initialAmount) {
97+
throw new Error('Invalid maxAmount: must be greater than initialAmount');
98+
}
99+
100+
if (amountPerSecond <= 0n) {
101+
throw new Error('Invalid amountPerSecond: must be a positive number');
102+
}
103+
104+
if (startTime <= 0) {
105+
throw new Error('Invalid startTime: must be a positive number');
106+
}
107+
108+
if (startTime > TIMESTAMP_UPPER_BOUND_SECONDS) {
109+
throw new Error(
110+
'Invalid startTime: must be less than or equal to 253402300799',
111+
);
112+
}
113+
114+
const initialAmountHex = toHexString({ value: initialAmount, size: 32 });
115+
const maxAmountHex = toHexString({ value: maxAmount, size: 32 });
116+
const amountPerSecondHex = toHexString({ value: amountPerSecond, size: 32 });
117+
const startTimeHex = toHexString({ value: startTime, size: 32 });
118+
119+
const hexValue = `0x${tokenAddressHex}${initialAmountHex}${maxAmountHex}${amountPerSecondHex}${startTimeHex}`;
120+
121+
return prepareResult(hexValue, encodingOptions);
122+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { BytesLike } from '@metamask/utils';
2+
3+
import {
4+
defaultOptions,
5+
prepareResult,
6+
type EncodingOptions,
7+
type ResultValue,
8+
} from '../returns';
9+
import type { Hex } from '../types';
10+
11+
/**
12+
* Terms for configuring an ExactCalldata caveat.
13+
*/
14+
export type ExactCalldataTerms = {
15+
/** The expected calldata to match against. */
16+
callData: BytesLike;
17+
};
18+
19+
/**
20+
* Creates terms for an ExactCalldata caveat that ensures the provided execution calldata
21+
* matches exactly the expected calldata.
22+
*
23+
* @param terms - The terms for the ExactCalldata caveat.
24+
* @param encodingOptions - The encoding options for the result.
25+
* @returns The terms as the calldata itself.
26+
* @throws Error if the `callData` is invalid.
27+
*/
28+
export function createExactCalldataTerms(
29+
terms: ExactCalldataTerms,
30+
encodingOptions?: EncodingOptions<'hex'>,
31+
): Hex;
32+
export function createExactCalldataTerms(
33+
terms: ExactCalldataTerms,
34+
encodingOptions: EncodingOptions<'bytes'>,
35+
): Uint8Array;
36+
/**
37+
* Creates terms for an ExactCalldata caveat that ensures the provided execution calldata
38+
* matches exactly the expected calldata.
39+
* @param terms - The terms for the ExactCalldata caveat.
40+
* @param encodingOptions - The encoding options for the result.
41+
* @returns The terms as the calldata itself.
42+
* @throws Error if the `callData` is invalid.
43+
*/
44+
export function createExactCalldataTerms(
45+
terms: ExactCalldataTerms,
46+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
47+
): Hex | Uint8Array {
48+
const { callData } = terms;
49+
50+
if (typeof callData === 'string' && !callData.startsWith('0x')) {
51+
throw new Error('Invalid callData: must be a hex string starting with 0x');
52+
}
53+
54+
// For exact calldata, the terms are simply the expected calldata
55+
return prepareResult(callData, encodingOptions);
56+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export { createValueLteTerms } from './valueLte';
2+
export { createTimestampTerms } from './timestamp';
3+
export { createNativeTokenPeriodTransferTerms } from './nativeTokenPeriodTransfer';
4+
export { createExactCalldataTerms } from './exactCalldata';
5+
export { createNativeTokenStreamingTerms } from './nativeTokenStreaming';
6+
export { createERC20StreamingTerms } from './erc20Streaming';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
10+
/**
11+
* Terms for configuring a periodic transfer allowance of native tokens.
12+
*/
13+
export type NativeTokenPeriodTransferTerms = {
14+
/** The maximum amount that can be transferred within each period (in wei). */
15+
periodAmount: bigint;
16+
/** The duration of each period in seconds. */
17+
periodDuration: number;
18+
/** Unix timestamp when the first period begins. */
19+
startDate: number;
20+
};
21+
22+
/**
23+
* Creates terms for a NativeTokenPeriodTransfer caveat that validates that native token (ETH) transfers
24+
* do not exceed a specified amount within a given time period. The transferable amount resets at the
25+
* beginning of each period, and any unused ETH is forfeited once the period ends.
26+
*
27+
* @param terms - The terms for the NativeTokenPeriodTransfer caveat.
28+
* @param encodingOptions - The encoding options for the result.
29+
* @returns The terms as a 96-byte hex string (32 bytes for each parameter).
30+
* @throws Error if any of the numeric parameters are invalid.
31+
*/
32+
export function createNativeTokenPeriodTransferTerms(
33+
terms: NativeTokenPeriodTransferTerms,
34+
encodingOptions?: EncodingOptions<'hex'>,
35+
): Hex;
36+
export function createNativeTokenPeriodTransferTerms(
37+
terms: NativeTokenPeriodTransferTerms,
38+
encodingOptions: EncodingOptions<'bytes'>,
39+
): Uint8Array;
40+
/**
41+
* Creates terms for a NativeTokenPeriodTransfer caveat that validates that native token (ETH) transfers
42+
* do not exceed a specified amount within a given time period.
43+
*
44+
* @param terms - The terms for the NativeTokenPeriodTransfer caveat.
45+
* @param encodingOptions - The encoding options for the result.
46+
* @returns The terms as a 96-byte hex string (32 bytes for each parameter).
47+
* @throws Error if any of the numeric parameters are invalid.
48+
*/
49+
export function createNativeTokenPeriodTransferTerms(
50+
terms: NativeTokenPeriodTransferTerms,
51+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
52+
): Hex | Uint8Array {
53+
const { periodAmount, periodDuration, startDate } = terms;
54+
55+
if (periodAmount <= 0n) {
56+
throw new Error('Invalid periodAmount: must be a positive number');
57+
}
58+
59+
if (periodDuration <= 0) {
60+
throw new Error('Invalid periodDuration: must be a positive number');
61+
}
62+
63+
if (startDate <= 0) {
64+
throw new Error('Invalid startDate: must be a positive number');
65+
}
66+
67+
const periodAmountHex = toHexString({ value: periodAmount, size: 32 });
68+
const periodDurationHex = toHexString({ value: periodDuration, size: 32 });
69+
const startDateHex = toHexString({ value: startDate, size: 32 });
70+
71+
const hexValue = `0x${periodAmountHex}${periodDurationHex}${startDateHex}`;
72+
73+
return prepareResult(hexValue, encodingOptions);
74+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
10+
// Upper bound for timestamps (January 1, 10000 CE)
11+
const TIMESTAMP_UPPER_BOUND_SECONDS = 253402300799;
12+
13+
/**
14+
* Terms for configuring a linear streaming allowance of native tokens.
15+
*/
16+
export type NativeTokenStreamingTerms = {
17+
/** The initial amount available immediately (in wei). */
18+
initialAmount: bigint;
19+
/** The maximum total amount that can be transferred (in wei). */
20+
maxAmount: bigint;
21+
/** The rate at which allowance increases per second (in wei). */
22+
amountPerSecond: bigint;
23+
/** Unix timestamp when streaming begins. */
24+
startTime: number;
25+
};
26+
27+
/**
28+
* Creates terms for the NativeTokenStreaming caveat, configuring a linear
29+
* streaming allowance of native tokens.
30+
*
31+
* @param terms - The terms for the NativeTokenStreaming caveat.
32+
* @param encodingOptions - The encoding options for the result.
33+
* @returns Hex-encoded terms for the caveat (128 bytes).
34+
* @throws Error if initialAmount is negative.
35+
* @throws Error if maxAmount is not positive.
36+
* @throws Error if maxAmount is less than initialAmount.
37+
* @throws Error if amountPerSecond is not positive.
38+
* @throws Error if startTime is not positive.
39+
* @throws Error if startTime exceeds upper bound.
40+
*/
41+
export function createNativeTokenStreamingTerms(
42+
terms: NativeTokenStreamingTerms,
43+
encodingOptions?: EncodingOptions<'hex'>,
44+
): Hex;
45+
export function createNativeTokenStreamingTerms(
46+
terms: NativeTokenStreamingTerms,
47+
encodingOptions: EncodingOptions<'bytes'>,
48+
): Uint8Array;
49+
/**
50+
* Creates terms for the NativeTokenStreaming caveat, configuring a linear
51+
* streaming allowance of native tokens.
52+
*
53+
* @param terms - The terms for the NativeTokenStreaming caveat.
54+
* @param encodingOptions - The encoding options for the result.
55+
* @returns The terms as a 128-byte hex string.
56+
* @throws Error if any of the numeric parameters are invalid.
57+
*/
58+
export function createNativeTokenStreamingTerms(
59+
terms: NativeTokenStreamingTerms,
60+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
61+
): Hex | Uint8Array {
62+
const { initialAmount, maxAmount, amountPerSecond, startTime } = terms;
63+
64+
if (initialAmount < 0n) {
65+
throw new Error('Invalid initialAmount: must be greater than zero');
66+
}
67+
68+
if (maxAmount <= 0n) {
69+
throw new Error('Invalid maxAmount: must be a positive number');
70+
}
71+
72+
if (maxAmount < initialAmount) {
73+
throw new Error('Invalid maxAmount: must be greater than initialAmount');
74+
}
75+
76+
if (amountPerSecond <= 0n) {
77+
throw new Error('Invalid amountPerSecond: must be a positive number');
78+
}
79+
80+
if (startTime <= 0) {
81+
throw new Error('Invalid startTime: must be a positive number');
82+
}
83+
84+
if (startTime > TIMESTAMP_UPPER_BOUND_SECONDS) {
85+
throw new Error(
86+
'Invalid startTime: must be less than or equal to 253402300799',
87+
);
88+
}
89+
90+
const initialAmountHex = toHexString({ value: initialAmount, size: 32 });
91+
const maxAmountHex = toHexString({ value: maxAmount, size: 32 });
92+
const amountPerSecondHex = toHexString({ value: amountPerSecond, size: 32 });
93+
const startTimeHex = toHexString({ value: startTime, size: 32 });
94+
95+
const hexValue = `0x${initialAmountHex}${maxAmountHex}${amountPerSecondHex}${startTimeHex}`;
96+
97+
return prepareResult(hexValue, encodingOptions);
98+
}

0 commit comments

Comments
 (0)