Skip to content
8 changes: 8 additions & 0 deletions packages/7715-permission-types/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Utility functions to create caveats array for each permission type ([#265](https://github.com/MetaMask/smart-accounts-kit/pull/265))
- `createErc20TokenStreamCaveats()`
- `createErc20TokenPeriodicCaveats()`
- `createErc20TokenAllowanceCaveats()`
- `createNativeTokenStreamCaveats()`
- `createNativeTokenPeriodicCaveats()`
- `createNativeTokenAllowanceCaveats()`
- `createTokenApprovalRevocationCaveats()`
- Add `makePermissionDecoderConfigs` to resolve the `PermissionDecoderConfig`s to be used with @metamask/gator-permissions-controller ([#259](https://github.com/MetaMask/smart-accounts-kit/pull/259))

## [0.7.1]
Expand Down
15 changes: 15 additions & 0 deletions packages/7715-permission-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,26 @@ export type {
RevokeExecutionPermissionRequestParams,
RevokeExecutionPermissionResponseResult,
MetaMaskBasePermissionData,
Populated,
} from './types';

export type { PayeeRule, RedeemerRule, ExpiryRule } from './permissions';
export {
makePermissionDecoderConfigs,
createErc20TokenAllowanceCaveats,
type Erc20TokenAllowanceEnforcers,
createErc20TokenPeriodicCaveats,
type Erc20TokenPeriodicEnforcers,
createErc20TokenStreamCaveats,
type Erc20TokenStreamEnforcers,
createNativeTokenAllowanceCaveats,
type NativeTokenAllowanceEnforcers,
createNativeTokenPeriodicCaveats,
type NativeTokenPeriodicEnforcers,
createNativeTokenStreamCaveats,
type NativeTokenStreamEnforcers,
createTokenApprovalRevocationCaveats,
type TokenApprovalRevocationEnforcers,
type DeployedContractsByName,
type PermissionDecoderConfig,
} from './permissions';
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { hexToNumber } from '@metamask/utils';
import type { Caveat } from '@metamask/delegation-core';
import {
createERC20TokenPeriodTransferTerms,
createValueLteTerms,
} from '@metamask/delegation-core';
import { hexToBigInt, hexToNumber } from '@metamask/utils';

import type { Erc20TokenAllowancePermission } from '../../types';
import type { Erc20TokenAllowancePermission, Populated } from '../../types';
import { expiryRuleDecoder } from '../rules/expiry';
import { erc20PayeeRuleDecoder } from '../rules/payee';
import { redeemerRuleDecoder } from '../rules/redeemer';
import type {
ChecksumCaveat,
ChecksumEnforcersByChainId,
DecodedPermissionData,
MakePermissionDecoderConfig,
PermissionDecoderConfig,
} from '../types';
import {
getByteLength,
Expand All @@ -26,7 +31,7 @@ import {
*/
export function makeErc20TokenAllowanceDecoderConfig(
contractAddresses: ChecksumEnforcersByChainId,
): MakePermissionDecoderConfig {
): PermissionDecoderConfig {
const {
timestampEnforcer,
erc20PeriodicEnforcer,
Expand Down Expand Up @@ -111,3 +116,62 @@ function validateAndDecodeData(

return { tokenAddress, allowanceAmount, startTime };
}

/**
* Enforcers required to build ERC-20 token allowance caveats.
*/
export type Erc20TokenAllowanceEnforcers = Pick<
ChecksumEnforcersByChainId,
'erc20PeriodicEnforcer' | 'valueLteEnforcer'
>;

/**
* Builds the erc20-token-allowance caveats required for this permission type.
*
* @param options0 - Caveat builder arguments.
* @param options0.permission - Fully populated erc20-token-allowance permission data.
* @param options0.contracts - Enforcer addresses used to construct caveats.
* @returns The ERC-20 allowance and zero-value caveats.
*/
export function createErc20TokenAllowanceCaveats({
permission,
contracts,
}: {
permission: Populated<Erc20TokenAllowancePermission>;
contracts: Erc20TokenAllowanceEnforcers;
}): Caveat[] {
const { tokenAddress, allowanceAmount, startTime } = permission.data;
const allowanceAmountBigInt = hexToBigInt(allowanceAmount);

if (allowanceAmountBigInt === 0n) {
throw new Error(
'Invalid erc20-token-allowance permission: allowanceAmount must be a positive number.',
);
}

if (startTime <= 0) {
throw new Error(
'Invalid erc20-token-allowance permission: startTime must be a positive number.',
);
}

const erc20PeriodCaveat: Caveat = {
enforcer: contracts.erc20PeriodicEnforcer,
terms: createERC20TokenPeriodTransferTerms({
tokenAddress,
periodAmount: allowanceAmountBigInt,
// delegation-core accepts bigint for encoding although the type is `number`.
periodDuration: BigInt(UINT256_MAX) as unknown as number,
Comment on lines +163 to +164

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a temporary measure, until we migrate to the fixed allowance enforcer.

startDate: startTime,
}),
args: '0x',
};

const valueLteCaveat: Caveat = {
enforcer: contracts.valueLteEnforcer,
terms: createValueLteTerms({ maxValue: 0n }),
args: '0x',
};

return [erc20PeriodCaveat, valueLteCaveat];
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { decodeERC20TokenPeriodTransferTerms } from '@metamask/delegation-core';
import { bigIntToHex } from '@metamask/utils';
import type { Caveat } from '@metamask/delegation-core';
import {
createERC20TokenPeriodTransferTerms,
createValueLteTerms,
decodeERC20TokenPeriodTransferTerms,
} from '@metamask/delegation-core';
import { bigIntToHex, hexToBigInt } from '@metamask/utils';

import type { Erc20TokenPeriodicPermission } from '../../types';
import type { Erc20TokenPeriodicPermission, Populated } from '../../types';
import { expiryRuleDecoder } from '../rules/expiry';
import { erc20PayeeRuleDecoder } from '../rules/payee';
import { redeemerRuleDecoder } from '../rules/redeemer';
import type {
ChecksumCaveat,
ChecksumEnforcersByChainId,
DecodedPermissionData,
MakePermissionDecoderConfig,
PermissionDecoderConfig,
} from '../types';
import {
getTermsByEnforcer,
Expand All @@ -25,7 +30,7 @@ import {
*/
export function makeErc20TokenPeriodicDecoderConfig(
contractAddresses: ChecksumEnforcersByChainId,
): MakePermissionDecoderConfig {
): PermissionDecoderConfig {
const {
timestampEnforcer,
erc20PeriodicEnforcer,
Expand Down Expand Up @@ -117,3 +122,74 @@ function validateAndDecodeData(
startTime,
};
}

/**
* Enforcers required to build ERC-20 token periodic caveats.
*/
export type Erc20TokenPeriodicEnforcers = Pick<
ChecksumEnforcersByChainId,
'erc20PeriodicEnforcer' | 'valueLteEnforcer'
>;

/**
* Builds the erc20-token-periodic caveats required for this permission type.
*
* @param options0 - Caveat builder arguments.
* @param options0.permission - Fully populated erc20-token-periodic permission data.
* @param options0.contracts - Enforcer addresses used to construct caveats.
* @returns The ERC-20 periodic and zero-value caveats.
*/
export function createErc20TokenPeriodicCaveats({
permission,
contracts,
}: {
permission: Populated<Erc20TokenPeriodicPermission>;
contracts: Erc20TokenPeriodicEnforcers;
}): Caveat[] {
const { tokenAddress, periodAmount, periodDuration, startTime } =
permission.data;
const periodAmountBigInt = hexToBigInt(periodAmount);

if (periodAmountBigInt === 0n) {
throw new Error(
'Invalid erc20-token-periodic permission: periodAmount must be a positive number.',
);
}

if (periodDuration <= 0) {
throw new Error(
'Invalid erc20-token-periodic permission: periodDuration must be a positive number.',
);
}

if (periodDuration > MAX_PERIOD_DURATION) {
throw new Error(
'Invalid erc20-token-periodic permission: periodDuration must be less than or equal to MAX_PERIOD_DURATION.',
);
}

if (startTime <= 0) {
throw new Error(
'Invalid erc20-token-periodic permission: startTime must be a positive number.',
);
}

const erc20PeriodCaveat: Caveat = {
enforcer: contracts.erc20PeriodicEnforcer,
terms: createERC20TokenPeriodTransferTerms({
tokenAddress,
periodAmount: periodAmountBigInt,
periodDuration,
startDate: startTime,
}),
args: '0x',
};

const valueLteCaveat: Caveat = {
enforcer: contracts.valueLteEnforcer,
terms: createValueLteTerms({ maxValue: 0n }),
args: '0x',
};

return [erc20PeriodCaveat, valueLteCaveat];
}
Comment thread
jeffsmale90 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { decodeERC20StreamingTerms } from '@metamask/delegation-core';
import { bigIntToHex } from '@metamask/utils';
import type { Caveat } from '@metamask/delegation-core';
import {
createERC20StreamingTerms,
createValueLteTerms,
decodeERC20StreamingTerms,
} from '@metamask/delegation-core';
import { bigIntToHex, hexToBigInt } from '@metamask/utils';

import type { Erc20TokenStreamPermission } from '../../types';
import type { Erc20TokenStreamPermission, Populated } from '../../types';
import { expiryRuleDecoder } from '../rules/expiry';
import { erc20PayeeRuleDecoder } from '../rules/payee';
import { redeemerRuleDecoder } from '../rules/redeemer';
import type {
ChecksumCaveat,
ChecksumEnforcersByChainId,
DecodedPermissionData,
MakePermissionDecoderConfig,
ChecksumEnforcersByChainId,
PermissionDecoderConfig,
} from '../types';
import { getTermsByEnforcer, ZERO_32_BYTES } from '../utils';

Expand All @@ -21,7 +26,7 @@ import { getTermsByEnforcer, ZERO_32_BYTES } from '../utils';
*/
export function makeErc20TokenStreamDecoderConfig(
contractAddresses: ChecksumEnforcersByChainId,
): MakePermissionDecoderConfig {
): PermissionDecoderConfig {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the other six caveats use MakePermissionDecoderConfig instead of PermissionDecoderConfig. These two types are identical, just with different names. We could leave it as is, but updating all instances to MakePermissionDecoderConfig would be better for consistency.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops - I removed the duplicate type, and settled on PermissionDecoderConfig as I felt that the Make prefix was unnecessary.

const {
timestampEnforcer,
erc20StreamingEnforcer,
Expand Down Expand Up @@ -104,3 +109,75 @@ function validateAndDecodeData(
startTime,
};
}

/**
* Enforcers required to build ERC-20 token stream caveats.
*/
export type Erc20TokenStreamEnforcers = Pick<
ChecksumEnforcersByChainId,
'erc20StreamingEnforcer' | 'valueLteEnforcer'
>;

/**
* Builds the ERC-20 stream caveats required for this permission type.
*
* @param options0 - Caveat builder arguments.
* @param options0.permission - Fully populated ERC-20 stream permission data.
* @param options0.contracts - Enforcer addresses used to construct caveats.
* @returns The ERC20 streaming and zero-value caveats.
*/
export function createErc20TokenStreamCaveats({
permission,
contracts,
}: {
permission: Populated<Erc20TokenStreamPermission>;
contracts: Erc20TokenStreamEnforcers;
}): Caveat[] {
const { initialAmount, maxAmount, amountPerSecond, startTime } =
permission.data;
const initialAmountBigInt = hexToBigInt(initialAmount);
const maxAmountBigInt = hexToBigInt(maxAmount);
const amountPerSecondBigInt = hexToBigInt(amountPerSecond);

if (maxAmountBigInt <= initialAmountBigInt) {
throw new Error(
'Invalid erc20-token-stream permission: maxAmount must be greater than initialAmount.',
);
}

if (amountPerSecondBigInt === 0n) {
throw new Error(
'Invalid erc20-token-stream permission: amountPerSecond must be a positive number.',
);
}

if (startTime <= 0) {
throw new Error(
'Invalid erc20-token-stream permission: startTime must be a positive number.',
);
}

// ERC20StreamingEnforcer: enforce token address, initial/max amount, amount per second, and start time.
const erc20StreamingCaveat: Caveat = {
enforcer: contracts.erc20StreamingEnforcer,
terms: createERC20StreamingTerms({
tokenAddress: permission.data.tokenAddress,
initialAmount: initialAmountBigInt,
maxAmount: maxAmountBigInt,
amountPerSecond: amountPerSecondBigInt,
startTime,
}),
args: '0x',
};

// ValueLteEnforcer: allow no native value (e.g. msg.value must be 0).
const valueLteCaveat: Caveat = {
enforcer: contracts.valueLteEnforcer,
terms: createValueLteTerms({
maxValue: 0n,
}),
args: '0x',
};

return [erc20StreamingCaveat, valueLteCaveat];
}
Loading
Loading