Skip to content

Commit bc3e734

Browse files
authored
feat: add terms builders to @metamask/delegation-core (#139)
* Add caveat terms builders to delegation core for all existing caveat builders - rename src/utils.ts to src/internalUtils.ts to ensure these aren't accidentally added to external API * Update CaveatBuilders to use underlying terms builders from delegation-core * Add line to CHANGELOG.md * Add additional validation to terms builders
1 parent 0444189 commit bc3e734

83 files changed

Lines changed: 3264 additions & 142 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/delegation-core/CHANGELOG.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
### Uncategorized
10+
### Added
11+
12+
- Add terms builders for all enforcers implemented in @metamask/smart-accounts-kit ([#139](https://github.com/metamask/smart-accounts-kit/pull/139))
13+
- `createAllowedMethodsTerms`
14+
- `createAllowedTargetsTerms`
15+
- `createArgsEqualityCheckTerms`
16+
- `createBlockNumberTerms`
17+
- `createDeployedTerms`
18+
- `createERC1155BalanceChangeTerms`
19+
- `createERC20BalanceChangeTerms`
20+
- `createERC20TransferAmountTerms`
21+
- `createERC721BalanceChangeTerms`
22+
- `createERC721TransferTerms`
23+
- `createExactCalldataBatchTerms`
24+
- `createExactExecutionTerms`
25+
- `createExactExecutionBatchTerms`
26+
- `createIdTerms`
27+
- `createLimitedCallsTerms`
28+
- `createMultiTokenPeriodTerms`
29+
- `createNativeBalanceChangeTerms`
30+
- `createNativeTokenPaymentTerms`
31+
- `createNativeTokenTransferAmountTerms`
32+
- `createOwnershipTransferTerms`
33+
- `createRedeemerTerms`
34+
- `createSpecificActionERC20TransferBatchTerms`
35+
36+
### Fixed
1137

1238
- Resolve yarn peer dependency warnings ([#123](https://github.com/metamask/smart-accounts-kit/pull/123))
1339

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { bytesToHex, remove0x, type BytesLike } from '@metamask/utils';
22

3+
import { toHexString } from '../internalUtils';
34
import {
45
defaultOptions,
56
prepareResult,
67
type EncodingOptions,
78
type ResultValue,
89
} from '../returns';
910
import type { Hex } from '../types';
10-
import { toHexString } from '../utils';
1111

1212
/**
1313
* Terms for configuring an AllowedCalldata caveat.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { bytesToHex, isHexString, type BytesLike } from '@metamask/utils';
2+
3+
import { concatHex } from '../internalUtils';
4+
import {
5+
defaultOptions,
6+
prepareResult,
7+
type EncodingOptions,
8+
type ResultValue,
9+
} from '../returns';
10+
import type { Hex } from '../types';
11+
12+
/**
13+
* Terms for configuring an AllowedMethods caveat.
14+
*/
15+
export type AllowedMethodsTerms = {
16+
/** An array of 4-byte method selectors that the delegate is allowed to call. */
17+
selectors: BytesLike[];
18+
};
19+
20+
const FUNCTION_SELECTOR_STRING_LENGTH = 10; // 0x + 8 hex chars
21+
const INVALID_SELECTOR_ERROR =
22+
'Invalid selector: must be a 4 byte hex string, abi function signature, or AbiFunction';
23+
24+
/**
25+
* Creates terms for an AllowedMethods caveat that restricts calls to a set of method selectors.
26+
*
27+
* @param terms - The terms for the AllowedMethods caveat.
28+
* @param encodingOptions - The encoding options for the result.
29+
* @returns The terms as concatenated method selectors.
30+
* @throws Error if the selectors array is empty or contains invalid selectors.
31+
*/
32+
export function createAllowedMethodsTerms(
33+
terms: AllowedMethodsTerms,
34+
encodingOptions?: EncodingOptions<'hex'>,
35+
): Hex;
36+
export function createAllowedMethodsTerms(
37+
terms: AllowedMethodsTerms,
38+
encodingOptions: EncodingOptions<'bytes'>,
39+
): Uint8Array;
40+
/**
41+
* Creates terms for an AllowedMethods caveat that restricts calls to a set of method selectors.
42+
*
43+
* @param terms - The terms for the AllowedMethods caveat.
44+
* @param encodingOptions - The encoding options for the result.
45+
* @returns The terms as concatenated method selectors.
46+
* @throws Error if the selectors array is empty or contains invalid selectors.
47+
*/
48+
export function createAllowedMethodsTerms(
49+
terms: AllowedMethodsTerms,
50+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
51+
): Hex | Uint8Array {
52+
const { selectors } = terms;
53+
54+
if (!selectors || selectors.length === 0) {
55+
throw new Error('Invalid selectors: must provide at least one selector');
56+
}
57+
58+
const normalizedSelectors = selectors.map((selector) => {
59+
if (typeof selector === 'string') {
60+
if (
61+
isHexString(selector) &&
62+
selector.length === FUNCTION_SELECTOR_STRING_LENGTH
63+
) {
64+
return selector;
65+
}
66+
throw new Error(INVALID_SELECTOR_ERROR);
67+
}
68+
69+
if (selector.length !== 4) {
70+
throw new Error(INVALID_SELECTOR_ERROR);
71+
}
72+
73+
return bytesToHex(selector);
74+
});
75+
76+
const hexValue = concatHex(normalizedSelectors);
77+
return prepareResult(hexValue, encodingOptions);
78+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { BytesLike } from '@metamask/utils';
2+
3+
import { concatHex, normalizeAddress } from '../internalUtils';
4+
import {
5+
defaultOptions,
6+
prepareResult,
7+
type EncodingOptions,
8+
type ResultValue,
9+
} from '../returns';
10+
import type { Hex } from '../types';
11+
12+
/**
13+
* Terms for configuring an AllowedTargets caveat.
14+
*/
15+
export type AllowedTargetsTerms = {
16+
/** An array of target addresses that the delegate is allowed to call. */
17+
targets: BytesLike[];
18+
};
19+
20+
/**
21+
* Creates terms for an AllowedTargets caveat that restricts calls to a set of target addresses.
22+
*
23+
* @param terms - The terms for the AllowedTargets caveat.
24+
* @param encodingOptions - The encoding options for the result.
25+
* @returns The terms as concatenated target addresses.
26+
* @throws Error if the targets array is empty or contains invalid addresses.
27+
*/
28+
export function createAllowedTargetsTerms(
29+
terms: AllowedTargetsTerms,
30+
encodingOptions?: EncodingOptions<'hex'>,
31+
): Hex;
32+
export function createAllowedTargetsTerms(
33+
terms: AllowedTargetsTerms,
34+
encodingOptions: EncodingOptions<'bytes'>,
35+
): Uint8Array;
36+
/**
37+
* Creates terms for an AllowedTargets caveat that restricts calls to a set of target addresses.
38+
*
39+
* @param terms - The terms for the AllowedTargets caveat.
40+
* @param encodingOptions - The encoding options for the result.
41+
* @returns The terms as concatenated target addresses.
42+
* @throws Error if the targets array is empty or contains invalid addresses.
43+
*/
44+
export function createAllowedTargetsTerms(
45+
terms: AllowedTargetsTerms,
46+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
47+
): Hex | Uint8Array {
48+
const { targets } = terms;
49+
50+
if (!targets || targets.length === 0) {
51+
throw new Error(
52+
'Invalid targets: must provide at least one target address',
53+
);
54+
}
55+
56+
const normalizedTargets = targets.map((target) =>
57+
normalizeAddress(target, 'Invalid targets: must be valid addresses'),
58+
);
59+
60+
const hexValue = concatHex(normalizedTargets);
61+
return prepareResult(hexValue, encodingOptions);
62+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { BytesLike } from '@metamask/utils';
2+
3+
import { normalizeHex } from '../internalUtils';
4+
import {
5+
defaultOptions,
6+
prepareResult,
7+
type EncodingOptions,
8+
type ResultValue,
9+
} from '../returns';
10+
import type { Hex } from '../types';
11+
12+
/**
13+
* Terms for configuring an ArgsEqualityCheck caveat.
14+
*/
15+
export type ArgsEqualityCheckTerms = {
16+
/** The expected args that must match exactly when redeeming the delegation. */
17+
args: BytesLike;
18+
};
19+
20+
/**
21+
* Creates terms for an ArgsEqualityCheck caveat that requires exact args matching.
22+
*
23+
* @param terms - The terms for the ArgsEqualityCheck caveat.
24+
* @param encodingOptions - The encoding options for the result.
25+
* @returns The terms as the args themselves.
26+
* @throws Error if args is not a valid hex string.
27+
*/
28+
export function createArgsEqualityCheckTerms(
29+
terms: ArgsEqualityCheckTerms,
30+
encodingOptions?: EncodingOptions<'hex'>,
31+
): Hex;
32+
export function createArgsEqualityCheckTerms(
33+
terms: ArgsEqualityCheckTerms,
34+
encodingOptions: EncodingOptions<'bytes'>,
35+
): Uint8Array;
36+
/**
37+
* Creates terms for an ArgsEqualityCheck caveat that requires exact args matching.
38+
*
39+
* @param terms - The terms for the ArgsEqualityCheck caveat.
40+
* @param encodingOptions - The encoding options for the result.
41+
* @returns The terms as the args themselves.
42+
* @throws Error if args is not a valid hex string.
43+
*/
44+
export function createArgsEqualityCheckTerms(
45+
terms: ArgsEqualityCheckTerms,
46+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
47+
): Hex | Uint8Array {
48+
const { args } = terms;
49+
50+
if (typeof args === 'string' && args === '0x') {
51+
return prepareResult(args, encodingOptions);
52+
}
53+
54+
const hexValue = normalizeHex(
55+
args,
56+
'Invalid config: args must be a valid hex string',
57+
);
58+
59+
return prepareResult(hexValue, encodingOptions);
60+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { toHexString } from '../internalUtils';
2+
import {
3+
defaultOptions,
4+
prepareResult,
5+
type EncodingOptions,
6+
type ResultValue,
7+
} from '../returns';
8+
import type { Hex } from '../types';
9+
10+
/**
11+
* Terms for configuring a BlockNumber caveat.
12+
*/
13+
export type BlockNumberTerms = {
14+
/** The block number after which the delegation is valid. Set to 0n to disable. */
15+
afterThreshold: bigint;
16+
/** The block number before which the delegation is valid. Set to 0n to disable. */
17+
beforeThreshold: bigint;
18+
};
19+
20+
/**
21+
* Creates terms for a BlockNumber caveat that constrains delegation validity by block range.
22+
*
23+
* @param terms - The terms for the BlockNumber caveat.
24+
* @param encodingOptions - The encoding options for the result.
25+
* @returns The terms as a 32-byte hex string (16 bytes for each threshold).
26+
* @throws Error if both thresholds are zero or if afterThreshold >= beforeThreshold when both are set.
27+
*/
28+
export function createBlockNumberTerms(
29+
terms: BlockNumberTerms,
30+
encodingOptions?: EncodingOptions<'hex'>,
31+
): Hex;
32+
export function createBlockNumberTerms(
33+
terms: BlockNumberTerms,
34+
encodingOptions: EncodingOptions<'bytes'>,
35+
): Uint8Array;
36+
/**
37+
* Creates terms for a BlockNumber caveat that constrains delegation validity by block range.
38+
*
39+
* @param terms - The terms for the BlockNumber caveat.
40+
* @param encodingOptions - The encoding options for the result.
41+
* @returns The terms as a 32-byte hex string (16 bytes for each threshold).
42+
* @throws Error if both thresholds are zero or if afterThreshold >= beforeThreshold when both are set.
43+
*/
44+
export function createBlockNumberTerms(
45+
terms: BlockNumberTerms,
46+
encodingOptions: EncodingOptions<ResultValue> = defaultOptions,
47+
): Hex | Uint8Array {
48+
const { afterThreshold, beforeThreshold } = terms;
49+
50+
if (afterThreshold < 0n || beforeThreshold < 0n) {
51+
throw new Error('Invalid thresholds: block numbers must be non-negative');
52+
}
53+
54+
if (afterThreshold === 0n && beforeThreshold === 0n) {
55+
throw new Error(
56+
'Invalid thresholds: At least one of afterThreshold or beforeThreshold must be specified',
57+
);
58+
}
59+
60+
if (beforeThreshold !== 0n && afterThreshold >= beforeThreshold) {
61+
throw new Error(
62+
'Invalid thresholds: afterThreshold must be less than beforeThreshold if both are specified',
63+
);
64+
}
65+
66+
const afterThresholdHex = toHexString({ value: afterThreshold, size: 16 });
67+
const beforeThresholdHex = toHexString({ value: beforeThreshold, size: 16 });
68+
const hexValue = `0x${afterThresholdHex}${beforeThresholdHex}`;
69+
70+
return prepareResult(hexValue, encodingOptions);
71+
}

0 commit comments

Comments
 (0)