generated from MetaMask/template-snap-monorepo
-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Store metadata when revoking a permission #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
V00D00-child
wants to merge
16
commits into
main
Choose a base branch
from
feat/store-metadata-when-revoking-permission
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
00f9018
Extend StoredGrantedPermission in profile sync to allow storing revoc…
V00D00-child 7bae408
Merge branch 'main' of github.com:MetaMask/snap-7715-permissions into…
V00D00-child 0fda362
Wire up fetching a transaction receipt given a transaction hash
V00D00-child 81e96d4
Expand revocation metadata to have a blockTimestamp value
V00D00-child f60ca10
Ensure revocation transaction has not failed before storing metadata …
V00D00-child 7b1ce40
Update comments
V00D00-child 637de8c
Add test for checkTransactionReceipt function
V00D00-child 632169f
Revocation metadata now defaults to undefined
V00D00-child 9b306d2
Make revocationMetadata required on StoredGrantedPermission type, but…
V00D00-child b89a9ca
Update StoredGrantedPermission zod scheme to have a default for revoc…
V00D00-child 3fb85d4
Add Zod schema for runtime validation of TransactionReceipt. Move del…
V00D00-child 81e61df
Bugfix: Unconfigured profile sync manager missing third parameter
V00D00-child 7093480
Rename BlockchainMetadataClient to BlockchainClient. Make revocationM…
V00D00-child 5dbb6e6
Add missing txHash validation in checkTransactionReceipt method
V00D00-child 9bde018
Merge branch 'main' into feat/store-metadata-when-revoking-permission
V00D00-child cd86102
Fix failing test
V00D00-child File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
215 changes: 215 additions & 0 deletions
215
packages/gator-permissions-snap/src/clients/blockchainClient.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| import { logger } from '@metamask/7715-permissions-shared/utils'; | ||
| import type { Hex } from '@metamask/delegation-core'; | ||
| import { | ||
| ChainDisconnectedError, | ||
| InvalidInputError, | ||
| ResourceUnavailableError, | ||
| type SnapsEthereumProvider, | ||
| } from '@metamask/snaps-sdk'; | ||
|
|
||
| import type { RetryOptions } from './types'; | ||
| import { | ||
| callContract, | ||
| ensureChain, | ||
| getTransactionReceipt, | ||
| } from '../utils/blockchain'; | ||
|
|
||
| /** | ||
| * Client that fetches blockchain data using the ethereum provider for on-chain checks. | ||
| */ | ||
| export class BlockchainClient { | ||
| readonly #ethereumProvider: SnapsEthereumProvider; | ||
|
|
||
| // keccak256('disabledDelegations(bytes32)') | ||
| static readonly #disabledDelegationsCalldata = '0x2d40d052'; | ||
|
|
||
| constructor({ | ||
| ethereumProvider, | ||
| }: { | ||
| ethereumProvider: SnapsEthereumProvider; | ||
| }) { | ||
| this.#ethereumProvider = ethereumProvider; | ||
| } | ||
|
|
||
| /** | ||
| * Determines if an error is retryable. | ||
| * @param error - The error to check. | ||
| * @returns True if the error is retryable. | ||
| */ | ||
| #isRetryableError(error: unknown): boolean { | ||
| // Don't retry chain disconnection errors or invalid input errors | ||
| if ( | ||
| error instanceof ChainDisconnectedError || | ||
| error instanceof InvalidInputError | ||
| ) { | ||
| return false; | ||
| } | ||
|
|
||
| // Retry other errors (network issues, temporary failures, etc.) | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a delegation is disabled on-chain by calling the DelegationManager contract. | ||
| * If the request fails, it will retry according to the retryOptions configuration. | ||
| * @param args - The parameters for checking delegation disabled status. | ||
| * @param args.delegationHash - The hash of the delegation to check. | ||
| * @param args.chainId - The chain ID in hex format. | ||
| * @param args.delegationManagerAddress - The address of the DelegationManager contract. | ||
| * @param args.retryOptions - Optional retry configuration. When not provided, defaults to 1 retry attempt with 1000ms delay. | ||
| * @returns True if the delegation is disabled, false if it is confirmed to be enabled. | ||
| * @throws InvalidInputError if input parameters are invalid. | ||
| * @throws ChainDisconnectedError if the provider is on the wrong chain. | ||
| * @throws ResourceUnavailableError if the on-chain check fails and we cannot determine the status. | ||
| */ | ||
| public async checkDelegationDisabledOnChain({ | ||
| delegationHash, | ||
| chainId, | ||
| delegationManagerAddress, | ||
| retryOptions, | ||
| }: { | ||
| delegationHash: Hex; | ||
| chainId: Hex; | ||
| delegationManagerAddress: Hex; | ||
| retryOptions?: RetryOptions; | ||
| }): Promise<boolean> { | ||
| logger.debug( | ||
| 'BlockchainTokenMetadataClient:checkDelegationDisabledOnChain()', | ||
| { | ||
| delegationHash, | ||
| chainId, | ||
| delegationManagerAddress, | ||
| }, | ||
| ); | ||
|
|
||
| if (!delegationHash) { | ||
| const message = 'No delegation hash provided'; | ||
| logger.error(message); | ||
| throw new InvalidInputError(message); | ||
| } | ||
|
|
||
| if (!chainId) { | ||
| const message = 'No chain ID provided'; | ||
| logger.error(message); | ||
| throw new InvalidInputError(message); | ||
| } | ||
|
|
||
| if (!delegationManagerAddress) { | ||
| const message = 'No delegation manager address provided'; | ||
| logger.error(message); | ||
| throw new InvalidInputError(message); | ||
| } | ||
|
|
||
| // Ensure we're on the correct chain | ||
| // This can throw ChainDisconnectedError - we want it to propagate | ||
| await ensureChain(this.#ethereumProvider, chainId); | ||
|
|
||
| // Encode the function call data for disabledDelegations(bytes32) | ||
| const encodedParams = delegationHash.slice(2).padStart(64, '0'); // Remove 0x and pad to 32 bytes | ||
| const callData = | ||
| `${BlockchainClient.#disabledDelegationsCalldata}${encodedParams}` as Hex; | ||
|
|
||
| try { | ||
| const result = await callContract({ | ||
| ethereumProvider: this.#ethereumProvider, | ||
| contractAddress: delegationManagerAddress, | ||
| callData, | ||
| retryOptions, | ||
| isRetryableError: (error) => this.#isRetryableError(error), | ||
| }); | ||
|
|
||
| // Parse the boolean result (32 bytes, last byte is the boolean value) | ||
| const isDisabled = | ||
| result !== | ||
| '0x0000000000000000000000000000000000000000000000000000000000000000'; | ||
|
|
||
| logger.debug('Delegation disabled status result', { isDisabled }); | ||
| return isDisabled; | ||
| } catch (error) { | ||
| const errorMessage = | ||
| error instanceof Error ? error.message : String(error); | ||
| logger.error( | ||
| `Failed to check delegation disabled status: ${errorMessage}`, | ||
| ); | ||
|
|
||
| // Re-throw critical errors - they should propagate | ||
| if ( | ||
| error instanceof InvalidInputError || | ||
| error instanceof ChainDisconnectedError | ||
| ) { | ||
| throw error; | ||
| } | ||
|
|
||
| // For other errors (network issues, contract call failures, etc.), | ||
| // we cannot determine the status, so throw an error instead of returning false | ||
| throw new ResourceUnavailableError( | ||
| `Unable to determine delegation disabled status: ${errorMessage}`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a transaction was successful by calling the eth_getTransactionReceipt method. | ||
| * @param args - The parameters for checking the transaction receipt. | ||
| * @param args.txHash - The hash of the transaction to check. | ||
| * @param args.chainId - The chain ID in hex format. | ||
| * @returns True if the transaction receipt is valid, false if it is not. | ||
| * @throws InvalidInputError if `chainId` is not specified | ||
| * @throws ChainDisconnectedError if the provider is on the wrong chain. | ||
| * @throws ResourceUnavailableError if the on-chain check fails and we cannot determine the status. | ||
| */ | ||
| public async checkTransactionReceipt({ | ||
| txHash, | ||
| chainId, | ||
| }: { | ||
| txHash: Hex; | ||
| chainId: Hex; | ||
| }): Promise<boolean> { | ||
| logger.debug('BlockchainMetadataClient:checkTransactionReceipt()', { | ||
| txHash, | ||
| chainId, | ||
| }); | ||
|
|
||
| if (!chainId) { | ||
| const message = 'No chain ID provided'; | ||
| logger.error(message); | ||
| throw new InvalidInputError(message); | ||
| } | ||
|
|
||
| if (!txHash) { | ||
| const message = 'No transaction hash provided'; | ||
| logger.error(message); | ||
| throw new InvalidInputError(message); | ||
| } | ||
|
|
||
| await ensureChain(this.#ethereumProvider, chainId); | ||
|
|
||
| try { | ||
| const result = await getTransactionReceipt({ | ||
| ethereumProvider: this.#ethereumProvider, | ||
| txHash, | ||
| isRetryableError: (error) => this.#isRetryableError(error), | ||
| }); | ||
|
|
||
| return result.status === '0x1'; | ||
| } catch (error) { | ||
| const errorMessage = | ||
| error instanceof Error ? error.message : String(error); | ||
| logger.error(`Failed to fetch transaction receipt: ${errorMessage}`); | ||
|
|
||
| // Re-throw critical errors - they should propagate | ||
| if ( | ||
| error instanceof InvalidInputError || | ||
| error instanceof ChainDisconnectedError | ||
| ) { | ||
| throw error; | ||
| } | ||
|
|
||
| // For other errors (network issues, contract call failures, etc.), | ||
| // we cannot determine the status, so throw an error instead of returning false | ||
| throw new ResourceUnavailableError( | ||
| `Failed to fetch transaction receipt: ${errorMessage}`, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.