Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
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
3 changes: 3 additions & 0 deletions packages/gator-permissions-snap/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,12 @@ export type DeepRequired<TParent> = TParent extends (infer U)[]
* An enum representing the time periods for which the stream rate can be calculated.
*/
export enum TimePeriod {
HOURLY = 'Hourly',
DAILY = 'Daily',
WEEKLY = 'Weekly',
BIWEEKLY = 'Biweekly',
MONTHLY = 'Monthly',
YEARLY = 'Yearly',
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Box, Section } from '@metamask/snaps-sdk/jsx';
import {
periodAmountRule,
periodTypeRule,
periodDurationRule,
startTimeRule,
expiryRule,
} from './rules';
Expand Down Expand Up @@ -32,12 +31,7 @@ export async function createConfirmationContent({
<Box>
<Section>
{renderRules({
rules: [
startTimeRule,
periodAmountRule,
periodTypeRule,
periodDurationRule,
],
rules: [startTimeRule, periodAmountRule, periodTypeRule],
context,
metadata,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {
type Hex,
} from '@metamask/utils';

import { TimePeriod } from '../../core/types';
import type { TokenMetadataService } from '../../services/tokenMetadataService';
import { TIME_PERIOD_TO_SECONDS } from '../../utils/time';
import { getClosestTimePeriod, TIME_PERIOD_TO_SECONDS } from '../../utils/time';
import { parseUnits, formatUnitsFromHex } from '../../utils/value';
import {
validateAndParseAmount,
Expand Down Expand Up @@ -178,19 +177,8 @@ export async function buildContext({
decimals,
});

const periodDuration = data.periodDuration.toString();

// Determine the period type based on the duration
let periodType: TimePeriod | 'Other';
if (periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.DAILY].toString()) {
periodType = TimePeriod.DAILY;
} else if (
periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.WEEKLY].toString()
) {
periodType = TimePeriod.WEEKLY;
} else {
periodType = 'Other';
}
const periodType = getClosestTimePeriod(data.periodDuration);
const periodDuration = TIME_PERIOD_TO_SECONDS[periodType].toString();

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { InvalidInputError } from '@metamask/snaps-sdk';

import { TimePeriod } from '../../core/types';
import type { RuleDefinition } from '../../core/types';
import { TIME_PERIOD_TO_SECONDS } from '../../utils/time';
Expand All @@ -9,7 +11,6 @@ import type {

export const PERIOD_AMOUNT_ELEMENT = 'erc20-token-periodic-period-amount';
export const PERIOD_TYPE_ELEMENT = 'erc20-token-periodic-period-type';
export const PERIOD_DURATION_ELEMENT = 'erc20-token-periodic-period-duration';
export const START_TIME_ELEMENT = 'erc20-token-periodic-start-date';
export const EXPIRY_ELEMENT = 'erc20-token-periodic-expiry';

Expand Down Expand Up @@ -42,22 +43,42 @@ export const periodTypeRule: RuleDefinition<
Erc20TokenPeriodicMetadata
> = {
name: PERIOD_TYPE_ELEMENT,
label: 'Period duration',
label: 'Frequency',
type: 'dropdown',
getRuleData: ({ context, metadata }) => ({
isAdjustmentAllowed: context.isAdjustmentAllowed,
value: context.permissionDetails.periodType,
isVisible: true,
tooltip: 'The duration of the period',
options: [TimePeriod.DAILY, TimePeriod.WEEKLY, 'Other'],
options: [
TimePeriod.HOURLY,
TimePeriod.DAILY,
TimePeriod.WEEKLY,
TimePeriod.BIWEEKLY,
TimePeriod.MONTHLY,
TimePeriod.YEARLY,
],
error: metadata.validationErrors.periodTypeError,
}),
updateContext: (context: Erc20TokenPeriodicContext, value: string) => {
const periodType = value as TimePeriod | 'Other';
const periodDuration =
periodType === 'Other'
? context.permissionDetails.periodDuration
: Number(TIME_PERIOD_TO_SECONDS[periodType]).toString();
// Validate that value is a valid TimePeriod
if (!Object.values(TimePeriod).includes(value as TimePeriod)) {
throw new InvalidInputError(
`Invalid period type: "${value}". Valid options are: ${Object.values(TimePeriod).join(', ')}`,
);
}

const periodType = value as TimePeriod;
const periodSeconds = TIME_PERIOD_TO_SECONDS[periodType];

// This should never happen if the above check passed, but be defensive
if (periodSeconds === undefined) {
throw new InvalidInputError(
`Period type "${periodType}" is not mapped to a duration. This indicates a system error.`,
);
}

const periodDuration = Number(periodSeconds).toString();

return {
...context,
Expand All @@ -70,29 +91,6 @@ export const periodTypeRule: RuleDefinition<
},
};

export const periodDurationRule: RuleDefinition<
Erc20TokenPeriodicContext,
Erc20TokenPeriodicMetadata
> = {
name: PERIOD_DURATION_ELEMENT,
label: 'Duration (seconds)',
type: 'number',
getRuleData: ({ context, metadata }) => ({
value: context.permissionDetails.periodDuration,
isAdjustmentAllowed: context.isAdjustmentAllowed,
isVisible: context.permissionDetails.periodType === 'Other',
tooltip: 'The length of each period in seconds',
error: metadata.validationErrors.periodDurationError,
}),
updateContext: (context: Erc20TokenPeriodicContext, value: string) => ({
...context,
permissionDetails: {
...context.permissionDetails,
periodDuration: value,
},
}),
};

export const startTimeRule: RuleDefinition<
Erc20TokenPeriodicContext,
Erc20TokenPeriodicMetadata
Expand Down Expand Up @@ -162,7 +160,6 @@ export const expiryRule: RuleDefinition<
export const allRules = [
periodAmountRule,
periodTypeRule,
periodDurationRule,
startTimeRule,
expiryRule,
];
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
zPermission,
zMetaMaskPermissionData,
zAddress,
zTimestamp,
zStartTime,
} from '@metamask/7715-permissions-shared/types';
import { z } from 'zod';
Expand All @@ -15,6 +14,7 @@ import type {
TimePeriod,
BaseMetadata,
} from '../../core/types';
import { zPeriodDuration } from '../../utils/time';

export type Erc20TokenPeriodicMetadata = BaseMetadata & {
validationErrors: {
Expand All @@ -29,7 +29,7 @@ export type Erc20TokenPeriodicMetadata = BaseMetadata & {
export type Erc20TokenPeriodicContext = BaseContext & {
permissionDetails: {
periodAmount: string;
periodType: TimePeriod | 'Other';
periodType: TimePeriod;
periodDuration: string;
startTime: number;
};
Expand All @@ -41,7 +41,7 @@ export const zErc20TokenPeriodicPermission = zPermission.extend({
zMetaMaskPermissionData,
z.object({
periodAmount: zHexStr,
periodDuration: zTimestamp,
periodDuration: zPeriodDuration,
startTime: zStartTime,
tokenAddress: zAddress,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Box, Section } from '@metamask/snaps-sdk/jsx';
import {
periodAmountRule,
periodTypeRule,
periodDurationRule,
startTimeRule,
expiryRule,
} from './rules';
Expand Down Expand Up @@ -32,12 +31,7 @@ export async function createConfirmationContent({
<Box>
<Section>
{renderRules({
rules: [
startTimeRule,
periodAmountRule,
periodTypeRule,
periodDurationRule,
],
rules: [startTimeRule, periodAmountRule, periodTypeRule],
context,
metadata,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {
type Hex,
} from '@metamask/utils';

import { TimePeriod } from '../../core/types';
import type { TokenMetadataService } from '../../services/tokenMetadataService';
import { TIME_PERIOD_TO_SECONDS } from '../../utils/time';
import { getClosestTimePeriod, TIME_PERIOD_TO_SECONDS } from '../../utils/time';
import { parseUnits, formatUnitsFromHex } from '../../utils/value';
import {
validateAndParseAmount,
Expand Down Expand Up @@ -178,19 +177,8 @@ export async function buildContext({
decimals,
});

const periodDuration = data.periodDuration.toString();

// Determine the period type based on the duration
let periodType: TimePeriod | 'Other';
if (periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.DAILY].toString()) {
periodType = TimePeriod.DAILY;
} else if (
periodDuration === TIME_PERIOD_TO_SECONDS[TimePeriod.WEEKLY].toString()
) {
periodType = TimePeriod.WEEKLY;
} else {
periodType = 'Other';
}
const periodType = getClosestTimePeriod(data.periodDuration);
const periodDuration = TIME_PERIOD_TO_SECONDS[periodType].toString();

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { InvalidInputError } from '@metamask/snaps-sdk';

import { TimePeriod } from '../../core/types';
import type { RuleDefinition } from '../../core/types';
import { TIME_PERIOD_TO_SECONDS } from '../../utils/time';
Expand All @@ -9,7 +11,6 @@ import type {

export const PERIOD_AMOUNT_ELEMENT = 'native-token-periodic-period-amount';
export const PERIOD_TYPE_ELEMENT = 'native-token-periodic-period-type';
export const PERIOD_DURATION_ELEMENT = 'native-token-periodic-period-duration';
export const START_TIME_ELEMENT = 'native-token-periodic-start-date';
export const EXPIRY_ELEMENT = 'native-token-periodic-expiry';

Expand Down Expand Up @@ -42,22 +43,42 @@ export const periodTypeRule: RuleDefinition<
NativeTokenPeriodicMetadata
> = {
name: PERIOD_TYPE_ELEMENT,
label: 'Period duration',
label: 'Frequency',
type: 'dropdown',
getRuleData: ({ context, metadata }) => ({
isAdjustmentAllowed: context.isAdjustmentAllowed,
value: context.permissionDetails.periodType,
isVisible: true,
tooltip: 'The duration of the period',
options: [TimePeriod.DAILY, TimePeriod.WEEKLY, 'Other'],
options: [
TimePeriod.HOURLY,
TimePeriod.DAILY,
TimePeriod.WEEKLY,
TimePeriod.BIWEEKLY,
TimePeriod.MONTHLY,
TimePeriod.YEARLY,
],
error: metadata.validationErrors.periodTypeError,
}),
updateContext: (context: NativeTokenPeriodicContext, value: string) => {
const periodType = value as TimePeriod | 'Other';
const periodDuration =
periodType === 'Other'
? context.permissionDetails.periodDuration
: Number(TIME_PERIOD_TO_SECONDS[periodType]).toString();
// Validate that value is a valid TimePeriod
if (!Object.values(TimePeriod).includes(value as TimePeriod)) {
throw new InvalidInputError(
`Invalid period type: "${value}". Valid options are: ${Object.values(TimePeriod).join(', ')}`,
);
}

const periodType = value as TimePeriod;
const periodSeconds = TIME_PERIOD_TO_SECONDS[periodType];

// This should never happen if the above check passed, but be defensive
if (periodSeconds === undefined) {
throw new InvalidInputError(
`Period type "${periodType}" is not mapped to a duration. This indicates a system error.`,
);
}

const periodDuration = Number(periodSeconds).toString();

return {
...context,
Expand All @@ -70,29 +91,6 @@ export const periodTypeRule: RuleDefinition<
},
};

export const periodDurationRule: RuleDefinition<
NativeTokenPeriodicContext,
NativeTokenPeriodicMetadata
> = {
name: PERIOD_DURATION_ELEMENT,
label: 'Duration (seconds)',
type: 'number',
getRuleData: ({ context, metadata }) => ({
value: context.permissionDetails.periodDuration,
isAdjustmentAllowed: context.isAdjustmentAllowed,
isVisible: context.permissionDetails.periodType === 'Other',
tooltip: 'The length of each period in seconds',
error: metadata.validationErrors.periodDurationError,
}),
updateContext: (context: NativeTokenPeriodicContext, value: string) => ({
...context,
permissionDetails: {
...context.permissionDetails,
periodDuration: value,
},
}),
};

export const startTimeRule: RuleDefinition<
NativeTokenPeriodicContext,
NativeTokenPeriodicMetadata
Expand Down Expand Up @@ -162,7 +160,6 @@ export const expiryRule: RuleDefinition<
export const allRules = [
periodAmountRule,
periodTypeRule,
periodDurationRule,
startTimeRule,
expiryRule,
];
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
zHexStr,
zPermission,
zMetaMaskPermissionData,
zTimestamp,
zStartTime,
} from '@metamask/7715-permissions-shared/types';
import { z } from 'zod';
Expand All @@ -14,6 +13,7 @@ import type {
TimePeriod,
BaseMetadata,
} from '../../core/types';
import { zPeriodDuration } from '../../utils/time';

export type NativeTokenPeriodicMetadata = BaseMetadata & {
validationErrors: {
Expand All @@ -28,7 +28,7 @@ export type NativeTokenPeriodicMetadata = BaseMetadata & {
export type NativeTokenPeriodicContext = BaseContext & {
permissionDetails: {
periodAmount: string;
periodType: TimePeriod | 'Other';
periodType: TimePeriod;
periodDuration: string;
startTime: number;
};
Expand All @@ -40,7 +40,7 @@ export const zNativeTokenPeriodicPermission = zPermission.extend({
zMetaMaskPermissionData,
z.object({
periodAmount: zHexStr,
periodDuration: zTimestamp,
periodDuration: zPeriodDuration,
startTime: zStartTime,
}),
),
Expand Down
Loading
Loading