Skip to content

Commit 476e653

Browse files
authored
Add new time periods for ERC20 and native token permissions (#190)
* Add new time periods and refactor related logic for ERC20 and native token permissions * Update tests * Add tests for getClosestTimePeriod function and related time periods * Remove debug logs and add comments for time period constants in context and time utility files * Enhance time period validation and error handling in ERC20 and native token contexts; add tests for edge cases in getClosestTimePeriod function * Refactor periodDuration type to use zPeriodDuration in ERC20 and native token contexts; add zPeriodDuration definition * Refactor time period handling to simplify duration conversion; update tests and labels for consistency * Refactor time period handling in ERC20 and native token contexts to remove getClosestTimePeriod function; simplify duration conversion logic * Refactor period handling to replace periodType with periodDuration in ERC20 and native token contexts; update related tests for consistency * Refactor periodDuration handling to change type from string to number across ERC20 and native token contexts; update related tests for consistency
1 parent 8b714c6 commit 476e653

File tree

20 files changed

+610
-1405
lines changed

20 files changed

+610
-1405
lines changed

packages/gator-permissions-snap/src/core/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,12 @@ export type DeepRequired<TParent> = TParent extends (infer U)[]
9191
* An enum representing the time periods for which the stream rate can be calculated.
9292
*/
9393
export enum TimePeriod {
94+
HOURLY = 'Hourly',
9495
DAILY = 'Daily',
9596
WEEKLY = 'Weekly',
97+
BIWEEKLY = 'Biweekly',
9698
MONTHLY = 'Monthly',
99+
YEARLY = 'Yearly',
97100
}
98101

99102
/**

packages/gator-permissions-snap/src/permissions/contextValidation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,12 @@ export function calculateAmountPerSecond(
157157
* @param periodDuration - The period duration string to validate.
158158
* @returns Object containing parsed duration and any validation error.
159159
*/
160-
export function validatePeriodDuration(periodDuration: string): {
160+
export function validatePeriodDuration(periodDuration: number): {
161161
duration: number | undefined;
162162
error: string | undefined;
163163
} {
164164
try {
165-
const duration = parseInt(periodDuration, 10);
165+
const duration = periodDuration;
166166
if (isNaN(duration) || duration <= 0) {
167167
return {
168168
duration: undefined,

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/content.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Box, Section } from '@metamask/snaps-sdk/jsx';
33

44
import {
55
periodAmountRule,
6-
periodTypeRule,
76
periodDurationRule,
87
startTimeRule,
98
expiryRule,
@@ -32,12 +31,7 @@ export async function createConfirmationContent({
3231
<Box>
3332
<Section>
3433
{renderRules({
35-
rules: [
36-
startTimeRule,
37-
periodAmountRule,
38-
periodTypeRule,
39-
periodDurationRule,
40-
],
34+
rules: [startTimeRule, periodAmountRule, periodDurationRule],
4135
context,
4236
metadata,
4337
})}

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/context.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import {
88
type Hex,
99
} from '@metamask/utils';
1010

11-
import { TimePeriod } from '../../core/types';
1211
import type { TokenMetadataService } from '../../services/tokenMetadataService';
13-
import { TIME_PERIOD_TO_SECONDS } from '../../utils/time';
1412
import { parseUnits, formatUnitsFromHex } from '../../utils/value';
1513
import {
1614
validateAndParseAmount,
@@ -76,7 +74,7 @@ export async function applyContext({
7674
periodAmount: bigIntToHex(
7775
parseUnits({ formatted: permissionDetails.periodAmount, decimals }),
7876
),
79-
periodDuration: parseInt(permissionDetails.periodDuration, 10),
77+
periodDuration: permissionDetails.periodDuration,
8078
startTime: permissionDetails.startTime,
8179
justification: originalRequest.permission.data.justification,
8280
tokenAddress: originalRequest.permission.data.tokenAddress,
@@ -178,19 +176,7 @@ export async function buildContext({
178176
decimals,
179177
});
180178

181-
const periodDuration = data.periodDuration.toString();
182-
183-
// Determine the period type based on the duration
184-
let periodType: TimePeriod | 'Other';
185-
if (periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.DAILY].toString()) {
186-
periodType = TimePeriod.DAILY;
187-
} else if (
188-
periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.WEEKLY].toString()
189-
) {
190-
periodType = TimePeriod.WEEKLY;
191-
} else {
192-
periodType = 'Other';
193-
}
179+
const { periodDuration } = data;
194180

195181
const startTime = data.startTime ?? Math.floor(Date.now() / 1000);
196182

@@ -220,7 +206,6 @@ export async function buildContext({
220206
},
221207
permissionDetails: {
222208
periodAmount,
223-
periodType,
224209
periodDuration,
225210
startTime,
226211
},

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/rules.ts

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { InvalidInputError } from '@metamask/snaps-sdk';
2+
13
import { TimePeriod } from '../../core/types';
24
import type { RuleDefinition } from '../../core/types';
3-
import { TIME_PERIOD_TO_SECONDS } from '../../utils/time';
5+
import { getClosestTimePeriod, TIME_PERIOD_TO_SECONDS } from '../../utils/time';
46
import { getIconData } from '../iconUtil';
57
import type {
68
Erc20TokenPeriodicContext,
@@ -9,7 +11,6 @@ import type {
911

1012
export const PERIOD_AMOUNT_ELEMENT = 'erc20-token-periodic-period-amount';
1113
export const PERIOD_TYPE_ELEMENT = 'erc20-token-periodic-period-type';
12-
export const PERIOD_DURATION_ELEMENT = 'erc20-token-periodic-period-duration';
1314
export const START_TIME_ELEMENT = 'erc20-token-periodic-start-date';
1415
export const EXPIRY_ELEMENT = 'erc20-token-periodic-expiry';
1516

@@ -37,62 +38,51 @@ export const periodAmountRule: RuleDefinition<
3738
}),
3839
};
3940

40-
export const periodTypeRule: RuleDefinition<
41+
export const periodDurationRule: RuleDefinition<
4142
Erc20TokenPeriodicContext,
4243
Erc20TokenPeriodicMetadata
4344
> = {
4445
name: PERIOD_TYPE_ELEMENT,
45-
label: 'Period duration',
46+
label: 'Frequency',
4647
type: 'dropdown',
4748
getRuleData: ({ context, metadata }) => ({
4849
isAdjustmentAllowed: context.isAdjustmentAllowed,
49-
value: context.permissionDetails.periodType,
50+
value: getClosestTimePeriod(context.permissionDetails.periodDuration),
5051
isVisible: true,
5152
tooltip: 'The duration of the period',
52-
options: [TimePeriod.DAILY, TimePeriod.WEEKLY, 'Other'],
53-
error: metadata.validationErrors.periodTypeError,
53+
options: Object.values(TimePeriod),
54+
error: metadata.validationErrors.periodDurationError,
5455
}),
5556
updateContext: (context: Erc20TokenPeriodicContext, value: string) => {
56-
const periodType = value as TimePeriod | 'Other';
57-
const periodDuration =
58-
periodType === 'Other'
59-
? context.permissionDetails.periodDuration
60-
: Number(TIME_PERIOD_TO_SECONDS[periodType]).toString();
57+
// Validate that value is a valid TimePeriod
58+
if (!Object.values(TimePeriod).includes(value as TimePeriod)) {
59+
throw new InvalidInputError(
60+
`Invalid period type: "${value}". Valid options are: ${Object.values(TimePeriod).join(', ')}`,
61+
);
62+
}
63+
64+
const periodType = value as TimePeriod;
65+
const periodSeconds = TIME_PERIOD_TO_SECONDS[periodType];
66+
67+
// This should never happen if the above check passed, but be defensive
68+
if (periodSeconds === undefined) {
69+
throw new InvalidInputError(
70+
`Period type "${periodType}" is not mapped to a duration. This indicates a system error.`,
71+
);
72+
}
73+
74+
const periodDuration = Number(periodSeconds);
6175

6276
return {
6377
...context,
6478
permissionDetails: {
6579
...context.permissionDetails,
66-
periodType,
6780
periodDuration,
6881
},
6982
};
7083
},
7184
};
7285

73-
export const periodDurationRule: RuleDefinition<
74-
Erc20TokenPeriodicContext,
75-
Erc20TokenPeriodicMetadata
76-
> = {
77-
name: PERIOD_DURATION_ELEMENT,
78-
label: 'Duration (seconds)',
79-
type: 'number',
80-
getRuleData: ({ context, metadata }) => ({
81-
value: context.permissionDetails.periodDuration,
82-
isAdjustmentAllowed: context.isAdjustmentAllowed,
83-
isVisible: context.permissionDetails.periodType === 'Other',
84-
tooltip: 'The length of each period in seconds',
85-
error: metadata.validationErrors.periodDurationError,
86-
}),
87-
updateContext: (context: Erc20TokenPeriodicContext, value: string) => ({
88-
...context,
89-
permissionDetails: {
90-
...context.permissionDetails,
91-
periodDuration: value,
92-
},
93-
}),
94-
};
95-
9686
export const startTimeRule: RuleDefinition<
9787
Erc20TokenPeriodicContext,
9888
Erc20TokenPeriodicMetadata
@@ -161,7 +151,6 @@ export const expiryRule: RuleDefinition<
161151

162152
export const allRules = [
163153
periodAmountRule,
164-
periodTypeRule,
165154
periodDurationRule,
166155
startTimeRule,
167156
expiryRule,

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/types.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
zPermission,
44
zMetaMaskPermissionData,
55
zAddress,
6-
zTimestamp,
76
zStartTime,
87
} from '@metamask/7715-permissions-shared/types';
98
import { z } from 'zod';
@@ -12,15 +11,14 @@ import type {
1211
DeepRequired,
1312
TypedPermissionRequest,
1413
BaseContext,
15-
TimePeriod,
1614
BaseMetadata,
1715
} from '../../core/types';
16+
import { zPeriodDuration } from '../../utils/time';
1817

1918
export type Erc20TokenPeriodicMetadata = BaseMetadata & {
2019
validationErrors: {
2120
periodAmountError?: string;
2221
periodDurationError?: string;
23-
periodTypeError?: string;
2422
startTimeError?: string;
2523
expiryError?: string;
2624
};
@@ -29,8 +27,7 @@ export type Erc20TokenPeriodicMetadata = BaseMetadata & {
2927
export type Erc20TokenPeriodicContext = BaseContext & {
3028
permissionDetails: {
3129
periodAmount: string;
32-
periodType: TimePeriod | 'Other';
33-
periodDuration: string;
30+
periodDuration: number;
3431
startTime: number;
3532
};
3633
};
@@ -41,7 +38,7 @@ export const zErc20TokenPeriodicPermission = zPermission.extend({
4138
zMetaMaskPermissionData,
4239
z.object({
4340
periodAmount: zHexStr,
44-
periodDuration: zTimestamp,
41+
periodDuration: zPeriodDuration,
4542
startTime: zStartTime,
4643
tokenAddress: zAddress,
4744
}),

packages/gator-permissions-snap/src/permissions/nativeTokenPeriodic/content.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Box, Section } from '@metamask/snaps-sdk/jsx';
33

44
import {
55
periodAmountRule,
6-
periodTypeRule,
76
periodDurationRule,
87
startTimeRule,
98
expiryRule,
@@ -32,12 +31,7 @@ export async function createConfirmationContent({
3231
<Box>
3332
<Section>
3433
{renderRules({
35-
rules: [
36-
startTimeRule,
37-
periodAmountRule,
38-
periodTypeRule,
39-
periodDurationRule,
40-
],
34+
rules: [startTimeRule, periodAmountRule, periodDurationRule],
4135
context,
4236
metadata,
4337
})}

packages/gator-permissions-snap/src/permissions/nativeTokenPeriodic/context.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import {
88
type Hex,
99
} from '@metamask/utils';
1010

11-
import { TimePeriod } from '../../core/types';
1211
import type { TokenMetadataService } from '../../services/tokenMetadataService';
13-
import { TIME_PERIOD_TO_SECONDS } from '../../utils/time';
1412
import { parseUnits, formatUnitsFromHex } from '../../utils/value';
1513
import {
1614
validateAndParseAmount,
@@ -77,7 +75,7 @@ export async function applyContext({
7775
periodAmount: bigIntToHex(
7876
parseUnits({ formatted: permissionDetails.periodAmount, decimals }),
7977
),
80-
periodDuration: parseInt(permissionDetails.periodDuration, 10),
78+
periodDuration: permissionDetails.periodDuration,
8179
startTime: permissionDetails.startTime,
8280
justification: originalRequest.permission.data.justification,
8381
};
@@ -178,19 +176,7 @@ export async function buildContext({
178176
decimals,
179177
});
180178

181-
const periodDuration = data.periodDuration.toString();
182-
183-
// Determine the period type based on the duration
184-
let periodType: TimePeriod | 'Other';
185-
if (periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.DAILY].toString()) {
186-
periodType = TimePeriod.DAILY;
187-
} else if (
188-
periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.WEEKLY].toString()
189-
) {
190-
periodType = TimePeriod.WEEKLY;
191-
} else {
192-
periodType = 'Other';
193-
}
179+
const { periodDuration } = data;
194180

195181
const startTime = data.startTime ?? Math.floor(Date.now() / 1000);
196182

@@ -220,7 +206,6 @@ export async function buildContext({
220206
},
221207
permissionDetails: {
222208
periodAmount,
223-
periodType,
224209
periodDuration,
225210
startTime,
226211
},

0 commit comments

Comments
 (0)